Skip to content

Commit

Permalink
GS: Fix edge case of broken alpha with no-DSB+ATST
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek authored and refractionpcsx2 committed Apr 15, 2022
1 parent 0592abd commit 3f31a4d
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 59 deletions.
27 changes: 27 additions & 0 deletions pcsx2/GS/Renderers/Common/GSDevice.cpp
Expand Up @@ -477,6 +477,33 @@ bool GSDevice::ResizeTarget(GSTexture** t)
return ResizeTexture(t, GSTexture::Type::RenderTarget, s.x, s.y);
}

void GSDevice::SetHWDrawConfigForAlphaPass(GSHWDrawConfig::PSSelector* ps,
GSHWDrawConfig::ColorMaskSelector* cms,
GSHWDrawConfig::BlendState* bs,
GSHWDrawConfig::DepthStencilSelector* dss)
{
// only need to compute the alpha component (allow the shader to optimize better)
ps->no_ablend = false;
ps->only_alpha = true;

// definitely don't need to compute software blend (this may get rid of some barriers)
ps->blend_a = ps->blend_b = ps->blend_c = ps->blend_d = 0;

// only write alpha (RGB=0,A=1)
cms->wrgba = (1 << 3);

// no need for hardware blending, since we're not writing RGB
bs->enable = false;

// if depth writes are on, we can optimize to an EQUAL test, otherwise we leave the tests alone
// since the alpha channel isn't blended, the last fragment wins and this'll be okay
if (dss->zwe)
{
dss->zwe = false;
dss->ztst = ZTST_GEQUAL;
}
}

GSAdapter::operator std::string() const
{
char buf[sizeof "12345678:12345678:12345678:12345678"];
Expand Down
9 changes: 8 additions & 1 deletion pcsx2/GS/Renderers/Common/GSDevice.h
Expand Up @@ -557,6 +557,8 @@ struct alignas(16) GSHWDrawConfig
DestinationAlphaMode destination_alpha;
bool datm : 1;
bool line_expand : 1;
bool separate_alpha_pass : 1;
bool second_separate_alpha_pass : 1;

struct AlphaPass
{
Expand All @@ -569,7 +571,6 @@ struct alignas(16) GSHWDrawConfig
static_assert(sizeof(AlphaPass) == 24, "alpha pass is 24 bytes");

AlphaPass alpha_second_pass;
AlphaPass alpha_third_pass;

VSConstantBuffer cb_vs;
PSConstantBuffer cb_ps;
Expand Down Expand Up @@ -775,6 +776,12 @@ class GSDevice : public GSAlignedClass<32>
return (IsDualSourceBlendFactor(m_blendMap[index].src) ||
IsDualSourceBlendFactor(m_blendMap[index].dst));
}

/// Alters the pipeline configuration for drawing the separate alpha pass.
static void SetHWDrawConfigForAlphaPass(GSHWDrawConfig::PSSelector* ps,
GSHWDrawConfig::ColorMaskSelector* cms,
GSHWDrawConfig::BlendState* bs,
GSHWDrawConfig::DepthStencilSelector* dss);
};

struct GSAdapter
Expand Down
20 changes: 20 additions & 0 deletions pcsx2/GS/Renderers/DX11/GSDevice11.cpp
Expand Up @@ -1348,6 +1348,16 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)

DrawIndexedPrimitive();

if (config.separate_alpha_pass)
{
GSHWDrawConfig::BlendState sap_blend = {};
SetHWDrawConfigForAlphaPass(&config.ps, &config.colormask, &sap_blend, &config.depth);
SetupOM(config.depth, convertSel(config.colormask, sap_blend), config.blend.constant);
SetupPS(config.ps, &config.cb_ps, config.sampler);

DrawIndexedPrimitive();
}

