From e550d1033965ba77c177467accd80dc9cda9fad3 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 20 Sep 2026 20:53:57 -0400 Subject: [PATCH] [MG_Backend] (Magma): an indirect draw's client-memory arrays are staged from the commands The commands live in GPU memory, so the draw is issued natively and the vertex range was left unknown (vertexCount = 0, instanceCount = 1). That is exactly right for a buffer-backed array and wrong for a client-memory one: its bytes must be in the store before the GPU reads the command, and the range the upload is bounded by came out as one element - so the quad the command named was never staged and the draw painted nothing. The words are now resolved on the CPU for exactly that case, with the shadow reconciled first (they may be shader-written), and the draw stays native. --- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index b96cd78e..80295d96 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -13085,10 +13085,49 @@ void main() { } // The command parameters live on the GPU, so the vertex range is unknown here; - // resident vertex buffers are uploaded in full regardless. + // resident vertex buffers are uploaded in full regardless. A CLIENT-MEMORY ARRAY is the + // one exception: its bytes have to be staged BEFORE the GPU reads the command, so for + // exactly that case the words are resolved on the CPU and this range is what bounds the + // staging. The draw itself stays native - the GPU reads the same commands. DrawCmdParam vertexRange{}; vertexRange.vertexCount = 0; vertexRange.instanceCount = 1; + { + const auto& currentVAO = MGB_CTX->GetBoundVertexArray(); + Bool clientArray = false; + if (currentVAO) { + for (const auto& attribute : currentVAO->GetAllAttributes()) { + if (attribute.Enabled && !attribute.Buffer) { + clientArray = true; + break; + } + } + } + if (clientArray) { + // The commands may be shader-written, and a stale shadow would size the upload + // off words no shader ever wrote. + drawBuffer->SyncGpuWrites(); + const Uint8* commandHostBytes = drawBuffer->MappedData(); + if (commandHostBytes == nullptr) { + MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: a client-memory vertex array needs " + "the commands on the CPU and the indirect buffer has no readable shadow"); + return; + } + Uint64 lastElement = 0; + for (GLsizei idraw = 0; idraw < drawcount; ++idraw) { + VkDrawIndirectCommand command{}; + Memcpy(&command, + commandHostBytes + commandOffset + static_cast(idraw) * static_cast(stride), + sizeof(command)); + lastElement = std::max(lastElement, + static_cast(command.firstVertex) + command.vertexCount); + vertexRange.firstInstance = std::max(vertexRange.firstInstance, command.firstInstance); + vertexRange.instanceCount = std::max(vertexRange.instanceCount, command.instanceCount); + } + vertexRange.firstVertex = 0; + vertexRange.vertexCount = static_cast(lastElement); + } + } if (!SetupDraw(frame, mode, DrawSetupAspect::IndirectDrawBuffer, vertexRange)) { return;