GS/HW: Improve accuracy of RGB_ONLY AFAIL

Instead of breaking the draw into two passes, which breaks when
fragments overlap each other and blending is enabled, use blending to
leave the value of Ad intact when a pixel fails the alpha test.

In the case of DATE being enabled, prefer PrimID over stencil, as since
we are changing Ad on a per-fragment basis, with some fragments not
being modified, stencil DATE will become desynchronized with the value
of Ad.
This commit is contained in:
Stenzek 2024-03-27 19:33:55 +10:00 committed by Connor McLaughlin
parent b644f85f51
commit a317e9c038
16 changed files with 195 additions and 103 deletions

View File

@ -701,29 +701,30 @@ float4 tfx(float4 T, float4 C)
return C_out;
}
void atst(float4 C)
bool atst(float4 C)
{
float a = C.a;
if(PS_ATST == 0)
if(PS_ATST == 1)
{
// nothing to do
}
else if(PS_ATST == 1)
{
if (a > AREF) discard;
return (a <= AREF);
}
else if(PS_ATST == 2)
{
if (a < AREF) discard;
return (a >= AREF);
}
else if(PS_ATST == 3)
{
if (abs(a - AREF) > 0.5f) discard;
return (abs(a - AREF) <= 0.5f);
}
else if(PS_ATST == 4)
{
if (abs(a - AREF) < 0.5f) discard;
return (abs(a - AREF) >= 0.5f);
}
else
{
// nothing to do
return true;
}
}
@ -786,8 +787,6 @@ float4 ps_color(PS_INPUT input)
float4 C = tfx(T, input.c);
atst(C);
C = fog(C, input.t.z);
return C;
@ -964,6 +963,12 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy)
PS_OUTPUT ps_main(PS_INPUT input)
{
float4 C = ps_color(input);
bool atst_pass = atst(C);
#if PS_AFAIL == 0 // KEEP or ATST off
if (!atst_pass)
discard;
#endif
PS_OUTPUT output;
@ -1119,6 +1124,11 @@ PS_OUTPUT ps_main(PS_INPUT input)
ps_fbmask(C, input.p.xy);
#if PS_AFAIL == 3 // RGB_ONLY
// Use alpha blend factor to determine whether to update A.
alpha_blend.a = float(atst_pass);
#endif
#if !PS_NO_COLOR
output.c0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f;
output.c0.rgb = PS_HDR ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f;

View File

@ -640,20 +640,21 @@ vec4 tfx(vec4 T, vec4 C)
return C_out;
}
void atst(vec4 C)
bool atst(vec4 C)
{
float a = C.a;
#if (PS_ATST == 0)
// nothing to do
#elif (PS_ATST == 1)
if (a > AREF) discard;
#if (PS_ATST == 1)
return (a <= AREF);
#elif (PS_ATST == 2)
if (a < AREF) discard;
return (a >= AREF);
#elif (PS_ATST == 3)
if (abs(a - AREF) > 0.5f) discard;
return (abs(a - AREF) <= 0.5f);
#elif (PS_ATST == 4)
if (abs(a - AREF) < 0.5f) discard;
return (abs(a - AREF) >= 0.5f);
#else
// nothing to do
return true;
#endif
}
@ -984,6 +985,12 @@ void ps_main()
#endif
vec4 C = ps_color();
bool atst_pass = atst(C);
#if PS_AFAIL == 0 // KEEP or ATST off
if (!atst_pass)
discard;
#endif
// Must be done before alpha correction
@ -1102,6 +1109,11 @@ void ps_main()
ps_fbmask(C);
#if PS_AFAIL == 3 // RGB_ONLY
// Use alpha blend factor to determine whether to update A.
alpha_blend.a = float(atst_pass);
#endif
#if !PS_NO_COLOR
#if PS_RTA_CORRECTION
SV_Target0.a = C.a / 128.0f;

View File

