Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 146 additions & 13 deletions bin/resources/shaders/dx11/tfx.fx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,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));
// TODO: check if we might cause inf/nan
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.5 * 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.5 + i) * (2.0 * 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
Expand Down Expand Up @@ -251,21 +393,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
Expand Down
7 changes: 7 additions & 0 deletions pcsx2-qt/Settings/GraphicsHardwareRenderingSettingsTab.ui
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="swAnsio">
<property name="text">
<string>Shader Ansiotropic Filtering</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="enableHWFixes">
<property name="text">
<string>Manual Hardware Renderer Fixes</string>
Expand Down
1 change: 1 addition & 0 deletions pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
connect(m_hw.trilinearFiltering, &QComboBox::currentIndexChanged, this,
&GraphicsSettingsWidget::onTrilinearFilteringChanged);
onTrilinearFilteringChanged();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.swAnsio, "EmuCore/GS", "SWAnisotropy", true);

//////////////////////////////////////////////////////////////////////////
// SW Settings
Expand Down
1 change: 1 addition & 0 deletions pcsx2/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ struct Pcsx2Config
GSCASMode CASMode = DEFAULT_CAS_MODE;
u8 Dithering = 2;
u8 MaxAnisotropy = 0;
bool SWAnisotropy = true;
u8 TVShader = 0;
s16 GetSkipCountFunctionId = -1;
s16 BeforeDrawFunctionId = -1;
Expand Down
4 changes: 2 additions & 2 deletions pcsx2/GS/Renderers/DX11/D3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ wil::com_ptr_nothrow<ID3DBlob> 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<ID3DBlob> blob;
wil::com_ptr_nothrow<ID3DBlob> error_blob;
Expand Down
29 changes: 21 additions & 8 deletions pcsx2/GS/Renderers/DX11/GSDevice11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1765,9 +1765,11 @@ void GSDevice11::SetupVS(VSSelector sel, const GSHWDrawConfig::VSConstantBuffer*
IASetInputLayout(i->second.il.get());
}

void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstantBuffer* cb, PSSamplerSelector ssel)
void GSDevice11::SetupPS(const PixelShaderSelector& ps_sel, const GSHWDrawConfig::PSConstantBuffer* cb, PSSamplerSelector ssel)
{
auto i = std::as_const(m_ps).find(sel);
const GSHWDrawConfig::PSSelector& sel = ps_sel.ps;

auto i = std::as_const(m_ps).find(ps_sel);

if (i == m_ps.end())
{
Expand Down Expand Up @@ -1837,8 +1839,10 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
sm.AddMacro("PS_COLOR_FEEDBACK", sel.color_feedback);
sm.AddMacro("PS_DEPTH_FEEDBACK", sel.depth_feedback);

sm.AddMacro("PS_ANISOTROPIC_FILTERING", ps_sel.sw_ansio ? ps_sel.sw_ansio_level : 0);

wil::com_ptr_nothrow<ID3D11PixelShader> 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;
i = m_ps.try_emplace(ps_sel, std::move(ps)).first;
}

if (cb && m_ps_cb_cache.Update(*cb))
Expand All @@ -1855,6 +1859,9 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
pxAssert(ssel.biln == 0);
}

if (ps_sel.sw_ansio)
ssel.aniso = false;

auto i = std::as_const(m_ps_ss).find(ssel.key);

if (i != m_ps_ss.end())
Expand Down Expand Up @@ -2825,8 +2832,13 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
if (config.pal)
PSSetShaderResource(1, config.pal);

PixelShaderSelector pss;
pss.ps = config.ps;
pss.sw_ansio = (GSConfig.SWAnisotropy & config.sampler.aniso);
pss.sw_ansio_level = pss.sw_ansio ? GSConfig.MaxAnisotropy : 0;

SetupVS(config.vs, &config.cb_vs);
SetupPS(config.ps, &config.cb_ps, config.sampler);
SetupPS(pss, &config.cb_ps, config.sampler);

if (primid_texture)
{
Expand All @@ -2840,7 +2852,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)

config.ps.date = 3;
config.alpha_second_pass.ps.date = 3;
SetupPS(config.ps, nullptr, config.sampler);
SetupPS(pss, nullptr, config.sampler);
PSSetShaderResource(3, primid_texture);
}

