From ae324777644f2903e8a177455b6db6e0f8213fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 5 Oct 2026 15:57:21 -0600 Subject: [PATCH] Core: Let 16-bit CPU accesses through the depth mirrors see the GE's layout With the software renderer, which now stores depth as the GE does, a 16-bit read or write at VRAM's 0x04200000 or 0x04600000 mirror goes to where the GE's 16-bit or 32-bit color layout puts that depth pixel (ppsspp-re geprobe exp88: the mirrors are the layout's exact inverse). Games access depth there with nothing else, so other widths stay plain. The accessors check the address, then whether the GPU core is the software renderer (CoreParameter); the hardware renderers keep depth plain and handle the layout their own way. The renderer keeps the layouts up to date with the EDRAM address translation. With it, the JITs send 16-bit loads and stores to the interpreter, and the IR passes don't change a load's width to or from 16 bits. The pspautotests that read depth through the mirrors now do so 16 bits at a time, re-recorded on hardware unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/MIPS/ARM/ArmCompLoadStore.cpp | 4 ++++ Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 4 ++++ Core/MIPS/IR/IRNativeCommon.cpp | 19 ++++++++++++++--- Core/MIPS/IR/IRPassSimplify.cpp | 8 ++++++-- Core/MIPS/x86/CompLoadStore.cpp | 4 ++++ Core/MemMap.cpp | 12 +++++++++++ Core/MemMap.h | 22 +++++++++++++++++++- Core/MemMapFunctions.cpp | 4 ++-- GPU/Common/DepthSwizzle.h | 28 ++++++++++++++++++++++++-- GPU/Software/SoftGpu.cpp | 3 +++ GPU/Software/SoftGpu.h | 14 ------------- pspautotests | 2 +- 12 files changed, 99 insertions(+), 25 deletions(-) diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp index 8a2fed055f..249d4a9f69 100644 --- a/Core/MIPS/ARM/ArmCompLoadStore.cpp +++ b/Core/MIPS/ARM/ArmCompLoadStore.cpp @@ -284,6 +284,10 @@ namespace MIPSComp MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; int o = op>>26; + // 16-bit accesses can go through the depth layout (Memory::DepthMirrored16). + if (Memory::DepthMirrorsActive() && (o == 33 || o == 37 || o == 41)) { + DISABLE; + } if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) { // Don't load anything into $zr return; diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 3b8ac2859d..f293b94329 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -279,6 +279,10 @@ namespace MIPSComp { MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; int o = op >> 26; + // 16-bit accesses can go through the depth layout (Memory::DepthMirrored16). + if (Memory::DepthMirrorsActive() && (o == 33 || o == 37 || o == 41)) { + DISABLE; + } if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) { // Don't load anything into $zr return; diff --git a/Core/MIPS/IR/IRNativeCommon.cpp b/Core/MIPS/IR/IRNativeCommon.cpp index 399089b8dc..07d0787c17 100644 --- a/Core/MIPS/IR/IRNativeCommon.cpp +++ b/Core/MIPS/IR/IRNativeCommon.cpp @@ -280,10 +280,17 @@ void IRNativeBackend::CompileIRInst(IRInst inst) { CompIR_Div(inst); break; - case IROp::Load8: - case IROp::Load8Ext: case IROp::Load16: case IROp::Load16Ext: + // 16-bit accesses can go through the depth layout (Memory::DepthMirrored16). + if (Memory::DepthMirrorsActive()) + CompIR_Generic(inst); + else + CompIR_Load(inst); + break; + + case IROp::Load8: + case IROp::Load8Ext: case IROp::Load32: case IROp::Load32Linked: CompIR_Load(inst); @@ -302,8 +309,14 @@ void IRNativeBackend::CompileIRInst(IRInst inst) { CompIR_VecLoad(inst); break; - case IROp::Store8: case IROp::Store16: + if (Memory::DepthMirrorsActive()) + CompIR_Generic(inst); + else + CompIR_Store(inst); + break; + + case IROp::Store8: case IROp::Store32: CompIR_Store(inst); break; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 11d3059dde..1961892748 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -6,6 +6,7 @@ #include "Common/Data/Convert/SmallDataConvert.h" #include "Common/Log.h" #include "Core/Config.h" +#include "Core/MemMap.h" #include "Core/MIPS/MIPSVFPUUtils.h" #include "Core/MIPS/IR/IRAnalysis.h" #include "Core/MIPS/IR/IRInterpreter.h" @@ -1357,7 +1358,10 @@ bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { for (int i = 0, n = (int)in.GetInstructions().size(); i < n; i++) { IRInst inst = in.GetInstructions()[i]; - if (inst.op == IROp::Load32 || inst.op == IROp::Load16 || inst.op == IROp::Load16Ext) { + // With the depth mirrors, a load's width decides whether it goes through the depth layout + // (Memory::DepthMirrored16), so 16-bit loads keep theirs, and no load becomes one. + const bool depthMirrors = Memory::DepthMirrorsActive(); + if (inst.op == IROp::Load32 || ((inst.op == IROp::Load16 || inst.op == IROp::Load16Ext) && !depthMirrors)) { int dest = IRDestGPR(GetIRMeta(inst)); for (int j = i + 1; j < n; j++) { const IRInstMeta laterInst = GetIRMeta(in.GetInstructions()[j]); @@ -1375,7 +1379,7 @@ bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { if (mask == 0xff) { nextSkip = j; } - } else if ((mask & 0xffff0000) == 0 && inst.op == IROp::Load32) { + } else if ((mask & 0xffff0000) == 0 && inst.op == IROp::Load32 && !depthMirrors) { inst.op = IROp::Load16; if (mask == 0xffff) { nextSkip = j; diff --git a/Core/MIPS/x86/CompLoadStore.cpp b/Core/MIPS/x86/CompLoadStore.cpp index d010cb3777..501f8f641c 100644 --- a/Core/MIPS/x86/CompLoadStore.cpp +++ b/Core/MIPS/x86/CompLoadStore.cpp @@ -302,6 +302,10 @@ namespace MIPSComp { MIPSGPReg rs = _RS; MIPSGPReg rt = _RT; int o = op>>26; + // 16-bit accesses can go through the depth layout (Memory::DepthMirrored16). + if (Memory::DepthMirrorsActive() && (o == 33 || o == 37 || o == 41)) { + DISABLE; + } if (((op >> 29) & 1) == 0 && rt == MIPS_REG_ZERO) { // Don't load anything into $zr return; diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index c925c541e2..2f2c0b40a8 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -82,6 +82,18 @@ static u8 *m_pUncachedVRAM[4]; // Required for HD Remasters to work properly. // This replaces RAM_NORMAL_SIZE at runtime. u32 g_MemorySize; +int g_depthTranslationIndex = DepthTranslationIndex(0x400); + +bool DepthMirrorsActive() { + return PSP_CoreParameter().gpuCore == GPUCORE_SOFTWARE; +} + +u32 DepthMirroredSlow(u32 address) { + if (!DepthMirrorsActive()) + return address; + const DepthLayout &layout = DEPTH_LAYOUTS[(address >> 22) & 1][g_depthTranslationIndex]; + return (address & 0x40000000) | 0x04000000 | layout.Stored(address & 0x001FFFFF); +} // Used to store the PSP model on game startup. u32 g_PSPModel; u32 g_UserPartitionSize; diff --git a/Core/MemMap.h b/Core/MemMap.h index 83fe8ec2a0..9bda44338d 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -29,6 +29,7 @@ #include "Common/CommonTypes.h" #include "Common/Swap.h" #include "Core/Opcode.h" +#include "GPU/Common/DepthSwizzle.h" // PPSSPP is very aggressive about trying to do memory accesses directly, for speed. // This can be a problem when debugging though, as stray memory reads and writes will @@ -192,7 +193,25 @@ inline float ReadUnchecked_Float(const u32 address) { #endif } -inline u16 ReadUnchecked_U16(const u32 address) { +// With the software renderer, which stores depth as the GE does, VRAM's 0x04200000 and 0x04600000 mirrors show it +// through the GE's 16-bit and 32-bit color layouts (GPU/Common/DepthSwizzle.h, ppsspp-re geprobe exp88). Only 16-bit +// accesses go through them: games read and write depth with nothing else. The layouts are fixed but for the EDRAM +// address translation, which the software renderer keeps here as DepthTranslationIndex(). +extern int g_depthTranslationIndex; + +// Whether the depth mirrors apply the layouts: only with the software renderer. Hardware renderers keep depth +// plain and handle the games that need the layout their own way. +bool DepthMirrorsActive(); +u32 DepthMirroredSlow(u32 address); + +inline u32 DepthMirrored16(u32 address) { + if ((address & 0xBFA00000) == 0x04200000) + return DepthMirroredSlow(address); + return address; +} + +inline u16 ReadUnchecked_U16(u32 address) { + address = DepthMirrored16(address); #ifdef MASKED_PSP_MEMORY return *(u16_le *)(base + (address & MEMVIEW32_MASK)); #else @@ -233,6 +252,7 @@ inline void WriteUnchecked_Float(float data, u32 address) { } inline void WriteUnchecked_U16(u16 data, u32 address) { + address = DepthMirrored16(address); #ifdef MASKED_PSP_MEMORY *(u16_le *)(base + (address & MEMVIEW32_MASK)) = data; #else diff --git a/Core/MemMapFunctions.cpp b/Core/MemMapFunctions.cpp index af5419b1e3..13456028ae 100644 --- a/Core/MemMapFunctions.cpp +++ b/Core/MemMapFunctions.cpp @@ -134,7 +134,7 @@ u8 ReadOrException_U8(const u32 address) { u16 ReadOrException_U16(const u32 address) { u16_le value = 0; - ReadMemoryOrException(value, address); + ReadMemoryOrException(value, DepthMirrored16(address)); return (u16)value; } @@ -155,7 +155,7 @@ void WriteOrException_U8(const u8 _Data, const u32 address) { } void WriteOrException_U16(const u16 _Data, const u32 address) { - WriteMemoryOrException(address, _Data); + WriteMemoryOrException(DepthMirrored16(address), _Data); } void WriteOrException_U32(const u32 _Data, const u32 address) { diff --git a/GPU/Common/DepthSwizzle.h b/GPU/Common/DepthSwizzle.h index abaf69a52c..e33fa3b6a3 100644 --- a/GPU/Common/DepthSwizzle.h +++ b/GPU/Common/DepthSwizzle.h @@ -33,13 +33,13 @@ struct DepthLayout { int rotShift = 0; uint32_t xorBits = 0; - uint32_t Stored(uint32_t offset) const { + constexpr uint32_t Stored(uint32_t offset) const { const uint32_t f = offset & rotMask; return ((offset & ~rotMask) | (((f << 1) | (f >> rotShift)) & rotMask)) ^ xorBits; } }; -inline DepthLayout GetDepthLayout(uint32_t translation, bool color32) { +constexpr DepthLayout GetDepthLayout(uint32_t translation, bool color32) { DepthLayout layout; if (translation == 0) { layout.xorBits = 0x600; @@ -60,3 +60,27 @@ inline DepthLayout GetDepthLayout(uint32_t translation, bool color32) { } return layout; } + +// The translations as an index 0 to 4: 0, 0x200, 0x400, 0x800, 0x1000. +constexpr int DepthTranslationIndex(uint32_t translation) { + int index = 0; + while (translation >= 0x200 && index < 4) { + translation >>= 1; + index++; + } + return index; +} + +constexpr uint32_t DepthTranslationFromIndex(int index) { + return index == 0 ? 0 : 0x100 << index; +} + +// Every layout there is, by [color32][translation index]. +inline constexpr DepthLayout DEPTH_LAYOUTS[2][5] = { + { GetDepthLayout(0, false), GetDepthLayout(0x200, false), GetDepthLayout(0x400, false), GetDepthLayout(0x800, false), GetDepthLayout(0x1000, false) }, + { GetDepthLayout(0, true), GetDepthLayout(0x200, true), GetDepthLayout(0x400, true), GetDepthLayout(0x800, true), GetDepthLayout(0x1000, true) }, +}; + +// A couple of exp8-exp10's measurements (TestDepthSwizzle has more). +static_assert(DEPTH_LAYOUTS[0][2].Stored(0x088000) == 0x08a040, "16-bit layout at the default translation"); +static_assert(DEPTH_LAYOUTS[1][0].Stored(0x03171e) == 0x03111e, "32-bit layout at translation 0"); diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index d90794c644..28e97ed811 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -475,6 +475,8 @@ void SoftGPU::DeviceRestore(Draw::DrawContext *draw) { } SoftGPU::~SoftGPU() { + // The firmware's default, until the next software renderer sets it. + Memory::g_depthTranslationIndex = DepthTranslationIndex(0x400); if (fbTex) { fbTex->Release(); fbTex = nullptr; @@ -1154,6 +1156,7 @@ void SoftGPU::UpdateDepthBuffer() { depthbuf.base = gstate.getDepthBufAddress() & 0x001FFFF0; depthbuf.translation = GetAddrTranslation(); depthbuf.layout = GetDepthLayout(depthbuf.translation, gstate.FrameBufFormat() == GE_FORMAT_8888); + Memory::g_depthTranslationIndex = DepthTranslationIndex(depthbuf.translation); } void SoftGPU::DoState(PointerWrap &p) { diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index 84ef4c6374..f7e41bba1c 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -76,20 +76,6 @@ struct DepthBuffer { } }; -// The index PixelFuncID::depthLayout keeps a translation as (0, 0x200, 0x400, 0x800, 0x1000). -inline int DepthTranslationIndex(uint32_t translation) { - int index = 0; - while (translation >= 0x200 && index < 4) { - translation >>= 1; - index++; - } - return index; -} - -inline uint32_t DepthTranslationFromIndex(int index) { - return index == 0 ? 0 : 0x100 << index; -} - enum class SoftDirty : uint64_t { NONE = 0, diff --git a/pspautotests b/pspautotests index 6729c819b0..c662bd80cd 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 6729c819b01c84c6585b6c6e1648e04c30b4711b +Subproject commit c662bd80cdb312df62086ff87e52174ede5bd3f0