From e8e9ac5870adf09628e5724132c702e4c178d8d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Nov 2026 11:18:04 +0100 Subject: [PATCH 1/2] Vulkan: Split frames with more than 1000 render passes into several submits A Brave Story frame dump has 7347 render passes in one frame. On an Adreno 506 (Redmi Note 4, kernel driver kgsl) submitting that fails in the kernel with ENOMEM: adreno_ringbuffer_submitcmd needs a physically contiguous order-7 allocation for it. The driver doesn't report the error, the frame fence never signals, and the emulator hangs in vkWaitForFences. FlushSync once a frame reaches 1000 steps. Ordinary frames are far below that. Co-Authored-By: Claude Opus 5.5 --- Common/GPU/Vulkan/VulkanRenderManager.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 81b5860801..f44a77daa5 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -27,6 +27,9 @@ using namespace PPSSPP_VK; +// See BindFramebufferAsRenderTarget. +static const size_t MAX_STEPS_PER_SUBMIT = 1000; + // renderPass is an example of the "compatibility class" or RenderPassType type. bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType, VkSampleCountFlagBits sampleCount, double scheduleTime, int countToCompile) { // Good torture test to test the shutdown-while-precompiling-shaders issue on PC where it's normally @@ -1356,6 +1359,12 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR } } + // Adreno's kernel driver allocates a physically contiguous block per submit, sized by the render passes in it. + // With thousands of passes (Brave Story) that fails with ENOMEM and the fence never signals, so split the frame. + if (steps_.size() >= MAX_STEPS_PER_SUBMIT) { + FlushSync(); + } + VKRStep *step = new VKRStep{ VKRStepType::RENDER }; step->render.framebuffer = fb; step->render.swapchain = fb ? nullptr : swapchain; From 983defba6d387cdacbdc4fe20f79ebe452a76076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 12 Nov 2026 11:34:30 +0100 Subject: [PATCH 2/2] Frame dump replay: Don't read command data through struct pointers The data in a dump is only 2-byte aligned at times. Reading two fields of a struct there can compile to LDRD on 32-bit ARM, which faults: replaying 11583 Weird shader bug Z-fighting_4 died with SIGBUS (BUS_ADRALN) in DumpExecute::Display on a Galaxy A2 Core. Copy the data out with memcpy. Co-Authored-By: Claude Opus 5.5 --- GPU/Debugger/Playback.cpp | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp index 70444ef323..c17050fe0e 100644 --- a/GPU/Debugger/Playback.cpp +++ b/GPU/Debugger/Playback.cpp @@ -106,6 +106,15 @@ static bool g_opDone = true; static bool g_cancelled = false; static int g_drawLimit = 0; +// Command data in a dump is only 2-byte aligned at times. Reading it through a struct pointer can then compile +// to LDRD/LDM on 32-bit ARM, which fault on that (SIGBUS in 11583 Weird shader bug Z-fighting_4). +template +static T ReadPushbuf(const std::vector &pushbuf, u32 ptr) { + T value; + memcpy(&value, pushbuf.data() + ptr, sizeof(T)); + return value; +} + void SetReplayDrawLimit(int lastPrim) { g_drawLimit = lastPrim; } @@ -632,7 +641,9 @@ void DumpExecute::Init(u32 ptr, u32 sz) { // Dumps from before savedContextVersion 1 hold PPSSPP's old context layout, with the matrices as raw // floats; the new one ends its commands with an END. Restoring an old one as new turned its matrices // into garbage and the scene disappeared (4140). The PSP replayer tells them apart the same way. - const u32_le *context = (const u32_le *)(pushbuf_.data() + ptr); + std::vector contextBuf(512); + memcpy(contextBuf.data(), pushbuf_.data() + ptr, std::min(sz, (u32)(contextBuf.size() * sizeof(u32_le)))); + const u32_le *context = contextBuf.data(); bool oldLayout = true; for (int i = 17; i < 512; ++i) { if (context[i] == GE_CMD_END << 24) { @@ -849,9 +860,9 @@ void DumpExecute::ClutAddr(u32 ptr, u32 sz) { u32 addr; u32 flags; }; - const ClutAddrData *data = (const ClutAddrData *)(pushbuf_.data() + ptr); - execClutAddr = data->addr; - execClutFlags = data->flags; + const ClutAddrData data = ReadPushbuf(pushbuf_, ptr); + execClutAddr = data.addr; + execClutFlags = data.flags; } void DumpExecute::Clut(u32 ptr, u32 sz) { @@ -941,7 +952,8 @@ void DumpExecute::Memset(u32 ptr, u32 sz) { u32 sz; }; - const MemsetCommand *data = (const MemsetCommand *)(pushbuf_.data() + ptr); + const MemsetCommand cmd = ReadPushbuf(pushbuf_, ptr); + const MemsetCommand *data = &cmd; const u32 size = Memory::ClampValidSizeAt(data->dest, data->sz); if (Memory::IsVRAMAddress(data->dest) && size != 0) { @@ -956,7 +968,7 @@ void DumpExecute::Memset(u32 ptr, u32 sz) { } void DumpExecute::MemcpyDest(u32 ptr, u32 sz) { - execMemcpyDest = *(const u32 *)(pushbuf_.data() + ptr); + execMemcpyDest = ReadPushbuf(pushbuf_, ptr); } void DumpExecute::Memcpy(u32 ptr, u32 sz) { @@ -996,7 +1008,8 @@ void DumpExecute::Framebuf(int level, u32 ptr, u32 sz, bool needed) { u32 pad; }; - FramebufData *framebuf = (FramebufData *)(pushbuf_.data() + ptr); + const FramebufData header = ReadPushbuf(pushbuf_, ptr); + const FramebufData *framebuf = &header; if (lastTex_[level] != framebuf->addr || lastBufw_[level] != framebuf->bufw) { u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; @@ -1042,7 +1055,8 @@ void DumpExecute::MarkNeededTargets() { if (cmd.type < CommandType::FRAMEBUF0 || cmd.type > CommandType::FRAMEBUF7 || cmd.sz < 16) { continue; } - const u32 *header = (const u32 *)(pushbuf_.data() + cmd.ptr); + u32 header[3]; + memcpy(header, pushbuf_.data() + cmd.ptr, sizeof(header)); if (!Memory::IsVRAMAddress(header[0])) { continue; } @@ -1122,7 +1136,8 @@ void DumpExecute::Display(u32 ptr, u32 sz, bool allowFlip) { int linesize, pixelFormat; }; - DisplayBufData *disp = (DisplayBufData *)(pushbuf_.data() + ptr); + const DisplayBufData data = ReadPushbuf(pushbuf_, ptr); + const DisplayBufData *disp = &data; // Sync up drawing. SyncStall();