From f0f0b11576e03720f0d9ec0f29e1e8e428da319c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Oct 2026 13:05:43 +0200 Subject: [PATCH 1/2] GPU: Reset the flip count when creating a GPU Framebuffer ages are measured in flips, and the count carried over from the previous game. Headless replaying several frame dumps in one process (frametests.py --ios does, 100 per launch) then rendered a dump differently from replaying it alone: Colin McRae Rally lost its car and trees whenever any dump came before it. Co-Authored-By: Claude Opus 5.5 --- GPU/GPUCommon.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index fc9ccc613d..7372fc989d 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -57,7 +57,9 @@ GPUCommon::GPUCommon(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : Reinitialize(); gstate.Reset(); gstate_c.Reset(); - gpuStats.ResetFrame(); + // Including the flip count, which framebuffer ages are measured in. Otherwise a frame dump replayed after + // another in the same process (headless) renders differently from one replayed alone. + gpuStats.Reset(); PPGeSetDrawContext(draw); ResetMatrices(); From 64ae00da91fe0f4cc40a94bfa549e82c24af68e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 17 Oct 2026 13:16:06 +0200 Subject: [PATCH 2/2] Darwin: Keep the view reservation intact while releasing views MemoryMap_Shutdown deallocated each view, opening holes in the reserved range, and then released the whole range. A mapping another thread got in a hole meanwhile was unmapped along with it. On iOS that was Metal's command buffer memory: headless crashed in vkQueueSubmit about a hundred frame dumps into a launch. Map inaccessible pages over a released view instead, so the range stays ours until ReleaseViewSpace. Co-Authored-By: Claude Opus 5.5 --- Common/MemArenaDarwin.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Common/MemArenaDarwin.cpp b/Common/MemArenaDarwin.cpp index ee4683382e..3736780d8a 100644 --- a/Common/MemArenaDarwin.cpp +++ b/Common/MemArenaDarwin.cpp @@ -84,6 +84,17 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) { void MemArena::ReleaseView(s64 offset, void* view, size_t size) { vm_address_t addr = (vm_address_t)view; + if (reserved_size != 0 && addr >= reserved_mem && addr + size <= reserved_mem + reserved_size) { + // Put the reserved pages back instead of unmapping. A hole would let another thread's allocation + // (Metal maps its command buffer memory at any free address) land in it, and ReleaseViewSpace() + // would then unmap that too. + kern_return_t retval = vm_map(mach_task_self(), &addr, size, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, MACH_PORT_NULL, 0, false, + VM_PROT_NONE, VM_PROT_ALL, VM_INHERIT_DEFAULT); + if (retval == KERN_SUCCESS) { + return; + } + WARN_LOG(Log::MemMap, "Couldn't restore the reservation under a view at %p (%d)", view, (int)retval); + } vm_deallocate(mach_task_self(), addr, size); }