[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.
This commit is contained in:
2026-09-20 20:53:57 -04:00
parent cf7366276e
commit e550d10339
@@ -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<SizeT>(idraw) * static_cast<SizeT>(stride),
sizeof(command));
lastElement = std::max<Uint64>(lastElement,
static_cast<Uint64>(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<Uint32>(lastElement);
}
}
if (!SetupDraw(frame, mode, DrawSetupAspect::IndirectDrawBuffer, vertexRange)) {
return;