Merge pull request #22887 from hrydgard/fix-stencil-rebuild-cost

Reinterpret: Write the stencil from the source, in the same render pass
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-11-02 15:48:41 +01:00
commit ef6ca2a303
8 files changed
+157 -57

No files matched your search

+23 -9
View File
@@ -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<Draw2DPipelineInfo (ShaderWriter &)> generate) {
Draw2DPipeline *Draw2D::Create2DPipeline(std::function<Draw2DPipelineInfo (ShaderWriter &)> generate, Slice<const char *> extensions) {
Ensure2DResources();
using namespace Draw;
@@ -264,7 +271,7 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::function<Draw2DPipelineInfo (Shade
if (gstate_c.Use(GPU_USE_SINGLE_PASS_STEREO)) {
flags = ShaderWriterFlags::FS_AUTO_STEREO;
}
ShaderWriter writer(fsCode, shaderLanguageDesc, ShaderStage::Fragment, Slice<const char *>::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::function<Draw2DPipelineInfo (Shade
};
InputLayout *inputLayout = draw_->CreateInputLayout(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::function<Draw2DPipelineInfo (Shade
};
}
void Draw2D::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 Draw2D::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) {
float dX = 1.0f / (float)dstWidth;
float dY = 1.0f / (float)dstHeight;
float sX = 1.0f / (float)srcWidth;
@@ -339,10 +352,10 @@ void Draw2D::Blit(Draw2DPipeline *pipeline, float srcX1, float srcY1, float srcX
{ -1.0f + 2.0f * dX * dstX2 + xOffset, -(1.0f - 2.0f * dY * dstY2) + yOffset, sX * srcX2, sY * srcY2 },
};
DrawStrip2D(nullptr, vtx, 4, linear, pipeline, srcWidth, srcHeight, scaleFactor);
DrawStrip2D(nullptr, vtx, 4, linear, pipeline, srcWidth, srcHeight, scaleFactor, stencilValue);
}
void Draw2D::DrawStrip2D(Draw::Texture *tex, const Draw2DVertex *verts, int vertexCount, bool linearFilter, Draw2DPipeline *pipeline, float texW, float texH, int scaleFactor) {
void Draw2D::DrawStrip2D(Draw::Texture *tex, const Draw2DVertex *verts, int vertexCount, bool linearFilter, Draw2DPipeline *pipeline, float texW, float texH, int scaleFactor, float stencilValue) {
using namespace Draw;
_dbg_assert_(pipeline);
@@ -354,10 +367,11 @@ void Draw2D::DrawStrip2D(Draw::Texture *tex, const Draw2DVertex *verts, int vert
linearFilter = false;
}
Draw2DUB ub;
Draw2DUB ub{};
ub.texSizeX = tex ? tex->Width() : texW;
ub.texSizeY = tex ? tex->Height() : texH;
ub.scaleFactor = (float)scaleFactor;
ub.stencilValue = stencilValue;
draw_->BindPipeline(pipeline->pipeline);
draw_->UpdateDynamicUniformBuffer(&ub, sizeof(ub));
+7 -3
View File
@@ -42,9 +42,13 @@ struct Draw2DPipelineInfo {
RasterChannel writeChannel;
Slice<SamplerDef> 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<Draw2DPipelineInfo(ShaderWriter &)> generate);
Draw2DPipeline *Create2DPipeline(std::function<Draw2DPipelineInfo(ShaderWriter &)> generate, Slice<const char *> extensions = Slice<const char *>::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:
+21 -4
View File
@@ -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<const char *>(exportExtensions) : Slice<const char *>::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_);
+3 -1
View File
@@ -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.
+34 -4
View File
@@ -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;
}
+13 -1
View File
@@ -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);
+33 -19
View File
@@ -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);
}
+23 -16
View File
@@ -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<const char *> writerExtensions = output == ReinterpretOutput::STENCIL_EXPORT ? Slice<const char *>(extensions) : Slice<const char *>::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);
}
}
}
}