Merge pull request #22906 from hrydgard/bitplane-copy

Keep one framebuffer copy for a run of draws with the same write mask
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-11-03 13:55:03 +01:00
commit 19b2894c2a
11 files changed
+64 -6

No files matched your search

+8
View File
@@ -30,6 +30,7 @@
#include "Core/Config.h"
#include "GPU/GPUCommon.h"
#include "GPU/Common/DrawEngineCommon.h"
#include "GPU/Common/FramebufferManagerCommon.h"
#include "GPU/GPUStateSIMDUtil.h"
#include "GPU/Common/SplineCommon.h"
#include "GPU/Common/DepthRaster.h"
@@ -775,6 +776,13 @@ void DrawEngineCommon::ApplyFramebufferRead(FBOTexState *fboTexState) {
gstate_c.Dirty(DIRTY_SHADERBLEND);
}
int DrawEngineCommon::ShaderReadBindFlags() const {
// The mask forces blending and the logic op into the shader too, so check what the GE asks for.
const bool maskOnly = pipelineState_.maskState.applyFramebufferRead && !gstate.isAlphaBlendEnabled() &&
!(gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY);
return BINDFBCOLOR_MAY_COPY | (maskOnly ? BINDFBCOLOR_MASK_READ : 0) | BINDFBCOLOR_UNCACHED;
}
int DrawEngineCommon::ComputeNumVertsToDecode() const {
int sum = 0;
for (int i = 0; i < numDrawVerts_; i++) {
+2
View File
@@ -195,6 +195,8 @@ protected:
int ComputeNumVertsToDecode() const;
void ApplyFramebufferRead(FBOTexState *fboTexState);
// For BindFramebufferAsColorTexture, when the draw reads the target for shader blending.
int ShaderReadBindFlags() const;
void InitDepthRaster();
void ShutdownDepthRaster();
+21 -1
View File
@@ -119,6 +119,7 @@ void FramebufferManagerCommon::BeginFrame(const DisplayLayoutConfig &config) {
DecimateFBOs();
presentation_->BeginFrame(config);
currentRenderVfb_ = nullptr;
DiscardFramebufferCopy();
// Hack.
displayLayoutConfigCopy_ = config;
@@ -1420,6 +1421,11 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF
draw_->BindFramebufferAsTexture(currentFramebufferCopy_, stage, Draw::Aspect::COLOR_BIT, layer);
return true;
}
const bool maskRead = (flags & BINDFBCOLOR_MASK_READ) != 0;
if (maskRead && maskCopy_ && maskCopyVfb_ == framebuffer && maskCopyMask_ == gstate.getColorMask()) {
draw_->BindFramebufferAsTexture(maskCopy_, stage, Draw::Aspect::COLOR_BIT, layer);
return true;
}
// There's a special case we can handle here, where the game is texturing from the same pixels being read, in order to
// implement a DST*DST blending function, which the PSP can't do. However on the PC we can absolutely do this!
@@ -1436,7 +1442,7 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF
return true;
}
Draw::Framebuffer *renderCopy = GetTempFBO(TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight);
Draw::Framebuffer *renderCopy = GetTempFBO(maskRead ? TempFBO::MASK_COPY : TempFBO::COPY, framebuffer->renderWidth, framebuffer->renderHeight);
if (renderCopy) {
VirtualFramebuffer copyInfo = *framebuffer;
copyInfo.fbo = renderCopy;
@@ -1451,6 +1457,11 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF
if (!partial && (flags & BINDFBCOLOR_UNCACHED) == 0) {
currentFramebufferCopy_ = renderCopy;
}
if (!partial && maskRead) {
maskCopy_ = renderCopy;
maskCopyVfb_ = framebuffer;
maskCopyMask_ = gstate.getColorMask();
}
gpuStats.perFrame.numCopiesForSelfTex++;
} else {
// Failed to get temp FBO? Weird.
@@ -3208,6 +3219,14 @@ void FramebufferManagerCommon::DestroyAllFBOs() {
drawPixelsCache_.clear();
}
void FramebufferManagerCommon::CheckMaskCopyStillValid(const VirtualFramebuffer *vfb) {
// Anything but a draw with the same write mask, without blending or a logic op, can change the bits the copy holds.
if (vfb != maskCopyVfb_ || gstate.isModeClear() || gstate.getColorMask() != maskCopyMask_ ||
gstate.isAlphaBlendEnabled() || (gstate.isLogicOpEnabled() && gstate.getLogicOp() != GE_LOGIC_COPY)) {
maskCopy_ = nullptr;
}
}
static const char *TempFBOReasonToString(TempFBO reason) {
switch (reason) {
case TempFBO::DEPAL: return "depal";
@@ -3216,6 +3235,7 @@ static const char *TempFBOReasonToString(TempFBO reason) {
case TempFBO::STENCIL: return "stencil";
case TempFBO::DEPTH_SHIFT: return "depth_shift";
case TempFBO::SAMPLE0: return "sample0";
case TempFBO::MASK_COPY: return "mask_copy";
default: break;
}
return "";
+22
View File
@@ -214,6 +214,8 @@ enum BindFramebufferColorFlags {
// Used when rendering to a temporary surface (e.g. not the current render target.)
BINDFBCOLOR_FORCE_SELF = 8,
BINDFBCOLOR_UNCACHED = 16,
// The draw reads only the bits its write mask keeps. See maskCopy_.
BINDFBCOLOR_MASK_READ = 32,
};
enum DrawTextureFlags {
@@ -241,6 +243,8 @@ enum class TempFBO {
DEPTH_SHIFT,
// The first samples of a multisampled framebuffer.
SAMPLE0,
// A copy for draws that read only the bits outside their write mask.
MASK_COPY,
};
inline Draw::DataFormat GEFormatToThin3D(GEBufferFormat geFormat) {
@@ -522,7 +526,19 @@ public:
void DiscardFramebufferCopy() {
currentFramebufferCopy_ = nullptr;
maskCopy_ = nullptr;
}
// The target hasn't changed, but texturing from it should see the latest drawing.
void DiscardSelfTextureCopy() {
currentFramebufferCopy_ = nullptr;
}
// Call before a draw to the current render target.
void CheckMaskCopy(const VirtualFramebuffer *vfb) {
if (maskCopy_) {
CheckMaskCopyStillValid(vfb);
}
}
void CheckMaskCopyStillValid(const VirtualFramebuffer *vfb);
bool PresentedThisFrame() const;
void SetPresentationOutput(const OutputParams *output);
@@ -640,6 +656,12 @@ protected:
VirtualFramebuffer *currentRenderVfb_ = nullptr;
Draw::Framebuffer *currentFramebufferCopy_ = nullptr;
// A draw with a write mask only reads the bits the mask keeps, and doesn't change them. So while the same
// mask draws on, without blending or a logic op, one copy serves every draw: DBZ Tenkaichi Tag Team draws
// its shadows one bit at a time, which took a copy per flush.
Draw::Framebuffer *maskCopy_ = nullptr;
const VirtualFramebuffer *maskCopyVfb_ = nullptr;
u32 maskCopyMask_ = 0;
// The range of PSP memory that may contain FBOs. So we can skip iterating.
u32 framebufColorRangeEnd_ = 0;
+2
View File
@@ -348,6 +348,8 @@ bool FramebufferManagerCommon::PerformWriteStencilFromMemory(u32 addr, int size,
if (!CheckStencilBits(src, dstBuffer, values, usedBits))
return false;
DiscardFramebufferCopy();
if (usedBits == 0) {
if (flags & WriteStencil::STENCIL_IS_ZERO) {
// Common when creating buffers, it's already 0.
+1 -1
View File
@@ -156,7 +156,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) {
ApplyStencilReplaceAndLogicOpIgnoreBlend(blendState.replaceAlphaWithStencil, blendState);
if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) {
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY | BINDFBCOLOR_UNCACHED, 0);
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), ShaderReadBindFlags(), 0);
// No sampler required, we do a plain Load in the pixel shader.
fboTexBound_ = true;
fboTexBindState = FBO_TEX_NONE;
+1 -1
View File
@@ -148,7 +148,7 @@ void DrawEngineGLES::ApplyDrawState(int prim) {
// fboTexNeedsBind_ won't be set if we can read directly from the target.
if (fboTexBindState == FBO_TEX_COPY_BIND_TEX) {
// Note that this is positions, not UVs, that we need the copy from.
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), BINDFBCOLOR_MAY_COPY | BINDFBCOLOR_UNCACHED, 0);
framebufferManager_->BindFramebufferAsColorTexture(1, framebufferManager_->GetCurrentRenderVFB(), ShaderReadBindFlags(), 0);
// If we are rendering at a higher resolution, linear is probably best for the dest color.
renderManager->SetTextureSampler(1, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, GL_LINEAR, GL_LINEAR, 0.0f);
fboTexBound_ = true;
+1
View File
@@ -1607,6 +1607,7 @@ void GPUCommon::FlushImm() {
}
if (vfb) {
CheckDepthUsage(vfb);
framebufferManager_->CheckMaskCopy(vfb);
}
bool antialias = (immFlags_ & GE_IMM_ANTIALIAS) != 0;
+4 -1
View File
@@ -956,6 +956,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
}
CheckDepthUsage(vfb);
framebufferManager_->CheckMaskCopy(vfb);
const void *verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr);
const void *inds = nullptr;
@@ -1334,6 +1335,7 @@ void GPUCommonHW::Execute_Bezier(u32 op, u32 diff) {
}
CheckDepthUsage(vfb);
framebufferManager_->CheckMaskCopy(vfb);
if (!Memory::IsValidAddress(gstate_c.vertexAddr)) {
ERROR_LOG(Log::G3D, "Bad vertex address %08x!", gstate_c.vertexAddr);
@@ -1409,6 +1411,7 @@ void GPUCommonHW::Execute_Spline(u32 op, u32 diff) {
}
CheckDepthUsage(vfb);
framebufferManager_->CheckMaskCopy(vfb);
if (!Memory::IsValidAddress(gstate_c.vertexAddr)) {
ERROR_LOG(Log::G3D, "Bad vertex address %08x!", gstate_c.vertexAddr);
@@ -1522,7 +1525,7 @@ void GPUCommonHW::Execute_TexFlush(u32 op, u32 diff) {
// Games call this when they need the effect of drawing to be visible to texturing.
// And for a bunch of other reasons, but either way, this is what we need to do.
// It's possible we could also use this as a hint for the texture cache somehow.
framebufferManager_->DiscardFramebufferCopy();
framebufferManager_->DiscardSelfTextureCopy();
}
void GPUCommonHW::FlushPendingDrawing() {
+1 -1
View File
@@ -552,7 +552,7 @@ void DrawEngineMetal::ConvertStateToKeys(int prim) {
void DrawEngineMetal::BindShaderBlendTex() {
if (!gstate.isModeClear() && fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) {
VirtualFramebuffer *curRenderVfb = framebufferManager_->GetCurrentRenderVFB();
bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, curRenderVfb, BINDFBCOLOR_MAY_COPY | BINDFBCOLOR_UNCACHED, Draw::ALL_LAYERS);
bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, curRenderVfb, ShaderReadBindFlags(), Draw::ALL_LAYERS);
_dbg_assert_(bindResult);
boundSecondary_ = (__bridge id<MTLTexture>)(void *)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE1_IMAGEVIEW);
fboTexBound_ = true;
+1 -1
View File
@@ -352,7 +352,7 @@ void DrawEngineVulkan::BindShaderBlendTex() {
if (!gstate.isModeClear()) {
if (fboTexBindState_ == FBO_TEX_COPY_BIND_TEX) {
VirtualFramebuffer *curRenderVfb = framebufferManager_->GetCurrentRenderVFB();
bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, curRenderVfb, BINDFBCOLOR_MAY_COPY | BINDFBCOLOR_UNCACHED, Draw::ALL_LAYERS);
bool bindResult = framebufferManager_->BindFramebufferAsColorTexture(1, curRenderVfb, ShaderReadBindFlags(), Draw::ALL_LAYERS);
_dbg_assert_(bindResult);
boundSecondary_ = (VkImageView)draw_->GetNativeObject(Draw::NativeObject::BOUND_TEXTURE1_IMAGEVIEW);
fboTexBound_ = true;