From 62503e6dd726486ed2b51b9b26def7c143c871dd Mon Sep 17 00:00:00 2001 From: NKR0ne Date: Sat, 25 Jul 2026 08:35:30 -0400 Subject: [PATCH] Latte: mirror small 1D-tiled render targets back to guest memory Titles that reduce a frame down to a tiny surface on the GPU and then read the result back on the CPU never received that data, because enableReadback was only set for TM_LINEAR_ALIGNED surfaces. Disney Infinity does this for auto exposure. It reduces the frame through 1152x600 -> 284x150 -> 96x48 -> 17x16 -> 4x8 -> a final 1x8 R32_FLOAT holding average scene luminance, then reads that value on the CPU to derive an exposure scalar which it uploads as a shader uniform. The chain is TM_1D_TILED_THIN1, so it was never mirrored back, the game read stale zeros and uploaded an exposure of 0, and the lighting shader (which ends in colour * uf_remappedPS[13].x) rendered the entire scene black. UI is drawn after that multiply and stayed visible, so the symptom was a black scene with working audio, menus and HUD on both the Vulkan and OpenGL backends. Also enable readback for TM_1D_TILED_THIN1 surfaces of at most 64x64. Full size render targets are unaffected. Co-Authored-By: Claude Opus 5 --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 74b089a8..15b2776d 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1312,6 +1312,15 @@ LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddre { this->enableReadback = true; } + else if (this->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 && (width * height) <= 64 * 64) + { + // Some titles reduce a frame down to a tiny surface on the GPU and then read the result + // back on the CPU - Disney Infinity does this for auto exposure, ending in a 1x8 R32_FLOAT + // holding average scene luminance. Those surfaces are 1D tiled rather than linear, so they + // would never be mirrored back and the title reads stale memory, computes an exposure of + // zero and renders the whole scene black. They are small enough that mirroring them is cheap. + this->enableReadback = true; + } // calculate number of potential mip levels (from effective size) sint32 effectiveWidth = width;