if (config.alpha_second_pass.enable)
{
preprocessSel(config.alpha_second_pass.ps);
Expand All @@ -1365,6 +1375,16 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
SetupOM(config.alpha_second_pass.depth, convertSel(config.alpha_second_pass.colormask, config.blend), config.blend.constant);

DrawIndexedPrimitive();

if (config.second_separate_alpha_pass)
{
GSHWDrawConfig::BlendState sap_blend = {};
SetHWDrawConfigForAlphaPass(&config.alpha_second_pass.ps, &config.alpha_second_pass.colormask, &sap_blend, &config.alpha_second_pass.depth);
SetupOM(config.alpha_second_pass.depth, convertSel(config.alpha_second_pass.colormask, sap_blend), config.blend.constant);
SetupPS(config.alpha_second_pass.ps, &config.cb_ps, config.sampler);

DrawIndexedPrimitive();
}
}

EndScene();
Expand Down
45 changes: 17 additions & 28 deletions pcsx2/GS/Renderers/HW/GSRendererNew.cpp
Expand Up @@ -1821,41 +1821,30 @@ void GSRendererNew::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sour

if (blending_alpha_pass)
{
// ensure alpha blend output is disabled on both passes
// write alpha blend as the single alpha output
m_conf.ps.no_ablend = true;

// if we're doing RGBA then Z alpha testing, we can't combine Z and A, and we need a third pass :(
if (ate_RGBA_then_Z)
{
// move second pass to third pass, since we want to write A first
std::memcpy(&m_conf.alpha_third_pass, &m_conf.alpha_second_pass, sizeof(m_conf.alpha_third_pass));
m_conf.alpha_third_pass.ps.no_ablend = true;
m_conf.alpha_second_pass.enable = false;
}

if (!m_conf.alpha_second_pass.enable)
// there's a case we can skip this: RGB_then_ZA alternate handling.
// but otherwise, we need to write alpha separately.
if (m_conf.colormask.wa)
{
m_conf.alpha_second_pass.enable = true;
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));

// disable alpha writes on first pass
m_conf.colormask.wa = false;
m_conf.separate_alpha_pass = true;
}

// only need to compute the alpha component (allow the shader to optimize better)
m_conf.alpha_second_pass.ps.no_ablend = false;
m_conf.alpha_second_pass.ps.only_alpha = true;
m_conf.alpha_second_pass.colormask.wr = m_conf.alpha_second_pass.colormask.wg = m_conf.alpha_second_pass.colormask.wb = false;
m_conf.alpha_second_pass.colormask.wa = true;

// if depth writes are on, we can optimize to an EQUAL test, otherwise we leave the tests alone
// since the alpha channel isn't blended, the last fragment wins and this'll be okay
if (m_conf.alpha_second_pass.depth.zwe)
// do we need to do this for the failed alpha fragments?
if (m_conf.alpha_second_pass.enable)
{
m_conf.alpha_second_pass.depth.zwe = false;
m_conf.alpha_second_pass.depth.ztst = ZTST_GEQUAL;
// there's also a case we can skip here: when we're not writing RGB, there's
// no blending, so we can just write the normal alpha!
const u8 second_pass_wrgba = m_conf.alpha_second_pass.colormask.wrgba;
if ((second_pass_wrgba & (1 << 3)) != 0 && second_pass_wrgba != (1 << 3))
{
// this sucks. potentially up to 4 passes. but no way around it when we don't have dual-source blend.
m_conf.alpha_second_pass.ps.no_ablend = true;
m_conf.alpha_second_pass.colormask.wa = false;
m_conf.second_separate_alpha_pass = true;
}
}
}

Expand Down
44 changes: 28 additions & 16 deletions pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp
Expand Up @@ -1961,6 +1961,25 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)

SendHWDraw(config);

