[Fix] (Magma, P8-SV): the wire arm's 3D native mip blit uses one layer with the depth in its z offsets, the depth/stencil mip decline gets its WireDeclineTally row (MipmapDepthStencilAspect), the depth shader mip refuses 1D/1D-array/3D view types before the chain grows, and glDispatchComputeIndirect is issued as vkCmdDispatchIndirect from the wire store (P8-D's barrier rule, wdsp counter, two named declines)

This commit is contained in:
2026-09-30 03:44:03 -04:00
parent cbbaf77f6d
commit fc415aee76
10 changed files with 161 additions and 23 deletions
@@ -937,12 +937,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void DispatchComputeIndirect(GLintptr indirect) {
#if MOBILEGL_BUILD_DISAGGREGATED
// P8-SV: THE GPU READS THE GROUP COUNTS on the wire arm too - VulkanRenderer's
// DispatchComputeIndirect takes its transport branch into DispatchWireComputeIndirect
// (WireDraw.inc), which issues vkCmdDispatchIndirect from the verb's store. The CPU read
// that was here (ReadWireBuffer, a whole-GPU wait once a shader had written the store) is gone.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
Uint32 groups[3]{};
if (indirect >= 0 && pVulkanRenderer->GetWireBufferManager().ReadWireBuffer(
MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer,
static_cast<Uint64>(indirect), sizeof(groups), groups))
pVulkanRenderer->DispatchCompute(groups[0], groups[1], groups[2]);
pVulkanRenderer->DispatchComputeIndirect(indirect);
return;
}
#endif
@@ -7789,6 +7789,15 @@ void main() {
void VulkanRenderer::DispatchComputeIndirect(GLintptr indirect) {
SplitOversizedRecording();
#if MOBILEGL_BUILD_DISAGGREGATED
// P8-SV: the wire arm's own indirect dispatch, DispatchCompute's shape one call over.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
RewindWireDescriptorSetsIfDue();
m_textureManager->CollectGarbage();
DispatchWireComputeIndirect(indirect);
return;
}
#endif
m_textureManager->CollectGarbage();
auto& frame = m_frameContext.GetCurrent();
// See DispatchCompute: the dispatch accessor, not the draw one.
@@ -693,6 +693,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// a transport (still the `buffer-legacy-arm` role violation).
Bool m_wireNativeIndirectDraw = false;
void DispatchWireCompute(GLuint x, GLuint y, GLuint z);
// P8-SV: glDispatchComputeIndirect on the wire arm - vkCmdDispatchIndirect from the
// dispatch-indirect store, as the monolith arm issues it (WireDraw.inc).
void DispatchWireComputeIndirect(GLintptr offset);
void RewindWireDescriptorSetsIfDue();
UniquePtr<RenderPassEntry> m_wireDrawPass;
Vector<VkImageView> m_wireDrawViews;
@@ -58,7 +58,7 @@ MGL_WIRE_DECLINE(CurrentAttributePayload)
MGL_WIRE_DECLINE(CurrentAttributeUpload)
MGL_WIRE_DECLINE(BeginRenderPass)
// ---- DispatchWireCompute ----------------------------------------------------------------
// ---- DispatchWireCompute (and DispatchWireComputeIndirect, P8-SV) -------------------------
MGL_WIRE_DECLINE(ComputeProgramLinkOrSpirv)
MGL_WIRE_DECLINE(ComputeTextureResourcesUnprepared)
MGL_WIRE_DECLINE(ComputePipelineUnavailable)
@@ -116,3 +116,13 @@ MGL_WIRE_DECLINE(IndirectBufferUnbound)
MGL_WIRE_DECLINE(IndirectCommandRange)
MGL_WIRE_DECLINE(IndirectCountRange)
MGL_WIRE_DECLINE(IndirectIndexStore)
// ---- P8-SV -------------------------------------------------------------------------------
// VulkanRenderer::GenerateWireMipmap: a combined depth/stencil (or stencil-only) chain, which no
// arm generates (CONTRACT-P7 §3.2). It logged but was never counted, so MGWIRE-DECLINES could
// not see it. Counted once per refused glGenerateMipmap, whose chain is left as it was.
MGL_WIRE_DECLINE(MipmapDepthStencilAspect)
// VulkanRenderer::DispatchWireComputeIndirect, in statement order: the dispatch-indirect store
// is missing, or the three group-count words lie outside it or are misaligned.
MGL_WIRE_DECLINE(DispatchIndirectBufferUnbound)
MGL_WIRE_DECLINE(DispatchIndirectRange)
@@ -64,6 +64,14 @@
if (source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT)
return false;
if (source.is3D || destination.is3D) return false;
// P8-SV: THE PASS MAKES 2D VIEWS (makeView below), which a 1D, 1D-array or 3D image does
// not admit. A caller that fills viewType from the image gets a refusal here, and the
// wire arm's GenerateWireMipmap asks the same question before it grows the chain.
for (const VkImageViewType viewType : {source.viewType, destination.viewType}) {
if (viewType == VK_IMAGE_VIEW_TYPE_1D || viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY ||
viewType == VK_IMAGE_VIEW_TYPE_3D)
return false;
}
if (source.format != destination.format) return false;
// DEPTH ONLY. A combined depth/stencil image can be sampled for its depth and
// rendered as a depth attachment, but this pass writes gl_FragDepth and NOTHING
@@ -770,4 +770,80 @@
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdDispatch(frame.commandBuffer, x, y, z);
}
// ---- P8-SV: THE WIRE ARM'S INDIRECT DISPATCH READS ITS GROUP COUNTS ON THE GPU ----------
//
// The monolith arm's shape (VulkanRenderer.cpp's DispatchComputeIndirect) with the wire
// store's own VkBuffer in place of the frontend buffer's resident slice. Before this the wire
// arm read the three words through ReadWireBuffer and dispatched them from the CPU, and a store
// a shader had written made that read a whole-GPU wait (WaitForWireBufferHostAccess) per call.
// There is no shape the device cannot issue, so there is no CPU path left: a record whose
// store or words are unusable is declined by name.
//
// The barrier is P8-D's draw rule (DrawWireIndirectNative): a store marked shader-written gets
// one MEMORY_WRITE -> INDIRECT_COMMAND_READ barrier before its next indirect read, whether or
// not the application issued GL_COMMAND_BARRIER_BIT; DRAW_INDIRECT is the stage that consumes
// VkDispatchIndirectCommand too. It is TAKEN before the bindings below, because binding this
// dispatch's own storage buffers marks them written, and a store that is both this dispatch's
// parameters and one of its outputs must keep that mark for the next reader.
void VulkanRenderer::DispatchWireComputeIndirect(GLintptr offset) {
RetireWireDrawPass();
const auto& state = MG_Pipe::MGPipeApplier();
constexpr Uint64 kCommandSize = 3 * sizeof(Uint32); // GL 4.6 10.3.10 / VkDispatchIndirectCommand
BufferSlice parameters{};
if (MG_Pipe::MGPipeHandleIsNull(state.VerbDispatchIndirectBuffer) ||
!m_bufferManager.AcquireWireSlice(BufferKind::Indirect, state.VerbDispatchIndirectBuffer, parameters)) {
MGL_WIRE_DECLINE_AT(DispatchIndirectBufferUnbound, "an indirect dispatch names no live dispatch-indirect store");
return;
}
// GL refuses an offset that is negative or not a multiple of four (INVALID_VALUE) and a
// command past the end of the buffer (INVALID_OPERATION); a peer's record that breaks
// either is declined, not issued.
if (offset < 0 || (static_cast<Uint64>(offset) & 3u) != 0 || static_cast<Uint64>(offset) > parameters.size ||
parameters.size - static_cast<Uint64>(offset) < kCommandSize) {
MGL_WIRE_DECLINE_AT(DispatchIndirectRange, "the dispatch group counts lie outside their store or are misaligned");
return;
}
auto& frame = m_frameContext.GetCurrent();
if (m_bufferManager.TakeWireIndirectReadBarrier(state.VerbDispatchIndirectBuffer)) {
if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording();
if (VkRenderPassManager::GetActiveRenderPass() != nullptr)
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr);
if (MG_Util::PipeStats::Enabled())
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::WireIndirectBarriers, 1);
}
const auto program = WireProgramSource(state.DispatchProgram);
if (!program.GetLinkStatus() || !program.GetSpirvStatus()) {
MGL_WIRE_DECLINE_AT(ComputeProgramLinkOrSpirv, "the compute program is not linked or carries no SPIR-V");
return;
}
const auto& programObj = m_programFactory->GetOrCreateProgram(program, {});
if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) {
MGL_WIRE_DECLINE_AT(ComputeTextureResourcesUnprepared, "a sampled/storage image could not be made ready for the dispatch");
return;
}
const auto pipeline = GetOrCreateComputePipeline(programObj);
if (!pipeline) {
MGL_WIRE_DECLINE_AT(ComputePipelineUnavailable, "no compute pipeline could be created for this dispatch");
return;
}
// PrepareWireTextureResources may have flushed and begun a new recording.
auto& dispatchFrame = m_frameContext.GetCurrent();
if (!dispatchFrame.isCommandRecording) m_frameContext.BeginCommandRecording();
if (!m_uniformManager->BindProgramUniformBuffers(dispatchFrame.commandBuffer, program, programObj,
m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE)) {
MGL_WIRE_DECLINE_AT(ComputeUniformBufferBinding, "the dispatch's uniform/storage bindings could not be bound");
return;
}
vkCmdBindPipeline(dispatchFrame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdDispatchIndirect(dispatchFrame.commandBuffer, parameters.buffer,
parameters.offset + static_cast<VkDeviceSize>(offset));
if (MG_Util::PipeStats::Enabled())
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::WireIndirectNativeDispatches, 1);
}
#endif
@@ -1394,12 +1394,16 @@
// release build both arms ship as. Nothing in GL promises a stencil mip either - the
// pass writes gl_FragDepth and would leave the stencil aspect of the level it produced
// undefined, which is worse than leaving the whole level alone and saying so.
//
// P8-SV: a NAMED decline (WireDeclines.def MipmapDepthStencilAspect), so MGWIRE-DECLINES
// counts it like every other refusal on this arm; the line keeps its words.
if (resource->aspect != VK_IMAGE_ASPECT_COLOR_BIT && resource->aspect != VK_IMAGE_ASPECT_DEPTH_BIT) {
MGLOG_E_ONCE("DirectVulkan::GenerateWireMipmap: aspect 0x%x (a combined depth/stencil or "
"stencil-only texture, format %d) has no mip generation on either arm; "
"declining and leaving levels %u..%u as they are",
static_cast<Uint32>(resource->aspect), static_cast<Int>(resource->format),
base + 1, base + count - 1);
MGL_WIRE_DECLINE_AT(MipmapDepthStencilAspect,
"DirectVulkan::GenerateWireMipmap: aspect 0x%x (a combined depth/stencil or "
"stencil-only texture, format %d) has no mip generation on either arm; "
"declining and leaving levels %u..%u as they are",
static_cast<Uint32>(resource->aspect), static_cast<Int>(resource->format),
base + 1, base + count - 1);
return;
}
// P8-F: WHICH ARM TAKES THIS CHAIN IS DECIDED BEFORE THE CHAIN IS GROWN, for the
@@ -1417,18 +1421,23 @@
VK_FILTER_LINEAR : VK_FILTER_NEAREST;
const Bool layersInRange = baseLayer < resource->arrayLayers &&
layerCount <= resource->arrayLayers - baseLayer;
// The depth arm's shape: the layer window, and what WireDepthMipmap.inc's pass refuses
// for an image this function hands it - a format that does not both sample and attach
// as depth (its other refusals, 3D / multisample / a default image, are never built here).
const auto depthPassFeatures =
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
const Bool depthArmTakesShape =
layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures;
// The colour arm's shape (the two shader shapes are explained at the colour arm below).
const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT;
// Both shader arms render a 2D view of one layer of one level, so a 1D, 1D-array or 3D
// image is a shape neither takes.
const Bool planar = resource->viewType != VK_IMAGE_VIEW_TYPE_1D &&
resource->viewType != VK_IMAGE_VIEW_TYPE_1D_ARRAY &&
resource->viewType != VK_IMAGE_VIEW_TYPE_3D;
// The depth arm's shape: the layer window, and what WireDepthMipmap.inc's pass refuses
// for an image this function hands it (the view type is passed to it below) - a 1D,
// 1D-array or 3D view type, and a format that does not both sample and attach as depth.
// Its other refusals, multisample and a default image, are never built here. P8-SV: the
// view type half is new; before it a 1D depth chain reached the pass, which made a 2D
// view of the 1D image.
const auto depthPassFeatures =
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
const Bool depthArmTakesShape = planar &&
layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures;
// The colour arm's shape (the two shader shapes are explained at the colour arm below).
const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT;
// The shader arm samples through a sampler2D and writes a vec4, which is what
// BlitWireColorImage's own float check enforces; naming it here rather than letting the
// blit return false is what puts the reason in the decline line.
@@ -1477,6 +1486,10 @@
source.image = resource->image;
source.format = format;
source.aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
// P8-SV: the image's own view type, so the pass can refuse a shape it cannot
// render (it makes 2D views) instead of receiving the struct's 2D default.
source.viewType = resource->viewType;
source.is3D = resource->viewType == VK_IMAGE_VIEW_TYPE_3D;
source.layout = resource->layout;
source.trackedLayout = &resource->layout;
source.levels = resource->mipLevels;
@@ -1603,14 +1616,24 @@
if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access,
VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition");
// P8-SV: A 3D IMAGE HAS ONE ARRAY LAYER, AND ITS DEPTH IS THE Z EXTENT OF THE BLIT.
// ResolveWireTextureStorage answers `layerCount` in slices for Tex3D (Depth >> level,
// VkTextureManager.cpp) - the addressing attachments, descriptors and readback use - and
// passing that on as the blit's subresource layerCount names layers the image (created
// with arrayLayers = 1) does not have (VUID-vkCmdBlitImage-srcImage-00240,
// -srcSubresource-01707, -dstSubresource-01708). The monolith arm blits layer 0, count 1,
// with the z offsets below halving the depth level by level; so does this one now.
const Bool volume = resource->viewType == VK_IMAGE_VIEW_TYPE_3D;
const Uint32 blitBaseLayer = volume ? 0u : baseLayer;
const Uint32 blitLayerCount = volume ? 1u : layerCount;
for (Uint32 level=base+1;level<base+count;++level) {
VkImageLayout previous = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1);
VkImageBlit blit{};
blit.srcSubresource = {resource->aspect,level-1,baseLayer,layerCount};
blit.dstSubresource = {resource->aspect,level,baseLayer,layerCount};
blit.srcSubresource = {resource->aspect,level-1,blitBaseLayer,blitLayerCount};
blit.dstSubresource = {resource->aspect,level,blitBaseLayer,blitLayerCount};
blit.srcOffsets[1] = {static_cast<Int32>(std::max(resource->extent.width>>(level-1),1u)),
static_cast<Int32>(std::max(resource->extent.height>>(level-1),1u)),
static_cast<Int32>(std::max(resource->depth>>(level-1),1u))};
+3 -1
View File
@@ -583,8 +583,10 @@ namespace {
PS::AddCalls(PS::CallClass::WireIndirectNativeDraws, 3);
PS::AddCalls(PS::CallClass::WireIndirectCpuExpansions, 2);
PS::AddCalls(PS::CallClass::WireHostWaitMicros, 1500);
PS::AddCalls(PS::CallClass::WireIndirectNativeDispatches, 4);
const String line = PS::FormatWindowLine();
EXPECT_NE(line.find("windr[wind=3 wixp=2 wibar=0 whw=0 whwi=0 whwus=1500]"), String::npos) << line;
EXPECT_NE(line.find("windr[wind=3 wixp=2 wibar=0 whw=0 whwi=0 whwus=1500 wdsp=4]"), String::npos) << line;
EXPECT_STREQ(PS::NameOf(PS::CallClass::WireIndirectNativeDispatches), "wire-indirect-native-dispatches");
EXPECT_STREQ(PS::NameOf(PS::CallClass::WireIndirectNativeDraws), "wire-indirect-native-draws");
EXPECT_STREQ(PS::NameOf(PS::CallClass::WireIndirectCpuExpansions), "wire-indirect-cpu-expansions");
EXPECT_STREQ(PS::NameOf(PS::CallClass::WireIndirectBarriers), "wire-indirect-barriers");
+3
View File
@@ -240,6 +240,7 @@ namespace MobileGL::MG_Util::PipeStats {
"residual-pulls", "server-verb-boundaries", "wire-records",
"wire-indirect-native-draws", "wire-indirect-cpu-expansions", "wire-indirect-barriers",
"wire-host-waits", "wire-host-waits-indirect", "wire-host-wait-us",
"wire-indirect-native-dispatches",
#endif
};
const char* const kGateNames[kGateCount] = {
@@ -701,6 +702,8 @@ namespace MobileGL::MG_Util::PipeStats {
line += " whw=" + std::to_string(calls[static_cast<Uint32>(CallClass::WireHostWaits)]);
line += " whwi=" + std::to_string(calls[static_cast<Uint32>(CallClass::WireHostWaitsIndirect)]);
line += " whwus=" + std::to_string(calls[static_cast<Uint32>(CallClass::WireHostWaitMicros)]);
// P8-SV: the indirect dispatches, on the same bracket (their barriers are in `wibar`).
line += " wdsp=" + std::to_string(calls[static_cast<Uint32>(CallClass::WireIndirectNativeDispatches)]);
#endif
line += "] gates[";
for (Uint32 i = 0; i < kGateCount; ++i) {
+4
View File
@@ -283,6 +283,10 @@ namespace MobileGL::MG_Util::PipeStats {
WireHostWaits,
WireHostWaitsIndirect,
WireHostWaitMicros,
// P8-SV: `wdsp` - glDispatchComputeIndirect calls issued as vkCmdDispatchIndirect from the
// wire store (VulkanRenderer::DispatchWireComputeIndirect). Their barriers count in `wibar`
// with the draws': one barrier per shader write, whichever indirect read consumes it.
WireIndirectNativeDispatches,
#endif
Count
};