Use framebuffer fetch depth extension to better handle alpha testing.

This commit is contained in:
Jean-Philip Desjardins 2025-03-31 13:24:35 -04:00
parent 5627084a1e
commit 99770a53b3
3 changed files with 80 additions and 9 deletions

View file

@ -312,6 +312,10 @@ void CGSH_OpenGL::CheckExtensions()
{
m_hasFramebufferFetchExtension = true;
}
else if(!strcmp(extensionName, "GL_ARM_shader_framebuffer_fetch_depth_stencil"))
{
m_hasFramebufferFetchDepthExtension = true;
}
}
}
@ -1060,6 +1064,20 @@ bool CGSH_OpenGL::CanRegionRepeatClampModeSimplified(uint32 clampMin, uint32 cla
return false;
}
bool CGSH_OpenGL::RequiresAlphaTestDepthTest_DepthFetch(const TEST& test) const
{
if(!m_hasFramebufferFetchDepthExtension) return false;
//If alpha test is not enabled, bail
if(!test.nAlphaEnabled) return false;
//If depth test not enabled, bail
if(!test.nDepthEnabled) return false;
//If depth test doesn't need depth value to compare, bail
if(!((test.nDepthMethod == DEPTH_TEST_GEQUAL) || (test.nDepthMethod == DEPTH_TEST_GREATER))) return false;
//If alpha test doesn't discard depth (independently of other channels), bail
if(!((test.nAlphaFail == ALPHA_TEST_FAIL_FBONLY) || (test.nAlphaFail == ALPHA_TEST_FAIL_RGBONLY))) return false;
return true;
}
void CGSH_OpenGL::FillShaderCapsFromTexture(SHADERCAPS& shaderCaps, const uint64& tex0Reg, const uint64& tex1Reg, const uint64& texAReg, const uint64& clampReg)
{
auto tex0 = make_convertible<TEX0>(tex0Reg);
@ -1143,6 +1161,17 @@ void CGSH_OpenGL::FillShaderCapsFromTest(SHADERCAPS& shaderCaps, const uint64& t
shaderCaps.alphaTestMethod = test.nAlphaMethod;
shaderCaps.alphaFailMethod = test.nAlphaFail;
}
if(RequiresAlphaTestDepthTest_DepthFetch(test))
{
//If we use depthbuffer fetch to reject depth writes on alpha test fail,
//we need to handle depth test ourselves since we still need to potentially
//discard the fragment based on its depth value. Since our strategy for rejecting
//depth writes relies on providing the previous depth value to the current
//fragment, the built-in depth test won't be able to give proper results.
shaderCaps.alphaTestDepthTest_DepthFetch = 1;
shaderCaps.alphaTestDepthTestEqual_DepthFetch = (test.nDepthMethod == DEPTH_TEST_GEQUAL);
}
}
else
{

View file

@ -95,6 +95,8 @@ private:
unsigned int hasDestAlphaTest : 1;
unsigned int destAlphaTestRef : 1;
unsigned int alphaFailMethod : 2;
unsigned int alphaTestDepthTest_DepthFetch : 1;
unsigned int alphaTestDepthTestEqual_DepthFetch : 1;
bool isIndexedTextureSource() const
{
@ -356,6 +358,7 @@ private:
void SetupFogColor(uint64);
static bool CanRegionRepeatClampModeSimplified(uint32, uint32);
bool RequiresAlphaTestDepthTest_DepthFetch(const TEST&) const;
void FillShaderCapsFromTexture(SHADERCAPS&, const uint64&, const uint64&, const uint64&, const uint64&);
void FillShaderCapsFromTest(SHADERCAPS&, const uint64&);
void FillShaderCapsFromAlpha(SHADERCAPS&, bool, const uint64&);
@ -474,4 +477,5 @@ private:
//If GPU has framebuffer fetch extension, some things will be done
//within the shader, such alpha blending
bool m_hasFramebufferFetchExtension = false;
bool m_hasFramebufferFetchDepthExtension = false;
};

View file

@ -135,7 +135,9 @@ Framework::OpenGl::CShader CGSH_OpenGL::GenerateVertexShader(const SHADERCAPS& c
Framework::OpenGl::CShader CGSH_OpenGL::GenerateFragmentShader(const SHADERCAPS& caps)
{
bool alphaTestCanDiscardDepth = (caps.hasAlphaTest) && ((caps.alphaFailMethod == ALPHA_TEST_FAIL_FBONLY) || (caps.alphaFailMethod == ALPHA_TEST_FAIL_RGBONLY));
bool useFramebufferFetch = (caps.hasAlphaBlend || caps.hasAlphaTest || caps.hasDestAlphaTest) && m_hasFramebufferFetchExtension;
bool useFramebufferFetchDepth = alphaTestCanDiscardDepth && m_hasFramebufferFetchDepthExtension;
std::stringstream shaderBuilder;
shaderBuilder << GLSL_VERSION << std::endl;
@ -144,6 +146,10 @@ Framework::OpenGl::CShader CGSH_OpenGL::GenerateFragmentShader(const SHADERCAPS&
{
shaderBuilder << "#extension GL_EXT_shader_framebuffer_fetch : require" << std::endl;
}
if(useFramebufferFetchDepth)
{
shaderBuilder << "#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require" << std::endl;
}
shaderBuilder << "precision mediump float;" << std::endl;
@ -354,6 +360,10 @@ Framework::OpenGl::CShader CGSH_OpenGL::GenerateFragmentShader(const SHADERCAPS&
shaderBuilder << " bool outputColor = true;" << std::endl;
shaderBuilder << " bool outputAlpha = true;" << std::endl;
}
if(useFramebufferFetchDepth)
{
shaderBuilder << " bool outputDepth = true;" << std::endl;
}
if(caps.hasAlphaTest)
{
@ -420,7 +430,33 @@ Framework::OpenGl::CShader CGSH_OpenGL::GenerateFragmentShader(const SHADERCAPS&
// ----------------------
shaderBuilder << " gl_FragDepth = v_depth;" << std::endl;
if(useFramebufferFetchDepth)
{
shaderBuilder << " if(outputDepth)" << std::endl;
shaderBuilder << " {" << std::endl;
shaderBuilder << " gl_FragDepth = v_depth;" << std::endl;
shaderBuilder << " }" << std::endl;
shaderBuilder << " else" << std::endl;
shaderBuilder << " {" << std::endl;
shaderBuilder << " highp float dstDepth = gl_LastFragDepthARM;" << std::endl;
if(caps.alphaTestDepthTest_DepthFetch)
{
if(caps.alphaTestDepthTestEqual_DepthFetch)
{
shaderBuilder << " if(!(v_depth >= dstDepth)) discard;" << std::endl;
}
else
{
shaderBuilder << " if(!(v_depth > dstDepth)) discard;" << std::endl;
}
}
shaderBuilder << " gl_FragDepth = dstDepth;" << std::endl;
shaderBuilder << " }" << std::endl;
}
else
{
shaderBuilder << " gl_FragDepth = v_depth;" << std::endl;
}
// ----------------------
@ -526,6 +562,11 @@ std::string CGSH_OpenGL::GenerateAlphaTestSection(ALPHA_TEST_METHOD testMethod,
// Failure note: We rather accept writing depth here, than discarding the
// whole pixel, as most games work better with this hack.
// Breaks foliage in some games (Grandia X/3, Naruto, etc.)
// If we have depth buffer fetch, we can properly handle it.
if(m_hasFramebufferFetchDepthExtension)
{
shaderBuilder << " outputDepth = false;" << std::endl;
}
break;
case ALPHA_TEST_FAIL_ZBONLY:
// Only write depth
@ -546,17 +587,14 @@ std::string CGSH_OpenGL::GenerateAlphaTestSection(ALPHA_TEST_METHOD testMethod,
if(m_hasFramebufferFetchExtension)
{
shaderBuilder << " outputAlpha = false;" << std::endl;
// TODO: Prevent depth writing
// Failure note: We rather accept writing depth here, than discarding the
// whole pixel, as most games work better with this hack.
}
else
if(m_hasFramebufferFetchDepthExtension)
{
// TODO: Prevent color and depth writing without extension
// Failure note: We are somewhat out of luck, and just draw the pixel as is.
// This is completely wrong, but nothing we can do about it at this point.
shaderBuilder << " outputDepth = false;" << std::endl;
}
// TODO: Prevent alpha and depth writing without extension
// Failure note: We are somewhat out of luck, and just draw the pixel as is.
// This is completely wrong, but nothing we can do about it at this point.
break;
}