@ -260,6 +260,7 @@ void main()
#define PS_TFX 0
#define PS_TCC 1
#define PS_ATST 1
#define PS_AFAIL 0
#define PS_FOG 0
#define PS_BLEND_HW 0
#define PS_A_MASKED 0
@ -885,29 +886,30 @@ vec4 tfx(vec4 T, vec4 C)
return C_out;
}
void atst(vec4 C)
bool atst(vec4 C)
{
float a = C.a;
#if (PS_ATST == 0)
#if (PS_ATST == 1)
{
// nothing to do
}
#elif (PS_ATST == 1)
{
if (a > AREF) discard;
return (a <= AREF);
}
#elif (PS_ATST == 2)
{
if (a < AREF) discard;
return (a >= AREF);
}
#elif (PS_ATST == 3)
{
if (abs(a - AREF) > 0.5f) discard;
return (abs(a - AREF) <= 0.5f);
}
#elif (PS_ATST == 4)
{
if (abs(a - AREF) < 0.5f) discard;
return (abs(a - AREF) >= 0.5f);
}
#else
{
// nothing to do
return true;
}
#endif
}
@ -968,8 +970,6 @@ vec4 ps_color()
vec4 C = tfx(T, vsIn.c);
atst(C);
C = fog(C, vsIn.t.z);
return C;
@ -1238,6 +1238,12 @@ void main()
#endif
vec4 C = ps_color();
bool atst_pass = atst(C);
#if PS_AFAIL == 0 // KEEP or ATST off
if (!atst_pass)
discard;
#endif
// Must be done before alpha correction
@ -1258,7 +1264,7 @@ void main()
vec4 alpha_blend = vec4(C.a / 128.0f);
#endif
// Correct the ALPHA value based on the output format
// Correct the ALPHA value based on the output format
#if (PS_DST_FMT == FMT_16)
float A_one = 128.0f; // alpha output will be 0x80
C.a = (PS_FBA != 0) ? A_one : step(128.0f, C.a) * A_one;
@ -1357,6 +1363,11 @@ void main()
ps_fbmask(C);
#if PS_AFAIL == 3 // RGB_ONLY
// Use alpha blend factor to determine whether to update A.
alpha_blend.a = float(atst_pass);
#endif
#if !PS_NO_COLOR
#if PS_RTA_CORRECTION
o_col0.a = C.a / 128.0f;

View File

