From 880ea48e2da078abc72630886d463e7e4294fe98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 27 Aug 2022 10:05:57 +0200 Subject: [PATCH] Implement the stencil/alpha reverse trick for all backends --- GPU/D3D11/StateMappingD3D11.cpp | 28 ++++++++++++++++++++++++++++ GPU/Directx9/StateMappingDX9.cpp | 22 ++++++++++++++++++++++ GPU/GLES/StateMappingGLES.cpp | 25 +++++++++++++++++++------ GPU/Vulkan/StateMappingVulkan.cpp | 27 +++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 6 deletions(-) diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index 4758cd8694..873328879a 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -293,6 +293,34 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { keys_.depthStencil.stencilWriteMask = stencilState.writeMask; dynState_.useStencil = true; dynState_.stencilRef = stencilState.testRef; + + // Nasty special case for Spongebob and similar where it tries to write zeros to alpha/stencil during + // depth-fail. We can't write to alpha then because the pixel is killed. However, we can invert the depth + // test and modify the alpha function... + if (gstate.isDepthTestEnabled() && !gstate.isDepthWriteEnabled() && stencilState.zFail == GE_STENCILOP_ZERO && + stencilState.sFail == GE_STENCILOP_KEEP && stencilState.zPass == GE_STENCILOP_KEEP && + stencilState.testFunc == GE_COMP_ALWAYS && + stencilState.writeMask == 0xFF && stencilState.testMask == 0xFF && stencilState.testRef == 0xFF) { + keys_.blend.blendEnable = true; + keys_.blend.blendOpAlpha = D3D11_BLEND_OP_ADD; + keys_.blend.blendOpColor = D3D11_BLEND_OP_ADD; + keys_.blend.srcColor = D3D11_BLEND_ZERO; + keys_.blend.destColor = D3D11_BLEND_ZERO; + keys_.blend.logicOpEnable = false; + keys_.blend.srcAlpha = D3D11_BLEND_ZERO; + keys_.blend.destAlpha = D3D11_BLEND_ZERO; + keys_.blend.colorWriteMask = D3D11_COLOR_WRITE_ENABLE_ALPHA; + + keys_.depthStencil.depthCompareOp = D3D11_COMPARISON_LESS; // Inverse of GREATER_EQUAL + keys_.depthStencil.stencilCompareFunc = D3D11_COMPARISON_ALWAYS; + // Invert + keys_.depthStencil.stencilPassOp = D3D11_STENCIL_OP_ZERO; + keys_.depthStencil.stencilFailOp = D3D11_STENCIL_OP_ZERO; + keys_.depthStencil.stencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + + // TODO: Need to set in a way that carries over to the next draw.. + gstate_c.Dirty(DIRTY_BLEND_STATE); + } } else { keys_.depthStencil.stencilTestEnable = false; dynState_.useStencil = false; diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp index 247a6e9091..ad144562b6 100644 --- a/GPU/Directx9/StateMappingDX9.cpp +++ b/GPU/Directx9/StateMappingDX9.cpp @@ -247,6 +247,28 @@ void DrawEngineDX9::ApplyDrawState(int prim) { dxstate.stencilCompareMask.set(stencilState.testMask); dxstate.stencilOp.set(stencilOps[stencilState.sFail], stencilOps[stencilState.zFail], stencilOps[stencilState.zPass]); dxstate.stencilWriteMask.set(stencilState.writeMask); + + // Nasty special case for Spongebob and similar where it tries to write zeros to alpha/stencil during + // depth-fail. We can't write to alpha then because the pixel is killed. However, we can invert the depth + // test and modify the alpha function... + if (gstate.isDepthTestEnabled() && !gstate.isDepthWriteEnabled() && stencilState.zFail == GE_STENCILOP_ZERO && + stencilState.sFail == GE_STENCILOP_KEEP && stencilState.zPass == GE_STENCILOP_KEEP && + stencilState.testFunc == GE_COMP_ALWAYS && + stencilState.writeMask == 0xFF && stencilState.testMask == 0xFF && stencilState.testRef == 0xFF) { + + dxstate.blend.set(true); + dxstate.blendEquation.set(D3DBLENDOP_ADD, D3DBLENDOP_ADD); + dxstate.blendFunc.set(D3DBLEND_ZERO, D3DBLEND_ZERO, D3DBLEND_ZERO, D3DBLEND_ZERO); + dxstate.colorMask.set(8); + + dxstate.depthFunc.set(D3DCMP_LESS); + dxstate.stencilFunc.set(D3DCMP_ALWAYS); + // Invert + dxstate.stencilOp.set(D3DSTENCILOP_ZERO, D3DSTENCILOP_KEEP, D3DSTENCILOP_ZERO); + + // TODO: Need to set in a way that carries over to the next draw.. + gstate_c.Dirty(DIRTY_BLEND_STATE); + } } else { dxstate.stencilTest.disable(); } diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp index 4ebdd165d2..61b752fbb0 100644 --- a/GPU/GLES/StateMappingGLES.cpp +++ b/GPU/GLES/StateMappingGLES.cpp @@ -144,7 +144,6 @@ void DrawEngineGLES::ApplyDrawState(int prim) { bool useBufferedRendering = framebufferManager_->UseBufferedRendering(); if (gstate_c.IsDirty(DIRTY_BLEND_STATE)) { - gstate_c.Clean(DIRTY_BLEND_STATE); gstate_c.SetAllowFramebufferRead(!g_Config.bDisableSlowFramebufEffects); if (gstate.isModeClear()) { @@ -208,7 +207,6 @@ void DrawEngineGLES::ApplyDrawState(int prim) { } else { renderManager->SetNoBlendAndMask(mask); } - #ifndef USING_GLES2 if (gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { renderManager->SetLogicOp(gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY, @@ -219,8 +217,6 @@ void DrawEngineGLES::ApplyDrawState(int prim) { } if (gstate_c.IsDirty(DIRTY_RASTER_STATE)) { - gstate_c.Clean(DIRTY_RASTER_STATE); - // Dither bool dither = gstate.isDitherEnabled(); bool cullEnable; @@ -247,7 +243,6 @@ void DrawEngineGLES::ApplyDrawState(int prim) { } if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) { - gstate_c.Clean(DIRTY_DEPTHSTENCIL_STATE); GenericStencilFuncState stencilState; ConvertStencilFuncState(stencilState); @@ -265,13 +260,29 @@ void DrawEngineGLES::ApplyDrawState(int prim) { renderManager->SetStencilFunc(stencilState.enabled, compareOps[stencilState.testFunc], stencilState.testRef, stencilState.testMask); renderManager->SetStencilOp(stencilState.writeMask, stencilOps[stencilState.sFail], stencilOps[stencilState.zFail], stencilOps[stencilState.zPass]); } else { + // Nasty special case for Spongebob and similar where it tries to write zeros to alpha/stencil during + // depth-fail. We can't write to alpha then because the pixel is killed. However, we can invert the depth + // test and modify the alpha function... + if (gstate.isDepthTestEnabled() && !gstate.isDepthWriteEnabled() && stencilState.zFail == GE_STENCILOP_ZERO && + stencilState.sFail == GE_STENCILOP_KEEP && stencilState.zPass == GE_STENCILOP_KEEP && + stencilState.testFunc == GE_COMP_ALWAYS && + stencilState.writeMask == 0xFF && stencilState.testMask == 0xFF && stencilState.testRef == 0xFF) { + + renderManager->SetBlendAndMask(0x8, true, GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO, GL_ADD, GL_ADD); + renderManager->SetDepth(true, false, GL_LESS); + renderManager->SetStencilFunc(true, GL_ALWAYS, 0xFF, 0xFF); + renderManager->SetStencilOp(0xFF, GL_ZERO, GL_KEEP, GL_ZERO); + + // TODO: Need to set in a way that carries over to the next draw.. + gstate_c.Dirty(DIRTY_BLEND_STATE); + } + renderManager->SetStencilDisabled(); } } } if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) { - gstate_c.Clean(DIRTY_VIEWPORTSCISSOR_STATE); ConvertViewportAndScissor(useBufferedRendering, framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(), framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(), @@ -284,6 +295,8 @@ void DrawEngineGLES::ApplyDrawState(int prim) { vpAndScissor.viewportW, vpAndScissor.viewportH, vpAndScissor.depthRangeMin, vpAndScissor.depthRangeMax }); } + + gstate_c.Clean(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_BLEND_STATE); } void DrawEngineGLES::ApplyDrawStateLate(bool setStencilValue, int stencilValue) { diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index 566708480a..10f3e377b2 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -301,6 +301,33 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag dynState.stencilRef = stencilState.testRef; dynState.stencilCompareMask = stencilState.testMask; dynState.stencilWriteMask = stencilState.writeMask; + + // Nasty special case for Spongebob and similar where it tries to write zeros to alpha/stencil during + // depth-fail. We can't write to alpha then because the pixel is killed. However, we can invert the depth + // test and modify the alpha function... + if (gstate.isDepthTestEnabled() && !gstate.isDepthWriteEnabled() && stencilState.zFail == GE_STENCILOP_ZERO && + stencilState.sFail == GE_STENCILOP_KEEP && stencilState.zPass == GE_STENCILOP_KEEP && + stencilState.testFunc == GE_COMP_ALWAYS && + stencilState.writeMask == 0xFF && stencilState.testMask == 0xFF && stencilState.testRef == 0xFF) { + key.blendEnable = true; + key.blendOpAlpha = VK_BLEND_OP_ADD; + key.blendOpColor = VK_BLEND_OP_ADD; + key.srcColor = VK_BLEND_FACTOR_ZERO; + key.destColor = VK_BLEND_FACTOR_ZERO; + key.logicOpEnable = false; + key.srcAlpha = VK_BLEND_FACTOR_ZERO; + key.destAlpha = VK_BLEND_FACTOR_ZERO; + key.colorWriteMask = VK_COLOR_COMPONENT_A_BIT; + key.depthCompareOp = VK_COMPARE_OP_LESS; // Inverse of GREATER_EQUAL + key.stencilCompareOp = VK_COMPARE_OP_ALWAYS; + // Invert + key.stencilPassOp = VK_STENCIL_OP_ZERO; + key.stencilFailOp = VK_STENCIL_OP_ZERO; + key.stencilDepthFailOp = VK_STENCIL_OP_KEEP; + + // TODO: Need to set in a way that carries over to the next draw.. + gstate_c.Dirty(DIRTY_BLEND_STATE); + } } else { key.stencilTestEnable = false; key.stencilCompareOp = VK_COMPARE_OP_ALWAYS;