if (config.separate_alpha_pass)
{
GSHWDrawConfig::BlendState dummy_bs;
SetHWDrawConfigForAlphaPass(&psel.ps, &config.colormask, &dummy_bs, &config.depth);
SetupPipeline(psel);
OMSetColorMaskState(config.alpha_second_pass.colormask);
SetupOM(config.alpha_second_pass.depth);
OMSetBlendState();
SendHWDraw(config);

// restore blend state if we're doing a second pass
if (config.alpha_second_pass.enable)
{
OMSetBlendState(config.blend.enable, s_gl_blend_factors[config.blend.src_factor],
s_gl_blend_factors[config.blend.dst_factor], s_gl_blend_ops[config.blend.op],
config.blend.constant_enable, config.blend.constant);
}
}

if (config.alpha_second_pass.enable)
{
// cbuffer will definitely be dirty if aref changes, no need to check it
Expand All @@ -1975,25 +1994,18 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
SetupPipeline(psel);
OMSetColorMaskState(config.alpha_second_pass.colormask);
SetupOM(config.alpha_second_pass.depth);

SendHWDraw(config);
}
if (config.alpha_third_pass.enable)
{
// cbuffer will definitely be dirty if aref changes, no need to check it
if (config.cb_ps.FogColor_AREF.a != config.alpha_third_pass.ps_aref)

if (config.second_separate_alpha_pass)
{
config.cb_ps.FogColor_AREF.a = config.alpha_third_pass.ps_aref;
WriteToStreamBuffer(m_fragment_uniform_stream_buffer.get(), g_ps_cb_index,
m_uniform_buffer_alignment, &config.cb_ps, sizeof(config.cb_ps));
GSHWDrawConfig::BlendState dummy_bs;
SetHWDrawConfigForAlphaPass(&psel.ps, &config.colormask, &dummy_bs, &config.depth);
SetupPipeline(psel);
OMSetColorMaskState(config.alpha_second_pass.colormask);
SetupOM(config.alpha_second_pass.depth);
OMSetBlendState();
SendHWDraw(config);
}

psel.ps = config.alpha_third_pass.ps;
SetupPipeline(psel);
OMSetColorMaskState(config.alpha_third_pass.colormask);
SetupOM(config.alpha_third_pass.depth);

SendHWDraw(config);
}

if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
Expand Down
30 changes: 16 additions & 14 deletions pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp
Expand Up @@ -3026,7 +3026,15 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)

// now we can do the actual draw
if (BindDrawPipeline(pipe))
{
SendHWDraw(config, draw_rt);
if (config.separate_alpha_pass)
{
SetHWDrawConfigForAlphaPass(&pipe.ps, &pipe.cms, &pipe.bs, &pipe.dss);
if (BindDrawPipeline(pipe))
SendHWDraw(config, draw_rt);
}
}

// and the alpha pass
if (config.alpha_second_pass.enable)
Expand All @@ -3041,23 +3049,17 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
pipe.ps = config.alpha_second_pass.ps;
pipe.cms = config.alpha_second_pass.colormask;
pipe.dss = config.alpha_second_pass.depth;
pipe.bs = config.blend;
if (BindDrawPipeline(pipe))
SendHWDraw(config, draw_rt);
}
if (config.alpha_third_pass.enable)
{
// cbuffer will definitely be dirty if aref changes, no need to check it
if (config.cb_ps.FogColor_AREF.a != config.alpha_third_pass.ps_aref)
{
config.cb_ps.FogColor_AREF.a = config.alpha_third_pass.ps_aref;
SetPSConstantBuffer(config.cb_ps);
}

pipe.ps = config.alpha_third_pass.ps;
pipe.cms = config.alpha_third_pass.colormask;
pipe.dss = config.alpha_third_pass.depth;
if (BindDrawPipeline(pipe))
SendHWDraw(config, draw_rt);
if (config.second_separate_alpha_pass)
{
SetHWDrawConfigForAlphaPass(&pipe.ps, &pipe.cms, &pipe.bs, &pipe.dss);
if (BindDrawPipeline(pipe))
SendHWDraw(config, draw_rt);
}
}
}

if (copy_ds)
Expand Down

0 comments on commit 3f31a4d

Please sign in to comment.