diff --git a/.github/workflows/scripts/windows/build-dependencies.bat b/.github/workflows/scripts/windows/build-dependencies.bat index 57c46319b6694..45f4a90cb73f3 100644 --- a/.github/workflows/scripts/windows/build-dependencies.bat +++ b/.github/workflows/scripts/windows/build-dependencies.bat @@ -98,6 +98,8 @@ set SHADERC_SPIRVTOOLS=6337eb62cadd7d124ac6789bf39c0f71148f0a73 set AGILITYSDK=1.619.2 set DXHEADERS=1.619.1 +rem Bump ShaderCacheVersion when updating DXC, otherwise shader linking will break. +set DXC=1.9.2602.24 call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 3529cc37297a5a7aae4486843b9fd41c30df1d79a770f85e240b537dcc327ca5 || goto error call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 37fba768f2780580dfae535ad6654cb9dc0bf2272e71b9b9781988de9ed0dac0 || goto error @@ -131,6 +133,7 @@ call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error call :downloadfile "agility-sdk-%AGILITYSDK%.nupkg" "https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/%AGILITYSDK%" eb92d90bb23b2ec23410c41d791e41dbdbec942ab946924d1fdcb31eac6f0735 || goto error call :downloadfile "DirectX-Headers-%DXHEADERS%.zip" "https://github.com/microsoft/DirectX-Headers/archive/v%DXHEADERS%.zip" 9eb8b102a90a42e4ea72a825f7d249d55ec90d164f030966c9b7784b93374927 || goto error +call :downloadfile "DirectXShaderCompiler-%DXC%.zip" "https://github.com/microsoft/DirectXShaderCompiler/archive/v%DXC%.zip" 54797369a304422f7b98bf0ab1e5824a63190912a99976502f14f6b9b63828b4 || goto error call :downloadfile "rapidyaml-%RAPIDYAML%-src.zip" "https://github.com/biojppm/rapidyaml/releases/download/v%RAPIDYAML%/rapidyaml-%RAPIDYAML%-src.zip" 96276f55b9fa7837ac8f3f72fd52965879cbb5d5d2e6af548c69a177fb078304 || goto error call :downloadfile "shaderc-%SHADERC%.zip" "https://github.com/google/shaderc/archive/refs/tags/v%SHADERC%.zip" f9401cc5cb36c276cd1e072b6595dbd728148e8dba389e50f7339e2d388dbc08 || goto error @@ -519,6 +522,17 @@ cmake --build build --parallel || goto error ninja -C build install || goto error cd .. || goto error +echo "Building DirectXShaderCompiler..." +rmdir /S /Q "DirectXShaderCompiler-%DXC%" +%SEVENZIP% x "DirectXShaderCompiler-%DXC%.zip" || goto error +cd "DirectXShaderCompiler-%DXC%" || goto error +rem cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DLLVM_ENABLE_EH=ON -DLLVM_DEFAULT_TARGET_TRIPLE="dxil-ms-dx" -DLLVM_TARGETS_TO_BUILD=None -DCLANG_CL=OFF -DLLVM_INCLUDE_TESTS=OFF -DHLSL_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DCLANG_BUILD_EXAMPLES=OFF -DLLVM_INCLUDE_DOCS=OFF -DCLANG_ENABLE_ARCMT=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_OPTIMIZED_TABLEGEN=OFF -B build -G Ninja || goto error +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -C .\cmake\caches\PredefinedParams.cmake -DHLSL_DISABLE_SOURCE_GENERATION=ON -DLLVM_INCLUDE_TESTS=OFF -DHLSL_INCLUDE_TESTS=OFF -DENABLE_SPIRV_CODEGEN=OFF -DSPIRV_BUILD_TESTS=OFF -DHLSL_BUILD_DXILCONV=OFF -DLLVM_BUILD_TOOLS=OFF -B build -G Ninja || goto error +cmake --build build --target dxcompiler --parallel || goto error +rem Instead of installing all of LLVM, just copy the files needed. +copy "build\bin\dxcompiler.dll" "%INSTALLDIR%\bin\dxcompiler.dll" || goto error +cd .. || goto error + echo Building shaderc... rmdir /S /Q "shaderc-%SHADERC%" %SEVENZIP% x "shaderc-%SHADERC%.zip" || goto error diff --git a/bin/resources/shaders/common/ffx_a.h b/bin/resources/shaders/common/ffx_a.h index 6e9af9fc31b2c..211734ebd92dc 100644 --- a/bin/resources/shaders/common/ffx_a.h +++ b/bin/resources/shaders/common/ffx_a.h @@ -1062,9 +1062,9 @@ AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} //------------------------------------------------------------------------------------------------------------------------------ - AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1<>off)&mask;} + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1U<>off)&mask;} AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} - AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1<= 1 && PS_DATE <= 3) || GS_FORWARD_PRIMID - uint primid : SV_PrimitiveID; -#endif -}; - #ifdef PIXEL_SHADER -struct PS_OUTPUT -{ -#define NUM_RTS 0 - -#if PS_RETURN_COLOR - #if PS_DATE == 1 || PS_DATE == 2 - float c : SV_Target; - #else - - float4 c0 : SV_Target0; - - #undef NUM_RTS - #define NUM_RTS 1 - - #if !PS_NO_COLOR1 - float4 c1 : SV_Target1; - #endif - #endif -#endif - -#if PS_RETURN_DEPTH - // In DX12 we do depth feedback loops with a color copy. - #if SW_DEPTH && PS_NO_COLOR1 && PS_DEPTH_FEEDBACK_SUPPORT == 2 - #if NUM_RTS > 0 - float depth_color : SV_Target1; - #else - float depth_color : SV_Target0; - #endif - #endif - #if PS_HAS_CONSERVATIVE_DEPTH && !SW_DEPTH - float depth : SV_DepthLessEqual; - #else - float depth : SV_Depth; - #endif -#endif - -#undef NUM_RTS -}; - -Texture2D Texture : register(t0); -Texture2D Palette : register(t1); -#if !PS_ROV_COLOR -Texture2D RtTexture : register(t2); -#endif -Texture2D PrimMinTexture : register(t3); -#if !PS_ROV_DEPTH -Texture2D DepthTexture : register(t4); -#endif -SamplerState TextureSampler : register(s0); - -#if PS_ROV_COLOR -RasterizerOrderedTexture2D RtTextureRov : register(u0); -static float4 rov_rt_value; -#endif - -#if PS_ROV_DEPTH -RasterizerOrderedTexture2D DepthTextureRov : register(u1); -static float rov_depth_value; -#endif - -#ifdef DX12 -cbuffer cb1 : register(b1) -#else -cbuffer cb1 -#endif -{ - float3 FogColor; - float AREF; - float4 WH; - float2 TA; - float MaxDepthPS; - float Af; - uint4 FbMask; - float4 HalfTexel; - float4 MinMax; - float4 LODParams; - float4 STRange; - int4 ChannelShuffle; - float2 ChannelShuffleOffset; - float2 TC_OffsetHack; - float2 STScale; - float4x4 DitherMatrix; - float ScaledScaleFactor; - float RcpScaleFactor; - float _pad0_cb1; - float _pad1_cb1; - float LineCovScale; - float _pad2_cb1; - float _pad3_cb1; - float _pad4_cb1; -}; - -float4 RtLoad(int2 xy) -{ -#if PS_ROV_COLOR - return rov_rt_value; -#else - return RtTexture.Load(int3(int2(xy), 0)); -#endif -} - -float DepthLoad(int2 xy) -{ -#if PS_ROV_DEPTH - return rov_depth_value; -#else - return DepthTexture.Load(int3(int2(xy), 0)); -#endif -} - -void RtWrite(int2 xy, float4 c) -{ -#if PS_ROV_COLOR - RtTextureRov[xy] = c; -#endif -} - -void DepthWrite(int2 xy, float d) -{ -#if PS_ROV_DEPTH - DepthTextureRov[xy] = d; -#endif -} - -#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1) -float manual_lod(float uv_w) -{ - // FIXME add LOD: K - ( LOG2(Q) * (1 << L)) - float K = LODParams.x; - float L = LODParams.y; - float bias = LODParams.z; - float max_lod = LODParams.w; - - float gs_lod = K - log2(abs(uv_w)) * L; - // FIXME max useful ? - //return max(min(gs_lod, max_lod) - bias, 0.0f); - return min(gs_lod, max_lod) - bias; -} -#endif - -#if PS_ANISOTROPIC_FILTERING > 1 -bool2 nan_or_inf(float2 xy) -{ - // FXC (<=SM5.1) may optimise away isnan and isinf. - // DXC (>=SM6.0) will preserve them. -#ifdef __hlsl_dx_compiler - return isinf(xy) | isnan(xy); -#else - return (asuint(xy) & 0x7f800000) == 0x7f800000; -#endif -} - -float4 sample_c_af(float2 uv, float uv_w) -{ - // HW sampler will reject bad UVs, match that here. - uv = any(nan_or_inf(uv)) ? float2(0.0f, 0.0f) : uv; - - // Large floating point values risk NaN/Inf values. - // Above this value floats lose decimal precision, so seems a resonable limit for UVs. - uv = clamp(uv, -8388608.0f, 8388608.0f); - - // Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations - // And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt - // With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/ - float2 sz; - Texture.GetDimensions(sz.x, sz.y); - float2 dX = ddx(uv) * sz; - float2 dY = ddy(uv) * sz; - - float length_x = length(dX); - float length_y = length(dY); - - // Calculate Ellipse Transform - bool d_zero = length_x < 0.001f || length_y < 0.001f; - float f = (dX.x * dY.y - dX.y * dY.x); - bool d_par = f < 0.001f; - bool d_per = dot(dX, dY) < 0.001f; - bool d_inf_nan = any(nan_or_inf(dX) | nan_or_inf(dY)); - - if (!(d_zero || d_par || d_per || d_inf_nan)) - { - float A = dX.y * dX.y + dY.y * dY.y; - float B = -2 * (dX.x * dX.y + dY.x * dY.y); - float C = dX.x * dX.x + dY.x * dY.x; - float F = f * f; - - float p = A - C; - float q = A + C; - float t = sqrt(p * p + B * B); - - float sqrt_num_plus = sqrt(F * (t + p)); - float sqrt_num_minus = sqrt(F * (t - p)); - - float inv_sqrt_denom_plus = rsqrt(t * (q + t)); - float inv_sqrt_denom_minus = rsqrt(t * (q - t)); - - float signB = sign(B); - - float2 new_dX = float2( - sqrt_num_plus * inv_sqrt_denom_plus, - sqrt_num_minus * inv_sqrt_denom_plus * signB - ); - - float2 new_dY = float2( - sqrt_num_minus * inv_sqrt_denom_minus * -signB, - sqrt_num_plus * inv_sqrt_denom_minus - ); - - d_inf_nan = any(nan_or_inf(new_dX) | nan_or_inf(new_dY)); - if (!d_inf_nan) - { - dX = new_dX; - dY = new_dY; - length_x = length(dX); - length_y = length(dY); - } - } - - // Compute AF values - bool is_major_x = length_x > length_y; - float length_major = is_major_x ? length_x : length_y; - float length_minor = is_major_x ? length_y : length_x; - - float aniso_ratio; - float length_lod; - float2 aniso_line; - - if (length_major <= 1.0f) - { - // A zero length_major would result in NaN Lod and break sampling. - // A small length_major would result in aniso_ratio getting clamped to 1. - // Perform isotropic filtering instead. - aniso_ratio = 1.0f; - length_lod = length_major; - aniso_line = float2(0.0f, 0.0f); - } - else - { - float2 aniso_line_dir = is_major_x ? dX : dY; - - aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING); - length_lod = length_major / aniso_ratio; - - // clamp to top Lod - if (length_lod < 1.0f) - aniso_ratio = max(1.0f, aniso_ratio * length_lod); - - aniso_ratio = round(aniso_ratio); - - aniso_line = aniso_line_dir * 0.5f * (1.0f / sz); - } - -#if PS_AUTOMATIC_LOD == 1 - float lod = log2(length_lod); -#elif PS_MANUAL_LOD == 1 - float lod = manual_lod(uv_w); -#else - float lod = 0.0f; // No Lod -#endif - - float4 colour; - if (aniso_ratio == 1.0f) - colour = Texture.SampleLevel(TextureSampler, uv, lod); - else - { - float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f); - float2 segment = (2.0f * aniso_line) / aniso_ratio; - - int aniso_ratio_i = (int)aniso_ratio; - for (int i = 0; i < aniso_ratio_i; i++) - { - float2 d = -aniso_line + (0.5f + i) * segment; - float2 uv_sample = uv + d; - float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod); - num += sample_colour; - } - - colour = num / aniso_ratio; - } - return colour; -} -#endif - -float4 sample_c(float2 uv, float uv_w, int2 xy) -{ -#if PS_TEX_IS_FB == 1 - return RtLoad(xy); -#elif PS_REGION_RECT == 1 - return Texture.Load(int3(int2(uv), 0)); -#else - if (PS_POINT_SAMPLER) - { - // Weird issue with ATI/AMD cards, - // it looks like they add 127/128 of a texel to sampling coordinates - // occasionally causing point sampling to erroneously round up. - // I'm manually adjusting coordinates to the centre of texels here, - // though the centre is just paranoia, the top left corner works fine. - // As of 2018 this issue is still present. - uv = (trunc(uv * WH.zw) + float2(0.5, 0.5)) / WH.zw; - } -#if !PS_ADJS && !PS_ADJT - uv *= STScale; -#else - #if PS_ADJS - uv.x = (uv.x - STRange.x) * STRange.z; - #else - uv.x = uv.x * STScale.x; - #endif - #if PS_ADJT - uv.y = (uv.y - STRange.y) * STRange.w; - #else - uv.y = uv.y * STScale.y; - #endif -#endif - -#if PS_ANISOTROPIC_FILTERING > 1 - return sample_c_af(uv, uv_w); -#elif PS_AUTOMATIC_LOD == 1 - return Texture.Sample(TextureSampler, uv); -#elif PS_MANUAL_LOD == 1 - return Texture.SampleLevel(TextureSampler, uv, manual_lod(uv_w)); -#else - return Texture.SampleLevel(TextureSampler, uv, 0); // No lod -#endif -#endif -} - -float4 sample_p(uint u) -{ - return Palette.Load(int3(int(u), 0, 0)); -} - -float4 sample_p_norm(float u) -{ - return sample_p(uint(u * 255.5f)); -} - -float4 clamp_wrap_uv(float4 uv) -{ - float4 tex_size = WH.xyxy; - - if(PS_WMS == PS_WMT) - { - if(PS_REGION_RECT != 0 && PS_WMS == 0) - { - uv = frac(uv); - } - else if(PS_REGION_RECT != 0 && PS_WMS == 1) - { - uv = saturate(uv); - } - else if(PS_WMS == 2) - { - uv = clamp(uv, MinMax.xyxy, MinMax.zwzw); - } - else if(PS_WMS == 3) - { - #if PS_FST == 0 - // wrap negative uv coords to avoid an off by one error that shifted - // textures. Fixes Xenosaga's hair issue. - uv = frac(uv); - #endif - uv = (float4)(((uint4)(uv * tex_size) & asuint(MinMax.xyxy)) | asuint(MinMax.zwzw)) / tex_size; - } - } - else - { - if(PS_REGION_RECT != 0 && PS_WMS == 0) - { - uv.xz = frac(uv.xz); - } - else if(PS_REGION_RECT != 0 && PS_WMS == 1) - { - uv.xz = saturate(uv.xz); - } - else if(PS_WMS == 2) - { - uv.xz = clamp(uv.xz, MinMax.xx, MinMax.zz); - } - else if(PS_WMS == 3) - { - #if PS_FST == 0 - uv.xz = frac(uv.xz); - #endif - uv.xz = (float2)(((uint2)(uv.xz * tex_size.xx) & asuint(MinMax.xx)) | asuint(MinMax.zz)) / tex_size.xx; - } - if(PS_REGION_RECT != 0 && PS_WMT == 0) - { - uv.yw = frac(uv.yw); - } - else if(PS_REGION_RECT != 0 && PS_WMT == 1) - { - uv.yw = saturate(uv.yw); - } - else if(PS_WMT == 2) - { - uv.yw = clamp(uv.yw, MinMax.yy, MinMax.ww); - } - else if(PS_WMT == 3) - { - #if PS_FST == 0 - uv.yw = frac(uv.yw); - #endif - uv.yw = (float2)(((uint2)(uv.yw * tex_size.yy) & asuint(MinMax.yy)) | asuint(MinMax.ww)) / tex_size.yy; - } - } - - if(PS_REGION_RECT != 0) - { - // Normalized -> Integer Coordinates. - uv = clamp(uv * WH.zwzw + STRange.xyxy, STRange.xyxy, STRange.zwzw); - } - - return uv; -} - -float4x4 sample_4c(float4 uv, float uv_w, int2 xy) -{ - float4x4 c; - - c[0] = sample_c(uv.xy, uv_w, xy); - c[1] = sample_c(uv.zy, uv_w, xy); - c[2] = sample_c(uv.xw, uv_w, xy); - c[3] = sample_c(uv.zw, uv_w, xy); - - return c; -} - -uint4 sample_4_index(float4 uv, float uv_w, int2 xy) -{ - float4 c; - - c.x = sample_c(uv.xy, uv_w, xy).a; - c.y = sample_c(uv.zy, uv_w, xy).a; - c.z = sample_c(uv.xw, uv_w, xy).a; - c.w = sample_c(uv.zw, uv_w, xy).a; - - // Denormalize value - uint4 i; - - if (PS_RTA_SRC_CORRECTION) - { - i = uint4(round(c * 128.25f)); // Denormalize value - } - else - { - i = uint4(c * 255.5f); // Denormalize value - } - - if (PS_PAL_FMT == 1) - { - // 4HL - return i & 0xFu; - } - else if (PS_PAL_FMT == 2) - { - // 4HH - return i >> 4u; - } - else - { - // 8 - return i; - } -} - -float4x4 sample_4p(uint4 u) -{ - float4x4 c; - - c[0] = sample_p(u.x); - c[1] = sample_p(u.y); - c[2] = sample_p(u.z); - c[3] = sample_p(u.w); - - return c; -} - -uint fetch_raw_depth(int2 xy) -{ -#if PS_TEX_IS_FB == 1 - float4 col = RtLoad(xy); -#else - float4 col = Texture.Load(int3(xy, 0)); -#endif - return (uint)(col.r * exp2(32.0f)); -} - -float4 fetch_raw_color(int2 xy) -{ -#if PS_TEX_IS_FB == 1 - return RtLoad(xy); -#else - return Texture.Load(int3(xy, 0)); -#endif -} - -float4 fetch_c(int2 uv) -{ -#if PS_TEX_IS_FB == 1 - return RtLoad(uv); -#else - return Texture.Load(int3(uv, 0)); -#endif -} - -////////////////////////////////////////////////////////////////////// -// Depth sampling -////////////////////////////////////////////////////////////////////// - -int2 clamp_wrap_uv_depth(int2 uv) -{ - int4 mask = asint(MinMax) << 4; - if (PS_WMS == PS_WMT) - { - if (PS_WMS == 2) - { - uv = clamp(uv, mask.xy, mask.zw); - } - else if (PS_WMS == 3) - { - uv = (uv & mask.xy) | mask.zw; - } - } - else - { - if (PS_WMS == 2) - { - uv.x = clamp(uv.x, mask.x, mask.z); - } - else if (PS_WMS == 3) - { - uv.x = (uv.x & mask.x) | mask.z; - } - if (PS_WMT == 2) - { - uv.y = clamp(uv.y, mask.y, mask.w); - } - else if (PS_WMT == 3) - { - uv.y = (uv.y & mask.y) | mask.w; - } - } - return uv; -} - -float4 sample_depth(float2 st, float2 pos) -{ - float2 uv_f = (float2)clamp_wrap_uv_depth(int2(st)) * (float2)ScaledScaleFactor; - -#if PS_REGION_RECT == 1 - uv_f = clamp(uv_f + STRange.xy, STRange.xy, STRange.zw); -#endif - - int2 uv = (int2)uv_f; - float4 t = (float4)(0.0f); - - if (PS_TALES_OF_ABYSS_HLE == 1) - { - // Warning: UV can't be used in channel effect - uint depth = fetch_raw_depth(pos); - - // Convert msb based on the palette - t = Palette.Load(int3((depth >> 8u) & 0xFFu, 0, 0)) * 255.0f; - } - else if (PS_URBAN_CHAOS_HLE == 1) - { - // Depth buffer is read as a RGB5A1 texture. The game try to extract the green channel. - // So it will do a first channel trick to extract lsb, value is right-shifted. - // Then a new channel trick to extract msb which will shifted to the left. - // OpenGL uses a FLOAT32 format for the depth so it requires a couple of conversion. - // To be faster both steps (msb&lsb) are done in a single pass. - - // Warning: UV can't be used in channel effect - uint depth = fetch_raw_depth(pos); - - // Convert lsb based on the palette - t = Palette.Load(int3(depth & 0xFFu, 0, 0)) * 255.0f; - - // Msb is easier - float green = (float)((depth >> 8u) & 0xFFu) * 36.0f; - green = min(green, 255.0f); - t.g += green; - } - else if (PS_DEPTH_FMT == 1) - { - // Based on ps_convert_depth32_rgba8 of convert - - // Convert a FLOAT32 depth texture into a RGBA color texture - uint d = uint(fetch_c(uv).r * exp2(32.0f)); - t = float4(uint4((d & 0xFFu), ((d >> 8) & 0xFFu), ((d >> 16) & 0xFFu), (d >> 24))); - } - else if (PS_DEPTH_FMT == 2) - { - // Based on ps_convert_depth16_rgb5a1 of convert - - // Convert a FLOAT32 (only 16 lsb) depth into a RGB5A1 color texture - uint d = uint(fetch_c(uv).r * exp2(32.0f)); - t = float4(uint4((d & 0x1Fu), ((d >> 5) & 0x1Fu), ((d >> 10) & 0x1Fu), (d >> 15) & 0x01u)) * float4(8.0f, 8.0f, 8.0f, 128.0f); - } - else if (PS_DEPTH_FMT == 3) - { - // Convert a RGBA/RGB5A1 color texture into a RGBA/RGB5A1 color texture - t = fetch_c(uv) * 255.0f; - } - - if (PS_AEM_FMT == FMT_24) - { - t.a = ((PS_AEM == 0) || any(bool3(t.rgb))) ? 255.0f * TA.x : 0.0f; - } - else if (PS_AEM_FMT == FMT_16) - { - t.a = t.a >= 128.0f ? 255.0f * TA.y : ((PS_AEM == 0) || any(bool3(t.rgb))) ? 255.0f * TA.x : 0.0f; - } - else if (PS_PAL_FMT != 0 && !PS_TALES_OF_ABYSS_HLE && !PS_URBAN_CHAOS_HLE) - { - t = trunc(sample_4p(uint4(t.aaaa))[0] * 255.0f + 0.05f); - } - - return t; -} - -////////////////////////////////////////////////////////////////////// -// Fetch a Single Channel -////////////////////////////////////////////////////////////////////// - -float4 fetch_red(int2 xy) -{ - float4 rt; - - if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) - { - uint depth = (fetch_raw_depth(xy)) & 0xFFu; - rt = (float4)(depth) / 255.0f; - } - else - { - rt = fetch_raw_color(xy); - } - - return sample_p_norm(rt.r) * 255.0f; -} - -float4 fetch_green(int2 xy) -{ - float4 rt; - - if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) - { - uint depth = (fetch_raw_depth(xy) >> 8u) & 0xFFu; - rt = (float4)(depth) / 255.0f; - } - else - { - rt = fetch_raw_color(xy); - } - - return sample_p_norm(rt.g) * 255.0f; -} - -float4 fetch_blue(int2 xy) -{ - float4 rt; - - if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) - { - uint depth = (fetch_raw_depth(xy) >> 16u) & 0xFFu; - rt = (float4)(depth) / 255.0f; - } - else - { - rt = fetch_raw_color(xy); - } - - return sample_p_norm(rt.b) * 255.0f; -} - -float4 fetch_alpha(int2 xy) -{ - float4 rt = fetch_raw_color(xy); - return sample_p_norm(rt.a) * 255.0f; -} - -float4 fetch_rgb(int2 xy) -{ - float4 rt = fetch_raw_color(xy); - float4 c = float4(sample_p_norm(rt.r).r, sample_p_norm(rt.g).g, sample_p_norm(rt.b).b, 1.0); - return c * 255.0f; -} - -float4 fetch_gXbY(int2 xy) -{ - if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) - { - uint depth = fetch_raw_depth(xy); - uint bg = (depth >> (8u + uint(ChannelShuffle.w))) & 0xFFu; - return (float4)(bg); - } - else - { - int4 rt = (int4)(fetch_raw_color(xy) * 255.0); - int green = (rt.g >> ChannelShuffle.w) & ChannelShuffle.z; - int blue = (rt.b << ChannelShuffle.y) & ChannelShuffle.x; - return (float4)(green | blue); - } -} - -float4 sample_color(float2 st, float uv_w, int2 xy) -{ - #if PS_TCOFFSETHACK - st += TC_OffsetHack.xy; - #endif - - float4 t; - float4x4 c; - float2 dd; - - if (PS_LTF == 0 && PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_REGION_RECT == 0 && PS_WMS < 2 && PS_WMT < 2) - { - c[0] = sample_c(st, uv_w, xy); - } - else - { - float4 uv; - - if(PS_LTF) - { - uv = st.xyxy + HalfTexel; - dd = frac(uv.xy * WH.zw); - - if(PS_FST == 0) - { - dd = clamp(dd, (float2)0.0f, (float2)0.9999999f); - } - } - else - { - uv = st.xyxy; - } - - uv = clamp_wrap_uv(uv); - -#if PS_PAL_FMT != 0 - c = sample_4p(sample_4_index(uv, uv_w, xy)); -#else - c = sample_4c(uv, uv_w, xy); -#endif - } - - [unroll] - for (uint i = 0; i < 4; i++) - { - if(PS_AEM_FMT == FMT_24) - { - c[i].a = !PS_AEM || any(c[i].rgb) ? TA.x : 0; - } - else if(PS_AEM_FMT == FMT_16) - { - c[i].a = c[i].a >= 0.5 ? TA.y : !PS_AEM || any(int3(c[i].rgb * 255.0f) & 0xF8) ? TA.x : 0; - } - } - - if(PS_LTF) - { - t = lerp(lerp(c[0], c[1], dd.x), lerp(c[2], c[3], dd.x), dd.y); - } - else - { - t = c[0]; - } - - if (PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_RTA_SRC_CORRECTION) - t.a = t.a * (128.5f / 255.0f); - - return trunc(t * 255.0f + 0.05f); -} - -float4 tfx(float4 T, float4 C) -{ - float4 C_out; - float4 FxT = trunc((C * T) / 128.0f); - -#if (PS_TFX == 0) - C_out = FxT; -#elif (PS_TFX == 1) - C_out = T; -#elif (PS_TFX == 2) - C_out.rgb = FxT.rgb + C.a; - C_out.a = T.a + C.a; -#elif (PS_TFX == 3) - C_out.rgb = FxT.rgb + C.a; - C_out.a = T.a; -#else - C_out = C; -#endif - -#if (PS_TCC == 0) - C_out.a = C.a; -#endif - -#if (PS_TFX == 0) || (PS_TFX == 2) || (PS_TFX == 3) - // Clamp only when it is useful - C_out = min(C_out, 255.0f); -#endif - - return C_out; -} - -bool atst(float4 C) -{ - float a = C.a; - -#if PS_ATST == PS_ATST_LEQUAL - - return (a <= AREF); - -#elif PS_ATST == PS_ATST_GEQUAL - - return (a >= AREF); - -#elif PS_ATST == PS_ATST_EQUAL - - return (abs(a - AREF) <= 0.5f); - -#elif PS_ATST == PS_ATST_NOTEQUAL - - return (abs(a - AREF) >= 0.5f); - -#else - - return true; - -#endif -} - -float4 fog(float4 c, float f) -{ - if(PS_FOG) - { - c.rgb = trunc(lerp(FogColor, c.rgb, (f * 255.0f) / 256.0f)); - } - - return c; -} - -float4 ps_color(PS_INPUT input) -{ -#if PS_FST == 0 - float2 st = input.t.xy / input.t.w; - float2 st_int = input.ti.zw / input.t.w; -#else - float2 st = input.ti.xy; - float2 st_int = input.ti.zw; -#endif - -#if PS_CHANNEL_FETCH == 1 - float4 T = fetch_red(int2(input.p.xy + ChannelShuffleOffset)); -#elif PS_CHANNEL_FETCH == 2 - float4 T = fetch_green(int2(input.p.xy + ChannelShuffleOffset)); -#elif PS_CHANNEL_FETCH == 3 - float4 T = fetch_blue(int2(input.p.xy + ChannelShuffleOffset)); -#elif PS_CHANNEL_FETCH == 4 - float4 T = fetch_alpha(int2(input.p.xy + ChannelShuffleOffset)); -#elif PS_CHANNEL_FETCH == 5 - float4 T = fetch_rgb(int2(input.p.xy + ChannelShuffleOffset)); -#elif PS_CHANNEL_FETCH == 6 - float4 T = fetch_gXbY(int2(input.p.xy + ChannelShuffleOffset)); -#elif PS_DEPTH_FMT > 0 - float4 T = sample_depth(st_int, input.p.xy); -#else - float4 T = sample_color(st, input.t.w, int2(input.p.xy)); -#endif - - if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) - { - uint4 denorm_c_before = uint4(T); - if (PS_PROCESS_BA & SHUFFLE_READ) - { - T.r = float((denorm_c_before.b << 3) & 0xF8u); - T.g = float(((denorm_c_before.b >> 2) & 0x38u) | ((denorm_c_before.a << 6) & 0xC0u)); - T.b = float((denorm_c_before.a << 1) & 0xF8u); - T.a = float(denorm_c_before.a & 0x80u); - } - else - { - T.r = float((denorm_c_before.r << 3) & 0xF8u); - T.g = float(((denorm_c_before.r >> 2) & 0x38u) | ((denorm_c_before.g << 6) & 0xC0u)); - T.b = float((denorm_c_before.g << 1) & 0xF8u); - T.a = float(denorm_c_before.g & 0x80u); - } - - T.a = (T.a >= 127.5f ? TA.y : !PS_AEM || any(int3(T.rgb) & 0xF8) ? TA.x : 0) * 255.0f; - } - - float4 C = tfx(T, input.c); - - C = fog(C, input.t.z); - - return C; -} - -void ps_fbmask(inout float4 C, float2 pos_xy) -{ - if (PS_FBMASK) - { - float multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; - float4 RT = trunc(RtLoad(int2(pos_xy)) * multi + 0.1f); - C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask)); - } -} - -void ps_dither(inout float3 C, float As, float2 pos_xy) -{ - if (PS_DITHER > 0 && PS_DITHER < 3) - { - int2 fpos; - - if (PS_DITHER == 2) - fpos = int2(pos_xy); - else - fpos = int2(pos_xy * RcpScaleFactor); - - float value = DitherMatrix[fpos.x & 3][fpos.y & 3]; - - // The idea here is we add on the dither amount adjusted by the alpha before it goes to the hw blend - // so after the alpha blend the resulting value should be the same as (Cs - Cd) * As + Cd + Dither. - if (PS_DITHER_ADJUST) - { - float Alpha = PS_BLEND_C == 2 ? Af : As; - value *= Alpha > 0.0f ? min(1.0f / Alpha, 1.0f) : 1.0f; - } - - if (PS_ROUND_INV) - C -= value; - else - C += value; - } -} - -void ps_color_clamp_wrap(inout float3 C) -{ - // When dithering the bottom 3 bits become meaningless and cause lines in the picture - // so we need to limit the color depth on dithered items - if (SW_BLEND || (PS_DITHER > 0 && PS_DITHER < 3) || PS_FBMASK) - { - if (PS_DST_FMT == FMT_16 && PS_BLEND_MIX == 0 && PS_ROUND_INV) - C += 7.0f; // Need to round up, not down since the shader will invert - - // Standard Clamp - if (PS_COLCLIP == 0 && PS_COLCLIP_HW == 0) - C = clamp(C, (float3)0.0f, (float3)255.0f); - - // In 16 bits format, only 5 bits of color are used. It impacts shadows computation of Castlevania - if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER)) - C = (float3)((int3)C & (int3)0xF8); - else if (PS_COLCLIP == 1 || PS_COLCLIP_HW == 1) - C = (float3)((int3)C & (int3)0xFF); - } - else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0) - C = (float3)((int3)C & (int3)0xF8); -} - -void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy) -{ - float As = As_rgba.a; - - if (SW_BLEND) - { - // PABE - if (PS_PABE) - { - // As_rgba needed for accumulation blend to manipulate Cd. - // No blending so early exit - if (As < 1.0f) - { - As_rgba.rgb = (float3)0.0f; - return; - } - - As_rgba.rgb = (float3)1.0f; - } - - float4 RT = SW_BLEND_NEEDS_RT ? RtLoad(int2(pos_xy)) : (float4)0.0f; - - if (PS_SHUFFLE && SW_BLEND_NEEDS_RT) - { - uint4 denorm_rt = uint4(RT); - if (PS_PROCESS_BA & SHUFFLE_WRITE) - { - RT.r = float((denorm_rt.b << 3) & 0xF8u); - RT.g = float(((denorm_rt.b >> 2) & 0x38u) | ((denorm_rt.a << 6) & 0xC0u)); - RT.b = float((denorm_rt.a << 1) & 0xF8u); - RT.a = float(denorm_rt.a & 0x80u); - } - else - { - RT.r = float((denorm_rt.r << 3) & 0xF8u); - RT.g = float(((denorm_rt.r >> 2) & 0x38u) | ((denorm_rt.g << 6) & 0xC0u)); - RT.b = float((denorm_rt.g << 1) & 0xF8u); - RT.a = float(denorm_rt.g & 0x80u); - } - } - - float Ad = PS_RTA_CORRECTION ? trunc(RT.a * 128.0f + 0.1f) / 128.0f : trunc(RT.a * 255.0f + 0.1f) / 128.0f; - float color_multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; - float3 Cd = trunc(RT.rgb * color_multi + 0.1f); - float3 Cs = Color.rgb; - - float3 A = (PS_BLEND_A == 0) ? Cs : ((PS_BLEND_A == 1) ? Cd : (float3)0.0f); - float3 B = (PS_BLEND_B == 0) ? Cs : ((PS_BLEND_B == 1) ? Cd : (float3)0.0f); - float C = (PS_BLEND_C == 0) ? As : ((PS_BLEND_C == 1) ? Ad : Af); - float3 D = (PS_BLEND_D == 0) ? Cs : ((PS_BLEND_D == 1) ? Cd : (float3)0.0f); - - // As/Af clamp alpha for Blend mix - // We shouldn't clamp blend mix with blend hw 1 as we want alpha higher - float C_clamped = C; - if (PS_BLEND_MIX > 0 && PS_BLEND_HW != 1 && PS_BLEND_HW != 2) - C_clamped = saturate(C_clamped); - - if (PS_BLEND_A == PS_BLEND_B) - Color.rgb = D; - // In blend_mix, HW adds on some alpha factor * dst. - // Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation. - // Instead, apply an offset to convert HW's round to a floor. - // Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision. - // But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399 - // Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause. - // 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256). - else if (PS_BLEND_MIX == 2) - Color.rgb = ((A - B) * C_clamped + D) + (124.0f / 256.0f); - else if (PS_BLEND_MIX == 1) - Color.rgb = ((A - B) * C_clamped + D) - (124.0f / 256.0f); - else - Color.rgb = trunc(((A - B) * C) + D); - - if (PS_BLEND_HW == 1) - { - // As or Af - As_rgba.rgb = (float3)C; - // Subtract 1 for alpha to compensate for the changed equation, - // if c.rgb > 255.0f then we further need to adjust alpha accordingly, - // we pick the lowest overflow from all colors because it's the safest, - // we divide by 255 the color because we don't know Cd value, - // changed alpha should only be done for hw blend. - float3 alpha_compensate = max((float3)1.0f, Color.rgb / (float3)255.0f); - As_rgba.rgb -= alpha_compensate; - } - else if (PS_BLEND_HW == 2) - { - // Since we can't do Cd*(Alpha + 1) - Cs*Alpha in hw blend - // what we can do is adjust the Cs value that will be - // subtracted, this way we can get a better result in hw blend. - // Result is still wrong but less wrong than before. - float division_alpha = 1.0f + C; - Color.rgb /= (float3)division_alpha; - } - else if (PS_BLEND_HW == 3) - { - // As, Ad or Af clamped. - As_rgba.rgb = (float3)C_clamped; - // Cs*(Alpha + 1) might overflow, if it does then adjust alpha value - // that is sent on second output to compensate. - float3 overflow_check = (Color.rgb - (float3)255.0f) / 255.0f; - float3 alpha_compensate = max((float3)0.0f, overflow_check); - As_rgba.rgb -= alpha_compensate; - } - } - else - { - float3 Alpha = PS_BLEND_C == 2 ? (float3)Af : (float3)As; - - if (PS_BLEND_HW == 1) - { - // Needed for Cd * (As/Ad/F + 1) blending modes - Color.rgb = (float3)255.0f; - } - else if (PS_BLEND_HW == 2) - { - // Cd*As,Cd*Ad or Cd*F - Color.rgb = saturate(Alpha - (float3)1.0f) * (float3)255.0f; - } - else if (PS_BLEND_HW == 3 && PS_RTA_CORRECTION == 0) - { - // Needed for Cs*Ad, Cs*Ad + Cd, Cd - Cs*Ad - // Multiply Color.rgb by (255/128) to compensate for wrong Ad/255 value when rgb are below 128. - // When any color channel is higher than 128 then adjust the compensation automatically - // to give us more accurate colors, otherwise they will be wrong. - // The higher the value (>128) the lower the compensation will be. - float max_color = max(max(Color.r, Color.g), Color.b); - float color_compensate = 255.0f / max(128.0f, max_color); - Color.rgb *= (float3)color_compensate; - } - else if (PS_BLEND_HW == 4) - { - // Needed for Cd * (1 - Ad) and Cd*(1 + Alpha). - As_rgba.rgb = Alpha * (float3)(128.0f / 255.0f); - Color.rgb = (float3)127.5f; - } - else if (PS_BLEND_HW == 5) - { - // Needed for Cs*Alpha + Cd*(1 - Alpha). - Alpha *= (float3)(128.0f / 255.0f); - As_rgba.rgb = (Alpha - (float3)0.5f); - Color.rgb = (Color.rgb * Alpha); - } - else if (PS_BLEND_HW == 6) - { - // Needed for Cd*Alpha + Cs*(1 - Alpha). - Alpha *= (float3)(128.0f / 255.0f); - As_rgba.rgb = Alpha; - Color.rgb *= (Alpha - (float3)0.5f); - } - } -} - -#if PS_ROV_EARLYDEPTHSTENCIL -[earlydepthstencil] -#endif - -#if PS_ROV_COLOR || PS_ROV_DEPTH - #define DISCARD { rov_discard_color = true; rov_discard_depth = true; } - #define DISCARD_COLOR rov_discard_color = true - #define DISCARD_DEPTH rov_discard_depth = true -#else - #define DISCARD discard - #define DISCARD_COLOR o_col0 = RtLoad(input.p.xy) - #define DISCARD_DEPTH input.p.z = DepthLoad(input.p.xy) -#endif - -#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) -PS_OUTPUT ps_main(PS_INPUT input) -#else -void ps_main(PS_INPUT input) -#endif -{ - // Must floor before depth testing. -#if PS_ZFLOOR - input.p.z = floor(input.p.z * exp2(32.0f)) * exp2(-32.0f); -#endif - -#if PS_ROV_COLOR - rov_rt_value = RtTextureRov[input.p.xy]; -#endif - -#if PS_ROV_DEPTH - rov_depth_value = DepthTextureRov[input.p.xy]; -#endif - -#if PS_ROV_COLOR || PS_ROV_DEPTH - bool rov_discard_color = false; - bool rov_discard_depth = false; -#endif - - // Use ROV discard macro for since we cannot do - // conditional discard based on value read from ROV. -#if PS_ZTST == ZTST_GEQUAL - if (input.p.z < DepthLoad(input.p.xy)) - DISCARD; -#elif PS_ZTST == ZTST_GREATER - if (input.p.z <= DepthLoad(input.p.xy)) - DISCARD; -#endif - - float4 C = ps_color(input); - -#if PS_AA1 - #if PS_AA1 == PS_AA1_LINE - // Blur only outer part of the line by scaling coverage. - float cov = clamp(LineCovScale * (1.0f - abs(input.inv_cov)), 0.0f, 1.0f); - #else - float cov = clamp(1.0f - abs(input.inv_cov), 0.0f, 1.0f); - #endif - #if PS_ABE - if (floor(C.a) == 128.0f) // The coverage is only used if the fragment alpha is 128. - C.a = 128.0f * cov; - #else - C.a = 128.0f * cov; - #endif -#elif PS_FIXED_ONE_A - // AA (Fixed one) will output a coverage of 1.0 as alpha - C.a = 128.0f; -#endif - - bool atst_pass = atst(C); - -#if PS_ATST != PS_ATST_NONE && PS_AFAIL == AFAIL_KEEP - if (!atst_pass) - discard; -#endif - - if (PS_SCANMSK & 2) - { - // fail depth test on prohibited lines - if ((int(input.p.y) & 1) == (PS_SCANMSK & 1)) - discard; - } - - float4 alpha_blend = (float4)0.0f; - if (SW_AD_TO_HW) - { - float4 RT = PS_RTA_CORRECTION ? trunc(RtLoad(input.p.xy) * 128.0f + 0.1f) : trunc(RtLoad(input.p.xy) * 255.0f + 0.1f); - alpha_blend = (float4)(RT.a / 128.0f); - } - else - { - alpha_blend = (float4)(C.a / 128.0f); - } - - // Alpha correction - if (PS_DST_FMT == FMT_16) - { - float A_one = 128.0f; // alpha output will be 0x80 - C.a = PS_FBA ? A_one : step(A_one, C.a) * A_one; - } - else if ((PS_DST_FMT == FMT_32) && PS_FBA) - { - float A_one = 128.0f; - if (C.a < A_one) C.a += A_one; - } - -#if PS_DATE >= 5 - -#if PS_WRITE_RG == 1 - // Pseudo 16 bits access. - float rt_a = RtLoad(input.p.xy).g; -#else - float rt_a = RtLoad(input.p.xy).a; -#endif - -#if (PS_DATE & 3) == 1 - // DATM == 0: Pixel with alpha equal to 1 will failed - #if PS_RTA_CORRECTION - bool bad = (254.5f / 255.0f) < rt_a; - #else - bool bad = (127.5f / 255.0f) < rt_a; - #endif -#elif (PS_DATE & 3) == 2 - // DATM == 1: Pixel with alpha equal to 0 will failed - #if PS_RTA_CORRECTION - bool bad = rt_a < (254.5f / 255.0f); - #else - bool bad = rt_a < (127.5f / 255.0f); - #endif -#endif - -if (bad) - discard; -#endif - -#if PS_DATE == 3 - // Note gl_PrimitiveID == stencil_ceil will be the primitive that will update - // the bad alpha value so we must keep it. - int stencil_ceil = int(PrimMinTexture.Load(int3(input.p.xy, 0))); - if (int(input.primid) > stencil_ceil) - discard; -#endif - - // Output values -#if !PS_NO_COLOR - #if PS_DATE == 1 || PS_DATE == 2 - float o_col0; - #else - float4 o_col0; - #if !PS_NO_COLOR1 - float4 o_col1; - #endif - #endif -#endif - - // Get first primitive that will write a failling alpha value -#if PS_DATE == 1 - // DATM == 0 - // Pixel with alpha equal to 1 will failed (128-255) - o_col0 = (C.a > 127.5f) ? float(input.primid) : float(0x7FFFFFFF); - -#elif PS_DATE == 2 - - // DATM == 1 - // Pixel with alpha equal to 0 will failed (0-127) - o_col0 = (C.a < 127.5f) ? float(input.primid) : float(0x7FFFFFFF); - -#else - // Not primid DATE setup - - ps_blend(C, alpha_blend, input.p.xy); - - if (PS_SHUFFLE) - { - if (!PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) - { - uint4 denorm_c_after = uint4(C); - if (PS_PROCESS_BA & SHUFFLE_READ) - { - C.b = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u)); - C.a = float(((denorm_c_after.g >> 6) & 0x3u) | ((denorm_c_after.b >> 1) & 0x7Cu) | (denorm_c_after.a & 0x80u)); - } - else - { - C.r = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u)); - C.g = float(((denorm_c_after.g >> 6) & 0x3u) | ((denorm_c_after.b >> 1) & 0x7Cu) | (denorm_c_after.a & 0x80u)); - } - } - - - // Special case for 32bit input and 16bit output, shuffle used by The Godfather - if (PS_SHUFFLE_SAME) - { - uint4 denorm_c = uint4(C); - - if (PS_PROCESS_BA & SHUFFLE_READ) - C = (float4)(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u))); - else - C.ga = C.rg; - } - // Copy of a 16bit source in to this target - else if (PS_READ16_SRC) - { - uint4 denorm_c = uint4(C); - uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f); - C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)); - C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)); - } - else if (PS_SHUFFLE_ACROSS) - { - if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) - { - C.br = C.rb; - C.ag = C.ga; - } - else if(PS_PROCESS_BA & SHUFFLE_READ) - { - C.rb = C.bb; - C.ga = C.aa; - } - else - { - C.rb = C.rr; - C.ga = C.gg; - } - } - } - - ps_dither(C.rgb, alpha_blend.a, input.p.xy); - - // Color clamp/wrap needs to be done after sw blending and dithering - ps_color_clamp_wrap(C.rgb); - - ps_fbmask(C, input.p.xy); - -#if (PS_AFAIL == AFAIL_RGB_ONLY_DSB) && !PS_NO_COLOR1 - // Use alpha blend factor to determine whether to update A. - alpha_blend.a = float(atst_pass); -#endif - - // Output color scaling -#if !PS_NO_COLOR - o_col0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f; - o_col0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f; -#if !PS_NO_COLOR1 - o_col1 = alpha_blend; -#endif -#endif // !PS_NO_COLOR - - // Alpha test with feedback -#if PS_AFAIL == AFAIL_FB_ONLY - if (!atst_pass) - DISCARD_DEPTH; -#elif PS_AFAIL == AFAIL_ZB_ONLY - if (!atst_pass) - DISCARD_COLOR; -#elif PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z - if (!atst_pass) - { - o_col0.a = RtLoad(input.p.xy).a; // discard alpha - #if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z - DISCARD_DEPTH; - #endif - } -#endif - -#endif // PS_DATE != 1/2 - -#if PS_ZCLAMP - input.p.z = min(input.p.z, MaxDepthPS); -#endif - -#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z - if (!bool(input.interior)) - DISCARD_DEPTH; // No depth update for triangle edges. -#endif - -#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) - PS_OUTPUT output; -#endif - - // Color write back -#if PS_RETURN_COLOR - output.c0 = o_col0; - #if !PS_NO_COLOR1 - output.c1 = o_col1; - #endif -#elif PS_RETURN_COLOR_ROV - o_col0 = (FbMask == 0xFFu) ? RtLoad(input.p.xy) : o_col0; // channel masking - if (!rov_discard_color) - RtWrite(input.p.xy, o_col0); -#endif - - // Depth write back -#if PS_RETURN_DEPTH - output.depth = input.p.z; - #if SW_DEPTH && PS_NO_COLOR1 && PS_DEPTH_FEEDBACK_SUPPORT == 2 - // Output color clone for feedback. - output.depth_color = input.p.z; - #endif -#elif PS_RETURN_DEPTH_ROV - if (!rov_discard_depth) - DepthWrite(input.p.xy, input.p.z); -#endif - -#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) - return output; -#endif -} - +#define MONOLITHIC 1 + +#include "tfx_ps_resources.hlsl" + +#include "tfx_ps_rt.hlsl" +#include "tfx_ps_rta_correction.hlsl" +#include "tfx_ps_sample_af.hlsl" +#include "tfx_ps_sample.hlsl" +#include "tfx_ps_fetch.hlsl" +#include "tfx_ps_tfx.hlsl" +#include "tfx_ps_atst.hlsl" +#include "tfx_ps_fog.hlsl" +#include "tfx_ps_color.hlsl" +#include "tfx_ps_misc.hlsl" +#include "tfx_ps_blend.hlsl" +#include "tfx_ps_main.hlsl" #endif // PIXEL_SHADER ////////////////////////////////////////////////////////////////////// diff --git a/bin/resources/shaders/dx11/tfx_defines.hlsl b/bin/resources/shaders/dx11/tfx_defines.hlsl new file mode 100644 index 0000000000000..ccaa511019884 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_defines.hlsl @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef TFX_DEFINES_H +#define TFX_DEFINES_H + +#define FMT_32 0 +#define FMT_24 1 +#define FMT_16 2 + +#define SHUFFLE_READ 1 +#define SHUFFLE_WRITE 2 +#define SHUFFLE_READWRITE 3 + +#endif diff --git a/bin/resources/shaders/dx11/tfx_ps_atst.hlsl b/bin/resources/shaders/dx11/tfx_ps_atst.hlsl new file mode 100644 index 0000000000000..f7ecc6e8bdda5 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_atst.hlsl @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef PS_ATST_NONE +#define PS_ATST_NONE 0 +#define PS_ATST_LEQUAL 1 +#define PS_ATST_GEQUAL 2 +#define PS_ATST_EQUAL 3 +#define PS_ATST_NOTEQUAL 4 +#endif + +#ifndef PS_ATST +#define PS_ATST 1 +#endif + +#include "tfx_ps_resources.hlsl" + +export bool atst(float4 C) +{ + float a = C.a; +#if PS_ATST == PS_ATST_LEQUAL + return (a <= AREF); +#elif PS_ATST == PS_ATST_GEQUAL + return (a >= AREF); +#elif PS_ATST == PS_ATST_EQUAL + return (abs(a - AREF) <= 0.5f); +#elif PS_ATST == PS_ATST_NOTEQUAL + return (abs(a - AREF) >= 0.5f); +#else + return true; +#endif +} diff --git a/bin/resources/shaders/dx11/tfx_ps_blend.hlsl b/bin/resources/shaders/dx11/tfx_ps_blend.hlsl new file mode 100644 index 0000000000000..5366a7589eafb --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_blend.hlsl @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "tfx_defines.hlsl" +#include "tfx_ps_resources.hlsl" + +#ifndef PS_BLEND_HW +#define PS_BLEND_HW 0 +#define PS_A_MASKED 0 +#define PS_SHUFFLE 0 +#define PS_PROCESS_BA 0 +#define PS_COLCLIP_HW 0 +#define PS_BLEND_A 0 +#define PS_BLEND_B 0 +#define PS_BLEND_C 0 +#define PS_BLEND_D 0 +#define PS_BLEND_MIX 0 +#define PS_PABE 0 +#endif + +#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D) +#define SW_BLEND_NEEDS_RT (SW_BLEND && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1 || PS_BLEND_D == 1)) +#define SW_AD_TO_HW (PS_BLEND_C == 1 && PS_A_MASKED) + +float4 RtLoad(int2 xy); + +float rta_correction_factor(); +void rta_correction_blend_hw_3(inout float4 Color); + +export void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy) +{ + float As = As_rgba.a; + + if (SW_BLEND) + { + // PABE + if (PS_PABE) + { + // As_rgba needed for accumulation blend to manipulate Cd. + // No blending so early exit + if (As < 1.0f) + { + As_rgba.rgb = (float3)0.0f; + return; + } + + As_rgba.rgb = (float3)1.0f; + } + + float4 RT = SW_BLEND_NEEDS_RT ? RtLoad(int2(pos_xy)) : (float4) 0.0f; + + if (PS_SHUFFLE && SW_BLEND_NEEDS_RT) + { + uint4 denorm_rt = uint4(RT); + if (PS_PROCESS_BA & SHUFFLE_WRITE) + { + RT.r = float((denorm_rt.b << 3) & 0xF8u); + RT.g = float(((denorm_rt.b >> 2) & 0x38u) | ((denorm_rt.a << 6) & 0xC0u)); + RT.b = float((denorm_rt.a << 1) & 0xF8u); + RT.a = float(denorm_rt.a & 0x80u); + } + else + { + RT.r = float((denorm_rt.r << 3) & 0xF8u); + RT.g = float(((denorm_rt.r >> 2) & 0x38u) | ((denorm_rt.g << 6) & 0xC0u)); + RT.b = float((denorm_rt.g << 1) & 0xF8u); + RT.a = float(denorm_rt.g & 0x80u); + } + } + + float Ad = trunc(RT.a * rta_correction_factor() + 0.1f) / 128.0f; + float color_multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; + float3 Cd = trunc(RT.rgb * color_multi + 0.1f); + float3 Cs = Color.rgb; + + float3 A = (PS_BLEND_A == 0) ? Cs : ((PS_BLEND_A == 1) ? Cd : (float3)0.0f); + float3 B = (PS_BLEND_B == 0) ? Cs : ((PS_BLEND_B == 1) ? Cd : (float3)0.0f); + float C = (PS_BLEND_C == 0) ? As : ((PS_BLEND_C == 1) ? Ad : Af); + float3 D = (PS_BLEND_D == 0) ? Cs : ((PS_BLEND_D == 1) ? Cd : (float3)0.0f); + + // As/Af clamp alpha for Blend mix + // We shouldn't clamp blend mix with blend hw 1 as we want alpha higher + float C_clamped = C; + if (PS_BLEND_MIX > 0 && PS_BLEND_HW != 1 && PS_BLEND_HW != 2) + C_clamped = saturate(C_clamped); + + if (PS_BLEND_A == PS_BLEND_B) + Color.rgb = D; + // In blend_mix, HW adds on some alpha factor * dst. + // Truncating here wouldn't quite get the right result because it prevents the <1 bit here from combining with a <1 bit in dst to form a ≥1 amount that pushes over the truncation. + // Instead, apply an offset to convert HW's round to a floor. + // Since alpha is in 1/128 increments, subtracting (0.5 - 0.5/128 == 127/256) would get us what we want if GPUs blended in full precision. + // But they don't. Details here: https://github.com/PCSX2/pcsx2/pull/6809#issuecomment-1211473399 + // Based on the scripts at the above link, the ideal choice for Intel GPUs is 126/256, AMD 120/256. Nvidia is a lost cause. + // 124/256 seems like a reasonable compromise, providing the correct answer 99.3% of the time on Intel (vs 99.6% for 126/256), and 97% of the time on AMD (vs 97.4% for 120/256). + else if (PS_BLEND_MIX == 2) + Color.rgb = ((A - B) * C_clamped + D) + (124.0f / 256.0f); + else if (PS_BLEND_MIX == 1) + Color.rgb = ((A - B) * C_clamped + D) - (124.0f / 256.0f); + else + Color.rgb = trunc(((A - B) * C) + D); + + if (PS_BLEND_HW == 1) + { + // As or Af + As_rgba.rgb = (float3)C; + // Subtract 1 for alpha to compensate for the changed equation, + // if c.rgb > 255.0f then we further need to adjust alpha accordingly, + // we pick the lowest overflow from all colors because it's the safest, + // we divide by 255 the color because we don't know Cd value, + // changed alpha should only be done for hw blend. + float3 alpha_compensate = max((float3)1.0f, Color.rgb / (float3)255.0f); + As_rgba.rgb -= alpha_compensate; + } + else if (PS_BLEND_HW == 2) + { + // Since we can't do Cd*(Alpha + 1) - Cs*Alpha in hw blend + // what we can do is adjust the Cs value that will be + // subtracted, this way we can get a better result in hw blend. + // Result is still wrong but less wrong than before. + float division_alpha = 1.0f + C; + Color.rgb /= (float3)division_alpha; + } + else if (PS_BLEND_HW == 3) + { + // As, Ad or Af clamped. + As_rgba.rgb = (float3)C_clamped; + // Cs*(Alpha + 1) might overflow, if it does then adjust alpha value + // that is sent on second output to compensate. + float3 overflow_check = (Color.rgb - (float3)255.0f) / 255.0f; + float3 alpha_compensate = max((float3)0.0f, overflow_check); + As_rgba.rgb -= alpha_compensate; + } + } + else + { + float3 Alpha = PS_BLEND_C == 2 ? (float3)Af : (float3)As; + + if (PS_BLEND_HW == 1) + { + // Needed for Cd * (As/Ad/F + 1) blending modes + Color.rgb = (float3)255.0f; + } + else if (PS_BLEND_HW == 2) + { + // Cd*As,Cd*Ad or Cd*F + Color.rgb = saturate(Alpha - (float3)1.0f) * (float3)255.0f; + } + else if (PS_BLEND_HW == 3) + { + // Needed for Cs*Ad, Cs*Ad + Cd, Cd - Cs*Ad + // Multiply Color.rgb by (255/128) to compensate for wrong Ad/255 value when rgb are below 128. + // When any color channel is higher than 128 then adjust the compensation automatically + // to give us more accurate colors, otherwise they will be wrong. + // The higher the value (>128) the lower the compensation will be. + rta_correction_blend_hw_3(Color); + } + else if (PS_BLEND_HW == 4) + { + // Needed for Cd * (1 - Ad) and Cd*(1 + Alpha). + As_rgba.rgb = Alpha * (float3)(128.0f / 255.0f); + Color.rgb = (float3)127.5f; + } + else if (PS_BLEND_HW == 5) + { + // Needed for Cs*Alpha + Cd*(1 - Alpha). + Alpha *= (float3)(128.0f / 255.0f); + As_rgba.rgb = (Alpha - (float3)0.5f); + Color.rgb = (Color.rgb * Alpha); + } + else if (PS_BLEND_HW == 6) + { + // Needed for Cd*Alpha + Cs*(1 - Alpha). + Alpha *= (float3)(128.0f / 255.0f); + As_rgba.rgb = Alpha; + Color.rgb *= (Alpha - (float3)0.5f); + } + } +} + +export float4 ps_alpha_blend(float2 xy, float Ca) +{ + if (SW_AD_TO_HW) + { + float4 RT = trunc(RtLoad(xy) * rta_correction_factor() + 0.1f); + return (float4)(RT.a / 128.0f); + } + else + { + return (float4)(Ca / 128.0f); + } +} diff --git a/bin/resources/shaders/dx11/tfx_ps_color.hlsl b/bin/resources/shaders/dx11/tfx_ps_color.hlsl new file mode 100644 index 0000000000000..4b40d8f42e00a --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_color.hlsl @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "tfx_defines.hlsl" + +#ifndef PS_FST +#define PS_FST 0 +#define PS_AEM 0 +#define PS_SHUFFLE 0 +#define PS_SHUFFLE_SAME 0 +#define PS_PROCESS_BA 0 +#define PS_PROCESS_RG 0 +#define PS_READ16_SRC 0 +#define PS_DEPTH_FMT 0 +#define PS_CHANNEL_FETCH 0 +#endif + +#include "tfx_ps_resources.hlsl" + +float4 fetch_red(int2 xy); +float4 fetch_green(int2 xy); +float4 fetch_blue(int2 xy); +float4 fetch_alpha(int2 xy); +float4 fetch_rgb(int2 xy); +float4 fetch_gXbY(int2 xy); + +float4 sample_color(float2 st, float uv_w, int2 xy); +float4 sample_depth(float2 st, float2 pos); + +float4 tfx(float4 T, float4 C); + +float4 fog(float4 c, float f); + +// Uses PS_INPUT +export float4 ps_color(float4 p, float4 t, float4 ti, float4 c) +{ +#if PS_FST == 0 + float2 st = t.xy / t.w; + float2 st_int = ti.zw / t.w; +#else + float2 st = ti.xy; + float2 st_int = ti.zw; +#endif + +#if PS_CHANNEL_FETCH == 1 + float4 T = fetch_red(int2(p.xy + ChannelShuffleOffset)); +#elif PS_CHANNEL_FETCH == 2 + float4 T = fetch_green(int2(p.xy + ChannelShuffleOffset)); +#elif PS_CHANNEL_FETCH == 3 + float4 T = fetch_blue(int2(p.xy + ChannelShuffleOffset)); +#elif PS_CHANNEL_FETCH == 4 + float4 T = fetch_alpha(int2(p.xy + ChannelShuffleOffset)); +#elif PS_CHANNEL_FETCH == 5 + float4 T = fetch_rgb(int2(p.xy + ChannelShuffleOffset)); +#elif PS_CHANNEL_FETCH == 6 + float4 T = fetch_gXbY(int2(p.xy + ChannelShuffleOffset)); +#elif PS_DEPTH_FMT > 0 + float4 T = sample_depth(st_int, p.xy); +#else + float4 T = sample_color(st, t.w, int2(p.xy)); +#endif + + if (PS_SHUFFLE && !PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) + { + uint4 denorm_c_before = uint4(T); + if (PS_PROCESS_BA & SHUFFLE_READ) + { + T.r = float((denorm_c_before.b << 3) & 0xF8u); + T.g = float(((denorm_c_before.b >> 2) & 0x38u) | ((denorm_c_before.a << 6) & 0xC0u)); + T.b = float((denorm_c_before.a << 1) & 0xF8u); + T.a = float(denorm_c_before.a & 0x80u); + } + else + { + T.r = float((denorm_c_before.r << 3) & 0xF8u); + T.g = float(((denorm_c_before.r >> 2) & 0x38u) | ((denorm_c_before.g << 6) & 0xC0u)); + T.b = float((denorm_c_before.g << 1) & 0xF8u); + T.a = float(denorm_c_before.g & 0x80u); + } + + T.a = (T.a >= 127.5f ? TA.y : !PS_AEM || any(int3(T.rgb) & 0xF8) ? TA.x : 0) * 255.0f; + } + + float4 C = tfx(T, c); + + C = fog(C, t.z); + + return C; +} diff --git a/bin/resources/shaders/dx11/tfx_ps_fetch.hlsl b/bin/resources/shaders/dx11/tfx_ps_fetch.hlsl new file mode 100644 index 0000000000000..9fa04a89cd730 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_fetch.hlsl @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef PS_DEPTH_FMT +// fetch_red, fetch_green, fetch_blue, fetch_gXbY +#define PS_DEPTH_FMT 0 +// fetch_raw_depth, fetch_raw_color, fetch_c +#define PS_TEX_IS_FB 0 +#endif + +#include "tfx_ps_resources.hlsl" + +float4 RtLoad(int2 xy); + +float4 sample_p_norm(float u); + +uint fetch_raw_depth(int2 xy) +{ +#if PS_TEX_IS_FB == 1 + float4 col = RtLoad(xy); +#else + float4 col = Texture.Load(int3(xy, 0)); +#endif + return (uint)(col.r * exp2(32.0f)); +} + +float4 fetch_raw_color(int2 xy) +{ +#if PS_TEX_IS_FB == 1 + return RtLoad(xy); +#else + return Texture.Load(int3(xy, 0)); +#endif +} + +export float4 fetch_c(int2 uv) +{ +#if PS_TEX_IS_FB == 1 + return RtLoad(uv); +#else + return Texture.Load(int3(uv, 0)); +#endif +} + +export float4 fetch_red(int2 xy) +{ + float4 rt; + + if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) + { + uint depth = (fetch_raw_depth(xy)) & 0xFFu; + rt = (float4)(depth) / 255.0f; + } + else + { + rt = fetch_raw_color(xy); + } + + return sample_p_norm(rt.r) * 255.0f; +} + +export float4 fetch_green(int2 xy) +{ + float4 rt; + + if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) + { + uint depth = (fetch_raw_depth(xy) >> 8u) & 0xFFu; + rt = (float4)(depth) / 255.0f; + } + else + { + rt = fetch_raw_color(xy); + } + + return sample_p_norm(rt.g) * 255.0f; +} + +export float4 fetch_blue(int2 xy) +{ + float4 rt; + + if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) + { + uint depth = (fetch_raw_depth(xy) >> 16u) & 0xFFu; + rt = (float4)(depth) / 255.0f; + } + else + { + rt = fetch_raw_color(xy); + } + + return sample_p_norm(rt.b) * 255.0f; +} + +export float4 fetch_alpha(int2 xy) +{ + float4 rt = fetch_raw_color(xy); + return sample_p_norm(rt.a) * 255.0f; +} + +export float4 fetch_rgb(int2 xy) +{ + float4 rt = fetch_raw_color(xy); + float4 c = float4(sample_p_norm(rt.r).r, sample_p_norm(rt.g).g, sample_p_norm(rt.b).b, 1.0); + return c * 255.0f; +} + +export float4 fetch_gXbY(int2 xy) +{ + if ((PS_DEPTH_FMT == 1) || (PS_DEPTH_FMT == 2)) + { + uint depth = fetch_raw_depth(xy); + uint bg = (depth >> (8u + uint(ChannelShuffle.w))) & 0xFFu; + return (float4)(bg); + } + else + { + int4 rt = (int4)(fetch_raw_color(xy) * 255.0); + int green = (rt.g >> ChannelShuffle.w) & ChannelShuffle.z; + int blue = (rt.b << ChannelShuffle.y) & ChannelShuffle.x; + return (float4)(green | blue); + } +} diff --git a/bin/resources/shaders/dx11/tfx_ps_fog.hlsl b/bin/resources/shaders/dx11/tfx_ps_fog.hlsl new file mode 100644 index 0000000000000..db4988acd9aa8 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_fog.hlsl @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef PS_FOG +#define PS_FOG 0 +#endif + +#include "tfx_ps_resources.hlsl" + +export float4 fog(float4 c, float f) +{ + if(PS_FOG) + { + c.rgb = trunc(lerp(FogColor, c.rgb, (f * 255.0f) / 256.0f)); + } + + return c; +} diff --git a/bin/resources/shaders/dx11/tfx_ps_main.hlsl b/bin/resources/shaders/dx11/tfx_ps_main.hlsl new file mode 100644 index 0000000000000..dcc0033e70341 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_main.hlsl @@ -0,0 +1,469 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "tfx_defines.hlsl" +#include "tfx_ps_resources.hlsl" + +#ifndef VS_IIP +#define VS_IIP 0 +#endif + +#ifndef GS_IIP +#define GS_IIP 0 +#define GS_FORWARD_PRIMID 0 +#endif + +#ifndef ZTST_GEQUAL +#define ZTST_GEQUAL 2 +#define ZTST_GREATER 3 +#endif + +#ifndef AFAIL_KEEP +#define AFAIL_KEEP 0 +#define AFAIL_FB_ONLY 1 +#define AFAIL_ZB_ONLY 2 +#define AFAIL_RGB_ONLY 3 +#define AFAIL_RGB_ONLY_DSB 4 +#define AFAIL_RGB_ONLY_SW_Z 5 +#endif + +#ifndef PS_AA1_NONE +#define PS_AA1_NONE 0 +#define PS_AA1_LINE 1 +#define PS_AA1_TRIANGLE 2 +#define PS_AA1_TRIANGLE_SW_Z 3 +#endif + +#ifndef PS_ROV_DEPTH_NONE +#define PS_ROV_DEPTH_NONE 0 +#define PS_ROV_DEPTH_READ_WRITE 1 +#define PS_ROV_DEPTH_READ_ONLY 2 +#endif + +#ifndef PS_IIP +#define PS_IIP 0 +#define PS_SHUFFLE 0 +#define PS_SHUFFLE_SAME 0 +#define PS_PROCESS_BA 0 +#define PS_PROCESS_RG 0 +#define PS_SHUFFLE_ACROSS 0 +#define PS_READ16_SRC 0 +#define PS_WRITE_RG 0 +#define PS_COLCLIP_HW 0 +#define PS_FIXED_ONE_A 0 +#define PS_ZCLAMP 0 +#define PS_ZFLOOR 0 +#define PS_SCANMSK 0 +#define PS_NO_COLOR 0 +#define PS_NO_COLOR1 0 +#define PS_DATE 0 +#define PS_AA1 0 +#define PS_ABE 0 +#define PS_AFAIL 0 +#define PS_ZTST 0 + +#define PS_ROV_COLOR 0 +#define PS_ROV_DEPTH 0 +#endif + +#define NEEDS_RT_FOR_AFAIL (PS_AFAIL == AFAIL_ZB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z) +#define NEEDS_DEPTH_FOR_AFAIL (PS_AFAIL == AFAIL_FB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z) +#define NEEDS_DEPTH_FOR_ZTST (PS_ZTST == ZTST_GEQUAL || PS_ZTST == ZTST_GREATER) +#define NEEDS_DEPTH_FOR_AA1 (PS_AA1 == PS_AA1_TRIANGLE_SW_Z) +#define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1) +#define ZWRITE (PS_ZFLOOR || PS_ZCLAMP || SW_DEPTH) + +#define PS_RETURN_COLOR_ROV (!PS_NO_COLOR && PS_ROV_COLOR) +#define PS_RETURN_COLOR (!PS_NO_COLOR && !PS_ROV_COLOR) +#define PS_RETURN_DEPTH_ROV (PS_ROV_DEPTH == PS_ROV_DEPTH_READ_WRITE) +#define PS_RETURN_DEPTH (ZWRITE && !PS_ROV_DEPTH) +#define PS_ROV_EARLYDEPTHSTENCIL (PS_ROV_COLOR && !PS_ROV_DEPTH && !ZWRITE) + +#if !PS_ROV_DEPTH +Texture2D DepthTexture : register(t4); +#else +RasterizerOrderedTexture2D DepthTextureRov : register(u1); +static float rov_depth_value; +#endif + +struct PS_INPUT +{ + noperspective centroid float4 p : SV_Position; + float4 t : TEXCOORD0; + float4 ti : TEXCOORD2; +#if VS_IIP != 0 || GS_IIP != 0 || PS_IIP != 0 + float4 c : COLOR0; +#else + nointerpolation float4 c : COLOR0; +#endif + float inv_cov : COLOR1; // We use the inverse to make it simpler to interpolate. + nointerpolation uint interior : COLOR2; // 1 for triangle interior; 0 for edge; +#if (PS_DATE >= 1 && PS_DATE <= 3) || GS_FORWARD_PRIMID + uint primid : SV_PrimitiveID; +#endif +}; + +struct PS_OUTPUT +{ +#define NUM_RTS 0 + +#if PS_RETURN_COLOR + #if PS_DATE == 1 || PS_DATE == 2 + float c : SV_Target; + #else + + float4 c0 : SV_Target0; + + #undef NUM_RTS + #define NUM_RTS 1 + + #if !PS_NO_COLOR1 + float4 c1 : SV_Target1; + #endif + #endif +#endif + +#if PS_RETURN_DEPTH + // In DX12 we do depth feedback loops with a color copy. + #if SW_DEPTH && PS_NO_COLOR1 && PS_DEPTH_FEEDBACK_SUPPORT == 2 + #if NUM_RTS > 0 + float depth_color : SV_Target1; + #else + float depth_color : SV_Target0; + #endif + #endif + #if PS_HAS_CONSERVATIVE_DEPTH && !SW_DEPTH + float depth : SV_DepthLessEqual; + #else + float depth : SV_Depth; + #endif +#endif + +#undef NUM_RTS +}; + +void RtInit(int2 xy); +float4 RtLoad(int2 xy); +void RtWrite(int2 xy, float4 c); + +bool atst(float4 C); + +float4 ps_color(float4 p, float4 t, float4 ti, float4 c); +void ps_fbmask(inout float4 C, float2 pos_xy); +void ps_dither(inout float3 C, float As, float2 pos_xy); +void ps_color_clamp_wrap(inout float3 C); +void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy); +float4 ps_alpha_blend(float2 p, float Ca); +void ps_alpha_correction(inout float Ca); + +float rta_correction_factor(); +float rta_correction_lim(); + +float DepthLoad(int2 xy) +{ +#if PS_ROV_DEPTH + return rov_depth_value; +#else + return DepthTexture.Load(int3(int2(xy), 0)); +#endif +} + +void DepthWrite(int2 xy, float d) +{ +#if PS_ROV_DEPTH + DepthTextureRov[xy] = d; +#endif +} + +#if PS_ROV_EARLYDEPTHSTENCIL +[earlydepthstencil] +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH + #define DISCARD { rov_discard_color = true; rov_discard_depth = true; } + #define DISCARD_COLOR rov_discard_color = true + #define DISCARD_DEPTH rov_discard_depth = true +#else + #define DISCARD discard + #define DISCARD_COLOR o_col0 = RtLoad(input.p.xy) + #define DISCARD_DEPTH input.p.z = DepthLoad(input.p.xy) +#endif + +#ifndef MONOLITHIC +[shader("pixel")] +#endif +#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) +PS_OUTPUT ps_main(PS_INPUT input) +#else +void ps_main(PS_INPUT input) +#endif +{ + // Must floor before depth testing. +#if PS_ZFLOOR + input.p.z = floor(input.p.z * exp2(32.0f)) * exp2(-32.0f); +#endif + +#if PS_ROV_COLOR + RtInit(input.p.xy); +#endif + +#if PS_ROV_DEPTH + rov_depth_value = DepthTextureRov[input.p.xy]; +#endif + +#if PS_ROV_COLOR || PS_ROV_DEPTH + bool rov_discard_color = false; + bool rov_discard_depth = false; +#endif + + // Use ROV discard macro for since we cannot do + // conditional discard based on value read from ROV. +#if PS_ZTST == ZTST_GEQUAL + if (input.p.z < DepthLoad(input.p.xy)) + DISCARD; +#elif PS_ZTST == ZTST_GREATER + if (input.p.z <= DepthLoad(input.p.xy)) + DISCARD; +#endif + + float4 C = ps_color(input.p, input.t, input.ti, input.c); + +#if PS_AA1 + #if PS_AA1 == PS_AA1_LINE + // Blur only outer part of the line by scaling coverage. + float cov = clamp(LineCovScale * (1.0f - abs(input.inv_cov)), 0.0f, 1.0f); + #else + float cov = clamp(1.0f - abs(input.inv_cov), 0.0f, 1.0f); + #endif + #if PS_ABE + if (floor(C.a) == 128.0f) // The coverage is only used if the fragment alpha is 128. + C.a = 128.0f * cov; + #else + C.a = 128.0f * cov; + #endif +#elif PS_FIXED_ONE_A + // AA (Fixed one) will output a coverage of 1.0 as alpha + C.a = 128.0f; +#endif + + bool atst_pass = atst(C); + +#if PS_AFAIL == AFAIL_KEEP + // atst_pass always true with PS_ATST_NONE + if (!atst_pass) + discard; +#endif + + if (PS_SCANMSK & 2) + { + // fail depth test on prohibited lines + if ((int(input.p.y) & 1) == (PS_SCANMSK & 1)) + discard; + } + + float4 alpha_blend = ps_alpha_blend(input.p.xy, C.a); + + // Alpha correction + ps_alpha_correction(C.a); + +#if PS_DATE >= 5 + +#if PS_WRITE_RG == 1 + // Pseudo 16 bits access. + float rt_a = RtLoad(input.p.xy).g; +#else + float rt_a = RtLoad(input.p.xy).a; +#endif + +#if (PS_DATE & 3) == 1 + // DATM == 0: Pixel with alpha equal to 1 will failed + bool bad = rta_correction_lim() < rt_a; +#elif (PS_DATE & 3) == 2 + // DATM == 1: Pixel with alpha equal to 0 will failed + bool bad = rt_a < rta_correction_lim(); +#endif + + if (bad) + discard; + +#endif + +#if PS_DATE == 3 + // Note gl_PrimitiveID == stencil_ceil will be the primitive that will update + // the bad alpha value so we must keep it. + int stencil_ceil = int(PrimMinTexture.Load(int3(input.p.xy, 0))); + if (int(input.primid) > stencil_ceil) + discard; +#endif + + // Output values +#if !PS_NO_COLOR + #if PS_DATE == 1 || PS_DATE == 2 + float o_col0; + #else + float4 o_col0; + #if !PS_NO_COLOR1 + float4 o_col1; + #endif + #endif +#endif + + // Get first primitive that will write a failling alpha value +#if PS_DATE == 1 + // DATM == 0 + // Pixel with alpha equal to 1 will failed (128-255) + o_col0 = (C.a > 127.5f) ? float(input.primid) : float(0x7FFFFFFF); + +#elif PS_DATE == 2 + + // DATM == 1 + // Pixel with alpha equal to 0 will failed (0-127) + o_col0 = (C.a < 127.5f) ? float(input.primid) : float(0x7FFFFFFF); + +#else + // Not primid DATE setup + + ps_blend(C, alpha_blend, input.p.xy); + + if (PS_SHUFFLE) + { + if (!PS_SHUFFLE_SAME && !PS_READ16_SRC && !(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)) + { + uint4 denorm_c_after = uint4(C); + if (PS_PROCESS_BA & SHUFFLE_READ) + { + C.b = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u)); + C.a = float(((denorm_c_after.g >> 6) & 0x3u) | ((denorm_c_after.b >> 1) & 0x7Cu) | (denorm_c_after.a & 0x80u)); + } + else + { + C.r = float(((denorm_c_after.r >> 3) & 0x1Fu) | ((denorm_c_after.g << 2) & 0xE0u)); + C.g = float(((denorm_c_after.g >> 6) & 0x3u) | ((denorm_c_after.b >> 1) & 0x7Cu) | (denorm_c_after.a & 0x80u)); + } + } + + + // Special case for 32bit input and 16bit output, shuffle used by The Godfather + if (PS_SHUFFLE_SAME) + { + uint4 denorm_c = uint4(C); + + if (PS_PROCESS_BA & SHUFFLE_READ) + C = (float4)(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u))); + else + C.ga = C.rg; + } + // Copy of a 16bit source in to this target + else if (PS_READ16_SRC) + { + uint4 denorm_c = uint4(C); + uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f); + C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)); + C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)); + } + else if (PS_SHUFFLE_ACROSS) + { + if (PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE) + { + C.br = C.rb; + C.ag = C.ga; + } + else if(PS_PROCESS_BA & SHUFFLE_READ) + { + C.rb = C.bb; + C.ga = C.aa; + } + else + { + C.rb = C.rr; + C.ga = C.gg; + } + } + } + + ps_dither(C.rgb, alpha_blend.a, input.p.xy); + + // Color clamp/wrap needs to be done after sw blending and dithering + ps_color_clamp_wrap(C.rgb); + + ps_fbmask(C, input.p.xy); + +#if (PS_AFAIL == AFAIL_RGB_ONLY_DSB) && !PS_NO_COLOR1 + // Use alpha blend factor to determine whether to update A. + alpha_blend.a = float(atst_pass); +#endif + + // Output color scaling +#if !PS_NO_COLOR + o_col0.a = C.a / rta_correction_factor(); + o_col0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f; +#if !PS_NO_COLOR1 + o_col1 = alpha_blend; +#endif +#endif // !PS_NO_COLOR + + // Alpha test with feedback +#if PS_AFAIL == AFAIL_FB_ONLY + if (!atst_pass) + DISCARD_DEPTH; +#elif PS_AFAIL == AFAIL_ZB_ONLY + if (!atst_pass) + DISCARD_COLOR; +#elif PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z + if (!atst_pass) + { + o_col0.a = RtLoad(input.p.xy).a; // discard alpha + #if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z + DISCARD_DEPTH; + #endif + } +#endif + +#endif // PS_DATE != 1/2 + +#if PS_ZCLAMP + input.p.z = min(input.p.z, MaxDepthPS); +#endif + +#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z + if (!bool(input.interior)) + DISCARD_DEPTH; // No depth update for triangle edges. +#endif + +#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) + PS_OUTPUT output; +#endif + + // Color write back +#if PS_RETURN_COLOR + output.c0 = o_col0; + #if !PS_NO_COLOR1 + output.c1 = o_col1; + #endif +#elif PS_RETURN_COLOR_ROV + #ifdef __hlsl_dx_compiler + #define SELECT(cond, true_val, false_val) select(cond, true_val, false_val) + #else + #define SELECT(cond, true_val, false_val) (cond) ? true_val : false_val + #endif + o_col0 = SELECT(FbMask == 0xFFu, RtLoad(input.p.xy), o_col0); + if (!rov_discard_color) + RtWrite(input.p.xy, o_col0); +#endif + + // Depth write back +#if PS_RETURN_DEPTH + output.depth = input.p.z; +#if SW_DEPTH && PS_NO_COLOR1 && PS_DEPTH_FEEDBACK_SUPPORT == 2 + // Output color clone for feedback. + output.depth_color = input.p.z; +#endif +#elif PS_RETURN_DEPTH_ROV + if (!rov_discard_depth) + DepthWrite(input.p.xy, input.p.z); +#endif + +#if (PS_RETURN_COLOR || PS_RETURN_DEPTH) + return output; +#endif +} diff --git a/bin/resources/shaders/dx11/tfx_ps_misc.hlsl b/bin/resources/shaders/dx11/tfx_ps_misc.hlsl new file mode 100644 index 0000000000000..5c614259fb9b1 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_misc.hlsl @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "tfx_defines.hlsl" +#include "tfx_ps_resources.hlsl" + +#ifndef PS_BLEND_HW +// ps_color_clamp_wrap +#define PS_BLEND_HW 0 +// ps_alpha_correction +#define PS_FBA 0 +// ps_fbmask, ps_color_clamp_wrap +#define PS_FBMASK 0 +// ps_color_clamp_wrap, ps_alpha_correction +#define PS_DST_FMT 0 +// ps_fbmask, ps_color_clamp_wrap +#define PS_COLCLIP_HW 0 +// ps_color_clamp_wrap +#define PS_COLCLIP 0 +// SW_BLEND +#define PS_BLEND_A 0 +// SW_BLEND +#define PS_BLEND_B 0 +// ps_dither +#define PS_BLEND_C 0 +// SW_BLEND +#define PS_BLEND_D 0 +// ps_color_clamp_wrap +#define PS_BLEND_MIX 0 +// ps_dither +#define PS_ROUND_INV 0 +// ps_dither, ps_color_clamp_wrap +#define PS_DITHER 0 +// ps_dither +#define PS_DITHER_ADJUST 0 +#endif + +// ps_color_clamp_wrap +#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D) + +float4 RtLoad(int2 xy); + +export void ps_fbmask(inout float4 C, float2 pos_xy) +{ + if (PS_FBMASK) + { + float multi = PS_COLCLIP_HW ? 65535.0f : 255.0f; + float4 RT = trunc(RtLoad(int2(pos_xy)) * multi + 0.1f); + C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask)); + } +} + +export void ps_dither(inout float3 C, float As, float2 pos_xy) +{ + if (PS_DITHER > 0 && PS_DITHER < 3) + { + int2 fpos; + + if (PS_DITHER == 2) + fpos = int2(pos_xy); + else + fpos = int2(pos_xy * RcpScaleFactor); + + float value = DitherMatrix[fpos.x & 3][fpos.y & 3]; + + // The idea here is we add on the dither amount adjusted by the alpha before it goes to the hw blend + // so after the alpha blend the resulting value should be the same as (Cs - Cd) * As + Cd + Dither. + if (PS_DITHER_ADJUST) + { + float Alpha = PS_BLEND_C == 2 ? Af : As; + value *= Alpha > 0.0f ? min(1.0f / Alpha, 1.0f) : 1.0f; + } + + if (PS_ROUND_INV) + C -= value; + else + C += value; + } +} + +export void ps_color_clamp_wrap(inout float3 C) +{ + // When dithering the bottom 3 bits become meaningless and cause lines in the picture + // so we need to limit the color depth on dithered items + if (SW_BLEND || (PS_DITHER > 0 && PS_DITHER < 3) || PS_FBMASK) + { + if (PS_DST_FMT == FMT_16 && PS_BLEND_MIX == 0 && PS_ROUND_INV) + C += 7.0f; // Need to round up, not down since the shader will invert + + // Standard Clamp + if (PS_COLCLIP == 0 && PS_COLCLIP_HW == 0) + C = clamp(C, (float3)0.0f, (float3)255.0f); + + // In 16 bits format, only 5 bits of color are used. It impacts shadows computation of Castlevania + if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && (PS_BLEND_MIX == 0 || PS_DITHER)) + C = (float3)((int3)C & (int3)0xF8); + else if (PS_COLCLIP == 1 || PS_COLCLIP_HW == 1) + C = (float3)((int3)C & (int3)0xFF); + } + else if (PS_DST_FMT == FMT_16 && PS_DITHER != 3 && PS_BLEND_MIX == 0 && PS_BLEND_HW == 0) + C = (float3)((int3)C & (int3)0xF8); +} + +export void ps_alpha_correction(inout float Ca) +{ + if (PS_DST_FMT == FMT_16) + { + float A_one = 128.0f; // alpha output will be 0x80 + Ca = PS_FBA ? A_one : step(A_one, Ca) * A_one; + } + else if ((PS_DST_FMT == FMT_32) && PS_FBA) + { + float A_one = 128.0f; + if (Ca < A_one) Ca += A_one; + } +} diff --git a/bin/resources/shaders/dx11/tfx_ps_resources.hlsl b/bin/resources/shaders/dx11/tfx_ps_resources.hlsl new file mode 100644 index 0000000000000..efbfc2064a5e1 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_resources.hlsl @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef TFX_PS_RESOURCES_H +#define TFX_PS_RESOURCES_H + +Texture2D Texture : register(t0); +Texture2D Palette : register(t1); +Texture2D PrimMinTexture : register(t3); +SamplerState TextureSampler : register(s0); + +#ifdef DX12 +cbuffer cb1 : register(b1) +#else +cbuffer cb1 +#endif +{ + float3 FogColor; + float AREF; + float4 WH; + float2 TA; + float MaxDepthPS; + float Af; + uint4 FbMask; + float4 HalfTexel; + float4 MinMax; + float4 LODParams; + float4 STRange; + int4 ChannelShuffle; + float2 ChannelShuffleOffset; + float2 TC_OffsetHack; + float2 STScale; + float4x4 DitherMatrix; + float ScaledScaleFactor; + float RcpScaleFactor; + float _pad0_cb1; + float _pad1_cb1; + float LineCovScale; + float _pad2_cb1; + float _pad3_cb1; + float _pad4_cb1; +}; + +#endif diff --git a/bin/resources/shaders/dx11/tfx_ps_rt.hlsl b/bin/resources/shaders/dx11/tfx_ps_rt.hlsl new file mode 100644 index 0000000000000..89009838fc27f --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_rt.hlsl @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#if !PS_ROV_COLOR +Texture2D RtTexture : register(t2); +#endif + +#if PS_ROV_COLOR +RasterizerOrderedTexture2D RtTextureRov : register(u0); +static float4 rov_rt_value; +#endif + +export void RtInit(int2 xy) +{ +#if PS_ROV_COLOR + rov_rt_value = RtTextureRov[xy]; +#endif +} + +export float4 RtLoad(int2 xy) +{ +#if PS_ROV_COLOR + return rov_rt_value; +#else + return RtTexture.Load(int3(int2(xy), 0)); +#endif +} + +export void RtWrite(int2 xy, float4 c) +{ +#if PS_ROV_COLOR + RtTextureRov[xy] = c; +#endif +} diff --git a/bin/resources/shaders/dx11/tfx_ps_rta_correction.hlsl b/bin/resources/shaders/dx11/tfx_ps_rta_correction.hlsl new file mode 100644 index 0000000000000..18caeb78fba16 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_rta_correction.hlsl @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef PS_RTA_CORRECTION +#define PS_RTA_CORRECTION 0 +#endif + +export float rta_correction_factor() +{ +#if PS_RTA_CORRECTION + return 128.0f; +#else + return 255.0f; +#endif +} + +export float rta_correction_lim() +{ +#if PS_RTA_CORRECTION + return (254.5f / 255.0f); +#else + return (127.5f / 255.0f); +#endif +} + +export void rta_correction_blend_hw_3(inout float4 Color) +{ +#if PS_RTA_CORRECTION == 0 + // Needed for Cs*Ad, Cs*Ad + Cd, Cd - Cs*Ad + // Multiply Color.rgb by (255/128) to compensate for wrong Ad/255 value when rgb are below 128. + // When any color channel is higher than 128 then adjust the compensation automatically + // to give us more accurate colors, otherwise they will be wrong. + // The higher the value (>128) the lower the compensation will be. + float max_color = max(max(Color.r, Color.g), Color.b); + float color_compensate = 255.0f / max(128.0f, max_color); + Color.rgb *= (float3)color_compensate; +#endif +} diff --git a/bin/resources/shaders/dx11/tfx_ps_sample.hlsl b/bin/resources/shaders/dx11/tfx_ps_sample.hlsl new file mode 100644 index 0000000000000..752510d7e8972 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_sample.hlsl @@ -0,0 +1,454 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "tfx_ps_resources.hlsl" +#include "tfx_defines.hlsl" + +#ifndef PS_FST +// clamp_wrap_uv, sample_color +#define PS_FST 0 +// clamp_wrap_uv, sample_color, clamp_wrap_uv_depth +#define PS_WMS 0 +// clamp_wrap_uv, sample_color, clamp_wrap_uv_depth +#define PS_WMT 0 +// sample_c +#define PS_ADJS 0 +// sample_c +#define PS_ADJT 0 +// sample_color, sample_depth +#define PS_AEM_FMT FMT_32 +// sample_color, sample_depth +#define PS_AEM 0 +// sample_color +#define PS_LTF 1 +// sample_color +#define PS_TCOFFSETHACK 0 +// sample_depth +#define PS_DEPTH_FMT 0 +// sample_4_index, sample_color, sample_depth +#define PS_PAL_FMT 0 +// sample_depth +#define PS_TALES_OF_ABYSS_HLE 0 +// sample_depth +#define PS_URBAN_CHAOS_HLE 0 +// sample_4_index, sample_color +#define PS_RTA_SRC_CORRECTION 0 +// sample_c +#define PS_POINT_SAMPLER 0 +// sample_c, clamp_wrap_uv, sample_color, sample_depth +#define PS_REGION_RECT 0 +// manual_lod, sample_c +#define PS_AUTOMATIC_LOD 0 +// manual_lod, sample_c +#define PS_MANUAL_LOD 0 +// sample_c +#define PS_TEX_IS_FB 0 +// sample_c_af, sample_c +#define PS_ANISOTROPIC_FILTERING 0 +#endif + +#if PS_ANISOTROPIC_FILTERING > 1 +float4 sample_c_af(float2 uv, float uv_w); +#endif + +float4 RtLoad(int2 xy); + +float4 fetch_c(int2 uv); +uint fetch_raw_depth(int2 xy); + +#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1) +float manual_lod(float uv_w) +{ + // FIXME add LOD: K - ( LOG2(Q) * (1 << L)) + float K = LODParams.x; + float L = LODParams.y; + float bias = LODParams.z; + float max_lod = LODParams.w; + + float gs_lod = K - log2(abs(uv_w)) * L; + // FIXME max useful ? + //return max(min(gs_lod, max_lod) - bias, 0.0f); + return min(gs_lod, max_lod) - bias; +} +#endif + +float4 sample_c(float2 uv, float uv_w, int2 xy) +{ +#if PS_TEX_IS_FB == 1 + return RtLoad(xy); +#elif PS_REGION_RECT == 1 + return Texture.Load(int3(int2(uv), 0)); +#else + if (PS_POINT_SAMPLER) + { + // Weird issue with ATI/AMD cards, + // it looks like they add 127/128 of a texel to sampling coordinates + // occasionally causing point sampling to erroneously round up. + // I'm manually adjusting coordinates to the centre of texels here, + // though the centre is just paranoia, the top left corner works fine. + // As of 2018 this issue is still present. + uv = (trunc(uv * WH.zw) + float2(0.5, 0.5)) / WH.zw; + } +#if !PS_ADJS && !PS_ADJT + uv *= STScale; +#else + #if PS_ADJS + uv.x = (uv.x - STRange.x) * STRange.z; + #else + uv.x = uv.x * STScale.x; + #endif + #if PS_ADJT + uv.y = (uv.y - STRange.y) * STRange.w; + #else + uv.y = uv.y * STScale.y; + #endif +#endif + +#if PS_ANISOTROPIC_FILTERING > 1 + return sample_c_af(uv, uv_w); +#elif PS_AUTOMATIC_LOD == 1 + return Texture.Sample(TextureSampler, uv); +#elif PS_MANUAL_LOD == 1 + return Texture.SampleLevel(TextureSampler, uv, manual_lod(uv_w)); +#else + return Texture.SampleLevel(TextureSampler, uv, 0); // No lod +#endif +#endif +} + +float4 sample_p(uint u) +{ + return Palette.Load(int3(int(u), 0, 0)); +} + +export float4 sample_p_norm(float u) +{ + return sample_p(uint(u * 255.5f)); +} + +float4 clamp_wrap_uv(float4 uv) +{ + float4 tex_size = WH.xyxy; + + if(PS_WMS == PS_WMT) + { + if(PS_REGION_RECT != 0 && PS_WMS == 0) + { + uv = frac(uv); + } + else if(PS_REGION_RECT != 0 && PS_WMS == 1) + { + uv = saturate(uv); + } + else if(PS_WMS == 2) + { + uv = clamp(uv, MinMax.xyxy, MinMax.zwzw); + } + else if(PS_WMS == 3) + { + #if PS_FST == 0 + // wrap negative uv coords to avoid an off by one error that shifted + // textures. Fixes Xenosaga's hair issue. + uv = frac(uv); + #endif + uv = (float4)(((uint4)(uv * tex_size) & asuint(MinMax.xyxy)) | asuint(MinMax.zwzw)) / tex_size; + } + } + else + { + if(PS_REGION_RECT != 0 && PS_WMS == 0) + { + uv.xz = frac(uv.xz); + } + else if(PS_REGION_RECT != 0 && PS_WMS == 1) + { + uv.xz = saturate(uv.xz); + } + else if(PS_WMS == 2) + { + uv.xz = clamp(uv.xz, MinMax.xx, MinMax.zz); + } + else if(PS_WMS == 3) + { + #if PS_FST == 0 + uv.xz = frac(uv.xz); + #endif + uv.xz = (float2)(((uint2)(uv.xz * tex_size.xx) & asuint(MinMax.xx)) | asuint(MinMax.zz)) / tex_size.xx; + } + if(PS_REGION_RECT != 0 && PS_WMT == 0) + { + uv.yw = frac(uv.yw); + } + else if(PS_REGION_RECT != 0 && PS_WMT == 1) + { + uv.yw = saturate(uv.yw); + } + else if(PS_WMT == 2) + { + uv.yw = clamp(uv.yw, MinMax.yy, MinMax.ww); + } + else if(PS_WMT == 3) + { + #if PS_FST == 0 + uv.yw = frac(uv.yw); + #endif + uv.yw = (float2)(((uint2)(uv.yw * tex_size.yy) & asuint(MinMax.yy)) | asuint(MinMax.ww)) / tex_size.yy; + } + } + + if(PS_REGION_RECT != 0) + { + // Normalized -> Integer Coordinates. + uv = clamp(uv * WH.zwzw + STRange.xyxy, STRange.xyxy, STRange.zwzw); + } + + return uv; +} + +float4x4 sample_4c(float4 uv, float uv_w, int2 xy) +{ + float4x4 c; + + c[0] = sample_c(uv.xy, uv_w, xy); + c[1] = sample_c(uv.zy, uv_w, xy); + c[2] = sample_c(uv.xw, uv_w, xy); + c[3] = sample_c(uv.zw, uv_w, xy); + + return c; +} + +uint4 sample_4_index(float4 uv, float uv_w, int2 xy) +{ + float4 c; + + c.x = sample_c(uv.xy, uv_w, xy).a; + c.y = sample_c(uv.zy, uv_w, xy).a; + c.z = sample_c(uv.xw, uv_w, xy).a; + c.w = sample_c(uv.zw, uv_w, xy).a; + + // Denormalize value + uint4 i; + + if (PS_RTA_SRC_CORRECTION) + { + i = uint4(round(c * 128.25f)); // Denormalize value + } + else + { + i = uint4(c * 255.5f); // Denormalize value + } + + if (PS_PAL_FMT == 1) + { + // 4HL + return i & 0xFu; + } + else if (PS_PAL_FMT == 2) + { + // 4HH + return i >> 4u; + } + else + { + // 8 + return i; + } +} + +float4x4 sample_4p(uint4 u) +{ + float4x4 c; + + c[0] = sample_p(u.x); + c[1] = sample_p(u.y); + c[2] = sample_p(u.z); + c[3] = sample_p(u.w); + + return c; +} + +export float4 sample_color(float2 st, float uv_w, int2 xy) +{ +#if PS_TCOFFSETHACK + st += TC_OffsetHack.xy; +#endif + + float4 t; + float4x4 c; + float2 dd; + + if (PS_LTF == 0 && PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_REGION_RECT == 0 && PS_WMS < 2 && PS_WMT < 2) + { + c[0] = sample_c(st, uv_w, xy); + } + else + { + float4 uv; + + if (PS_LTF) + { + uv = st.xyxy + HalfTexel; + dd = frac(uv.xy * WH.zw); + + if (PS_FST == 0) + { + dd = clamp(dd, (float2) 0.0f, (float2) 0.9999999f); + } + } + else + { + uv = st.xyxy; + } + + uv = clamp_wrap_uv(uv); + +#if PS_PAL_FMT != 0 + c = sample_4p(sample_4_index(uv, uv_w, xy)); +#else + c = sample_4c(uv, uv_w, xy); +#endif + } + + [unroll] + for (uint i = 0; i < 4; i++) + { + if (PS_AEM_FMT == FMT_24) + { + c[i].a = !PS_AEM || any(c[i].rgb) ? TA.x : 0; + } + else if (PS_AEM_FMT == FMT_16) + { + c[i].a = c[i].a >= 0.5 ? TA.y : !PS_AEM || any(int3(c[i].rgb * 255.0f) & 0xF8) ? TA.x : 0; + } + } + + if (PS_LTF) + { + t = lerp(lerp(c[0], c[1], dd.x), lerp(c[2], c[3], dd.x), dd.y); + } + else + { + t = c[0]; + } + + if (PS_AEM_FMT == FMT_32 && PS_PAL_FMT == 0 && PS_RTA_SRC_CORRECTION) + t.a = t.a * (128.5f / 255.0f); + + return trunc(t * 255.0f + 0.05f); +} + +////////////////////////////////////////////////////////////////////// +// Depth sampling +////////////////////////////////////////////////////////////////////// + +int2 clamp_wrap_uv_depth(int2 uv) +{ + int4 mask = asint(MinMax) << 4; + if (PS_WMS == PS_WMT) + { + if (PS_WMS == 2) + { + uv = clamp(uv, mask.xy, mask.zw); + } + else if (PS_WMS == 3) + { + uv = (uv & mask.xy) | mask.zw; + } + } + else + { + if (PS_WMS == 2) + { + uv.x = clamp(uv.x, mask.x, mask.z); + } + else if (PS_WMS == 3) + { + uv.x = (uv.x & mask.x) | mask.z; + } + if (PS_WMT == 2) + { + uv.y = clamp(uv.y, mask.y, mask.w); + } + else if (PS_WMT == 3) + { + uv.y = (uv.y & mask.y) | mask.w; + } + } + return uv; +} + +export float4 sample_depth(float2 st, float2 pos) +{ + float2 uv_f = (float2)clamp_wrap_uv_depth(int2(st)) * (float2)ScaledScaleFactor; + +#if PS_REGION_RECT == 1 + uv_f = clamp(uv_f + STRange.xy, STRange.xy, STRange.zw); +#endif + + int2 uv = (int2)uv_f; + float4 t = (float4)(0.0f); + + if (PS_TALES_OF_ABYSS_HLE == 1) + { + // Warning: UV can't be used in channel effect + uint depth = fetch_raw_depth(pos); + + // Convert msb based on the palette + t = Palette.Load(int3((depth >> 8u) & 0xFFu, 0, 0)) * 255.0f; + } + else if (PS_URBAN_CHAOS_HLE == 1) + { + // Depth buffer is read as a RGB5A1 texture. The game try to extract the green channel. + // So it will do a first channel trick to extract lsb, value is right-shifted. + // Then a new channel trick to extract msb which will shifted to the left. + // OpenGL uses a FLOAT32 format for the depth so it requires a couple of conversion. + // To be faster both steps (msb&lsb) are done in a single pass. + + // Warning: UV can't be used in channel effect + uint depth = fetch_raw_depth(pos); + + // Convert lsb based on the palette + t = Palette.Load(int3(depth & 0xFFu, 0, 0)) * 255.0f; + + // Msb is easier + float green = (float)((depth >> 8u) & 0xFFu) * 36.0f; + green = min(green, 255.0f); + t.g += green; + } + else if (PS_DEPTH_FMT == 1) + { + // Based on ps_convert_depth32_rgba8 of convert + + // Convert a FLOAT32 depth texture into a RGBA color texture + uint d = uint(fetch_c(uv).r * exp2(32.0f)); + t = float4(uint4((d & 0xFFu), ((d >> 8) & 0xFFu), ((d >> 16) & 0xFFu), (d >> 24))); + } + else if (PS_DEPTH_FMT == 2) + { + // Based on ps_convert_depth16_rgb5a1 of convert + + // Convert a FLOAT32 (only 16 lsb) depth into a RGB5A1 color texture + uint d = uint(fetch_c(uv).r * exp2(32.0f)); + t = float4(uint4((d & 0x1Fu), ((d >> 5) & 0x1Fu), ((d >> 10) & 0x1Fu), (d >> 15) & 0x01u)) * float4(8.0f, 8.0f, 8.0f, 128.0f); + } + else if (PS_DEPTH_FMT == 3) + { + // Convert a RGBA/RGB5A1 color texture into a RGBA/RGB5A1 color texture + t = fetch_c(uv) * 255.0f; + } + + if (PS_AEM_FMT == FMT_24) + { + t.a = ((PS_AEM == 0) || any(bool3(t.rgb))) ? 255.0f * TA.x : 0.0f; + } + else if (PS_AEM_FMT == FMT_16) + { + t.a = t.a >= 128.0f ? 255.0f * TA.y : ((PS_AEM == 0) || any(bool3(t.rgb))) ? 255.0f * TA.x : 0.0f; + } + else if (PS_PAL_FMT != 0 && !PS_TALES_OF_ABYSS_HLE && !PS_URBAN_CHAOS_HLE) + { + t = trunc(sample_4p(uint4(t.aaaa))[0] * 255.0f + 0.05f); + } + + return t; +} diff --git a/bin/resources/shaders/dx11/tfx_ps_sample_af.hlsl b/bin/resources/shaders/dx11/tfx_ps_sample_af.hlsl new file mode 100644 index 0000000000000..e8776691984ba --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_sample_af.hlsl @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "tfx_ps_resources.hlsl" + +#ifndef PS_ANISOTROPIC_FILTERING +#define PS_AUTOMATIC_LOD 0 +#define PS_MANUAL_LOD 0 +#define PS_ANISOTROPIC_FILTERING 2 +#endif + +#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1) +float manual_lod(float uv_w); +#endif + +#if PS_ANISOTROPIC_FILTERING > 1 +bool2 nan_or_inf(float2 xy) +{ + // FXC (<=SM5.1) may optimise away isnan and isinf. + // DXC (>=SM6.0) will preserve them. +#ifdef __hlsl_dx_compiler + return isinf(xy) | isnan(xy); +#else + return (asuint(xy) & 0x7f800000) == 0x7f800000; +#endif +} + +export float4 sample_c_af(float2 uv, float uv_w) +{ + // HW sampler will reject bad UVs, match that here. + uv = any(nan_or_inf(uv)) ? float2(0.0f, 0.0f) : uv; + + // Large floating point values risk NaN/Inf values. + // Above this value floats lose decimal precision, so seems a resonable limit for UVs. + uv = clamp(uv, -8388608.0f, 8388608.0f); + + // Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations + // And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt + // With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/ + float2 sz; + Texture.GetDimensions(sz.x, sz.y); + float2 dX = ddx(uv) * sz; + float2 dY = ddy(uv) * sz; + + float length_x = length(dX); + float length_y = length(dY); + + // Calculate Ellipse Transform + bool d_zero = length_x < 0.001f || length_y < 0.001f; + float f = (dX.x * dY.y - dX.y * dY.x); + bool d_par = f < 0.001f; + bool d_per = dot(dX, dY) < 0.001f; + bool d_inf_nan = any(nan_or_inf(dX) | nan_or_inf(dY)); + + if (!(d_zero || d_par || d_per || d_inf_nan)) + { + float A = dX.y * dX.y + dY.y * dY.y; + float B = -2 * (dX.x * dX.y + dY.x * dY.y); + float C = dX.x * dX.x + dY.x * dY.x; + float F = f * f; + + float p = A - C; + float q = A + C; + float t = sqrt(p * p + B * B); + + float sqrt_num_plus = sqrt(F * (t + p)); + float sqrt_num_minus = sqrt(F * (t - p)); + + float inv_sqrt_denom_plus = rsqrt(t * (q + t)); + float inv_sqrt_denom_minus = rsqrt(t * (q - t)); + + float signB = sign(B); + + float2 new_dX = float2( + sqrt_num_plus * inv_sqrt_denom_plus, + sqrt_num_minus * inv_sqrt_denom_plus * signB + ); + + float2 new_dY = float2( + sqrt_num_minus * inv_sqrt_denom_minus * -signB, + sqrt_num_plus * inv_sqrt_denom_minus + ); + + d_inf_nan = any(nan_or_inf(new_dX) | nan_or_inf(new_dY)); + if (!d_inf_nan) + { + dX = new_dX; + dY = new_dY; + length_x = length(dX); + length_y = length(dY); + } + } + + // Compute AF values + bool is_major_x = length_x > length_y; + float length_major = is_major_x ? length_x : length_y; + float length_minor = is_major_x ? length_y : length_x; + + float aniso_ratio; + float length_lod; + float2 aniso_line; + + if (length_major <= 1.0f) + { + // A zero length_major would result in NaN Lod and break sampling. + // A small length_major would result in aniso_ratio getting clamped to 1. + // Perform isotropic filtering instead. + aniso_ratio = 1.0f; + length_lod = length_major; + aniso_line = float2(0.0f, 0.0f); + } + else + { + float2 aniso_line_dir = is_major_x ? dX : dY; + + aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING); + length_lod = length_major / aniso_ratio; + + // clamp to top Lod + if (length_lod < 1.0f) + aniso_ratio = max(1.0f, aniso_ratio * length_lod); + + aniso_ratio = round(aniso_ratio); + + aniso_line = aniso_line_dir * 0.5f * (1.0f / sz); + } + +#if PS_AUTOMATIC_LOD == 1 + float lod = log2(length_lod); +#elif PS_MANUAL_LOD == 1 + float lod = manual_lod(uv_w); +#else + float lod = 0.0f; // No Lod +#endif + + float4 colour; + if (aniso_ratio == 1.0f) + colour = Texture.SampleLevel(TextureSampler, uv, lod); + else + { + float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f); + float2 segment = (2.0f * aniso_line) / aniso_ratio; + + int aniso_ratio_i = (int)aniso_ratio; + for (int i = 0; i < aniso_ratio_i; i++) + { + float2 d = -aniso_line + (0.5f + i) * segment; + float2 uv_sample = uv + d; + float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod); + num += sample_colour; + } + + colour = num / aniso_ratio; + } + return colour; +} +#endif diff --git a/bin/resources/shaders/dx11/tfx_ps_tfx.hlsl b/bin/resources/shaders/dx11/tfx_ps_tfx.hlsl new file mode 100644 index 0000000000000..9c3a7a5f34050 --- /dev/null +++ b/bin/resources/shaders/dx11/tfx_ps_tfx.hlsl @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#ifndef PS_TFX +#define PS_TFX 0 +#define PS_TCC 0 +#endif + +export float4 tfx(float4 T, float4 C) +{ + float4 C_out; + float4 FxT = trunc((C * T) / 128.0f); + +#if (PS_TFX == 0) + C_out = FxT; +#elif (PS_TFX == 1) + C_out = T; +#elif (PS_TFX == 2) + C_out.rgb = FxT.rgb + C.a; + C_out.a = T.a + C.a; +#elif (PS_TFX == 3) + C_out.rgb = FxT.rgb + C.a; + C_out.a = T.a; +#else + C_out = C; +#endif + +#if (PS_TCC == 0) + C_out.a = C.a; +#endif + +#if (PS_TFX == 0) || (PS_TFX == 2) || (PS_TFX == 3) + // Clamp only when it is useful + C_out = min(C_out, 255.0f); +#endif + + return C_out; +} diff --git a/common/vsprops/LinkPCSX2Deps.props b/common/vsprops/LinkPCSX2Deps.props index f986272cd1bc0..d390b73afba1f 100644 --- a/common/vsprops/LinkPCSX2Deps.props +++ b/common/vsprops/LinkPCSX2Deps.props @@ -28,6 +28,7 @@ + diff --git a/common/vsprops/common.props b/common/vsprops/common.props index e605d17109278..8bbda572f973d 100644 --- a/common/vsprops/common.props +++ b/common/vsprops/common.props @@ -63,7 +63,7 @@ comctl32.lib;ws2_32.lib;shlwapi.lib;winmm.lib;rpcrt4.lib;iphlpapi.lib;%(AdditionalDependencies) - dxguid.lib;dinput8.lib;hid.lib;PowrProf.lib;d3dcompiler.lib;d3d11.lib;d3d12.lib;dxgi.lib;strmiids.lib;comsuppw.lib;OneCore.lib;dwmapi.lib;%(AdditionalDependencies) + dxguid.lib;dinput8.lib;hid.lib;PowrProf.lib;d3dcompiler.lib;dxcompiler.lib;d3d11.lib;d3d12.lib;dxgi.lib;strmiids.lib;comsuppw.lib;OneCore.lib;dwmapi.lib;%(AdditionalDependencies) opengl32.lib;%(AdditionalDependencies) true Windows diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 4652ff99f5ce9..b9db510f8e395 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1187,6 +1187,7 @@ if(WIN32) hid.lib PowrProf.lib d3dcompiler.lib + dxcompiler.lib d3d11.lib d3d12.lib dxgi.lib @@ -1311,7 +1312,7 @@ function(setup_main_executable target) # Copy dependency libraries. set(DEPS_BINDIR "${CMAKE_SOURCE_DIR}/deps/bin") - set(DEPS_TO_COPY freetype.dll harfbuzz.dll jpeg62.dll libpng16.dll libsharpyuv.dll libwebp.dll libwebpdemux.dll libwebpmux.dll lz4.dll SDL3.dll shaderc_shared.dll z.dll zstd.dll plutovg.dll plutosvg.dll ryml.dll) + set(DEPS_TO_COPY freetype.dll harfbuzz.dll jpeg62.dll libpng16.dll libsharpyuv.dll libwebp.dll libwebpdemux.dll libwebpmux.dll lz4.dll SDL3.dll shaderc_shared.dll z.dll zstd.dll plutovg.dll plutosvg.dll ryml.dll dxcompiler.dll) set(DEPS_TO_COPY $,kddockwidgets-qt6d.dll,kddockwidgets-qt6.dll> ${DEPS_TO_COPY} diff --git a/pcsx2/GS/GSShaderCompileIndicator.h b/pcsx2/GS/GSShaderCompileIndicator.h index 6cf2804b3f533..9fa10c99b37e4 100644 --- a/pcsx2/GS/GSShaderCompileIndicator.h +++ b/pcsx2/GS/GSShaderCompileIndicator.h @@ -22,7 +22,7 @@ namespace GSShaderCompileIndicator return hold; } - inline void OnCompileDone(u64 duration_ns, u64 start_time) + inline void OnCompileDone(u64 duration_ns, u64 start_time, bool partial) { const u64 now = Common::Timer::GetCurrentValue(); const u64 last = s_last_time.load(std::memory_order_relaxed); @@ -32,7 +32,8 @@ namespace GSShaderCompileIndicator s_time_ns.store(0, std::memory_order_relaxed); } - s_count.fetch_add(1, std::memory_order_relaxed); + if (!partial) + s_count.fetch_add(1, std::memory_order_relaxed); s_time_ns.fetch_add(duration_ns, std::memory_order_relaxed); s_last_time.store(now, std::memory_order_relaxed); } @@ -85,12 +86,21 @@ namespace GSShaderCompileIndicator struct CompileTimer { Common::Timer timer; + bool is_partial = false; CompileTimer() = default; + // For a shader compile with multiple steps; + // Intermediate steps/timers are to be marked partial. + // Final step/timer to me marked not partial. + explicit CompileTimer(bool partial) + { + is_partial = partial; + } + ~CompileTimer() { - OnCompileDone(static_cast(timer.GetTimeNanoseconds()), timer.GetStartValue()); + OnCompileDone(static_cast(timer.GetTimeNanoseconds()), timer.GetStartValue(), is_partial); } CompileTimer(const CompileTimer&) = delete; diff --git a/pcsx2/GS/Renderers/DX11/D3D.cpp b/pcsx2/GS/Renderers/DX11/D3D.cpp index 87c30875023fe..3997e609c7838 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D.cpp @@ -22,9 +22,11 @@ #include #include #include +#include #include #include "fmt/format.h" +#include "fmt/xchar.h" static u32 s_next_bad_shader_id = 1; @@ -478,17 +480,175 @@ const char* D3D::ShaderModelToCacheString(D3D::ShaderModel shader_model) case ShaderModel::SM50: return "sm50"; case ShaderModel::SM51: + case ShaderModel::SM60: + case ShaderModel::SM61: + case ShaderModel::SM62: + case ShaderModel::SM63: + case ShaderModel::SM64: return "sm51"; + case ShaderModel::SM65: + return "sm65"; default: return "unk"; } } -wil::com_ptr_nothrow D3D::CompileShader(D3D::ShaderType type, D3D::ShaderModel shader_model, bool debug, - const std::string_view code, const D3D_SHADER_MACRO* macros /* = nullptr */, - const char* entry_point /* = "main" */) +// Not COM +struct FxcResourceIncludeHandler final : ID3DInclude +{ +private: + std::unordered_map includes; + std::map file_dir; + std::vector> opened_files; + +public: + explicit FxcResourceIncludeHandler(const std::unordered_map& includes) + { + this->includes = includes; + } + virtual ~FxcResourceIncludeHandler() = default; + + virtual HRESULT Open(D3D_INCLUDE_TYPE, LPCSTR pFileName, LPCVOID pParentData, LPCVOID* ppData, UINT* pBytes) override + { + if (ppData == nullptr || pBytes == nullptr) + return E_POINTER; + + const std::string filename = Path::Canonicalize(pFileName); + + std::unordered_map::const_iterator iter; + if (pParentData != nullptr) + { + // Search same directory as file. + const std::string path = Path::Canonicalize(Path::Combine(file_dir[reinterpret_cast(pParentData)], filename)); + + iter = includes.find(path); + if (iter == includes.end()) + { + // Search root directory. + iter = includes.find(filename); + } + } + else + iter = includes.find(filename); + + if (iter != includes.end()) + { + std::unique_ptr source_data = std::make_unique_for_overwrite(iter->second.length()); + std::memcpy(source_data.get(), iter->second.c_str(), iter->second.length()); + *ppData = source_data.get(); + *pBytes = iter->second.length(); + + file_dir.emplace(source_data.get(), Path::GetDirectory(iter->first)); + opened_files.push_back(std::move(source_data)); + return S_OK; + } + + *ppData = nullptr; + *pBytes = 0; + return E_FAIL; + } + + virtual HRESULT Close(LPCVOID pData) override + { + for (std::vector>::iterator it = opened_files.begin(); it != opened_files.end();) + { + if (pData == it->get()) + { + file_dir.erase(it->get()); + opened_files.erase(it); + return S_OK; + } + } + + return E_FAIL; + } +}; + +// COM +struct DxcResourceIncludeHandler final : IDxcIncludeHandler +{ +private: + std::unordered_map includes; + wil::com_ptr_nothrow utils; + u32 ref_count; + +public: + explicit DxcResourceIncludeHandler(const std::unordered_map& includes) + { + this->includes = includes; + const HRESULT hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(utils.put())); + if (FAILED(hr)) + utils = nullptr; + ref_count = 1; + } + virtual ~DxcResourceIncludeHandler() = default; + + virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) override + { + if (ppvObject == nullptr) + return E_POINTER; + + if (riid == IID_IUnknown || riid == __uuidof(IDxcIncludeHandler)) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + else + { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + } + + virtual ULONG AddRef() override + { + ref_count++; + return ref_count; + } + + virtual ULONG Release() override + { + const u32 ret = ref_count--; + if (ref_count == 0) + delete this; + return ret; + } + + virtual HRESULT LoadSource(LPCWSTR pFilename, IDxcBlob** ppIncludeSource) override + { + if (ppIncludeSource == nullptr) + return E_POINTER; + + if (utils != nullptr) + { + std::string filename = StringUtil::WideStringToUTF8String(pFilename); + Path::Canonicalize(&filename); + + const auto iter = includes.find(filename); + if (iter != includes.end()) + { + IDxcBlobEncoding* source_blob; + const HRESULT hr = utils->CreateBlob(iter->second.c_str(), iter->second.length(), CP_UTF8, &source_blob); + if (SUCCEEDED(hr)) + { + *ppIncludeSource = source_blob; + return S_OK; + } + } + } + + *ppIncludeSource = nullptr; + return E_FAIL; + } +}; + +wil::com_ptr_nothrow D3D::CompileShaderDXBC(D3D::ShaderType type, D3D::ShaderModel shader_model, bool debug, + const std::string_view code, const char* name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /*= {} */) { const GSShaderCompileIndicator::CompileTimer compile_timer; + pxAssert(type != D3D::ShaderType::Libary); const char* target; switch (shader_model) @@ -526,9 +686,14 @@ wil::com_ptr_nothrow D3D::CompileShader(D3D::ShaderType type, D3D::Sha static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3; static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE; + // Untested + std::unique_ptr pInclude{nullptr}; + if (!includes.empty()) + pInclude = std::make_unique(includes); + wil::com_ptr_nothrow blob; wil::com_ptr_nothrow error_blob; - const HRESULT hr = D3DCompile(code.data(), code.size(), "0", macros, nullptr, entry_point, target, + const HRESULT hr = D3DCompile(code.data(), code.size(), name, macros, pInclude.get(), entry_point, target, debug ? flags_debug : flags_non_debug, 0, blob.put(), error_blob.put()); std::string error_string; @@ -566,3 +731,223 @@ wil::com_ptr_nothrow D3D::CompileShader(D3D::ShaderType type, D3D::Sha return blob; } + +wil::com_ptr_nothrow D3D::CompileShaderDXIL(D3D::ShaderType type, D3D::ShaderModel shader_model, bool debug, + const std::string_view code, const char* name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /*= {} */) +{ + // Note: Libraries are only supported in SM 6_3+, be can be linked down to SM6_0 + const GSShaderCompileIndicator::CompileTimer compile_timer(type == D3D::ShaderType::Libary); + + const wchar_t* target; + switch (shader_model) + { + case ShaderModel::SM60: + case ShaderModel::SM61: + case ShaderModel::SM62: + case ShaderModel::SM63: + case ShaderModel::SM64: + pxAssert(false); + break; + case ShaderModel::SM65: + default: + { + static constexpr std::array targets = {{L"vs_6_5", L"ps_6_5", L"cs_6_5", L"lib_6_5"}}; + target = targets[static_cast(type)]; + } + break; + } + + // Build arguments. + std::vector args; + std::wstring wentry_point; + std::wstring wname; + std::vector wdefines; // cppcheck-suppress variableScope + if (entry_point) + { + wentry_point = StringUtil::UTF8StringToWideString(entry_point); + args.push_back(L"-E"); + args.push_back(wentry_point.c_str()); + } + args.push_back(L"-T"); + args.push_back(target); + + if (debug) + { + args.push_back(L"-INPUT"); + wname = StringUtil::UTF8StringToWideString(name); + args.push_back(wname.c_str()); + args.push_back(L"-Od"); + args.push_back(L"-Zi"); + args.push_back(L"-Zss"); + args.push_back(L"-Qembed_debug"); + } + else + { + args.push_back(L"-O3"); + args.push_back(L"-Qstrip_reflect"); + } + + if (macros) + { + for (const D3D_SHADER_MACRO* macro = macros; macro->Name != nullptr; macro++) + wdefines.push_back(StringUtil::UTF8StringToWideString(fmt::format("{}={}", macro->Name, macro->Definition))); + + // Use a seperate loop to avoid invalidating the pointer returned from c_str(). + for (const std::wstring& define : wdefines) + { + args.push_back(L"-D"); + args.push_back(define.c_str()); + } + } + + wil::com_ptr_nothrow pInclude; + if (!includes.empty()) + *pInclude.put() = new DxcResourceIncludeHandler(includes); + + // Compile Shader. + wil::com_ptr_nothrow pCompiler; + DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(pCompiler.put())); + + const DxcBuffer source{code.data(), code.length(), DXC_CP_UTF8}; + wil::com_ptr_nothrow pResults; + HRESULT hr = pCompiler->Compile(&source, args.data(), args.size(), pInclude.get(), IID_PPV_ARGS(pResults.put())); + + if (FAILED(hr)) + { + Console.WriteLn("Compiler Failed"); + return {}; + } + + wil::com_ptr_nothrow error_string = nullptr; + pResults->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(error_string.put()), nullptr); + + pResults->GetStatus(&hr); + if (FAILED(hr)) + { + std::string target_utf8 = StringUtil::WideStringToUTF8String(target); + Console.WriteLn("Failed to compile '%s':\n%s", target_utf8.c_str(), error_string->GetStringPointer()); + + std::ofstream ofs(Path::Combine(EmuFolders::Logs, fmt::format("pcsx2_bad_shader_{}.txt", s_next_bad_shader_id++)), + std::ofstream::out | std::ofstream::binary); + if (ofs.is_open()) + { + ofs << code; + ofs << "\n\nCompile as " << target_utf8.c_str() << " failed: " << hr << "\n"; + ofs.write(error_string->GetStringPointer(), error_string->GetStringLength()); + ofs << "\n"; + if (macros) + { + for (const D3D_SHADER_MACRO* macro = macros; macro->Name != nullptr; macro++) + ofs << "#define " << macro->Name << " " << macro->Definition << "\n"; + } + ofs.close(); + } + + return {}; + } + + if (error_string->GetStringLength() != 0) + Console.Warning("'%s' compiled with warnings:\n%s", StringUtil::WideStringToUTF8String(target).c_str(), error_string->GetStringPointer()); + + wil::com_ptr_nothrow blob = nullptr; + pResults->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(blob.put()), nullptr); + + return blob; +} + +wil::com_ptr_nothrow D3D::CompileShader(D3D::ShaderType type, D3D::ShaderModel shader_model, bool debug, + const std::string_view code, const char* name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /*= {} */) +{ + if (static_cast(shader_model) < 0x65) + return CompileShaderDXBC(type, shader_model, debug, code, name, macros, entry_point, includes); + else + return CompileShaderDXIL(type, shader_model, debug, code, name, macros, entry_point, includes); +} + +wil::com_ptr_nothrow D3D::LinkShaderDXIL(ShaderType type, ShaderModel shader_model, bool debug, + const std::vector>& modules, const char* entry_point /* = "main" */) +{ + const GSShaderCompileIndicator::CompileTimer compile_timer; + pxAssert(type != D3D::ShaderType::Libary); + + const wchar_t* target; + switch (shader_model) + { + case ShaderModel::SM60: + case ShaderModel::SM61: + case ShaderModel::SM62: + case ShaderModel::SM63: + case ShaderModel::SM64: + pxAssert(false); + break; + case ShaderModel::SM65: + default: + { + static constexpr std::array targets = {{L"vs_6_5", L"ps_6_5", L"cs_6_5"}}; + target = targets[static_cast(type)]; + } + break; + } + + wil::com_ptr_nothrow pLinker; + DxcCreateInstance(CLSID_DxcLinker, IID_PPV_ARGS(pLinker.put())); + + wil::com_ptr_nothrow pUtils; + DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(pUtils.put())); + + std::vector names; + + names.reserve(modules.size()); + for (int i = 0; i < modules.size(); i++) + { + // ID3DBlob and IDxcBlob have the same GUID, but C++ considers them different types. + wil::com_ptr_nothrow pDxcBlob; + modules[i]->QueryInterface(IID_PPV_ARGS(pDxcBlob.put())); + + std::wstring name = fmt::format(L"lib{}", i); + + pLinker->RegisterLibrary(name.c_str(), pDxcBlob.get()); + names.push_back(std::move(name)); + } + + std::vector name_cstr; + name_cstr.reserve(modules.size()); + for (int i = 0; i < modules.size(); i++) + name_cstr.push_back(names[i].c_str()); + + wil::com_ptr_nothrow pResults; + HRESULT hr = pLinker->Link(StringUtil::UTF8StringToWideString(entry_point).c_str(), target, name_cstr.data(), name_cstr.size(), nullptr, 0, pResults.put()); + + if (FAILED(hr)) + { + Console.WriteLn("Link Failed"); + return {}; + } + + wil::com_ptr_nothrow error_string; + pResults->GetErrorBuffer(error_string.put()); + wil::com_ptr_nothrow error_utf8; + pUtils->GetBlobAsUtf8(error_string.get(), error_utf8.put()); + + pResults->GetStatus(&hr); + if (FAILED(hr)) + { + std::string target_utf8 = StringUtil::WideStringToUTF8String(target); + Console.WriteLn("Failed to link '%s':\n%s", target_utf8.c_str(), error_utf8->GetStringPointer()); + return {}; + } + + if (error_utf8->GetStringLength() != 0) + Console.Warning("'%s' linked with warnings:\n%s", StringUtil::WideStringToUTF8String(target).c_str(), error_utf8->GetStringPointer()); + + wil::com_ptr_nothrow dxcBlob = nullptr; + pResults->GetResult(dxcBlob.put()); + + // ID3DBlob and IDxcBlob have the same GUID, but C++ considers them different types. + wil::com_ptr_nothrow blob = nullptr; + dxcBlob->QueryInterface(IID_PPV_ARGS(blob.put())); + + return blob; +} diff --git a/pcsx2/GS/Renderers/DX11/D3D.h b/pcsx2/GS/Renderers/DX11/D3D.h index 61c07715fa9e5..1b1132e9e67f2 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.h +++ b/pcsx2/GS/Renderers/DX11/D3D.h @@ -60,7 +60,8 @@ namespace D3D { Vertex, Pixel, - Compute + Compute, + Libary, }; enum class ShaderModel @@ -69,10 +70,27 @@ namespace D3D SM41 = 0x41, // DX11 FL 10_1 SM50 = 0x50, // DX11 FL 11_0 SM51 = 0x51, // DX12 + SM60 = 0x60, + SM61 = 0x61, + SM62 = 0x62, + SM63 = 0x63, + SM64 = 0x64, + SM65 = 0x65, }; const char* ShaderModelToCacheString(ShaderModel shader_model); wil::com_ptr_nothrow CompileShader(ShaderType type, ShaderModel shader_model, bool debug, - const std::string_view code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); + const std::string_view code, const char* name, const D3D_SHADER_MACRO* macros = nullptr, + const char* entry_point = "main", const std::unordered_map& includes = {}); + + wil::com_ptr_nothrow CompileShaderDXBC(ShaderType type, ShaderModel shader_model, bool debug, + const std::string_view code, const char* name, const D3D_SHADER_MACRO* macros = nullptr, + const char* entry_point = "main", const std::unordered_map& includes = {}); + wil::com_ptr_nothrow CompileShaderDXIL(ShaderType type, ShaderModel shader_model, bool debug, + const std::string_view code, const char* name, const D3D_SHADER_MACRO* macros = nullptr, + const char* entry_point = "main", const std::unordered_map& includes = {}); + + wil::com_ptr_nothrow LinkShaderDXIL(ShaderType type, ShaderModel shader_model, bool debug, + const std::vector>& modules, const char* entry_point = "main"); }; // namespace D3D diff --git a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp index c0a5dc4873c5b..7fb8575c7493f 100644 --- a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.cpp @@ -271,13 +271,13 @@ D3D11ShaderCache::CacheIndexKey D3D11ShaderCache::GetCacheKey( } wil::com_ptr_nothrow D3D11ShaderCache::GetShaderBlob(D3D::ShaderType type, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros /* = nullptr */, - const char* entry_point /* = "main" */) + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /* = {} */) { const auto key = GetCacheKey(type, shader_code, macros, entry_point); auto iter = m_index.find(key); if (iter == m_index.end()) - return CompileAndAddShaderBlob(key, shader_code, macros, entry_point); + return CompileAndAddShaderBlob(key, shader_code, shader_name, macros, entry_point, includes); wil::com_ptr_nothrow blob; HRESULT hr = D3DCreateBlob(iter->second.blob_size, blob.put()); @@ -292,10 +292,10 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetShaderBlob(D3D::ShaderType t } wil::com_ptr_nothrow D3D11ShaderCache::GetVertexShader(ID3D11Device* device, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros /* = nullptr */, - const char* entry_point /* = "main" */) + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /* = {} */) { - wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Vertex, shader_code, macros, entry_point); + wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Vertex, shader_code, shader_name, macros, entry_point, includes); if (!blob) return {}; @@ -308,21 +308,23 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetVertexShader(ID3D1 return {}; } - const char* shader_name = entry_point; // Ideally we'd feed in a proper name - if (shader_name) + if (entry_point) { - GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); + std::string obj_name = std::string(shader_name) + "_" + std::string(entry_point); + GSDevice11::SetD3DDebugObjectName(shader.get(), obj_name.c_str()); } + else + GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); return shader; } bool D3D11ShaderCache::GetVertexShaderAndInputLayout(ID3D11Device* device, ID3D11VertexShader** vs, ID3D11InputLayout** il, const D3D11_INPUT_ELEMENT_DESC* layout, size_t layout_size, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros /* = nullptr */, - const char* entry_point /* = "main" */) + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /* = {} */) { - wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Vertex, shader_code, macros, entry_point); + wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Vertex, shader_code, shader_name, macros, entry_point, includes); if (!blob) return false; @@ -334,11 +336,13 @@ bool D3D11ShaderCache::GetVertexShaderAndInputLayout(ID3D11Device* device, ID3D1 return {}; } - const char* shader_name = entry_point; // Ideally we'd feed in a proper name - if (shader_name) + if (entry_point) { - GSDevice11::SetD3DDebugObjectName(actual_vs.get(), shader_name); + std::string obj_name = std::string(shader_name) + "_" + std::string(entry_point); + GSDevice11::SetD3DDebugObjectName(actual_vs.get(), obj_name.c_str()); } + else + GSDevice11::SetD3DDebugObjectName(actual_vs.get(), shader_name); hr = device->CreateInputLayout(layout, layout_size, blob->GetBufferPointer(), blob->GetBufferSize(), il); if (FAILED(hr)) @@ -352,10 +356,10 @@ bool D3D11ShaderCache::GetVertexShaderAndInputLayout(ID3D11Device* device, ID3D1 } wil::com_ptr_nothrow D3D11ShaderCache::GetPixelShader(ID3D11Device* device, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros /* = nullptr */, - const char* entry_point /* = "main" */) + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /* = {} */) { - wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Pixel, shader_code, macros, entry_point); + wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Pixel, shader_code, shader_name, macros, entry_point, includes); if (!blob) return {}; @@ -368,20 +372,22 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetPixelShader(ID3D11D return {}; } - const char* shader_name = entry_point; // Ideally we'd feed in a proper name - if (shader_name) + if (entry_point) { - GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); + std::string obj_name = std::string(shader_name) + "_" + std::string(entry_point); + GSDevice11::SetD3DDebugObjectName(shader.get(), obj_name.c_str()); } + else + GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); return shader; } wil::com_ptr_nothrow D3D11ShaderCache::GetComputeShader(ID3D11Device* device, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros /* = nullptr */, - const char* entry_point /* = "main" */) + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros /* = nullptr */, + const char* entry_point /* = "main" */, const std::unordered_map& includes /* = {} */) { - wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Compute, shader_code, macros, entry_point); + wil::com_ptr_nothrow blob = GetShaderBlob(D3D::ShaderType::Compute, shader_code, shader_name, macros, entry_point, includes); if (!blob) return {}; @@ -394,20 +400,23 @@ wil::com_ptr_nothrow D3D11ShaderCache::GetComputeShader(ID3 return {}; } - const char* shader_name = entry_point; // Ideally we'd feed in a proper name - if (shader_name) + if (entry_point) { - GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); + std::string obj_name = std::string(shader_name) + "_" + std::string(entry_point); + GSDevice11::SetD3DDebugObjectName(shader.get(), obj_name.c_str()); } + else + GSDevice11::SetD3DDebugObjectName(shader.get(), shader_name); return shader; } wil::com_ptr_nothrow D3D11ShaderCache::CompileAndAddShaderBlob(const CacheIndexKey& key, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point) + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros, const char* entry_point, + const std::unordered_map& includes) { wil::com_ptr_nothrow blob = - D3D::CompileShader(key.shader_type, m_shader_model, m_debug, shader_code, macros, entry_point); + D3D::CompileShader(key.shader_type, m_shader_model, m_debug, shader_code, shader_name, macros, entry_point, includes); if (!blob) return {}; diff --git a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.h b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.h index 4ab60e13e9551..3e2e0be7d6652 100644 --- a/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.h +++ b/pcsx2/GS/Renderers/DX11/D3D11ShaderCache.h @@ -22,21 +22,25 @@ class D3D11ShaderCache void Close(); wil::com_ptr_nothrow GetShaderBlob(D3D::ShaderType type, const std::string_view shader_code, - const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); + const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", + const std::unordered_map& includes = {}); wil::com_ptr_nothrow GetVertexShader(ID3D11Device* device, const std::string_view shader_code, - const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); + const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", + const std::unordered_map& includes = {}); bool GetVertexShaderAndInputLayout(ID3D11Device* device, ID3D11VertexShader** vs, ID3D11InputLayout** il, const D3D11_INPUT_ELEMENT_DESC* layout, size_t layout_size, const std::string_view shader_code, - const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); + const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", + const std::unordered_map& includes = {}); wil::com_ptr_nothrow GetPixelShader(ID3D11Device* device, const std::string_view shader_code, - const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); + const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", + const std::unordered_map& includes = {}); - wil::com_ptr_nothrow GetComputeShader(ID3D11Device* device, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros = nullptr, - const char* entry_point = "main"); + wil::com_ptr_nothrow GetComputeShader(ID3D11Device* device, const std::string_view shader_code, + const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", + const std::unordered_map& includes = {}); private: struct CacheIndexKey @@ -81,7 +85,8 @@ class D3D11ShaderCache bool ReadExisting(const std::string& index_filename, const std::string& blob_filename); wil::com_ptr_nothrow CompileAndAddShaderBlob(const CacheIndexKey& key, - const std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point); + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros, const char* entry_point, + const std::unordered_map& includes); std::FILE* m_index_file = nullptr; std::FILE* m_blob_file = nullptr; diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 9ce212193e4b3..4c6237bc0ddfd 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -217,6 +217,35 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) return false; m_tfx_source = std::move(*shader); + { +#define SHADER_FILE(include) \ + shader = ReadShaderSource("shaders/dx11/" include); \ + if (!shader.has_value()) \ + { \ + Host::ReportErrorAsync("GS", "Failed to read shaders/dx11/" include); \ + return false; \ + } \ + m_tfx_includes.emplace(include, std::move(*shader)); + + SHADER_FILE("tfx_defines.hlsl"); + SHADER_FILE("tfx_ps_resources.hlsl"); + + SHADER_FILE("tfx_ps_main.hlsl"); + SHADER_FILE("tfx_ps_atst.hlsl"); + SHADER_FILE("tfx_ps_blend.hlsl"); + SHADER_FILE("tfx_ps_color.hlsl"); + SHADER_FILE("tfx_ps_fetch.hlsl"); + SHADER_FILE("tfx_ps_fog.hlsl"); + SHADER_FILE("tfx_ps_misc.hlsl"); + SHADER_FILE("tfx_ps_rt.hlsl"); + SHADER_FILE("tfx_ps_rta_correction.hlsl"); + SHADER_FILE("tfx_ps_sample.hlsl"); + SHADER_FILE("tfx_ps_sample_af.hlsl"); + SHADER_FILE("tfx_ps_tfx.hlsl"); + +#undef SHADER_FILE + } + // convert D3D11_INPUT_ELEMENT_DESC il_convert[] = @@ -232,7 +261,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) ShaderMacro sm_vs; sm_vs.AddMacro("VERTEX_SHADER", 1); if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_convert.vs.put(), m_convert.il.put(), - il_convert, std::size(il_convert), *convert_hlsl, sm_vs.GetPtr(), "vs_main")) + il_convert, std::size(il_convert), *convert_hlsl, "convert.fx", sm_vs.GetPtr(), "vs_main")) { return false; } @@ -257,7 +286,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) sm_ps.AddMacro("HAS_FLOAT32_OUTPUT", static_cast(shader.Float32Output())); sm_ps.AddMacro(entry_point_macro.c_str(), 1); - m_convert.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, sm_ps.GetPtr(), entry_point); + m_convert.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, "convert.fx", sm_ps.GetPtr(), entry_point); if (!m_convert.ps[i]) return false; } @@ -266,14 +295,14 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) if (!shader.has_value()) return false; if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_present.vs.put(), m_present.il.put(), - il_convert, std::size(il_convert), *shader, nullptr, "vs_main")) + il_convert, std::size(il_convert), *shader, "present.fx", nullptr, "vs_main")) { return false; } for (size_t i = 0; i < std::size(m_present.ps); i++) { - m_present.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, ShaderEntryPoint(static_cast(i))); + m_present.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, "present.fx", nullptr, ShaderEntryPoint(static_cast(i))); if (!m_present.ps[i]) return false; } @@ -321,7 +350,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) for (size_t i = 0; i < std::size(m_merge.ps); i++) { const std::string entry_point(StringUtil::StdStringFromFormat("ps_main%zu", i)); - m_merge.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, entry_point.c_str()); + m_merge.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, "merge.fx", nullptr, entry_point.c_str()); if (!m_merge.ps[i]) return false; } @@ -355,7 +384,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) for (size_t i = 0; i < std::size(m_interlace.ps); i++) { const std::string entry_point(StringUtil::StdStringFromFormat("ps_main%zu", i)); - m_interlace.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, entry_point.c_str()); + m_interlace.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, "interlace.fx", nullptr, entry_point.c_str()); if (!m_interlace.ps[i]) return false; } @@ -372,7 +401,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) shader = ReadShaderSource("shaders/dx11/shadeboost.fx"); if (!shader.has_value()) return false; - m_shadeboost.ps = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, "ps_main"); + m_shadeboost.ps = m_shader_cache.GetPixelShader(m_dev.get(), *shader, "shadeboost.fx", nullptr, "ps_main"); if (!m_shadeboost.ps) return false; @@ -583,7 +612,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) ShaderMacro sm_ps; sm_ps.AddMacro("PIXEL_SHADER", 1); sm_ps.AddMacro(entry_point_macro.c_str(), 1); - m_date.primid_init_ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, sm_ps.GetPtr(), entry_point.c_str()); + m_date.primid_init_ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, "convert.fx", sm_ps.GetPtr(), entry_point.c_str()); if (!m_date.primid_init_ps[i]) return false; } @@ -1938,7 +1967,7 @@ void GSDevice11::DoFXAA(GSTexture* sTex, GSTexture* dTex) ShaderMacro sm; sm.AddMacro("FXAA_HLSL", "1"); - m_fxaa_ps = m_shader_cache.GetPixelShader(m_dev.get(), *shader, sm.GetPtr(), "main"); + m_fxaa_ps = m_shader_cache.GetPixelShader(m_dev.get(), *shader, "fxaa.fx", sm.GetPtr(), "main"); if (!m_fxaa_ps) return; } @@ -1987,11 +2016,11 @@ void GSDevice11::SetupVS(VSSelector sel, const GSHWDrawConfig::VSConstantBuffer* if (sel.expand == GSHWDrawConfig::VSExpand::None) { m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), vs.vs.put(), vs.il.put(), layout, - std::size(layout), m_tfx_source, sm.GetPtr(), "vs_main"); + std::size(layout), m_tfx_source, "tfx.fx", sm.GetPtr(), "vs_main", m_tfx_includes); } else { - vs.vs = m_shader_cache.GetVertexShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "vs_main_expand"); + vs.vs = m_shader_cache.GetVertexShader(m_dev.get(), m_tfx_source, "tfx.fx", sm.GetPtr(), "vs_main_expand", m_tfx_includes); } i = m_vs.try_emplace(sel.key, std::move(vs)).first; @@ -2082,7 +2111,7 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant sm.AddMacro("PS_ROV_COLOR", sel.rov_color); sm.AddMacro("PS_ROV_DEPTH", static_cast(sel.rov_depth)); - wil::com_ptr_nothrow ps = m_shader_cache.GetPixelShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "ps_main"); + wil::com_ptr_nothrow ps = m_shader_cache.GetPixelShader(m_dev.get(), m_tfx_source, "tfx.fx", sm.GetPtr(), "ps_main", m_tfx_includes); i = m_ps.try_emplace(sel, std::move(ps)).first; } @@ -2259,8 +2288,8 @@ bool GSDevice11::CreateCASShaders() {"CAS_SHARPEN_ONLY", "1"}, {nullptr, nullptr}}; - m_cas.cs_sharpen = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), sharpen_only_macros, "main"); - m_cas.cs_upscale = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), nullptr, "main"); + m_cas.cs_sharpen = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), "cas.hlsl", sharpen_only_macros, "main"); + m_cas.cs_upscale = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), "cas.hlsl", nullptr, "main"); if (!m_cas.cs_sharpen || !m_cas.cs_upscale) { Console.Error("D3D11: Failed to create CAS compute shaders."); @@ -2318,8 +2347,8 @@ bool GSDevice11::CreateImGuiResources() // clang-format on if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_imgui.vs.put(), m_imgui.il.put(), layout, - std::size(layout), hlsl.value(), nullptr, "vs_main") || - !(m_imgui.ps = m_shader_cache.GetPixelShader(m_dev.get(), hlsl.value(), nullptr, "ps_main"))) + std::size(layout), hlsl.value(), "imgui.fx", nullptr, "vs_main") || + !(m_imgui.ps = m_shader_cache.GetPixelShader(m_dev.get(), hlsl.value(), "imgui.fx", nullptr, "ps_main"))) { Console.Error("D3D11: Failed to compile ImGui shaders"); return false; diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.h b/pcsx2/GS/Renderers/DX11/GSDevice11.h index 3da31414cfb2d..f7388d13912c1 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.h +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.h @@ -304,6 +304,7 @@ class GSDevice11 final : public GSDevice D3D11ShaderCache m_shader_cache; std::string m_tfx_source; + std::unordered_map m_tfx_includes; protected: using GSDevice::DoStretchRect; // Suppress overloaded virtual function warning diff --git a/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.cpp b/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.cpp index 86570fef1aa7f..dc91f86b8b4a6 100644 --- a/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.cpp +++ b/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ +#include "GS/Renderers/DX12/GSDevice12.h" #include "GS/Renderers/DX12/D3D12ShaderCache.h" #include "GS/GS.h" #include "GS/GSShaderCompileIndicator.h" @@ -12,9 +13,12 @@ #include "common/Console.h" #include "common/MD5Digest.h" #include "common/Path.h" +#include "common/StringUtil.h" #include +//#define DEBUG_LIBS 1 + #pragma pack(push, 1) struct CacheIndexEntry { @@ -56,7 +60,6 @@ bool D3D12ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const bool D3D12ShaderCache::Open(D3D::ShaderModel shader_model, bool debug) { - // Only support SM5.1 for now, which is the minimum for D3D12. pxAssert(shader_model >= D3D::ShaderModel::SM51); m_shader_model = shader_model; m_debug = debug; @@ -188,6 +191,10 @@ bool D3D12ShaderCache::CreateNew( bool D3D12ShaderCache::ReadExisting(const std::string& index_filename, const std::string& blob_filename, std::FILE*& index_file, std::FILE*& blob_file, CacheIndex& index) { +#if DEBUG_LIBS + return false; +#endif + index_file = FileSystem::OpenCFile(index_filename.c_str(), "r+b"); if (!index_file) { @@ -268,16 +275,6 @@ std::string D3D12ShaderCache::GetCacheBaseFileName(const std::string_view type, return Path::Combine(EmuFolders::Cache, base_filename); } -union MD5Hash -{ - struct - { - u64 low; - u64 high; - }; - u8 hash[16]; -}; - D3D12ShaderCache::CacheIndexKey D3D12ShaderCache::GetShaderCacheKey( EntryType type, const std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point) { @@ -314,11 +311,76 @@ D3D12ShaderCache::CacheIndexKey D3D12ShaderCache::GetShaderCacheKey( key.macro_hash_high = hash_high; } - digest.Reset(); - digest.Update(entry_point, static_cast(std::strlen(entry_point))); - digest.Final(hash); - key.entry_point_low = hash_low; - key.entry_point_high = hash_high; + if (entry_point) + { + digest.Reset(); + digest.Update(entry_point, static_cast(std::strlen(entry_point))); + digest.Final(hash); + key.entry_point_low = hash_low; + key.entry_point_high = hash_high; + } + else + { + key.entry_point_low = 0; + key.entry_point_high = 0; + } + + return key; +} + +D3D12ShaderCache::CacheIndexKey D3D12ShaderCache::GetShaderCacheKey( + EntryType type, const std::vector>& shader, const D3D_SHADER_MACRO* macros, const char* entry_point) +{ + union + { + struct + { + u64 hash_low; + u64 hash_high; + }; + u8 hash[16]; + }; + + CacheIndexKey key = {}; + key.type = type; + + MD5Digest digest; + for (const std::pair& lib : shader) + { + digest.Reset(); + digest.Update(lib.first.data(), static_cast(lib.first.length())); + digest.Final(hash); + key.source_hash_low = hash_low; + key.source_hash_high = hash_high; + key.source_length = static_cast(lib.first.length()); + } + + if (macros) + { + digest.Reset(); + for (const D3D_SHADER_MACRO* macro = macros; macro->Name != nullptr; macro++) + { + digest.Update(macro->Name, std::strlen(macro->Name)); + digest.Update(macro->Definition, std::strlen(macro->Definition)); + } + digest.Final(hash); + key.macro_hash_low = hash_low; + key.macro_hash_high = hash_high; + } + + if (entry_point) + { + digest.Reset(); + digest.Update(entry_point, static_cast(std::strlen(entry_point))); + digest.Final(hash); + key.entry_point_low = hash_low; + key.entry_point_high = hash_high; + } + else + { + key.entry_point_low = 0; + key.entry_point_high = 0; + } return key; } @@ -393,13 +455,33 @@ D3D12ShaderCache::CacheIndexKey D3D12ShaderCache::GetPipelineCacheKey(const D3D1 return CacheIndexKey{h.low, h.high, 0, 0, 0, 0, length, EntryType::ComputePipeline}; } -D3D12ShaderCache::ComPtr D3D12ShaderCache::GetShaderBlob(EntryType type, std::string_view shader_code, - const D3D_SHADER_MACRO* macros /* = nullptr */, const char* entry_point /* = "main" */) +D3D12ShaderCache::ComPtr D3D12ShaderCache::GetShaderBlob(EntryType type, const std::string_view shader_code, const char* shader_name, + const D3D_SHADER_MACRO* macros /* = nullptr */, const char* entry_point /* = "main" */, const std::unordered_map& includes /*= {} */) { const auto key = GetShaderCacheKey(type, shader_code, macros, entry_point); auto iter = m_shader_index.find(key); if (iter == m_shader_index.end()) - return CompileAndAddShaderBlob(key, shader_code, macros, entry_point); + return CompileAndAddShaderBlob(key, shader_code, shader_name, macros, entry_point, includes); + + ComPtr blob; + HRESULT hr = D3DCreateBlob(iter->second.blob_size, blob.put()); + if (FAILED(hr) || std::fseek(m_shader_blob_file, iter->second.file_offset, SEEK_SET) != 0 || + std::fread(blob->GetBufferPointer(), 1, iter->second.blob_size, m_shader_blob_file) != iter->second.blob_size) + { + Console.Error("Read blob from file failed"); + return {}; + } + + return blob; +} + +D3D12ShaderCache::ComPtr D3D12ShaderCache::GetShaderBlob(EntryType type, const std::vector>& shader, + const D3D_SHADER_MACRO* macros /* = nullptr */, const char* entry_point /* = "main" */, const std::unordered_map& includes /*= {} */) +{ + const auto key = GetShaderCacheKey(type, shader, macros, entry_point); + auto iter = m_shader_index.find(key); + if (iter == m_shader_index.end()) + return CompileAndAddShaderBlob(key, shader, macros, entry_point, includes); ComPtr blob; HRESULT hr = D3DCreateBlob(iter->second.blob_size, blob.put()); @@ -482,23 +564,253 @@ D3D12ShaderCache::ComPtr D3D12ShaderCache::GetPipelineState } D3D12ShaderCache::ComPtr D3D12ShaderCache::CompileAndAddShaderBlob( - const CacheIndexKey& key, std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point) + const CacheIndexKey& key, const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros, const char* entry_point, const std::unordered_map& includes) { ComPtr blob; +#if DEBUG_LIBS + { + std::vector macro_list; + + if (macros) + { + const D3D_SHADER_MACRO* c_macro = macros; + + while (c_macro->Name != nullptr) + { + macro_list.push_back(fmt::format("{} = {}", c_macro->Name, c_macro->Definition)); + c_macro++; + } + } + + Console.WriteLnFmt("Compiling {} with macros:\n\t{}", shader_name, + macro_list.empty() ? "None" : StringUtil::JoinString(macro_list.begin(), macro_list.end(), "\n\t")); + } +#endif + + switch (key.type) + { + case EntryType::VertexShader: + blob = + D3D::CompileShader(D3D::ShaderType::Vertex, m_shader_model, m_debug, shader_code, shader_name, macros, entry_point, includes); + break; + case EntryType::PixelShader: + blob = + D3D::CompileShader(D3D::ShaderType::Pixel, m_shader_model, m_debug, shader_code, shader_name, macros, entry_point, includes); + break; + case EntryType::ComputeShader: + blob = + D3D::CompileShader(D3D::ShaderType::Compute, m_shader_model, m_debug, shader_code, shader_name, macros, entry_point, includes); + break; + case EntryType::LibraryShader: + blob = + D3D::CompileShader(D3D::ShaderType::Libary, m_shader_model, m_debug, shader_code, shader_name, macros, entry_point, includes); + break; + default: + break; + } + + if (!blob) + return {}; + + if (!m_shader_blob_file || std::fseek(m_shader_blob_file, 0, SEEK_END) != 0) + return blob; + + CacheIndexData data; + data.file_offset = static_cast(std::ftell(m_shader_blob_file)); + data.blob_size = static_cast(blob->GetBufferSize()); + + CacheIndexEntry entry = {}; + entry.source_hash_low = key.source_hash_low; + entry.source_hash_high = key.source_hash_high; + entry.macro_hash_low = key.macro_hash_low; + entry.macro_hash_high = key.macro_hash_high; + entry.entry_point_low = key.entry_point_low; + entry.entry_point_high = key.entry_point_high; + entry.source_length = key.source_length; + entry.shader_type = static_cast(key.type); + entry.blob_size = data.blob_size; + entry.file_offset = data.file_offset; + + if (std::fwrite(blob->GetBufferPointer(), 1, entry.blob_size, m_shader_blob_file) != entry.blob_size || + std::fflush(m_shader_blob_file) != 0 || std::fwrite(&entry, sizeof(entry), 1, m_shader_index_file) != 1 || + std::fflush(m_shader_index_file) != 0) + { + Console.Error("Failed to write shader blob to file"); + return blob; + } + + m_shader_index.emplace(key, data); + return blob; +} + +void D3D12ShaderCache::CollectIncludes(const std::string& shader_code, const std::unordered_map& includes, + std::vector::const_iterator>& included_files, std::string_view parent_path) +{ + // Collect includes, going line by line. + const std::vector lines = StringUtil::SplitString(shader_code, '\n'); + for (const std::string_view line : lines) + { + const std::string_view str = StringUtil::StripWhitespace(line); + if (str.starts_with("#include")) + { + // Extract path. + const size_t s = str.find_first_of('"'); + const size_t e = str.find_last_of('"'); + if (s == std::string_view::npos || e == std::string_view::npos) + { + pxAssert(false); + continue; + } + const std::string filename = std::string(str.substr(s + 1, e - s - 1)); + + std::unordered_map::const_iterator iter; + if (!parent_path.empty()) + { + // Search same directory as file. + const std::string path = Path::Canonicalize(Path::Combine(parent_path, filename)); + + iter = includes.find(path); + if (iter == includes.end()) + { + // Search root directory. + iter = includes.find(filename); + } + } + else + iter = includes.find(filename); + + if (iter == includes.end()) + { + pxAssert(false); + continue; + } + + // Check if we found this include already. + bool already_found = false; + for (auto& i : included_files) + { + if (i == iter) + { + already_found = true; + break; + } + } + if (already_found) + continue; + + included_files.push_back(iter); + CollectIncludes(iter->second, includes, included_files, Path::GetDirectory(filename)); + } + } +} + +D3D12ShaderCache::ComPtr D3D12ShaderCache::CompileAndAddShaderBlob( + const CacheIndexKey& key, const std::vector>& shader, const D3D_SHADER_MACRO* macros, const char* entry_point, const std::unordered_map& includes) +{ + pxAssert(key.type != EntryType::LibraryShader); + +#if DEBUG_LIBS + { + std::vector macro_list; + const D3D_SHADER_MACRO* c_macro = macros; + while (c_macro->Name != nullptr) + { + macro_list.push_back(fmt::format("{} = {}", c_macro->Name, c_macro->Definition)); + c_macro++; + } + + Console.WriteLnFmt("Compiling linked shader with macros:\n\t{}", + macro_list.empty() ? "None" : StringUtil::JoinString(macro_list.begin(), macro_list.end(), "\n\t")); + } +#endif + + std::vector> compiled_blobs; + compiled_blobs.reserve(shader.size()); + + for (const std::pair& lib : shader) + { + std::vector lib_macros; + + if (macros) + { + MD5Hash lib_hash; + MD5Digest digest; + digest.Update(lib.first.data(), static_cast(lib.first.length())); + digest.Final(lib_hash.hash); + + auto macro_iter = m_lib_defines.find(lib_hash); + if (macro_iter == m_lib_defines.end()) + macro_iter = m_lib_defines.emplace(lib_hash, std::unordered_map{}).first; + + const D3D_SHADER_MACRO* c_macro = macros; + + std::vector::const_iterator> included_files; + bool loaded_includes = false; + + while (c_macro->Name != nullptr) + { + bool hit = false; + const auto hit_iter = macro_iter->second.find(c_macro->Name); + if (hit_iter == macro_iter->second.end()) + { + // Search file + if (StringUtil::ContainsSubString(lib.first, c_macro->Name)) + hit = true; + // Search includes + if (!hit) + { + if (!loaded_includes) + { + CollectIncludes(lib.first, includes, included_files); + loaded_includes = true; + } + + for (auto& i : included_files) + { + if (StringUtil::ContainsSubString(i->second, c_macro->Name)) + { + hit = true; + break; + } + } + } + + macro_iter->second.emplace(c_macro->Name, hit); + } + else + hit = hit_iter->second; + + if (hit) + lib_macros.push_back(*c_macro); + c_macro++; + } + } + + lib_macros.push_back({nullptr, nullptr}); + + ComPtr lib_blob = GetShaderBlob(EntryType::LibraryShader, lib.first, lib.second, lib_macros.data(), nullptr, includes); + if (lib_blob == nullptr) + return nullptr; + + compiled_blobs.push_back(std::move(lib_blob)); + } + + // Now Link + ComPtr blob; switch (key.type) { case EntryType::VertexShader: blob = - D3D::CompileShader(D3D::ShaderType::Vertex, m_shader_model, m_debug, shader_code, macros, entry_point); + D3D::LinkShaderDXIL(D3D::ShaderType::Vertex, m_shader_model, m_debug, compiled_blobs, entry_point); break; case EntryType::PixelShader: blob = - D3D::CompileShader(D3D::ShaderType::Pixel, m_shader_model, m_debug, shader_code, macros, entry_point); + D3D::LinkShaderDXIL(D3D::ShaderType::Pixel, m_shader_model, m_debug, compiled_blobs, entry_point); break; case EntryType::ComputeShader: blob = - D3D::CompileShader(D3D::ShaderType::Compute, m_shader_model, m_debug, shader_code, macros, entry_point); + D3D::LinkShaderDXIL(D3D::ShaderType::Compute, m_shader_model, m_debug, compiled_blobs, entry_point); break; default: break; diff --git a/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.h b/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.h index 3b6422c1d6922..373d6fa4af79c 100644 --- a/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.h +++ b/pcsx2/GS/Renderers/DX12/D3D12ShaderCache.h @@ -26,6 +26,7 @@ class D3D12ShaderCache VertexShader, PixelShader, ComputeShader, + LibraryShader, GraphicsPipeline, ComputePipeline, }; @@ -39,23 +40,31 @@ class D3D12ShaderCache void Close(); __fi ComPtr GetVertexShader( - std::string_view shader_code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main") + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", const std::unordered_map& includes = {}) { - return GetShaderBlob(EntryType::VertexShader, shader_code, macros, entry_point); + return GetShaderBlob(EntryType::VertexShader, shader_code, shader_name, macros, entry_point, includes); } __fi ComPtr GetPixelShader( - std::string_view shader_code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main") + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", const std::unordered_map& includes = {}) { - return GetShaderBlob(EntryType::PixelShader, shader_code, macros, entry_point); + return GetShaderBlob(EntryType::PixelShader, shader_code, shader_name, macros, entry_point, includes); + } + __fi ComPtr GetPixelShader( + const std::vector>& shader, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", const std::unordered_map& includes = {}) + { + return GetShaderBlob(EntryType::PixelShader, shader, macros, entry_point, includes); } __fi ComPtr GetComputeShader( - std::string_view shader_code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main") + const std::string_view shader_code, const char* shader_name, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main") { - return GetShaderBlob(EntryType::ComputeShader, shader_code, macros, entry_point); + return GetShaderBlob(EntryType::ComputeShader, shader_code, shader_name, macros, entry_point); } - ComPtr GetShaderBlob(EntryType type, std::string_view shader_code, - const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main"); + ComPtr GetShaderBlob(EntryType type, const std::string_view shader_code, const char* shader_name, + const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", const std::unordered_map& includes = {}); + + ComPtr GetShaderBlob(EntryType type, const std::vector>& shader, + const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main", const std::unordered_map& includes = {}); ComPtr GetPipelineState(ID3D12Device* device, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& desc); ComPtr GetPipelineState(ID3D12Device* device, const D3D12_COMPUTE_PIPELINE_STATE_DESC& desc); @@ -95,9 +104,40 @@ class D3D12ShaderCache using CacheIndex = std::unordered_map; + union MD5Hash + { + struct + { + u64 low; + u64 high; + }; + u8 hash[16]; + + bool operator==(const MD5Hash& other) const + { + return (low == other.low) && (high == other.high); + } + bool operator!=(const MD5Hash& other) const + { + return !(*this == other); + } + }; + + struct MD5HashHasher + { + std::size_t operator()(const MD5Hash& e) const noexcept + { + std::size_t h = 0; + HashCombine(h, e.low, e.high); + return h; + } + }; + static std::string GetCacheBaseFileName(const std::string_view type, D3D::ShaderModel shader_model, bool debug); static CacheIndexKey GetShaderCacheKey( EntryType type, const std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point); + static CacheIndexKey GetShaderCacheKey( + EntryType type, const std::vector>& shader, const D3D_SHADER_MACRO* macros, const char* entry_point); static CacheIndexKey GetPipelineCacheKey(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& gpdesc); static CacheIndexKey GetPipelineCacheKey(const D3D12_COMPUTE_PIPELINE_STATE_DESC& gpdesc); @@ -107,8 +147,13 @@ class D3D12ShaderCache std::FILE*& blob_file, CacheIndex& index); void InvalidatePipelineCache(); - ComPtr CompileAndAddShaderBlob(const CacheIndexKey& key, std::string_view shader_code, - const D3D_SHADER_MACRO* macros, const char* entry_point); + void CollectIncludes(const std::string& shader_code, const std::unordered_map& includes, + std::vector::const_iterator>& included_files, std::string_view parent_path = {}); + + ComPtr CompileAndAddShaderBlob(const CacheIndexKey& key, const std::string_view shader_code, const char* shader_name, + const D3D_SHADER_MACRO* macros, const char* entry_point, const std::unordered_map& includes = {}); + ComPtr CompileAndAddShaderBlob(const CacheIndexKey& key, const std::vector>& shader, + const D3D_SHADER_MACRO* macros, const char* entry_point, const std::unordered_map& includes = {}); ComPtr CompileAndAddPipeline( ID3D12Device* device, const CacheIndexKey& key, const D3D12_GRAPHICS_PIPELINE_STATE_DESC& gpdesc); ComPtr CompileAndAddPipeline( @@ -125,4 +170,7 @@ class D3D12ShaderCache D3D::ShaderModel m_shader_model = D3D::ShaderModel::SM51; bool m_debug = false; + + // Track defines needed for each lib of a linked shader. + std::unordered_map, MD5HashHasher> m_lib_defines; }; diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 965dc820261ae..119c51cd8904a 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -974,7 +974,56 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle) m_tfx_source = std::move(*shader); } - if (!m_shader_cache.Open(D3D::ShaderModel::SM51, GSConfig.UseDebugDevice)) + { + std::optional shader; + +#define SHADER_FILE(file) \ + shader = ReadShaderSource("shaders/dx11/" file); \ + if (!shader.has_value()) \ + { \ + Host::ReportErrorAsync("GS", "Failed to read shaders/dx11/" file); \ + return false; \ + } \ + if (m_shader_linking) \ + m_tfx_lib_source.push_back(std::make_pair(std::move(*shader), file)); \ + else \ + m_tfx_includes.emplace(file, std::move(*shader)); + + SHADER_FILE("tfx_ps_main.hlsl"); + SHADER_FILE("tfx_ps_atst.hlsl"); + SHADER_FILE("tfx_ps_blend.hlsl"); + SHADER_FILE("tfx_ps_color.hlsl"); + SHADER_FILE("tfx_ps_fetch.hlsl"); + SHADER_FILE("tfx_ps_fog.hlsl"); + SHADER_FILE("tfx_ps_misc.hlsl"); + SHADER_FILE("tfx_ps_rt.hlsl"); + SHADER_FILE("tfx_ps_rta_correction.hlsl"); + SHADER_FILE("tfx_ps_sample.hlsl"); + SHADER_FILE("tfx_ps_sample_af.hlsl"); + SHADER_FILE("tfx_ps_tfx.hlsl"); + +#undef SHADER_FILE + } + + { + std::optional shader; + +#define SHADER_FILE(include) \ + shader = ReadShaderSource("shaders/dx11/" include); \ + if (!shader.has_value()) \ + { \ + Host::ReportErrorAsync("GS", "Failed to read shaders/dx11/" include); \ + return false; \ + } \ + m_tfx_includes.emplace(include, std::move(*shader)); + + SHADER_FILE("tfx_defines.hlsl"); + SHADER_FILE("tfx_ps_resources.hlsl"); + +#undef SHADER_FILE + } + + if (!m_shader_cache.Open(static_cast(m_shader_model), GSConfig.UseDebugDevice)) Console.Warning("D3D12: Shader cache failed to open."); if (!CreateRootSignatures()) @@ -1520,6 +1569,12 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id) DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported)); m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE); + D3D12_FEATURE_DATA_SHADER_MODEL device_shader_support = {D3D_SHADER_MODEL_6_5}; + hr = m_device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &device_shader_support, sizeof(device_shader_support)); + m_shader_model = SUCCEEDED(hr) ? device_shader_support.HighestShaderModel : D3D_SHADER_MODEL_5_1; + Console.WriteLnFmt("D3D12: Shader Model: {}.{}", (m_shader_model & 0xF0) >> 4, (m_shader_model & 0xF)); + m_shader_linking = (m_shader_model >= D3D_SHADER_MODEL_6_5) && !GSConfig.DisableShaderCache; + D3D12_FEATURE_DATA_ARCHITECTURE1 device_architecture1 = {}; hr = m_device->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE1, &device_architecture1, sizeof(device_architecture1)); m_uma = SUCCEEDED(hr) && device_architecture1.UMA; @@ -2295,8 +2350,8 @@ bool GSDevice12::CompileCASPipelines() static constexpr D3D_SHADER_MACRO sharpen_only_macros[] = {{"CAS_SHARPEN_ONLY", "1"}, {nullptr, nullptr}}; - const ComPtr cs_upscale(m_shader_cache.GetComputeShader(cas_source.value(), nullptr, "main")); - const ComPtr cs_sharpen(m_shader_cache.GetComputeShader(cas_source.value(), sharpen_only_macros, "main")); + const ComPtr cs_upscale(m_shader_cache.GetComputeShader(cas_source.value(), "cas.hlsl", nullptr, "main")); + const ComPtr cs_sharpen(m_shader_cache.GetComputeShader(cas_source.value(), "cas.hlsl", sharpen_only_macros, "main")); if (!cs_upscale || !cs_sharpen) return false; @@ -2324,8 +2379,8 @@ bool GSDevice12::CompileImGuiPipeline() return false; } - const ComPtr vs = m_shader_cache.GetVertexShader(hlsl.value(), nullptr, "vs_main"); - const ComPtr ps = m_shader_cache.GetPixelShader(hlsl.value(), nullptr, "ps_main"); + const ComPtr vs = m_shader_cache.GetVertexShader(hlsl.value(), "imgui.fx", nullptr, "vs_main"); + const ComPtr ps = m_shader_cache.GetPixelShader(hlsl.value(), "imgui.fx", nullptr, "ps_main"); if (!vs || !ps) { Console.Error("D3D12: Failed to compile ImGui shaders"); @@ -2704,17 +2759,17 @@ static void AddUtilityVertexAttributes(D3D12::GraphicsPipelineBuilder& gpb) gpb.SetPrimitiveTopologyType(D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE); } -GSDevice12::ComPtr GSDevice12::GetUtilityVertexShader(const std::string& source, const char* entry_point) +GSDevice12::ComPtr GSDevice12::GetUtilityVertexShader(const std::string& source, const char* name, const char* entry_point) { ShaderMacro sm_model; sm_model.AddMacro("VERTEX_SHADER", "1"); - return m_shader_cache.GetVertexShader(source, sm_model.GetPtr(), entry_point); + return m_shader_cache.GetVertexShader(source, name, sm_model.GetPtr(), entry_point); } -GSDevice12::ComPtr GSDevice12::GetUtilityPixelShader(const std::string& source, const char* entry_point) +GSDevice12::ComPtr GSDevice12::GetUtilityPixelShader(const std::string& source, const char* name, const char* entry_point) { ShaderMacro sm_model; - return m_shader_cache.GetPixelShader(source, sm_model.GetPtr(), entry_point); + return m_shader_cache.GetPixelShader(source, name, sm_model.GetPtr(), entry_point); } bool GSDevice12::CreateNullTexture() @@ -2826,7 +2881,7 @@ bool GSDevice12::CompileConvertPipelines() return false; } - m_convert_vs = GetUtilityVertexShader(*source, "vs_main"); + m_convert_vs = GetUtilityVertexShader(*source, "convert.fx", "vs_main"); if (!m_convert_vs) return false; @@ -2887,7 +2942,7 @@ bool GSDevice12::CompileConvertPipelines() sm.AddMacro("HAS_FLOAT32_OUTPUT", static_cast(shader.Float32Output())); sm.AddMacro(entry_point_macro.c_str(), 1); - ComPtr ps(m_shader_cache.GetPixelShader(*source, sm.GetPtr(), shader.EntryPoint())); + ComPtr ps(m_shader_cache.GetPixelShader(*source, "convert.fx", sm.GetPtr(), shader.EntryPoint())); if (!ps) return false; @@ -2934,7 +2989,7 @@ bool GSDevice12::CompileConvertPipelines() sm.AddMacro("PIXEL_SHADER", "1"); sm.AddMacro(entry_point_macro.c_str(), "1"); - ComPtr ps(m_shader_cache.GetPixelShader(*source, sm.GetPtr(), entry_point.c_str())); + ComPtr ps(m_shader_cache.GetPixelShader(*source, "convert.fx", sm.GetPtr(), entry_point.c_str())); if (!ps) return false; @@ -2970,7 +3025,7 @@ bool GSDevice12::CompilePresentPipelines() return false; } - ComPtr vs = GetUtilityVertexShader(*shader, "vs_main"); + ComPtr vs = GetUtilityVertexShader(*shader, "present.fx", "vs_main"); if (!vs) return false; @@ -2988,7 +3043,7 @@ bool GSDevice12::CompilePresentPipelines() { const int index = static_cast(i); - ComPtr ps(GetUtilityPixelShader(*shader, ShaderEntryPoint(i))); + ComPtr ps(GetUtilityPixelShader(*shader, "present.fx", ShaderEntryPoint(i))); if (!ps) return false; @@ -3024,7 +3079,7 @@ bool GSDevice12::CompileInterlacePipelines() for (int i = 0; i < static_cast(m_interlace.size()); i++) { - ComPtr ps(GetUtilityPixelShader(*source, StringUtil::StdStringFromFormat("ps_main%d", i).c_str())); + ComPtr ps(GetUtilityPixelShader(*source, "interlace.fx", StringUtil::StdStringFromFormat("ps_main%d", i).c_str())); if (!ps) return false; @@ -3059,7 +3114,7 @@ bool GSDevice12::CompileMergePipelines() for (int i = 0; i < static_cast(m_merge.size()); i++) { - ComPtr ps(GetUtilityPixelShader(*shader, StringUtil::StdStringFromFormat("ps_main%d", i).c_str())); + ComPtr ps(GetUtilityPixelShader(*shader, "interlace.fx", StringUtil::StdStringFromFormat("ps_main%d", i).c_str())); if (!ps) return false; @@ -3098,7 +3153,7 @@ bool GSDevice12::CompilePostProcessingPipelines() ShaderMacro sm; sm.AddMacro("FXAA_HLSL", "1"); - ComPtr ps = m_shader_cache.GetPixelShader(*shader, sm.GetPtr()); + ComPtr ps = m_shader_cache.GetPixelShader(*shader, "fxaa.fx", sm.GetPtr()); if (!ps) return false; @@ -3119,7 +3174,7 @@ bool GSDevice12::CompilePostProcessingPipelines() return false; } - ComPtr ps(GetUtilityPixelShader(*shader, "ps_main")); + ComPtr ps(GetUtilityPixelShader(*shader, "shadeboost.fx", "ps_main")); if (!ps) return false; @@ -3222,7 +3277,7 @@ const ID3DBlob* GSDevice12::GetTFXVertexShader(GSHWDrawConfig::VSSelector sel) sm.AddMacro("VS_EXPAND", static_cast(sel.expand)); const char* entry_point = (sel.expand != GSHWDrawConfig::VSExpand::None) ? "vs_main_expand" : "vs_main"; - ComPtr vs(m_shader_cache.GetVertexShader(m_tfx_source, sm.GetPtr(), entry_point)); + ComPtr vs(m_shader_cache.GetVertexShader(m_tfx_source, "tfx.fx", sm.GetPtr(), entry_point, m_tfx_includes)); it = m_tfx_vertex_shaders.emplace(sel.key, std::move(vs)).first; return it->second.get(); } @@ -3301,7 +3356,12 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector& sm.AddMacro("PS_ROV_COLOR", sel.rov_color); sm.AddMacro("PS_ROV_DEPTH", static_cast(sel.rov_depth)); - ComPtr ps(m_shader_cache.GetPixelShader(m_tfx_source, sm.GetPtr(), "ps_main")); + ComPtr ps; + if (m_shader_linking) + ps = m_shader_cache.GetPixelShader(m_tfx_lib_source, sm.GetPtr(), "ps_main", m_tfx_includes); + else + ps = m_shader_cache.GetPixelShader(m_tfx_source, "tfx.fx", sm.GetPtr(), "ps_main", m_tfx_includes); + it = m_tfx_pixel_shaders.emplace(sel, std::move(ps)).first; return it->second.get(); } diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.h b/pcsx2/GS/Renderers/DX12/GSDevice12.h index 691e176afb35c..767a817121fdd 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.h +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.h @@ -381,6 +381,8 @@ class GSDevice12 final : public GSDevice bool m_allow_tearing_supported = false; bool m_using_allow_tearing = false; bool m_is_exclusive_fullscreen = false; + D3D_SHADER_MODEL m_shader_model = D3D_SHADER_MODEL_5_1; + bool m_shader_linking = false; bool m_uma = false; bool m_typed_casting_supported = false; bool m_enhanced_barriers = false; @@ -440,6 +442,8 @@ class GSDevice12 final : public GSDevice D3D12ShaderCache m_shader_cache; ComPtr m_convert_vs; std::string m_tfx_source; + std::vector> m_tfx_lib_source; + std::unordered_map m_tfx_includes; void LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format, DXGI_FORMAT* uav_format) const; @@ -472,8 +476,8 @@ class GSDevice12 final : public GSDevice ComPtr CreateTFXPipeline(const PipelineSelector& p); const ID3D12PipelineState* GetTFXPipeline(const PipelineSelector& p); - ComPtr GetUtilityVertexShader(const std::string& source, const char* entry_point); - ComPtr GetUtilityPixelShader(const std::string& source, const char* entry_point); + ComPtr GetUtilityVertexShader(const std::string& source, const char* name, const char* entry_point); + ComPtr GetUtilityPixelShader(const std::string& source, const char* name, const char* entry_point); void FeedbackBarrier(const GSTexture12* texture); diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 5dcf68fcc1804..39693e20741ac 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -91,6 +91,20 @@ + + + + + + + + + + + + + + diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index b9e3c1a9225a7..1f4ad6695fe88 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -375,6 +375,48 @@ System\Ps2\GS\Shaders\Vulkan + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + + + System\Ps2\GS\Shaders\Direct3D + System\Ps2\GS\Shaders\Vulkan