@ -1,9 +1,10 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "GSDrawingContext.h"
#include "GSGL.h"
#include "GS.h"
#include "GS/GSDrawingContext.h"
#include "GS/GSGL.h"
#include "GS/GS.h"
#include "GS/GSUtil.h"
static int findmax(int tl, int br, int limit, int wm, int minuv, int maxuv)
{
@ -258,14 +259,14 @@ void GSDrawingContext::Dump(const std::string& filename)
fprintf(fp,
"TEST\n"
"\tATE:%u\n"
"\tATST:%u\n"
"\tATST:%s\n"
"\tAREF:%u\n"
"\tAFAIL:%u\n"
"\tAFAIL:%s\n"
"\tDATE:%u\n"
"\tDATM:%u\n"
"\tZTE:%u\n"
"\tZTST:%u\n\n",
TEST.ATE, TEST.ATST, TEST.AREF, TEST.AFAIL, TEST.DATE, TEST.DATM, TEST.ZTE, TEST.ZTST);
TEST.ATE, GSUtil::GetATSTName(TEST.ATST), TEST.AREF, GSUtil::GetAFAILName(TEST.AFAIL), TEST.DATE, TEST.DATM, TEST.ZTE, TEST.ZTST);
fprintf(fp,
"FBA\n"

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "GS/GS.h"
@ -7,6 +7,8 @@
#include "MultiISA.h"
#include "common/StringUtil.h"
#include <array>
#ifdef ENABLE_VULKAN
#include "GS/Renderers/Vulkan/GSDeviceVK.h"
#endif
@ -115,6 +117,19 @@ void GSUtil::Init()
s_maps.Init();
}
const char* GSUtil::GetATSTName(u32 atst)
{
static constexpr const char* names[] = {
"NEVER", "ALWAYS", "LESS", "LEQUAL", "EQUAL", "GEQUAL", "GREATER", "NOTEQUAL" };
return (atst < std::size(names)) ? names[atst] : "";
}
const char* GSUtil::GetAFAILName(u32 afail)
{
static constexpr const char* names[] = {"KEEP", "FB_ONLY", "ZB_ONLY", "RGB_ONLY"};
return (afail < std::size(names)) ? names[afail] : "";
}
GS_PRIM_CLASS GSUtil::GetPrimClass(u32 prim)
{
return (GS_PRIM_CLASS)s_maps.PrimClassField[prim];

View File

@ -11,6 +11,9 @@ class GSUtil
public:
static void Init();
static const char* GetATSTName(u32 atst);
static const char* GetAFAILName(u32 afail);
static GS_PRIM_CLASS GetPrimClass(u32 prim);
static int GetVertexCount(u32 prim);
static int GetClassVertexCount(u32 primclass);

View File

@ -304,6 +304,7 @@ struct alignas(16) GSHWDrawConfig
// Pixel test
u32 date : 3;
u32 atst : 3;
u32 afail : 2;
// Color sampling
u32 fst : 1; // Investigate to do it on the VS
u32 tfx : 3;

View File

@ -1667,6 +1667,7 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
sm.AddMacro("PS_TCC", sel.tcc);
sm.AddMacro("PS_DATE", sel.date);
sm.AddMacro("PS_ATST", sel.atst);
sm.AddMacro("PS_AFAIL", sel.afail);
sm.AddMacro("PS_FOG", sel.fog);
sm.AddMacro("PS_IIP", sel.iip);
sm.AddMacro("PS_BLEND_HW", sel.blend_hw);

View File

@ -2821,6 +2821,7 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector&
sm.AddMacro("PS_TCC", sel.tcc);
sm.AddMacro("PS_DATE", sel.date);
sm.AddMacro("PS_ATST", sel.atst);
sm.AddMacro("PS_AFAIL", sel.afail);
sm.AddMacro("PS_FOG", sel.fog);
sm.AddMacro("PS_IIP", sel.iip);
sm.AddMacro("PS_BLEND_HW", sel.blend_hw);

View File

@ -2290,7 +2290,7 @@ void GSRendererHW::Draw()
if (PRIM->FST)
{
pxAssert(lcm == 1);
pxAssert(((m_vt.m_min.t.uph(m_vt.m_max.t) == GSVector4::zero()).mask() & 3) == 3); // ratchet and clank (menu)
//pxAssert(((m_vt.m_min.t.uph(m_vt.m_max.t) == GSVector4::zero()).mask() & 3) == 3); // ratchet and clank (menu)
lcm = 1;
}
@ -5054,9 +5054,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
bool DATE_BARRIER = false;
bool DATE_one = false;
const bool ate_first_pass = m_cached_ctx.TEST.DoFirstPass();
const bool ate_second_pass = m_cached_ctx.TEST.DoSecondPass();
ResetStates();
const float scale_factor = rt ? rt->GetScale() : ds->GetScale();
@ -5443,6 +5440,80 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
}
}
// Warning must be done after EmulateZbuffer
// Depth test is always true so it can be executed in 2 passes (no order required) unlike color.
// The idea is to compute first the color which is independent of the alpha test. And then do a 2nd
// pass to handle the depth based on the alpha test.
const bool ate_first_pass = m_cached_ctx.TEST.DoFirstPass();
bool ate_second_pass = m_cached_ctx.TEST.DoSecondPass();
bool ate_RGBA_then_Z = false;
bool ate_RGB_then_Z = false;
GL_INS("%sAlpha Test, ATST=%s, AFAIL=%s", (ate_first_pass && ate_second_pass) ? "Complex" : "",
GSUtil::GetATSTName(m_cached_ctx.TEST.ATST), GSUtil::GetAFAILName(m_cached_ctx.TEST.AFAIL));
if (ate_first_pass && ate_second_pass)
{
const bool commutative_depth = (m_conf.depth.ztst == ZTST_GEQUAL && m_vt.m_eq.z) || (m_conf.depth.ztst == ZTST_ALWAYS) || !m_conf.depth.zwe;
const bool commutative_alpha = (m_context->ALPHA.C != 1) || !m_conf.colormask.wa; // when either Alpha Src or a constant, or not updating A
const u32 afail = m_cached_ctx.TEST.GetAFAIL(m_cached_ctx.FRAME.PSM);
ate_RGBA_then_Z = (afail == AFAIL_FB_ONLY) && commutative_depth;
ate_RGB_then_Z = (afail == AFAIL_RGB_ONLY) && commutative_depth && commutative_alpha;
}
if (ate_RGBA_then_Z)
{
GL_INS("Alternate ATE handling: ate_RGBA_then_Z");
// Render all color but don't update depth
// ATE is disabled here
m_conf.depth.zwe = false;
}
else
{
float aref = m_conf.cb_ps.FogColor_AREF.a;
EmulateATST(aref, m_conf.ps, false);
// avoid redundant cbuffer updates
m_conf.cb_ps.FogColor_AREF.a = aref;
m_conf.alpha_second_pass.ps_aref = aref;
if (ate_RGB_then_Z)
{
GL_INS("Alternate ATE handling: ate_RGB_then_Z");
// Blending might be off, ensure it's enabled.
// We write the alpha pass/fail to SRC1_ALPHA, which is used to update A.
m_conf.ps.afail = AFAIL_RGB_ONLY;
m_conf.ps.no_color1 = false;
if (!m_conf.blend.enable)
{
m_conf.blend = GSHWDrawConfig::BlendState(true, GSDevice::CONST_ONE, GSDevice::CONST_ZERO,
GSDevice::OP_ADD, GSDevice::SRC1_ALPHA, GSDevice::INV_SRC1_ALPHA, false, 0);
}
else
{
m_conf.blend.src_factor_alpha = GSDevice::SRC1_ALPHA;
m_conf.blend.dst_factor_alpha = GSDevice::INV_SRC1_ALPHA;
}
// If Z writes are on, unfortunately we can't single pass it.
// But we can write Z in the second pass instead.
ate_RGBA_then_Z = m_conf.depth.zwe;
ate_second_pass &= ate_RGBA_then_Z;
m_conf.depth.zwe = false;
// Swap stencil DATE for PrimID DATE, for both Z on and off cases.
// Because we're making some pixels pass, but not update A, the stencil won't be synced.
if (DATE && !DATE_BARRIER && features.primitive_id)
{
if (!DATE_PRIMID)
GL_INS("Swap stencil DATE for PrimID, due to AFAIL");
DATE_one = false;
DATE_PRIMID = true;
}
}
}
// No point outputting colours if we're just writing depth.
// We might still need the framebuffer for DATE, though.
if (!rt || m_conf.colormask.wrgba == 0)
@ -5580,47 +5651,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.cb_ps.FogColor_AREF = fc.blend32<8>(m_conf.cb_ps.FogColor_AREF);
}
// Warning must be done after EmulateZbuffer
// Depth test is always true so it can be executed in 2 passes (no order required) unlike color.
// The idea is to compute first the color which is independent of the alpha test. And then do a 2nd
// pass to handle the depth based on the alpha test.
bool ate_RGBA_then_Z = false;
bool ate_RGB_then_ZA = false;
if (ate_first_pass && ate_second_pass)
{
GL_DBG("Complex Alpha Test");
const bool commutative_depth = (m_conf.depth.ztst == ZTST_GEQUAL && m_vt.m_eq.z) || (m_conf.depth.ztst == ZTST_ALWAYS);
const bool commutative_alpha = (m_context->ALPHA.C != 1); // when either Alpha Src or a constant
ate_RGBA_then_Z = m_cached_ctx.TEST.GetAFAIL(m_cached_ctx.FRAME.PSM) == AFAIL_FB_ONLY && commutative_depth;
ate_RGB_then_ZA = m_cached_ctx.TEST.GetAFAIL(m_cached_ctx.FRAME.PSM) == AFAIL_RGB_ONLY && commutative_depth && commutative_alpha;
}
if (ate_RGBA_then_Z)
{
GL_DBG("Alternate ATE handling: ate_RGBA_then_Z");
// Render all color but don't update depth
// ATE is disabled here
m_conf.depth.zwe = false;
}
else if (ate_RGB_then_ZA)
{
GL_DBG("Alternate ATE handling: ate_RGB_then_ZA");
// Render RGB color but don't update depth/alpha
// ATE is disabled here
m_conf.depth.zwe = false;
m_conf.colormask.wa = false;
}
else
{
float aref = m_conf.cb_ps.FogColor_AREF.a;
EmulateATST(aref, m_conf.ps, false);
// avoid redundant cbuffer updates
m_conf.cb_ps.FogColor_AREF.a = aref;
m_conf.alpha_second_pass.ps_aref = aref;
}
GSTexture* tex_copy = nullptr;
if (tex)
{
@ -5666,11 +5696,14 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
if (ate_second_pass)
{
pxAssert(!env.PABE.PABE);
memcpy(&m_conf.alpha_second_pass.ps, &m_conf.ps, sizeof(m_conf.ps));
memcpy(&m_conf.alpha_second_pass.colormask, &m_conf.colormask, sizeof(m_conf.colormask));
memcpy(&m_conf.alpha_second_pass.depth, &m_conf.depth, sizeof(m_conf.depth));
std::memcpy(&m_conf.alpha_second_pass.ps, &m_conf.ps, sizeof(m_conf.ps));
std::memcpy(&m_conf.alpha_second_pass.colormask, &m_conf.colormask, sizeof(m_conf.colormask));
std::memcpy(&m_conf.alpha_second_pass.depth, &m_conf.depth, sizeof(m_conf.depth));
if (ate_RGBA_then_Z || ate_RGB_then_ZA)
// Not doing single pass AFAIL.
m_conf.alpha_second_pass.ps.afail = AFAIL_KEEP;
if (ate_RGBA_then_Z)
{
// Enable ATE as first pass to update the depth
// of pixels that passed the alpha test
@ -5683,7 +5716,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
EmulateATST(m_conf.alpha_second_pass.ps_aref, m_conf.alpha_second_pass.ps, true);
}
bool z = m_conf.depth.zwe;
bool r = m_conf.colormask.wr;
bool g = m_conf.colormask.wg;
@ -5706,12 +5738,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
z = !m_cached_ctx.ZBUF.ZMSK;
r = g = b = a = false;
}
else if (ate_RGB_then_ZA)
{
z = !m_cached_ctx.ZBUF.ZMSK;
a = (m_cached_ctx.FRAME.FBMSK & 0xFF000000) != 0xFF000000;
r = g = b = false;
}
if (z || r || g || b || a)
{
@ -5738,9 +5764,9 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
}
// RenderHW always renders first pass, replace first pass with second
memcpy(&m_conf.ps, &m_conf.alpha_second_pass.ps, sizeof(m_conf.ps));
memcpy(&m_conf.colormask, &m_conf.alpha_second_pass.colormask, sizeof(m_conf.colormask));
memcpy(&m_conf.depth, &m_conf.alpha_second_pass.depth, sizeof(m_conf.depth));
std::memcpy(&m_conf.ps, &m_conf.alpha_second_pass.ps, sizeof(m_conf.ps));
std::memcpy(&m_conf.colormask, &m_conf.alpha_second_pass.colormask, sizeof(m_conf.colormask));
std::memcpy(&m_conf.depth, &m_conf.alpha_second_pass.depth, sizeof(m_conf.depth));
m_conf.cb_ps.FogColor_AREF.a = m_conf.alpha_second_pass.ps_aref;
m_conf.alpha_second_pass.enable = false;
}

