GPU: Fix texture cache and framebuffer cache lifetime bugs

- Delete TexCacheEntry objects dropped on rehash instead of leaking them.
- Don't leave a released null entry in cache_ when the framebuffer match
  returns before the slot is refilled.
- Reset clutRenderAddress_ in Clear(), which releases the dynamic CLUT FBOs.
- Don't cache a null texture in drawPixelsCache_ when creation fails.
- Fix the reversed subtraction in the failed-FBO retry check.
- Remove the never-taken buffered-rendering early-out in UpdateRenderSize.
  Taking it would leave existing VFBs without an fbo.
- Include smoothedDepal in the depal shader cache key, and print/parse the
  debug IDs as 64-bit.
- Release depal pipelines through Draw2DPipeline::Release so the shader
  source isn't leaked, and make that null-safe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-29 09:35:17 -06:00
1 parent b04d03615b
commit b912b5823b
4 files changed
+18 -16

No files matched your search

+3 -1
View File
@@ -49,7 +49,9 @@ struct Draw2DPipeline {
Draw2DPipelineInfo info;
char *code;
void Release() {
pipeline->Release();
if (pipeline) {
pipeline->Release();
}
delete[] code;
delete this;
}
+4 -5
View File
@@ -103,10 +103,8 @@ bool FramebufferManagerCommon::UpdateRenderSize(int msaaLevel) {
presentation_->UpdateRenderSize(renderWidth_, renderHeight_);
// If just switching TO buffered rendering, no need to pause the threads. In fact this causes problems due to the open backbuffer renderpass.
if (!useBufferedRendering_ && newBuffered) {
return false;
}
// Switching to buffered rendering must also recreate the framebuffers: the existing VFBs have no fbo,
// and nothing else gives them one.
return newRender || newSettings;
}
@@ -1531,6 +1529,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G
Draw::Texture *tex = draw_->CreateTexture(desc);
if (!tex) {
ERROR_LOG(Log::G3D, "Failed to create DrawPixels texture");
return nullptr;
}
// We don't need to count here, already counted by numUploads by the caller.
@@ -1876,7 +1875,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
}
return;
}
if (!old.fbo && vfb->last_frame_failed != 0 && vfb->last_frame_failed - gpuStats.totals.numFlips < 63) {
if (!old.fbo && vfb->last_frame_failed != 0 && gpuStats.totals.numFlips - vfb->last_frame_failed < 63) {
// Don't constantly retry FBOs which failed to create.
return;
}
+6 -2
View File
@@ -757,12 +757,14 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
DEBUG_LOG(Log::TexCache, "%08x: Second cache already had one with hash %08x (tex has addr %08x)! (%dx%d)", texaddr, entry->fullhash, entry->addr, w, h);
// Just release the old entry, drop it on the ground.
ReleaseTexture(entry, true);
delete entry;
entry = nullptr;
}
} else {
// Just release the old video entry, drop it on the ground.
VERBOSE_LOG(Log::TexCache, "%08x: Dropping old invalidated video image (%dx%d)", texaddr, w, h);
ReleaseTexture(entry, true);
delete entry;
entry = nullptr;
}
@@ -802,9 +804,9 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
// other stuff like Gran Turismo or Gods Eater font rendering unless there are hash collisions..
DEBUG_LOG(Log::TexCache, "%08x: No entry for hash %08x in secondary cache, creating new in main cache.", texaddr, newFullHash);
}
// Well, not found, so we need to create a new entry.
cache_.erase(entryIter);
}
// The slot was released above. Erase it, since the framebuffer match below can return before it's refilled.
cache_.erase(entryIter);
entry = nullptr;
entryIter = cache_.end();
} else {
@@ -2740,6 +2742,8 @@ void TextureCacheCommon::Clear(bool delete_them) {
dynamicClutTemp_->Release();
dynamicClutTemp_ = nullptr;
}
// The dynamic CLUT lived in the framebuffers released above. Fall back to the RAM copy until the next LoadClut.
clutRenderAddress_ = 0xFFFFFFFF;
}
// One type of texture update can happen without the involvement of the CPU: Block transfers.
+5 -8
View File
@@ -79,10 +79,7 @@ Draw::SamplerState *TextureShaderCache::GetSampler(bool linearFilter) {
void TextureShaderCache::Clear() {
for (auto shader = pipelineCache_.begin(); shader != pipelineCache_.end(); ++shader) {
if (shader->second->pipeline) {
shader->second->pipeline->Release();
}
delete shader->second;
shader->second->Release();
}
pipelineCache_.clear();
if (nearestSampler_) {
@@ -99,7 +96,7 @@ Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETe
using namespace Draw;
// Generate an ID for depal shaders.
u64 id = ((u64)depthUpperBits << 32) | (clutMode & 0xFFFFFF) | (textureFormat << 24) | (bufferFormat << 28);
u64 id = ((u64)smoothedDepal << 63) | ((u64)depthUpperBits << 32) | (clutMode & 0xFFFFFF) | (textureFormat << 24) | (bufferFormat << 28);
auto shader = pipelineCache_.find(id);
if (shader != pipelineCache_.end()) {
@@ -135,14 +132,14 @@ Draw2DPipeline *TextureShaderCache::GetDepalettizeShader(uint32_t clutMode, GETe
std::vector<std::string> TextureShaderCache::DebugGetShaderIDs(DebugShaderType type) const {
std::vector<std::string> ids;
for (auto &entry : pipelineCache_) {
ids.push_back(StringFromFormat("%08x", entry.first));
ids.push_back(StringFromFormat("%016llx", (unsigned long long)entry.first));
}
return ids;
}
std::string TextureShaderCache::DebugGetShaderString(const std::string &idstr, DebugShaderType type, DebugShaderStringType stringType) const {
uint32_t id = 0;
if (sscanf(idstr.c_str(), "%08x", &id) == 0) {
unsigned long long id = 0;
if (sscanf(idstr.c_str(), "%llx", &id) != 1) {
return "";
}
auto iter = pipelineCache_.find(id);