GPU: Fix assorted leaks, null derefs and small rendering bugs

- Put the anisotropy level in the sampler key, so changing it applies on
  Vulkan and D3D11.
- Release CLUT textures at shutdown on GLES and D3D11.
- Fix the depth readback viewport, which squeezed the image whenever the
  read rectangle was smaller than the fbo.
- Test the computed depth, not the unset result, in the equal-depth clear
  check.
- Don't read back a CLUT from a framebuffer without an fbo.
- Tolerate null entries when releasing post-shader objects and CLUT
  textures after a failed creation.
- ImGe: Don't crash on a framebuffer without an fbo, or on GetVFB under the
  software renderer.

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 10:33:32 -06:00
1 parent 71bc3187db
commit e108e41675
10 files changed
+36 -12

No files matched your search

+3 -1
View File
@@ -199,7 +199,9 @@ bool FramebufferManagerCommon::ReadbackDepthbuffer(Draw::Framebuffer *fbo, int x
auto *blitFBO = GetTempFBO(TempFBO::Z_COPY, fbo->Width() * scaleX, fbo->Height() * scaleY);
draw_->BindFramebufferAsRenderTarget(blitFBO, { RPAction::DONT_CARE, RPAction::DONT_CARE, RPAction::DONT_CARE }, "ReadbackDepthbufferSync");
Draw::Viewport viewport = { 0.0f, 0.0f, (float)destW, (float)destH, 0.0f, 1.0f };
// The whole fbo is drawn, so the viewport has to cover all of it at the destination scale. Not just
// destW x destH, which would squeeze it whenever the read rectangle is smaller than the fbo.
Draw::Viewport viewport = { 0.0f, 0.0f, fbo->Width() * scaleX, fbo->Height() * scaleY, 0.0f, 1.0f };
draw_->SetViewport(viewport);
draw_->SetScissorRect(0, 0, fbo->Width() * scaleX, fbo->Height() * scaleY);
+2 -1
View File
@@ -3354,7 +3354,8 @@ void FramebufferManagerCommon::FlushBeforeCopy() {
// TODO: Replace with with depal, reading the palette from the texture on the GPU directly.
void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 loadBytes) {
VirtualFramebuffer *vfb = GetVFBAt(fb_address);
if (vfb && vfb->fb_stride != 0) {
// Without an fbo there's nothing to read back (ReadbackFramebuffer would read the backbuffer instead).
if (vfb && vfb->fb_stride != 0 && vfb->fbo) {
const u32 bpp = BufferFormatBytesPerPixel(vfb->fb_format);
int x = 0;
int y = 0;
+6 -2
View File
@@ -555,8 +555,12 @@ static void DoRelease(T *&obj) {
template <typename T>
static void DoReleaseVector(std::vector<T *> &list) {
for (auto &obj : list)
obj->Release();
for (auto &obj : list) {
// Can be null when a creation failed partway.
if (obj) {
obj->Release();
}
}
list.clear();
}
+1 -1
View File
@@ -198,7 +198,7 @@ SoftwareTransformAction RunSoftwareTransform(SoftwareTransformParams &params, in
float depth = std::clamp(transformed[1].z, 0.0f, 65535.0f) / 65535.0f;
// Non-zero depth clears are unusual, but some drivers don't match drawn depth values to cleared values.
// Games sometimes expect exact matches (see #12626, for example) for equal comparisons.
if (!(params.everUsedEqualDepth && gstate.isClearModeDepthMask() && result->depth > 0.0f && result->depth < 1.0f)) {
if (!(params.everUsedEqualDepth && gstate.isClearModeDepthMask() && depth > 0.0f && depth < 1.0f)) {
result->color = transformed[1].color0_32;
result->depth = depth;
gpuStats.perFrame.numClears++;
+5
View File
@@ -114,6 +114,8 @@ TextureCacheCommon::TextureCacheCommon(Draw::DrawContext *draw, Draw2D *draw2D)
}
TextureCacheCommon::~TextureCacheCommon() {
// Only DeviceLost cleared these, which the GLES and D3D11 backends don't go through at shutdown.
clutTextureCache_.Clear();
FreeAlignedMemory(clutBufConverted_);
FreeAlignedMemory(clutBufRaw_);
FreeAlignedMemory(expandClut_);
@@ -342,6 +344,9 @@ SamplerCacheKey TextureCacheCommon::GetSamplingParams(int maxLevel, const TexCac
break;
}
if (key.aniso) {
key.anisoLevel = (uint8_t)g_Config.iAnisotropyLevel;
}
return key;
}
+2
View File
@@ -86,6 +86,8 @@ struct SamplerCacheKey {
bool tClamp : 1;
bool aniso : 1;
bool texture3d : 1;
// Baked into the sampler objects, so it has to be part of the key, or a change wouldn't apply.
uint8_t anisoLevel;
};
};
bool operator < (const SamplerCacheKey &other) const {
+6 -2
View File
@@ -252,7 +252,9 @@ ClutTexture ClutTextureCache::GetClutTexture(GEPaletteFormat clutFormat, const u
void ClutTextureCache::Clear() {
for (auto tex = texCache_.begin(); tex != texCache_.end(); ++tex) {
tex->second->texture->Release();
if (tex->second->texture) {
tex->second->texture->Release();
}
delete tex->second;
}
texCache_.clear();
@@ -261,7 +263,9 @@ void ClutTextureCache::Clear() {
void ClutTextureCache::Decimate() {
for (auto tex = texCache_.begin(); tex != texCache_.end(); ) {
if (tex->second->lastFrame + DEPAL_TEXTURE_OLD_AGE < gpuStats.totals.numFlips) {
tex->second->texture->Release();
if (tex->second->texture) {
tex->second->texture->Release();
}
delete tex->second;
texCache_.erase(tex++);
} else {
+1 -1
View File
@@ -90,7 +90,7 @@ HRESULT SamplerCacheD3D11::GetOrCreateSampler(ID3D11Device *device, const Sample
samp.AddressV = key.tClamp ? D3D11_TEXTURE_ADDRESS_CLAMP : D3D11_TEXTURE_ADDRESS_WRAP;
samp.AddressW = samp.AddressU; // Mali benefits from all clamps being the same, and this one is irrelevant.
if (key.aniso) {
samp.MaxAnisotropy = (float)(1 << g_Config.iAnisotropyLevel);
samp.MaxAnisotropy = (float)(1 << key.anisoLevel);
} else {
samp.MaxAnisotropy = 1.0f;
}
+1 -1
View File
@@ -143,7 +143,7 @@ VkSampler SamplerCache::GetOrCreateSampler(const SamplerCacheKey &key) {
if (key.aniso) {
// Docs say the min of this value and the supported max are used.
samp.maxAnisotropy = 1 << g_Config.iAnisotropyLevel;
samp.maxAnisotropy = 1 << key.anisoLevel;
samp.anisotropyEnable = true;
} else {
samp.maxAnisotropy = 1.0f;
+9 -3
View File
@@ -96,9 +96,14 @@ void DrawFramebuffersWindow(ImConfig &cfg, FramebufferManagerCommon *framebuffer
ImGui::SliderFloat("Scale", &cfg.fbViewerZoom, 0.5f, 16.0f, "%.2f", ImGuiSliderFlags_Logarithmic);
// Now, draw the image of the selected framebuffer.
// Null without buffered rendering, or if creating it failed.
Draw::Framebuffer *fb = vfbs[cfg.selectedFramebuffer]->fbo;
ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(fb, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
ImGui::Image(texId, ImVec2(fb->Width() * cfg.fbViewerZoom, fb->Height() * cfg.fbViewerZoom));
if (fb) {
ImTextureID texId = ImGui_ImplThin3d_AddFBAsTextureTemp(fb, Draw::Aspect::COLOR_BIT, ImGuiPipeline::TexturedOpaque);
ImGui::Image(texId, ImVec2(fb->Width() * cfg.fbViewerZoom, fb->Height() * cfg.fbViewerZoom));
} else {
ImGui::TextUnformatted("(no framebuffer object)");
}
}
ImGui::End();
@@ -602,7 +607,8 @@ ImGeReadbackViewer::~ImGeReadbackViewer() {
}
VirtualFramebuffer *ImGeReadbackViewer::GetVFB(FramebufferManagerCommon *fbMan) const {
return fbMan->GetExactVFB(gstate.getFrameBufAddress(), gstate.FrameBufStride(), gstate.FrameBufFormat());
// fbMan is null with the software renderer.
return fbMan ? fbMan->GetExactVFB(gstate.getFrameBufAddress(), gstate.FrameBufStride(), gstate.FrameBufFormat()) : nullptr;
}
void ImGeReadbackViewer::DeviceLost() {