View File

@ -1803,6 +1803,7 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
setFnConstantB(m_fn_constants, pssel.fog, GSMTLConstantIndex_PS_FOG);
setFnConstantI(m_fn_constants, pssel.date, GSMTLConstantIndex_PS_DATE);
setFnConstantI(m_fn_constants, pssel.atst, GSMTLConstantIndex_PS_ATST);
setFnConstantI(m_fn_constants, pssel.afail, GSMTLConstantIndex_PS_AFAIL);
setFnConstantI(m_fn_constants, pssel.tfx, GSMTLConstantIndex_PS_TFX);
setFnConstantB(m_fn_constants, pssel.tcc, GSMTLConstantIndex_PS_TCC);
setFnConstantI(m_fn_constants, pssel.wms, GSMTLConstantIndex_PS_WMS);

View File

@ -162,6 +162,7 @@ enum GSMTLFnConstants
GSMTLConstantIndex_PS_FOG,
GSMTLConstantIndex_PS_DATE,
GSMTLConstantIndex_PS_ATST,
GSMTLConstantIndex_PS_AFAIL,
GSMTLConstantIndex_PS_TFX,
GSMTLConstantIndex_PS_TCC,
GSMTLConstantIndex_PS_WMS,

View File

@ -25,6 +25,7 @@ constant bool PS_FBA [[function_constant(GSMTLConstantIndex_PS_FB
constant bool PS_FOG [[function_constant(GSMTLConstantIndex_PS_FOG)]];
constant uint PS_DATE [[function_constant(GSMTLConstantIndex_PS_DATE)]];
constant uint PS_ATST [[function_constant(GSMTLConstantIndex_PS_ATST)]];
constant uint PS_AFAIL [[function_constant(GSMTLConstantIndex_PS_AFAIL)]];
constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TFX)]];
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
@ -850,8 +851,7 @@ struct PSMain
}
float4 C = tfx(T, IIP ? in.c : in.fc);
if (!atst(C))
discard_fragment();
fog(C, in.t.z);
return C;
@ -1054,6 +1054,9 @@ struct PSMain
}
float4 C = ps_color();
bool atst_pass = atst(C);
if (PS_AFAIL == 0 && !atst_pass)
discard_fragment();
// Must be done before alpha correction
@ -1189,6 +1192,10 @@ struct PSMain
ps_fbmask(C);
// Use alpha blend factor to determine whether to update A.
if (PS_AFAIL == 3) // RGB_ONLY
alpha_blend.a = float(atst_pass);
if (PS_COLOR0)
out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f;
out.c0.rgb = PS_HDR ? float3(C.rgb / 65535.f) : C.rgb / 255.f;

View File

@ -1348,6 +1348,7 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
+ fmt::format("#define PS_TFX {}\n", sel.tfx)
+ fmt::format("#define PS_TCC {}\n", sel.tcc)
+ fmt::format("#define PS_ATST {}\n", sel.atst)
+ fmt::format("#define PS_AFAIL {}\n", sel.afail)
+ fmt::format("#define PS_FOG {}\n", sel.fog)
+ fmt::format("#define PS_BLEND_HW {}\n", sel.blend_hw)
+ fmt::format("#define PS_A_MASKED {}\n", sel.a_masked)

View File

@ -4791,6 +4791,7 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
AddMacro(ss, "PS_TFX", sel.tfx);
AddMacro(ss, "PS_TCC", sel.tcc);
AddMacro(ss, "PS_ATST", sel.atst);
AddMacro(ss, "PS_AFAIL", sel.afail);
AddMacro(ss, "PS_FOG", sel.fog);
AddMacro(ss, "PS_BLEND_HW", sel.blend_hw);
AddMacro(ss, "PS_A_MASKED", sel.a_masked);

View File

@ -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 = 43;
static constexpr u32 SHADER_CACHE_VERSION = 44;