Expand Down Expand Up @@ -2896,22 +2908,23 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
config.ps.no_color1 = config.blend_multi_pass.no_color1;
config.ps.blend_hw = config.blend_multi_pass.blend_hw;
config.ps.dither = config.blend_multi_pass.dither;
SetupPS(config.ps, &config.cb_ps, config.sampler);
SetupPS(pss, &config.cb_ps, config.sampler);
SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend_multi_pass.blend), config.blend_multi_pass.blend.constant);
DrawIndexedPrimitive();
}

if (config.alpha_second_pass.enable)
{
pss.ps = config.alpha_second_pass.ps;
if (config.cb_ps.FogColor_AREF.a != config.alpha_second_pass.ps_aref)
{
config.cb_ps.FogColor_AREF.a = config.alpha_second_pass.ps_aref;
SetupPS(config.alpha_second_pass.ps, &config.cb_ps, config.sampler);
SetupPS(pss, &config.cb_ps, config.sampler);
}
else
{
// ps cbuffer hasn't changed, so don't bother checking
SetupPS(config.alpha_second_pass.ps, nullptr, config.sampler);
SetupPS(pss, nullptr, config.sampler);
}

SetupOM(config.alpha_second_pass.depth, OMBlendSelector(config.alpha_second_pass.colormask, config.blend), config.blend.constant);
Expand Down
37 changes: 34 additions & 3 deletions pcsx2/GS/Renderers/DX11/GSDevice11.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class GSDevice11 final : public GSDevice
{
public:
using VSSelector = GSHWDrawConfig::VSSelector;
using PSSelector = GSHWDrawConfig::PSSelector;
using PSSamplerSelector = GSHWDrawConfig::SamplerSelector;
using OMDepthStencilSelector = GSHWDrawConfig::DepthStencilSelector;

Expand All @@ -49,6 +48,38 @@ class GSDevice11 final : public GSDevice
};
static_assert(sizeof(OMBlendSelector) == sizeof(u64));

struct alignas(8) PixelShaderSelector
{
GSHWDrawConfig::PSSelector ps;

union
{
struct
{
u32 sw_ansio : 1;
u32 sw_ansio_level : 5;
};

u32 key;
};

__fi bool operator==(const PixelShaderSelector& p) const { return BitEqual(*this, p); }
__fi bool operator!=(const PixelShaderSelector& p) const { return !BitEqual(*this, p); }

__fi PixelShaderSelector() { std::memset(this, 0, sizeof(*this)); }
};
static_assert(sizeof(PixelShaderSelector) == 16, "Pixel shader selector is 24 bytes");

struct PixelShaderSelectorHash
{
std::size_t operator()(const PixelShaderSelector& e) const noexcept
{
std::size_t hash = 0;
HashCombine(hash, e.ps.key_hi, e.ps.key_lo, e.key);
return hash;
}
};

class ShaderMacro
{
struct mcstr
Expand Down Expand Up @@ -247,7 +278,7 @@ class GSDevice11 final : public GSDevice
std::unordered_map<u32, GSVertexShader11> m_vs;
wil::com_ptr_nothrow<ID3D11Buffer> m_vs_cb;
std::unordered_map<u32, wil::com_ptr_nothrow<ID3D11GeometryShader>> m_gs;
std::unordered_map<PSSelector, wil::com_ptr_nothrow<ID3D11PixelShader>, GSHWDrawConfig::PSSelectorHash> m_ps;
std::unordered_map<PixelShaderSelector, wil::com_ptr_nothrow<ID3D11PixelShader>, PixelShaderSelectorHash> m_ps;
wil::com_ptr_nothrow<ID3D11Buffer> m_ps_cb;
std::unordered_map<u32, wil::com_ptr_nothrow<ID3D11SamplerState>> m_ps_ss;
std::unordered_map<u32, wil::com_ptr_nothrow<ID3D11DepthStencilState>> m_om_dss;
Expand Down Expand Up @@ -350,7 +381,7 @@ class GSDevice11 final : public GSDevice
void UpdateSubresource(ID3D11Buffer* buffer, const void* cb_uniforms, void* cached_cb_uniforms, size_t cb_uniforms_size);

void SetupVS(VSSelector sel, const GSHWDrawConfig::VSConstantBuffer* cb);
void SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstantBuffer* cb, PSSamplerSelector ssel);
void SetupPS(const PixelShaderSelector& sel, const GSHWDrawConfig::PSConstantBuffer* cb, PSSamplerSelector ssel);
void SetupOM(OMDepthStencilSelector dssel, OMBlendSelector bsel, u8 afix);

void RenderHW(GSHWDrawConfig& config) override;
Expand Down
Loading
Loading