gpu: Preserve first-frame font render targets

Some games render HLE sceFont glyphs into small VRAM-backed framebuffers and immediately sample them as textures. This needs both the source glyph buffer and the temporary render target to be preserved before later commands overwrite or rebind them.

Save first-frame data from the previous render target before creating a new framebuffer, since ResizeFramebufFBO() makes the new framebuffer current. Also flush pending draws before PGF writes over a glyph buffer that may still be referenced by queued texture draws.

Backend software transform normally reports safe framebuffer size during draw flush, but first-frame readback can happen before queued draws reach that path. Estimate through-mode rectangle/triangle bounds before SubmitPrim() and feed those bounds to SetSafeSize(), using actual vertices clamped to scissor instead of a tiny-target heuristic.

Fixes missing text in Evangelion JO.
This commit is contained in:
nintendo424
2026-05-15 10:05:45 -04:00
parent 7afd3d8ea2
commit e5689d1adc
5 changed files with 87 additions and 2 deletions
+5
View File
@@ -641,6 +641,11 @@ void PGF::DrawCharacter(const GlyphImage *image, int clipX, int clipY, int clipW
int renderX2 = std::min(clipX + clipWidth - x, glyph.w + (xFrac > 0 ? 1 : 0));
int renderY2 = std::min(clipY + clipHeight - y, glyph.h + (yFrac > 0 ? 1 : 0));
if (gpu && renderX1 < renderX2 && renderY1 < renderY2) {
// The game may reuse this glyph buffer as a texture immediately after drawing it.
gpu->Flush();
}
if (xFrac == 0 && yFrac == 0) {
for (int yy = renderY1; yy < renderY2; ++yy) {
for (int xx = renderX1; xx < renderX2; ++xx) {
+65
View File
@@ -17,6 +17,7 @@
#include <algorithm>
#include <cfloat>
#include <cmath>
#include "Common/Data/Convert/ColorConv.h"
#include "Common/Profiler/Profiler.h"
@@ -590,6 +591,70 @@ bool DrawEngineCommon::TestBoundingBoxThrough(const void *vdata, int vertexCount
}
}
bool DrawEngineCommon::EstimateThroughPrimSafeSize(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertType, int *safeWidth, int *safeHeight) {
if (prim != GE_PRIM_RECTANGLES && prim != GE_PRIM_TRIANGLES) {
return false;
}
if ((vertType & GE_VTYPE_THROUGH_MASK) == 0 || (vertType & (GE_VTYPE_WEIGHT_MASK | GE_VTYPE_MORPHCOUNT_MASK)) != 0) {
return false;
}
const int stride = dec->VertexSize();
const int posOffset = dec->posoff;
IndexConverter conv(vertType, inds);
float minX = FLT_MAX;
float minY = FLT_MAX;
float maxX = -FLT_MAX;
float maxY = -FLT_MAX;
for (int i = 0; i < vertexCount; ++i) {
const u8 *posPtr = (const u8 *)verts + conv(i) * stride + posOffset;
float x;
float y;
switch (vertType & GE_VTYPE_POS_MASK) {
case GE_VTYPE_POS_8BIT:
x = 0.0f;
y = 0.0f;
break;
case GE_VTYPE_POS_16BIT:
{
const s16_le *pos = (const s16_le *)posPtr;
x = (float)pos[0];
y = (float)pos[1];
break;
}
case GE_VTYPE_POS_FLOAT:
{
const float_le *pos = (const float_le *)posPtr;
x = pos[0];
y = pos[1];
break;
}
default:
return false;
}
minX = std::min(minX, x);
minY = std::min(minY, y);
maxX = std::max(maxX, x);
maxY = std::max(maxY, y);
}
const int scissorX1 = gstate.getScissorX1();
const int scissorY1 = gstate.getScissorY1();
const int scissorX2 = gstate.getScissorX2() + 1;
const int scissorY2 = gstate.getScissorY2() + 1;
if (maxX <= scissorX1 || maxY <= scissorY1 || minX >= scissorX2 || minY >= scissorY2) {
return false;
}
*safeWidth = std::clamp((int)ceilf(maxX), 0, scissorX2);
*safeHeight = std::clamp((int)ceilf(maxY), 0, scissorY2);
return *safeWidth > 0 && *safeHeight > 0;
}
void DrawEngineCommon::ApplyFramebufferRead(FBOTexState *fboTexState) {
if (gstate_c.Use(GPU_USE_FRAMEBUFFER_FETCH)) {
*fboTexState = FBO_TEX_READ_FRAMEBUFFER;
+1
View File
@@ -106,6 +106,7 @@ public:
// Doesn't support indexing.
bool TestBoundingBoxFast(const void *control_points, int vertexCount, const VertexDecoder *dec, u32 vertType);
bool TestBoundingBoxThrough(const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead);
bool EstimateThroughPrimSafeSize(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertType, int *safeWidth, int *safeHeight);
void FlushPartialDecode() {
DecodeVerts(dec_, decoded_);
+6 -2
View File
@@ -540,6 +540,12 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
}
// This is where we actually create the framebuffer. The true is "force".
// ResizeFramebufFBO() makes this framebuffer current, so save first-frame
// data from the previous render target before the switch.
VirtualFramebuffer *previousRenderVfb = currentRenderVfb_;
if (useBufferedRendering_ && previousRenderVfb) {
DownloadFramebufferOnSwitch(previousRenderVfb);
}
ResizeFramebufFBO(vfb, drawing_width, drawing_height, true);
NotifyRenderFramebufferCreated(vfb);
@@ -1022,8 +1028,6 @@ void FramebufferManagerCommon::NotifyRenderFramebufferCreated(VirtualFramebuffer
if (!useBufferedRendering_) {
// Let's ignore rendering to targets that have not (yet) been displayed.
gstate_c.skipDrawReason |= SKIPDRAW_NON_DISPLAYED_FB;
} else if (currentRenderVfb_) {
DownloadFramebufferOnSwitch(currentRenderVfb_);
}
textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_CREATED);
+10
View File
@@ -1015,6 +1015,16 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
uint32_t vertTypeID = GetVertTypeID(vertexType, gstate.getUVGenMode(), g_Config.bSoftwareSkinning);
VertexDecoder *decoder = drawEngineCommon_->GetVertexDecoder(vertTypeID);
if (gstate.isModeThrough() && gstate.isTextureMapEnabled() && gstate.getColorMask() != 0xFFFFFFFF &&
gstate.getScissorX1() == 0 && gstate.getScissorY1() == 0 && Memory::IsVRAMAddress(gstate.getFrameBufAddress())) {
int safeWidth;
int safeHeight;
// First-frame readback can happen before queued draws reach backend transform.
if (drawEngineCommon_->EstimateThroughPrimSafeSize(verts, inds, prim, count, decoder, vertexType, &safeWidth, &safeHeight)) {
framebufferManager_->SetSafeSize(safeWidth, safeHeight);
}
}
// Through mode early-out for simple float 2D draws, like in Fate Extra CCC (very beneficial there due to avoiding texture loads)
if ((vertexType & (GE_VTYPE_THROUGH_MASK | GE_VTYPE_POS_MASK | GE_VTYPE_IDX_MASK)) == (GE_VTYPE_THROUGH_MASK | GE_VTYPE_POS_FLOAT | GE_VTYPE_IDX_NONE)) {
int bytesRead = 0;