diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index f5e8c0656423a..c69e81007375d 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -220,6 +220,148 @@ cbuffer cb1 float RcpScaleFactor; }; +#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 +float4 sample_c_af(float2 uv, float uv_w) +{ + // Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations + // 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; + + // Calculate Ellipse Transform + bool d_zero = length(dX) == 0 || length(dY) == 0; + bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0; + bool d_per = dot(dX, dY) == 0; + bool d_inf_nan = any(isinf(dX) | isinf(dY) | isnan(dX) | isnan(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 = (dX.x * dY.y - dY.x * dX.y); + float F = f * f; + + float p = A - C; + float q = A + C; + float t = sqrt(p * p + B * B); + + float2 new_dX = float2( + sqrt(F * (t + p) / (t * (q + t))), + sqrt(F * (t - p) / (t * (q + t))) * sign(B) + ); + + float2 new_dY = float2( + sqrt(F * (t - p) / (t * (q - t))) * -sign(B), + sqrt(F * (t + p) / (t * (q - t))) + ); + + d_inf_nan = any(isinf(new_dX) | isinf(new_dY) | isnan(new_dX) | isnan(new_dY)); + if (!d_inf_nan) + { + dX = new_dX; + dY = new_dY; + } + } + + // Compute AF values + float squared_length_x = dX.x * dX.x + dX.y * dX.y; + float squared_length_y = dY.x * dY.x + dY.y * dY.y; + float determinant = abs(dX.x * dY.y - dX.y * dY.x); + bool is_major_x = squared_length_x > squared_length_y; + float squared_length_major = is_major_x ? squared_length_x : squared_length_y; + float length_major = sqrt(squared_length_major); + + 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, 0); + } + else + { + float norm_major = 1.0f / length_major; + + float2 aniso_line_dir = float2( + (is_major_x ? dX.x : dY.x) * norm_major, + (is_major_x ? dX.y : dY.y) * norm_major + ); + + aniso_ratio = squared_length_major / determinant; + + // Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy. + if (aniso_ratio > PS_ANISOTROPIC_FILTERING) + { + // ratio is clamped - Lod is based on ratio (preserves area) + aniso_ratio = PS_ANISOTROPIC_FILTERING; + length_lod = length_major / PS_ANISOTROPIC_FILTERING; + } + else + { + // ratio not clamped - Lod is based on area + length_lod = determinant / length_major; + } + + // 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 * length_major * (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; // No Lod +#endif + + float4 colour; + if (aniso_ratio == 1.0f) + colour = Texture.SampleLevel(TextureSampler, uv, lod); + else + { + float4 num = float4(0, 0, 0, 0); + for (int i = 0; i < aniso_ratio; i++) + { + float2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio; + 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 @@ -252,21 +394,12 @@ float4 sample_c(float2 uv, float uv_w, int2 xy) #endif #endif -#if PS_AUTOMATIC_LOD == 1 +#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 - // 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 ? - //float lod = max(min(gs_lod, max_lod) - bias, 0.0f); - float lod = min(gs_lod, max_lod) - bias; - - return Texture.SampleLevel(TextureSampler, uv, lod); + return Texture.SampleLevel(TextureSampler, uv, manual_lod(uv_w)); #else return Texture.SampleLevel(TextureSampler, uv, 0); // No lod #endif diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index c9d51684cf3d8..a50a4bbeaa180 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -182,6 +182,147 @@ vec4 sample_from_depth() #if NEEDS_TEX +#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 +vec4 sample_c_af(vec2 uv, float uv_w) +{ + // Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations + // With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/ + vec2 sz = textureSize(TextureSampler, 0); + vec2 dX = dFdx(uv) * sz; + vec2 dY = dFdy(uv) * sz; + + // Calculate Ellipse Transform + bool d_zero = length(dX) == 0 || length(dY) == 0; + bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0; + bool d_per = dot(dX, dY) == 0; + bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(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 = (dX.x * dY.y - dY.x * dX.y); + float F = f * f; + + float p = A - C; + float q = A + C; + float t = sqrt(p * p + B * B); + + vec2 new_dX = vec2( + sqrt(F * (t + p) / (t * (q + t))), + sqrt(F * (t - p) / (t * (q + t))) * sign(B) + ); + + vec2 new_dY = vec2( + sqrt(F * (t - p) / (t * (q - t))) * -sign(B), + sqrt(F * (t + p) / (t * (q - t))) + ); + + d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY)); + if (!d_inf_nan) + { + dX = new_dX; + dY = new_dY; + } + } + + // Compute AF values + float squared_length_x = dX.x * dX.x + dX.y * dX.y; + float squared_length_y = dY.x * dY.x + dY.y * dY.y; + float determinant = abs(dX.x * dY.y - dX.y * dY.x); + bool is_major_x = squared_length_x > squared_length_y; + float squared_length_major = is_major_x ? squared_length_x : squared_length_y; + float length_major = sqrt(squared_length_major); + + float aniso_ratio; + float length_lod; + vec2 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 = vec2(0, 0); + } + else + { + float norm_major = 1.0f / length_major; + + vec2 aniso_line_dir = vec2( + (is_major_x ? dX.x : dY.x) * norm_major, + (is_major_x ? dX.y : dY.y) * norm_major + ); + + aniso_ratio = squared_length_major / determinant; + + // Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy. + if (aniso_ratio > PS_ANISOTROPIC_FILTERING) + { + // ratio is clamped - Lod is based on ratio (preserves area) + aniso_ratio = PS_ANISOTROPIC_FILTERING; + length_lod = length_major / PS_ANISOTROPIC_FILTERING; + } + else + { + // ratio not clamped - Lod is based on area + length_lod = determinant / length_major; + } + + // 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 * length_major * (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; // No Lod +#endif + + vec4 colour; + if (aniso_ratio == 1.0f) + colour = textureLod(TextureSampler, uv, lod); + else + { + vec4 num = vec4(0, 0, 0, 0); + for (int i = 0; i < aniso_ratio; i++) + { + vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio; + vec2 uv_sample = uv + d; + vec4 sample_colour = textureLod(TextureSampler, uv_sample, lod); + num += sample_colour; + } + + colour = num / aniso_ratio; + } + return colour; +} +#endif + vec4 sample_c(vec2 uv) { #if PS_TEX_IS_FB == 1 @@ -205,21 +346,12 @@ vec4 sample_c(vec2 uv) #endif #endif -#if PS_AUTOMATIC_LOD == 1 +#if PS_ANISOTROPIC_FILTERING > 1 + return sample_c_af(uv, PSin.t_float.w); +#elif PS_AUTOMATIC_LOD == 1 return texture(TextureSampler, uv); #elif PS_MANUAL_LOD == 1 - // 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(PSin.t_float.w)) * L; - // FIXME max useful ? - //float lod = max(min(gs_lod, max_lod) - bias, 0.0f); - float lod = min(gs_lod, max_lod) - bias; - - return textureLod(TextureSampler, uv, lod); + return textureLod(TextureSampler, uv, manual_lod(PSin.t_float.w)); #else return textureLod(TextureSampler, uv, 0.0f); // No lod #endif diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 787156ca2d2f5..7f885ca4c668f 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -416,6 +416,147 @@ layout(depth_less) out float gl_FragDepth; #if NEEDS_TEX +#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 +vec4 sample_c_af(vec2 uv, float uv_w) +{ + // Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations + // With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/ + vec2 sz = textureSize(Texture, 0); + vec2 dX = dFdx(uv) * sz; + vec2 dY = dFdy(uv) * sz; + + // Calculate Ellipse Transform + bool d_zero = length(dX) == 0 || length(dY) == 0; + bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0; + bool d_per = dot(dX, dY) == 0; + bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(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 = (dX.x * dY.y - dY.x * dX.y); + float F = f * f; + + float p = A - C; + float q = A + C; + float t = sqrt(p * p + B * B); + + vec2 new_dX = vec2( + sqrt(F * (t + p) / (t * (q + t))), + sqrt(F * (t - p) / (t * (q + t))) * sign(B) + ); + + vec2 new_dY = vec2( + sqrt(F * (t - p) / (t * (q - t))) * -sign(B), + sqrt(F * (t + p) / (t * (q - t))) + ); + + d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY)); + if (!d_inf_nan) + { + dX = new_dX; + dY = new_dY; + } + } + + // Compute AF values + float squared_length_x = dX.x * dX.x + dX.y * dX.y; + float squared_length_y = dY.x * dY.x + dY.y * dY.y; + float determinant = abs(dX.x * dY.y - dX.y * dY.x); + bool is_major_x = squared_length_x > squared_length_y; + float squared_length_major = is_major_x ? squared_length_x : squared_length_y; + float length_major = sqrt(squared_length_major); + + float aniso_ratio; + float length_lod; + vec2 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 = vec2(0, 0); + } + else + { + float norm_major = 1.0f / length_major; + + vec2 aniso_line_dir = vec2( + (is_major_x ? dX.x : dY.x) * norm_major, + (is_major_x ? dX.y : dY.y) * norm_major + ); + + aniso_ratio = squared_length_major / determinant; + + // Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy. + if (aniso_ratio > PS_ANISOTROPIC_FILTERING) + { + // ratio is clamped - Lod is based on ratio (preserves area) + aniso_ratio = PS_ANISOTROPIC_FILTERING; + length_lod = length_major / PS_ANISOTROPIC_FILTERING; + } + else + { + // ratio not clamped - Lod is based on area + length_lod = determinant / length_major; + } + + // 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 * length_major * (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; // No Lod +#endif + + vec4 colour; + if (aniso_ratio == 1.0f) + colour = textureLod(Texture, uv, lod); + else + { + vec4 num = vec4(0, 0, 0, 0); + for (int i = 0; i < aniso_ratio; i++) + { + vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio; + vec2 uv_sample = uv + d; + vec4 sample_colour = textureLod(Texture, uv_sample, lod); + num += sample_colour; + } + + colour = num / aniso_ratio; + } + return colour; +} +#endif + vec4 sample_c(vec2 uv) { #if PS_TEX_IS_FB @@ -439,21 +580,12 @@ vec4 sample_c(vec2 uv) #endif #endif -#if PS_AUTOMATIC_LOD == 1 +#if PS_ANISOTROPIC_FILTERING > 1 + return sample_c_af(uv, vsIn.t.w); +#elif PS_AUTOMATIC_LOD == 1 return texture(Texture, uv); #elif PS_MANUAL_LOD == 1 - // 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(vsIn.t.w)) * L; - // FIXME max useful ? - //float lod = max(min(gs_lod, max_lod) - bias, 0.0f); - float lod = min(gs_lod, max_lod) - bias; - - return textureLod(Texture, uv, lod); + return textureLod(Texture, uv, manual_lod(vsIn.t.w)); #else return textureLod(Texture, uv, 0); // No lod #endif diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index df1e28418e2b6..fafc735ac7ea4 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -415,6 +415,9 @@ struct alignas(16) GSHWDrawConfig // Scan mask u32 scanmsk : 2; + + // Other options + u32 sw_aniso : 5; }; struct @@ -889,6 +892,7 @@ class GSDevice : public GSAlignedClass<32> bool cas_sharpening : 1; ///< Supports sufficient functionality for contrast adaptive sharpening. bool test_and_sample_depth: 1; ///< Supports concurrently binding the depth-stencil buffer for sampling and depth testing. DepthFeedbackSupport depth_feedback : 2; ///< Support for depth feedback loops. + bool aniso_sw : 1; ///< Supports anisotropic filtering in the shader. FeatureSupport() { memset(this, 0, sizeof(*this)); diff --git a/pcsx2/GS/Renderers/DX11/D3D.cpp b/pcsx2/GS/Renderers/DX11/D3D.cpp index 9181ec6433831..13421ec3a469b 100644 --- a/pcsx2/GS/Renderers/DX11/D3D.cpp +++ b/pcsx2/GS/Renderers/DX11/D3D.cpp @@ -496,8 +496,8 @@ wil::com_ptr_nothrow D3D::CompileShader(D3D::ShaderType type, D3D_FEAT break; } - static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3; - static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE; + static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_IEEE_STRICTNESS; + static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE | D3DCOMPILE_IEEE_STRICTNESS; wil::com_ptr_nothrow blob; wil::com_ptr_nothrow error_blob; diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 0fd88f9d53715..5c1936010ba55 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -77,6 +77,7 @@ GSDevice11::GSDevice11() { m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None; } + m_features.aniso_sw = true; } GSDevice11::~GSDevice11() = default; @@ -1833,6 +1834,7 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant sm.AddMacro("PS_NO_COLOR", sel.no_color); sm.AddMacro("PS_NO_COLOR1", sel.no_color1); sm.AddMacro("PS_ZTST", sel.ztst); + sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso); wil::com_ptr_nothrow ps = m_shader_cache.GetPixelShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "ps_main"); i = m_ps.try_emplace(sel, std::move(ps)).first; diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index cd8b379bfa93c..94771e53a335c 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -1410,6 +1410,7 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id) { m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None; } + m_features.aniso_sw = true; m_features.dxt_textures = SupportsTextureFormat(DXGI_FORMAT_BC1_UNORM) && SupportsTextureFormat(DXGI_FORMAT_BC2_UNORM) && @@ -3157,6 +3158,7 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector& sm.AddMacro("PS_NO_COLOR", sel.no_color); sm.AddMacro("PS_NO_COLOR1", sel.no_color1); sm.AddMacro("PS_ZTST", sel.ztst); + sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso); ComPtr ps(m_shader_cache.GetPixelShader(m_tfx_source, sm.GetPtr(), "ps_main")); it = m_tfx_pixel_shaders.emplace(sel, std::move(ps)).first; diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 361e87ea1a209..59963361cd28f 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -7776,7 +7776,8 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt, // Aniso filtering doesn't work with textureLod so use texture (automatic_lod) instead. // Enable aniso only for triangles. Sprites are flat so aniso is likely useless (it would save perf for others primitives). const bool anisotropic = m_vt.m_primclass == GS_TRIANGLE_CLASS && !trilinear_manual; - m_conf.sampler.aniso = anisotropic; + m_conf.sampler.aniso = anisotropic && !g_gs_device->Features().aniso_sw; + m_conf.ps.sw_aniso = (anisotropic && g_gs_device->Features().aniso_sw) ? GSConfig.MaxAnisotropy : 0; m_conf.sampler.triln = trilinear; if (anisotropic && !trilinear_manual) m_conf.ps.automatic_lod = 1; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index d9409c243a08d..eb66a51a27536 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -943,6 +943,7 @@ static void OnMainThread(Fn&& fn) m_features.cas_sharpening = true; m_features.test_and_sample_depth = true; m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None; + m_features.aniso_sw = false; m_max_texture_size = m_dev.features.max_texsize; // Init metal stuff diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 73a4dbb939586..369470707d72e 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -860,6 +860,7 @@ bool GSDeviceOGL::CheckFeatures() { m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None; } + m_features.aniso_sw = true; if (GLAD_GL_ARB_shader_storage_buffer_object) { @@ -1558,6 +1559,7 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel) + fmt::format("#define PS_NO_COLOR {}\n", sel.no_color) + fmt::format("#define PS_NO_COLOR1 {}\n", sel.no_color1) + fmt::format("#define PS_ZTST {}\n", sel.ztst) + + fmt::format("#define PS_ANISOTROPIC_FILTERING {}", sel.sw_aniso) ; std::string src = GenGlslHeader("ps_main", GL_FRAGMENT_SHADER, macro); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 58895159ca5b5..7e417c143cfc3 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -2708,6 +2708,7 @@ bool GSDeviceVK::CheckFeatures() { m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None; } + m_features.aniso_sw = true; DevCon.WriteLn("Optional features:%s%s%s%s%s", m_features.primitive_id ? " primitive_id" : "", m_features.texture_barrier ? " texture_barrier" : "", m_features.framebuffer_fetch ? " framebuffer_fetch" : "", @@ -4855,6 +4856,7 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector AddMacro(ss, "PS_NO_COLOR", sel.no_color); AddMacro(ss, "PS_NO_COLOR1", sel.no_color1); AddMacro(ss, "PS_ZTST", sel.ztst); + AddMacro(ss, "PS_ANISOTROPIC_FILTERING", sel.sw_aniso); ss << m_tfx_source; VkShaderModule mod = g_vulkan_shader_cache->GetFragmentShader(ss.str()); diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 6f1bb67cd0ef5..a12fa310c2843 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -3,4 +3,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 85; // Last changed in PR 14195 +static constexpr u32 SHADER_CACHE_VERSION = 86; // Last changed in PR 14188