diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp index 80f3f2a68e..a451fa6e71 100644 --- a/GPU/Common/Draw2D.cpp +++ b/GPU/Common/Draw2D.cpp @@ -42,11 +42,17 @@ const UniformDef g_draw2Duniforms[2] = { { "float", "scaleFactor", 1}, }; +const UniformDef g_draw2DStencilUniforms[3] = { + { "vec2", "texSize", 0 }, + { "float", "scaleFactor", 1}, + { "float", "stencilValue", 2}, +}; + struct Draw2DUB { float texSizeX; float texSizeY; float scaleFactor; - float zScale; + float stencilValue; float zScaleInv; float zOffset; }; @@ -54,6 +60,7 @@ struct Draw2DUB { const UniformBufferDesc draw2DUBDesc{ sizeof(Draw2DUB), { { "texSize", -1, 0, UniformType::FLOAT2, 0 }, { "scaleFactor", -1, 1, UniformType::FLOAT1, 8 }, + { "stencilValue", -1, 2, UniformType::FLOAT1, 12 }, } }; Draw2DPipelineInfo GenerateDraw2DCopyColorFs(ShaderWriter &writer) { @@ -253,7 +260,7 @@ void Draw2D::Ensure2DResources() { } } -Draw2DPipeline *Draw2D::Create2DPipeline(std::function generate) { +Draw2DPipeline *Draw2D::Create2DPipeline(std::function generate, Slice extensions) { Ensure2DResources(); using namespace Draw; @@ -264,7 +271,7 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::function::empty(), flags); + ShaderWriter writer(fsCode, shaderLanguageDesc, ShaderStage::Fragment, extensions, flags); Draw2DPipelineInfo info = generate(writer); _assert_msg_(strlen(fsCode) < 8192, "Draw2D FS length error: %d", (int)strlen(fsCode)); @@ -282,10 +289,16 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::functionCreateInputLayout(desc); - BlendState *blend = draw_->CreateBlendState({ false, info.writeChannel == RASTER_COLOR ? 0xF : 0x0 }); + BlendState *blend = draw_->CreateBlendState({ false, (info.writeChannel == RASTER_COLOR && !info.writeStencil) ? 0xF : 0x0 }); DepthStencilStateDesc dsDesc{}; - if (info.writeChannel == RASTER_DEPTH) { + if (info.writeStencil) { + dsDesc.stencilEnabled = true; + dsDesc.stencil.compareOp = Comparison::ALWAYS; + dsDesc.stencil.failOp = StencilOp::REPLACE; + dsDesc.stencil.passOp = StencilOp::REPLACE; + dsDesc.stencil.depthFailOp = StencilOp::REPLACE; + } else if (info.writeChannel == RASTER_DEPTH) { dsDesc.depthTestEnabled = true; dsDesc.depthWriteEnabled = true; dsDesc.depthCompare = Draw::Comparison::ALWAYS; @@ -321,7 +334,7 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::functionWidth() : texW; ub.texSizeY = tex ? tex->Height() : texH; ub.scaleFactor = (float)scaleFactor; + ub.stencilValue = stencilValue; draw_->BindPipeline(pipeline->pipeline); draw_->UpdateDynamicUniformBuffer(&ub, sizeof(ub)); diff --git a/GPU/Common/Draw2D.h b/GPU/Common/Draw2D.h index a311cf06db..0bf1539020 100644 --- a/GPU/Common/Draw2D.h +++ b/GPU/Common/Draw2D.h @@ -42,9 +42,13 @@ struct Draw2DPipelineInfo { RasterChannel writeChannel; Slice samplers; bool readSamples = false; // Binds the source with BindFramebufferSamplesAsTexture. + // Writes only the stencil, with REPLACE: the reference from SetStencilParams, or exported by the shader. + bool writeStencil = false; }; extern const UniformDef g_draw2Duniforms[2]; +// g_draw2Duniforms plus stencilValue, for writeStencil pipelines. +extern const UniformDef g_draw2DStencilUniforms[3]; struct Draw2DPipeline { Draw::Pipeline *pipeline; @@ -67,11 +71,11 @@ public: void DeviceLost(); void DeviceRestore(Draw::DrawContext *draw); - Draw2DPipeline *Create2DPipeline(std::function generate); + Draw2DPipeline *Create2DPipeline(std::function generate, Slice extensions = Slice::empty()); - void DrawStrip2D(Draw::Texture *tex, const Draw2DVertex *verts, int vertexCount, bool linearFilter, Draw2DPipeline *pipeline, float texW = 0.0f, float texH = 0.0f, int scaleFactor = 0); + void DrawStrip2D(Draw::Texture *tex, const Draw2DVertex *verts, int vertexCount, bool linearFilter, Draw2DPipeline *pipeline, float texW = 0.0f, float texH = 0.0f, int scaleFactor = 0, float stencilValue = 0.0f); - void Blit(Draw2DPipeline *pipeline, float srcX1, float srcY1, float srcX2, float srcY2, float dstX1, float dstY1, float dstX2, float dstY2, float srcWidth, float srcHeight, float dstWidth, float dstHeight, bool linear, int scaleFactor); + void Blit(Draw2DPipeline *pipeline, float srcX1, float srcY1, float srcX2, float srcY2, float dstX1, float dstY1, float dstX2, float dstY2, float srcWidth, float srcHeight, float dstWidth, float dstHeight, bool linear, int scaleFactor, float stencilValue = 0.0f); void Ensure2DResources(); private: diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 62ad201f37..184b241fae 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -942,16 +942,16 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra // OK we have the pipeline, now just do the blit. BlitUsingRaster(src->fbo, 0.0f, 0.0f, srcWidth, srcHeight, dst->fbo, dstX1, dstY1, dstX2, dstY2, false, dst->renderScaleFactor, pipeline, pass_name); + if (src->fb_format != dst->fb_format) { + // In the same render pass. + WriteStencilFromReinterpret(src, dst, srcWidth, srcHeight, dstX1, dstY1, dstX2, dstY2); + } } if (scaleFactorX == 1.0f && dst->z_address == src->z_address && dst->z_stride == src->z_stride) { // We should also copy the depth buffer in this case! BlitFramebufferDepth(src, dst, true); } - - if (pipeline && src->fb_format != dst->fb_format) { - WriteStencilFromFramebufferAlpha(dst); - } } } @@ -993,6 +993,22 @@ Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat return pipeline; } +Draw2DPipeline *FramebufferManagerCommon::GetReinterpretStencilPipeline(GEBufferFormat from, GEBufferFormat to, bool useExport) { + // Which variant is decided by the device, so it doesn't change. + Draw2DPipeline *pipeline = reinterpretStencilFromTo_[(int)from][(int)to]; + if (!pipeline) { + const Draw::Bugs &bugs = draw_->GetBugs(); + const bool discardNeedsDepthWrite = bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL_MALI) || bugs.Has(Draw::Bugs::NO_DEPTH_CANNOT_DISCARD_STENCIL_ADRENO); + const ReinterpretOutput output = useExport ? ReinterpretOutput::STENCIL_EXPORT : ReinterpretOutput::STENCIL_BITS; + static const char *const exportExtensions[1] = { g_stencilExportExtension }; + pipeline = draw2D_.Create2DPipeline([from, to, output, discardNeedsDepthWrite](ShaderWriter &shaderWriter) -> Draw2DPipelineInfo { + return GenerateReinterpretFragmentShader(shaderWriter, from, to, output, discardNeedsDepthWrite); + }, useExport ? Slice(exportExtensions) : Slice::empty()); + reinterpretStencilFromTo_[(int)from][(int)to] = pipeline; + } + return pipeline; +} + void FramebufferManagerCommon::DestroyFramebuf(VirtualFramebuffer *v) { // Notify the texture cache of both the color and depth buffers. textureCache_->NotifyFramebuffer(v, NOTIFY_FB_DESTROYED); @@ -3785,6 +3801,7 @@ void FramebufferManagerCommon::ReleasePipelines() { for (int i = 0; i < ARRAY_SIZE(reinterpretFromTo_); i++) { for (int j = 0; j < ARRAY_SIZE(reinterpretFromTo_); j++) { DoRelease(reinterpretFromTo_[i][j]); + DoRelease(reinterpretStencilFromTo_[i][j]); } } DoRelease(stencilWriteSampler_); diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 6b7b53148d..f60b6b7f14 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -362,7 +362,7 @@ public: void ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor); bool PerformWriteStencilFromMemory(u32 addr, int size, WriteStencil flags); bool ReadbackStencilToMemory(u32 addr, int size); - void WriteStencilFromFramebufferAlpha(VirtualFramebuffer *dst); + void WriteStencilFromReinterpret(VirtualFramebuffer *src, VirtualFramebuffer *dst, float srcX2, float srcY2, float dstX1, float dstY1, float dstX2, float dstY2); // We changed our depth mode, gotta start over. // Ideally, we should convert depth buffers here, not just clear them. @@ -504,6 +504,7 @@ public: // If from==to, returns a copy pipeline. Draw2DPipeline *GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX); + Draw2DPipeline *GetReinterpretStencilPipeline(GEBufferFormat from, GEBufferFormat to, bool useExport); // Public to be used from the texture cache's depal shenanigans. void BlitUsingRaster( @@ -688,6 +689,7 @@ protected: // Safe, not optimal - there might be input attachment tricks, etc, but we can't use them // since we don't want N different implementations. Draw2DPipeline *reinterpretFromTo_[4][4]{}; + Draw2DPipeline *reinterpretStencilFromTo_[4][4]{}; // Common implementation of stencil buffer upload. Also not 100% optimal, but not performance // critical either. diff --git a/GPU/Common/ReinterpretFramebuffer.cpp b/GPU/Common/ReinterpretFramebuffer.cpp index a5e89c8eee..5749a94cf6 100644 --- a/GPU/Common/ReinterpretFramebuffer.cpp +++ b/GPU/Common/ReinterpretFramebuffer.cpp @@ -14,12 +14,20 @@ static const SamplerDef samplers[1] = { { 0, "tex", SamplerFlags::ARRAY_ON_VULKAN } }; +const char *const g_stencilExportExtension = "#extension GL_ARB_shader_stencil_export : require"; + // Requires full size integer math. It would be possible to make a floating point-only version with lots of // modulo and stuff, might do it one day. -Draw2DPipelineInfo GenerateReinterpretFragmentShader(ShaderWriter &writer, GEBufferFormat from, GEBufferFormat to) { +Draw2DPipelineInfo GenerateReinterpretFragmentShader(ShaderWriter &writer, GEBufferFormat from, GEBufferFormat to, ReinterpretOutput output, bool discardNeedsDepthWrite) { + const bool stencil = output != ReinterpretOutput::COLOR; + writer.HighPrecisionFloat(); writer.DeclareSamplers(samplers); + if (output == ReinterpretOutput::STENCIL_BITS && discardNeedsDepthWrite) { + writer.C("layout (depth_unchanged) out float gl_FragDepth;\n"); + } + if (writer.Lang().bitwiseOps) { switch (from) { case GE_FORMAT_4444: @@ -157,7 +165,11 @@ Draw2DPipelineInfo GenerateReinterpretFragmentShader(ShaderWriter &writer, GEBuf } } - writer.BeginFSMain(g_draw2Duniforms, varyings); + if (stencil) { + writer.BeginFSMain(g_draw2DStencilUniforms, varyings); + } else { + writer.BeginFSMain(g_draw2Duniforms, varyings); + } if (IsBufferFormat16Bit(from) && IsBufferFormat16Bit(to)) { writer.C(" vec4 val = ").SampleTexture2D("tex", "v_texcoord.xy").C(";\n"); @@ -189,12 +201,30 @@ Draw2DPipelineInfo GenerateReinterpretFragmentShader(ShaderWriter &writer, GEBuf writer.C(" vec4 outColor = unpackColor(u == 0.0 ? packColor(val.rg) : packColor(val.ba));\n"); } + if (stencil) { + // The same rounding as the stencil upload shader. + writer.C(" float alpha = floor(outColor.a * 255.99);\n"); + if (output == ReinterpretOutput::STENCIL_EXPORT) { + writer.C(" gl_FragStencilRefARB = int(alpha);\n"); + } else { + writer.C(" if (stencilValue > 0.0) {\n"); + writer.C(" float shifted = alpha / floor(stencilValue * 255.99);\n"); + writer.C(" if (mod(floor(shifted), 2.0) < 0.99) DISCARD;\n"); + writer.C(" }\n"); + if (discardNeedsDepthWrite) { + writer.C(" gl_FragDepth = gl_FragCoord.z;\n"); + } + } + } + writer.EndFSMain("outColor"); - return Draw2DPipelineInfo{ - "reinterpret", + Draw2DPipelineInfo info{ + stencil ? "reinterpret_stencil" : "reinterpret", RASTER_COLOR, RASTER_COLOR, }; + info.writeStencil = stencil; + return info; } diff --git a/GPU/Common/ReinterpretFramebuffer.h b/GPU/Common/ReinterpretFramebuffer.h index f1a78a9b88..bb2d468cd1 100644 --- a/GPU/Common/ReinterpretFramebuffer.h +++ b/GPU/Common/ReinterpretFramebuffer.h @@ -4,4 +4,16 @@ #include "GPU/ge_constants.h" #include "GPU/Common/Draw2D.h" -Draw2DPipelineInfo GenerateReinterpretFragmentShader(ShaderWriter &writer, GEBufferFormat from, GEBufferFormat to); +enum class ReinterpretOutput { + COLOR, + // The stencil from the reinterpreted alpha (the PSP's stencil is the alpha bits). The bit to write is in + // stencilValue (as in the stencil upload shader), and 0 writes everywhere, to clear. + STENCIL_BITS, + // The whole stencil in one draw, through GL_ARB_shader_stencil_export. + STENCIL_EXPORT, +}; + +// The extension the writer needs for STENCIL_EXPORT. +extern const char *const g_stencilExportExtension; + +Draw2DPipelineInfo GenerateReinterpretFragmentShader(ShaderWriter &writer, GEBufferFormat from, GEBufferFormat to, ReinterpretOutput output = ReinterpretOutput::COLOR, bool discardNeedsDepthWrite = false); diff --git a/GPU/Common/StencilCommon.cpp b/GPU/Common/StencilCommon.cpp index 52f47f81a7..5ec519b06e 100644 --- a/GPU/Common/StencilCommon.cpp +++ b/GPU/Common/StencilCommon.cpp @@ -264,8 +264,10 @@ void FramebufferManagerCommon::DrawStencilWritePasses(GEBufferFormat format, int // On the PSP the stencil is the alpha bits, so after reinterpreting the colors of another format into a buffer, its // stencil has to follow the new alpha (Ultimate Ghosts 'n Goblins sets 5551 alpha through a masked 565 blue write, -// then stencil tests on it). -void FramebufferManagerCommon::WriteStencilFromFramebufferAlpha(VirtualFramebuffer *dst) { +// then stencil tests on it). Called right after the color reinterpret, with the same rectangles, so dst is still the +// render target and the draws land in the same render pass. They read the alpha the same way from src, so dst needs +// no copy. +void FramebufferManagerCommon::WriteStencilFromReinterpret(VirtualFramebuffer *src, VirtualFramebuffer *dst, float srcX2, float srcY2, float dstX1, float dstY1, float dstX2, float dstY2) { using namespace Draw; int values = 0; @@ -275,30 +277,42 @@ void FramebufferManagerCommon::WriteStencilFromFramebufferAlpha(VirtualFramebuff case GE_FORMAT_8888: values = 256; break; default: return; } - if (!dst->fbo) { + if (!src->fbo || !dst->fbo) { return; } - const bool useExportShader = draw_->GetDeviceCaps().fragmentShaderStencilWriteSupported; - EnsureStencilWritePipeline(useExportShader); + const bool useExport = draw_->GetDeviceCaps().fragmentShaderStencilWriteSupported; + Draw2DPipeline *pipeline = GetReinterpretStencilPipeline(src->fb_format, dst->fb_format, useExport); - // The source can't be the target, so read the alpha from a copy. - const int w = dst->renderWidth; - const int h = dst->renderHeight; - Framebuffer *copy = GetTempFBO(TempFBO::COPY, w, h); - BlitUsingRaster(dst->fbo, 0.0f, 0.0f, (float)w, (float)h, copy, 0.0f, 0.0f, (float)w, (float)h, false, dst->renderScaleFactor, Get2DPipeline(DRAW2D_COPY_COLOR), "StencilFromAlpha_Copy"); + int srcW, srcH, dstW, dstH; + draw_->GetFramebufferDimensions(src->fbo, &srcW, &srcH); + draw_->GetFramebufferDimensions(dst->fbo, &dstW, &dstH); + draw_->BindFramebufferAsTexture(src->fbo, 0, Aspect::COLOR_BIT, ALL_LAYERS); - draw_->BindFramebufferAsRenderTarget(dst->fbo, { RPAction::KEEP, RPAction::KEEP, RPAction::CLEAR }, "StencilFromAlpha"); - Viewport viewport = { 0.0f, 0.0f, (float)w, (float)h, 0.0f, 1.0f }; - draw_->SetViewport(viewport); - draw_->SetScissorRect(0, 0, w, h); - draw_->BindFramebufferAsTexture(copy, TEX_SLOT_PSP_TEXTURE, Aspect::COLOR_BIT, 0); - draw_->BindSamplerStates(TEX_SLOT_PSP_TEXTURE, 1, &stencilWriteSampler_); - draw_->BindPipeline(stencilWritePipeline_); - DrawStencilWritePasses(dst->fb_format, values, 0xFF, useExportShader); + auto drawStencil = [&](uint8_t ref, uint8_t writeMask, float stencilValue) { + // Vulkan only applies the stencil params to a bound pipeline that uses stencil. + draw_->BindPipeline(pipeline->pipeline); + draw_->SetStencilParams(ref, writeMask, 0xFF); + draw2D_.Blit(pipeline, 0.0f, 0.0f, srcX2, srcY2, dstX1, dstY1, dstX2, dstY2, (float)srcW, (float)srcH, (float)dstW, (float)dstH, false, dst->renderScaleFactor, stencilValue); + }; + + if (useExport) { + drawStencil(0xFF, 0xFF, 0.0f); + } else { + // A draw can only set one bit, so clear first, then set each bit where the alpha has it. + drawStencil(0, 0xFF, 0.0f); + for (int i = 1; i < values; i += i) { + if (dst->fb_format == GE_FORMAT_4444) { + drawStencil(0xFF, (i << 4) | i, i * (16.0f / 255.0f)); + } else if (dst->fb_format == GE_FORMAT_5551) { + drawStencil(0xFF, 0xFF, i * (128.0f / 255.0f)); + } else { + drawStencil(0xFF, i, i * (1.0f / 255.0f)); + } + } + } textureCache_->ForgetLastTexture(); - draw_->Invalidate(InvalidationFlags::CACHED_RENDER_STATE); gstate_c.Dirty(DIRTY_ALL_RENDER_STATE); } diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp index 70358e5806..7af8c4dc31 100644 --- a/unittest/TestShaderGenerators.cpp +++ b/unittest/TestShaderGenerators.cpp @@ -199,22 +199,29 @@ bool TestReinterpretShaders() { std::string errorMessage; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - if (i == j) - continue; // useless shader! - ShaderWriter writer(buffer, desc, ShaderStage::Fragment); - GenerateReinterpretFragmentShader(writer, fmts[i], fmts[j]); - if (strlen(buffer) >= 8192) { - printf("Reinterpret fragment shader %d exceeded buffer:\n\n%s\n", (int)j, LineNumberString(buffer).c_str()); - failed = true; - } - if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) { - printf("Error compiling reinterpret fragment shader %d:\n\n%s\n\n%s\n", (int)j, LineNumberString(buffer).c_str(), errorMessage.c_str()); - failed = true; - } else { - if (g_testLog) { - printf("===\n%s\n===\n", buffer); + // Stencil export only on Vulkan, as GLSL_3xx is ES, which doesn't support it. + const int numOutputs = languages[k] == ShaderLanguage::GLSL_VULKAN ? 3 : 2; + for (int o = 0; o < numOutputs; o++) { + const ReinterpretOutput output = (ReinterpretOutput)o; + const char *extensions[1] = { g_stencilExportExtension }; + const Slice writerExtensions = output == ReinterpretOutput::STENCIL_EXPORT ? Slice(extensions) : Slice::empty(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == j) + continue; // useless shader! + ShaderWriter writer(buffer, desc, ShaderStage::Fragment, writerExtensions); + GenerateReinterpretFragmentShader(writer, fmts[i], fmts[j], output); + if (strlen(buffer) >= 8192) { + printf("Reinterpret fragment shader %d (output %d) exceeded buffer:\n\n%s\n", (int)j, o, LineNumberString(buffer).c_str()); + failed = true; + } + if (!TestCompileShader(buffer, languages[k], ShaderStage::Fragment, &errorMessage)) { + printf("Error compiling reinterpret fragment shader %d (output %d):\n\n%s\n\n%s\n", (int)j, o, LineNumberString(buffer).c_str(), errorMessage.c_str()); + failed = true; + } else { + if (g_testLog) { + printf("===\n%s\n===\n", buffer); + } } } }