mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
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:
1 parent
71bc3187db
commit
e108e41675
10 files changed
+36
-12
No files matched your search
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ SoftwareTransformAction RunSoftwareTransform(SoftwareTransformParams ¶ms, 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++;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user