mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Merge] (P8): integrate wave 2 package SV - Magma's 3D native mip blit uses one layer, the depth/stencil mip decline gets its tally row, 1D depth mips decline by name, and the wire arm issues glDispatchComputeIndirect natively
# Conflicts: # MobileGL/MG_IntegrationTest/CMakeLists.txt
This commit is contained in:
@@ -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))};
|
||||
|
||||
@@ -4902,6 +4902,80 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
PipeCatalogue.UnmigratedEmulationSitesMatchTheList PROPERTIES LABELS unit TIMEOUT 60)
|
||||
endif()
|
||||
|
||||
# P8-SV: Magma split residuals (docs/Disaggregated/notes/p8/SV.md) - the 3D native mip blit's layer
|
||||
# count, the 1D depth mip decline, the depth/stencil mip decline's tally row (asserted inside
|
||||
# F1WireScenario) and the wire arm's native glDispatchComputeIndirect. Public GL, so the sources join
|
||||
# the binary unguarded and the ambient `DirectGLES.` / `DirectVulkan.` monolith discovery runs them
|
||||
# too (the Espryt entries are the control). Split arms: DirectGLES through the registration macro
|
||||
# (which also replays them on Magma's informational tier), Magma's gated tier by hand:
|
||||
# `.Mip3D.` and `.IndirectDispatch.` - all three transports; the counter halves read only on the
|
||||
# inproc arm (WireIndirectPeek.h, WireDeclinePeek.h), the pixel/grid halves everywhere.
|
||||
# `.ShaderMipDepth1D.` - split + spawn: MGITEST_MAGMA_FORCE_SHADER_MIPMAP is read BY THE SERVER,
|
||||
# so a tcp copy would run the default arm (its tail is in spawn_lane_parity.py's
|
||||
# MAGMA_SERVER_ENV_KNOB_NO_TCP).
|
||||
# `.Mip3DValidation.` - inproc only: the 3D chain under VK_LAYER_KHRONOS_validation, red on any
|
||||
# vkCmdBlitImage VUID in the output. The server's Vulkan instance must be this process for its
|
||||
# stdout to be the entry's and for the case to prove the layer loaded (/proc/self/maps), so it
|
||||
# is in MAGMA_INPROC_ONLY. The lane that runs integration-magma-split in CI installs the layer.
|
||||
target_sources(MobileGLIntegrationTest PRIVATE Harness/WireDeclinePeek.cpp Scenarios/WireMipShapeScenario.cpp
|
||||
Scenarios/WireIndirectDispatchScenario.cpp)
|
||||
if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
mgl_itest_register_split_arms("WireMipShapeScenario.*" "")
|
||||
mgl_itest_register_split_arms("WireIndirectDispatchScenario.*" "")
|
||||
set(MGL_ITEST_MAGMA_SV_FLAVOUR_ENV
|
||||
"MOBILEGL_BACKEND_TYPE=DirectVulkan"
|
||||
"MOBILEGL_IPC_ROLE_SPLIT_STATE=1" "MOBILEGL_IPC_STRICT_ERRORS=1"
|
||||
"MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV})
|
||||
foreach(mglItestArm IN LISTS MGL_ITEST_MAGMA_ARMS)
|
||||
mgl_itest_magma_arm_shape(${mglItestArm})
|
||||
mgl_itest_join_environment(MGL_ITEST_MAGMA_SV_ENVIRONMENT
|
||||
${MGL_ITEST_MAGMA_SV_FLAVOUR_ENV} ${mglItestArmTransportEnv})
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.Mip3D."
|
||||
TEST_FILTER "WireMipShapeScenario.VolumeChainIsGeneratedAlongItsDepth"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-mip3d${mglItestArmLogTag}.log")
|
||||
foreach(dispatchCase
|
||||
CpuWrittenGroupCountsAreReadByTheGpuAtTheirOffsets
|
||||
ComputeWrittenGroupCountsDispatchWithoutAHostWait
|
||||
ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.IndirectDispatch."
|
||||
TEST_FILTER "WireIndirectDispatchScenario.${dispatchCase}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-dispatch${mglItestArmLogTag}-${dispatchCase}.log")
|
||||
endforeach()
|
||||
if (NOT mglItestArm STREQUAL "Tcp")
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.ShaderMipDepth1D."
|
||||
TEST_FILTER "WireMipShapeScenario.Depth1DChainWithoutANativeBlitDeclinesByName"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-depth1d${mglItestArmLogTag}.log")
|
||||
endif()
|
||||
endforeach()
|
||||
mgl_itest_magma_arm_shape(Split)
|
||||
mgl_itest_join_environment(MGL_ITEST_MAGMA_SV_ENVIRONMENT
|
||||
${MGL_ITEST_MAGMA_SV_FLAVOUR_ENV} ${mglItestArmTransportEnv})
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.Split.Mip3DValidation."
|
||||
TEST_FILTER "WireMipShapeScenario.VolumeChainIsGeneratedAlongItsDepth"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
FAIL_REGULAR_EXPRESSION "VUID-vkCmdBlitImage-"
|
||||
ENVIRONMENT "${MGL_ITEST_MAGMA_SV_ENVIRONMENT}\;VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation\;MGITEST_REQUIRE_VK_VALIDATION_LAYER=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-sv-mip3d-validation.log")
|
||||
endif()
|
||||
|
||||
# =====================================================================================
|
||||
# P7 PACKAGE L: THE INFORMATIONAL MAGMA CENSUS - TIER 2 OF TWO
|
||||
# =====================================================================================
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "WireDeclinePeek.h"
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#include <Config.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h>
|
||||
#include <cstring>
|
||||
#define MGITEST_WIRE_DECLINE_PEEK_LIVE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
#if defined(MGITEST_WIRE_DECLINE_PEEK_LIVE)
|
||||
bool PeekWireDeclineCount(const char* row, unsigned long long* out) {
|
||||
if (row == nullptr || out == nullptr) return false;
|
||||
namespace Magma = MobileGL::MG_Backend::DirectVulkan;
|
||||
for (MobileGL::SizeT i = 0; i < Magma::WireDeclineTally::kSiteCount; ++i) {
|
||||
const auto site = static_cast<Magma::WireDeclineSite>(i);
|
||||
if (std::strcmp(Magma::WireDeclineSiteName(site), row) != 0) continue;
|
||||
*out = Magma::WireDeclineTally::Get(site);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
bool PeekWireDeclineCount(const char*, unsigned long long*) { return false; }
|
||||
#endif
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -0,0 +1,20 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.h
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// P8-SV: one row of the Magma wire arm's decline tally (WireDeclines.def, the counts behind the
|
||||
// server's MGWIRE-DECLINES lines), read as a run total out of THIS process - WireIndirectPeek.h's
|
||||
// shape. Process-wide means the SERVER's counts only where the server is this process: the inproc
|
||||
// arm, never spawn or tcp, and never the monolith arm, which takes no wire path. False where the
|
||||
// reading cannot be taken: a pull build, Android, or a name that is not a row.
|
||||
#pragma once
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
bool PeekWireDeclineCount(const char* row, unsigned long long* out);
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -28,6 +28,7 @@ namespace MGITest {
|
||||
out->barriers = Stats::TotalCalls(Stats::CallClass::WireIndirectBarriers);
|
||||
out->hostWaits = Stats::TotalCalls(Stats::CallClass::WireHostWaits);
|
||||
out->indirectWaits = Stats::TotalCalls(Stats::CallClass::WireHostWaitsIndirect);
|
||||
out->nativeDispatches = Stats::TotalCalls(Stats::CallClass::WireIndirectNativeDispatches);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace MGITest {
|
||||
unsigned long long barriers = 0; // wire-indirect-barriers (`wibar`)
|
||||
unsigned long long hostWaits = 0; // wire-host-waits (`whw`)
|
||||
unsigned long long indirectWaits = 0; // wire-host-waits-indirect (`whwi`)
|
||||
unsigned long long nativeDispatches = 0; // P8-SV: wire-indirect-native-dispatches (`wdsp`)
|
||||
};
|
||||
|
||||
bool PeekWireIndirectCounters(WireIndirectCounters* out);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include "../Harness/PipeStatsWindow.h"
|
||||
#include "../Harness/SplitLane.h"
|
||||
#include "../Harness/WireDeclinePeek.h"
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@@ -659,6 +660,12 @@ TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearDepth(0.5);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
// P8-SV: the decline is a WireDeclines.def row now (MipmapDepthStencilAspect), so the server's
|
||||
// MGWIRE-DECLINES dump sees it. Readable where the server is this process: the inproc arm.
|
||||
// Red once (executed, reverted): the site back to a bare MGLOG_E_ONCE - the count stays 0.
|
||||
unsigned long long declinesBefore = 0;
|
||||
const bool counted = PeekSplitRuntime().transportName == "inproc" &&
|
||||
PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesBefore);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.error";
|
||||
// The session is what the retired Fatal used to take with it. A readback is a round trip
|
||||
@@ -669,6 +676,12 @@ TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) {
|
||||
glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &level0);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.readback";
|
||||
EXPECT_NEAR(level0, 0.5f, 0.002f) << "F1.DepthStencilMip.level0";
|
||||
if (counted) {
|
||||
unsigned long long declinesAfter = 0;
|
||||
ASSERT_TRUE(PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesAfter));
|
||||
EXPECT_EQ(declinesAfter - declinesBefore, 1u) << "F1.DepthStencilMip.tally: one named decline per refused "
|
||||
"glGenerateMipmap (WireDeclines.def MipmapDepthStencilAspect)";
|
||||
}
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
glDeleteTextures(1, &depthStencil);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDispatchScenario.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// P8-SV: THE MAGMA WIRE ARM ISSUES glDispatchComputeIndirect FROM THE WIRE STORE, AS THE MONOLITH
|
||||
// ARM DOES (docs/Disaggregated/notes/p8/SV.md).
|
||||
//
|
||||
// Before P8-SV the wire arm read the three group counts through ReadWireBuffer and dispatched them
|
||||
// from the CPU, so a dispatch buffer a shader had written cost a whole-GPU wait per call - the
|
||||
// dispatch twin of what P8-D removed for indirect draws (WireIndirectDrawScenario.cpp). The cases
|
||||
// pin both halves:
|
||||
// * THE GRID, on every arm and both backends - each dispatch marks exactly the work groups its
|
||||
// counts name, and GPU-written counts are the ones the dispatch sees (the buffer starts with
|
||||
// OTHER counts, so a stale read shows as the wrong grid);
|
||||
// * THE SERVER'S OWN COUNTERS (Harness/WireIndirectPeek.h), where they can be read - Magma on the
|
||||
// inproc arm: every call issued as vkCmdDispatchIndirect (`wdsp`), no host wait (`whw`), and the
|
||||
// INDIRECT_COMMAND_READ barrier exactly once per shader write (`wibar`).
|
||||
// The counters are read BEFORE the grids are read back: that readback is a host read of a store a
|
||||
// shader wrote, so it takes a host wait of its own, which is the application's and not the
|
||||
// dispatch's.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include "../Harness/WireIndirectPeek.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
// Grids up to 4 x 4 x 2: one cell per work group.
|
||||
constexpr std::uint32_t kGridX = 4, kGridY = 4, kGridZ = 2;
|
||||
constexpr std::uint32_t kCells = kGridX * kGridY * kGridZ;
|
||||
|
||||
// One invocation per work group, and each group marks its own cell.
|
||||
constexpr const char* kMarkSource = R"(#version 430 core
|
||||
layout(local_size_x = 1) in;
|
||||
layout(std430, binding = 2) buffer Marks { uint marks[]; };
|
||||
void main() {
|
||||
uvec3 g = gl_WorkGroupID;
|
||||
atomicAdd(marks[(g.z * 4u + g.y) * 4u + g.x], 1u);
|
||||
}
|
||||
)";
|
||||
// Copies `source` into `words`, one word per invocation: the compute write under test.
|
||||
constexpr const char* kCopySource = R"(#version 430 core
|
||||
layout(local_size_x = 1) in;
|
||||
layout(std430, binding = 0) buffer Words { uint words[]; };
|
||||
layout(std430, binding = 1) readonly buffer Source { uint source[]; };
|
||||
void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; }
|
||||
)";
|
||||
|
||||
using Grid = std::array<std::uint32_t, 3>;
|
||||
|
||||
class WireIndirectDispatchScenario : public ScenarioTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ScenarioTest::SetUp();
|
||||
if (!Ready()) return;
|
||||
m_magma = Gl().BackendName() == "DirectVulkan";
|
||||
m_transport = PeekSplitRuntime().transportName;
|
||||
m_mark = CompileCompute(kMarkSource);
|
||||
ASSERT_NE(m_mark, 0u);
|
||||
m_copy = CompileCompute(kCopySource);
|
||||
ASSERT_NE(m_copy, 0u);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "scene setup left a GL error behind";
|
||||
// The counters are armed by the first peek, so it has to come before any dispatch.
|
||||
if (CountersReadable()) {
|
||||
ASSERT_TRUE(PeekWireIndirectCounters(&m_before))
|
||||
<< "Magma on the inproc arm must be able to read the wire arm's own counters";
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (!Ready()) return;
|
||||
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, 0);
|
||||
for (GLuint& buffer : m_marks) {
|
||||
if (buffer != 0) glDeleteBuffers(1, &buffer);
|
||||
}
|
||||
for (GLuint* buffer : {&m_groupCounts, &m_source}) {
|
||||
if (*buffer != 0) glDeleteBuffers(1, buffer);
|
||||
*buffer = 0;
|
||||
}
|
||||
if (m_mark != 0) glDeleteProgram(m_mark);
|
||||
if (m_copy != 0) glDeleteProgram(m_copy);
|
||||
}
|
||||
|
||||
static GLuint CompileCompute(const char* source) {
|
||||
const GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
GLint ok = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||||
if (ok != GL_TRUE) {
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
const GLuint program = glCreateProgram();
|
||||
glAttachShader(program, shader);
|
||||
glLinkProgram(program);
|
||||
glDeleteShader(shader);
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &ok);
|
||||
if (ok != GL_TRUE) {
|
||||
glDeleteProgram(program);
|
||||
return 0;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
static void Fill(GLuint& name, GLenum target, const std::vector<std::uint32_t>& words) {
|
||||
if (name == 0) glGenBuffers(1, &name);
|
||||
glBindBuffer(target, name);
|
||||
glBufferData(target, static_cast<GLsizeiptr>(words.size() * sizeof(std::uint32_t)), words.data(),
|
||||
GL_DYNAMIC_COPY);
|
||||
}
|
||||
|
||||
// The dispatch-indirect store, holding CPU-written counts.
|
||||
void Parameters(const std::vector<std::uint32_t>& words) {
|
||||
Fill(m_groupCounts, GL_DISPATCH_INDIRECT_BUFFER, words);
|
||||
}
|
||||
|
||||
// The compute write: the parameter store (already holding its poison) receives `words`.
|
||||
void ComputeWriteParameters(const std::vector<std::uint32_t>& words) {
|
||||
Fill(m_source, GL_SHADER_STORAGE_BUFFER, words);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_groupCounts);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_source);
|
||||
glUseProgram(m_copy);
|
||||
glDispatchCompute(static_cast<GLuint>(words.size()), 1, 1);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0);
|
||||
}
|
||||
|
||||
// One indirect dispatch of the mark program into a fresh, zeroed cell buffer. Returns
|
||||
// the index of that buffer for ExpectGrid.
|
||||
size_t DispatchIndirect(GLintptr offset) {
|
||||
GLuint marks = 0;
|
||||
Fill(marks, GL_SHADER_STORAGE_BUFFER, std::vector<std::uint32_t>(kCells, 0u));
|
||||
m_marks.push_back(marks);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, marks);
|
||||
glUseProgram(m_mark);
|
||||
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, m_groupCounts);
|
||||
glDispatchComputeIndirect(offset);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0);
|
||||
return m_marks.size() - 1;
|
||||
}
|
||||
|
||||
// A round trip that reads no buffer store, so the server has applied every dispatch
|
||||
// above before the counters are read - and no host wait of its own is added.
|
||||
static void Settle() {
|
||||
BindDefaultFramebuffer();
|
||||
(void)ReadPixels(1, 1);
|
||||
}
|
||||
|
||||
void ExpectGrid(size_t which, const Grid& grid, const std::string& what) {
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
std::vector<std::uint32_t> cells(kCells, 0xDEADu);
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_marks[which]);
|
||||
glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, static_cast<GLsizeiptr>(kCells * sizeof(std::uint32_t)),
|
||||
cells.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << what << ": readback";
|
||||
size_t wrong = 0;
|
||||
std::string first;
|
||||
for (std::uint32_t z = 0; z < kGridZ; ++z) {
|
||||
for (std::uint32_t y = 0; y < kGridY; ++y) {
|
||||
for (std::uint32_t x = 0; x < kGridX; ++x) {
|
||||
const bool inside = x < grid[0] && y < grid[1] && z < grid[2];
|
||||
const std::uint32_t got = cells[(z * kGridY + y) * kGridX + x];
|
||||
if (got != (inside ? 1u : 0u) && wrong++ == 0) {
|
||||
first = "group (" + std::to_string(x) + "," + std::to_string(y) + "," +
|
||||
std::to_string(z) + ") = " + std::to_string(got);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(wrong, 0u) << what << ": expected exactly the " << grid[0] << "x" << grid[1] << "x"
|
||||
<< grid[2] << " grid marked once; first wrong " << first;
|
||||
}
|
||||
|
||||
// Magma's server in THIS process: the inproc arm. Spawn and tcp servers are other
|
||||
// processes, and the monolith arm never takes the wire path at all.
|
||||
bool CountersReadable() const { return m_magma && m_transport == "inproc"; }
|
||||
|
||||
WireIndirectCounters Delta() const {
|
||||
WireIndirectCounters now{};
|
||||
EXPECT_TRUE(PeekWireIndirectCounters(&now));
|
||||
WireIndirectCounters delta{};
|
||||
delta.nativeDraws = now.nativeDraws - m_before.nativeDraws;
|
||||
delta.cpuExpansions = now.cpuExpansions - m_before.cpuExpansions;
|
||||
delta.barriers = now.barriers - m_before.barriers;
|
||||
delta.hostWaits = now.hostWaits - m_before.hostWaits;
|
||||
delta.indirectWaits = now.indirectWaits - m_before.indirectWaits;
|
||||
delta.nativeDispatches = now.nativeDispatches - m_before.nativeDispatches;
|
||||
return delta;
|
||||
}
|
||||
|
||||
bool m_magma = false;
|
||||
std::string m_transport;
|
||||
WireIndirectCounters m_before{};
|
||||
GLuint m_mark = 0, m_copy = 0, m_groupCounts = 0, m_source = 0;
|
||||
std::vector<GLuint> m_marks;
|
||||
};
|
||||
|
||||
// CPU-written counts at two non-zero offsets of one store. On Magma/inproc both calls must
|
||||
// be issued natively, with no host wait and no barrier (no shader wrote the store).
|
||||
TEST_F(WireIndirectDispatchScenario, CpuWrittenGroupCountsAreReadByTheGpuAtTheirOffsets) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
Parameters({1, 1, 1, 3, 2, 1, 4, 1, 2});
|
||||
const size_t first = DispatchIndirect(12);
|
||||
const size_t second = DispatchIndirect(24);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
Settle();
|
||||
if (CountersReadable()) {
|
||||
const WireIndirectCounters delta = Delta();
|
||||
EXPECT_EQ(delta.nativeDispatches, 2u) << "every indirect dispatch must be vkCmdDispatchIndirect";
|
||||
EXPECT_EQ(delta.hostWaits, 0u) << "a host wait was taken with no GPU write to the counts";
|
||||
EXPECT_EQ(delta.barriers, 0u) << "no shader wrote this store, so no barrier is due";
|
||||
}
|
||||
ExpectGrid(first, {3, 2, 1}, "glDispatchComputeIndirect at byte 12");
|
||||
ExpectGrid(second, {4, 1, 2}, "glDispatchComputeIndirect at byte 24");
|
||||
}
|
||||
|
||||
// Counts a compute shader wrote, behind the application's own GL_COMMAND_BARRIER_BIT. The
|
||||
// store starts with OTHER counts, so a stale read dispatches the wrong grid. On
|
||||
// Magma/inproc: issued natively and - the point of the change - NO host wait.
|
||||
TEST_F(WireIndirectDispatchScenario, ComputeWrittenGroupCountsDispatchWithoutAHostWait) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
Parameters({1, 1, 1});
|
||||
ComputeWriteParameters({3, 2, 1});
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
|
||||
const size_t first = DispatchIndirect(0);
|
||||
|
||||
Parameters({1, 1, 1, 1, 1, 1});
|
||||
ComputeWriteParameters({1, 1, 1, 2, 2, 2});
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT);
|
||||
const size_t second = DispatchIndirect(12);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
Settle();
|
||||
if (CountersReadable()) {
|
||||
const WireIndirectCounters delta = Delta();
|
||||
EXPECT_EQ(delta.nativeDispatches, 2u) << "both dispatches must be issued natively";
|
||||
EXPECT_EQ(delta.hostWaits, 0u)
|
||||
<< "an indirect dispatch of shader-written counts waited for the GPU on the host";
|
||||
EXPECT_EQ(delta.barriers, 2u) << "one INDIRECT_COMMAND_READ barrier per freshly written store";
|
||||
}
|
||||
ExpectGrid(first, {3, 2, 1}, "compute-written counts at byte 0");
|
||||
ExpectGrid(second, {2, 2, 2}, "compute-written counts at byte 12");
|
||||
}
|
||||
|
||||
// Flywheel's shape, one call over: the counts are written and dispatched with no
|
||||
// glMemoryBarrier between (GL leaves it undefined). The wire arm orders it anyway, once per
|
||||
// write, as P8-D does for draws. Magma's split arms only: the monolith arm records no
|
||||
// dependency here (P8-D recorded that half for dev), and Espryt's is its driver's.
|
||||
TEST_F(WireIndirectDispatchScenario, ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
if (!m_magma || m_transport == "monolith") {
|
||||
GTEST_SKIP() << "the implicit INDIRECT_COMMAND_READ barrier is the Magma wire arm's (P8-D, P8-SV)";
|
||||
}
|
||||
Parameters({1, 1, 1});
|
||||
ComputeWriteParameters({3, 2, 1});
|
||||
const size_t first = DispatchIndirect(0);
|
||||
const size_t again = DispatchIndirect(0);
|
||||
ComputeWriteParameters({1, 2, 2});
|
||||
const size_t third = DispatchIndirect(0);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||
Settle();
|
||||
if (CountersReadable()) {
|
||||
const WireIndirectCounters delta = Delta();
|
||||
EXPECT_EQ(delta.nativeDispatches, 3u);
|
||||
EXPECT_EQ(delta.hostWaits, 0u) << "a host wait was taken";
|
||||
EXPECT_EQ(delta.barriers, 2u) << "exactly one barrier per write: the second dispatch reads no new write";
|
||||
}
|
||||
ExpectGrid(first, {3, 2, 1}, "the first dispatch after the write");
|
||||
ExpectGrid(again, {3, 2, 1}, "a second dispatch, no write between");
|
||||
ExpectGrid(third, {1, 2, 2}, "the dispatch after a second write");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace MGITest
|
||||
@@ -0,0 +1,220 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/WireMipShapeScenario.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// P8-SV: TWO glGenerateMipmap SHAPES THE MAGMA WIRE ARM GOT WRONG (docs/Disaggregated/notes/p8/SV.md).
|
||||
//
|
||||
// * A 3D chain through the NATIVE blit. The wire arm passed the level's depth as the blit's
|
||||
// subresource layerCount on an image with one array layer (VUID-vkCmdBlitImage-srcImage-00240,
|
||||
// -srcSubresource-01707, -dstSubresource-01708). Lavapipe blits a 3D image by its z offsets and
|
||||
// ignores layerCount, so the PIXELS below are right with or without the fix on this host; what
|
||||
// goes red on the host is the validation layer, which the `.Mip3DValidation.` entry runs this
|
||||
// case under (inproc, so the server's Vulkan instance is this process and its stdout is ours).
|
||||
// The pixel half runs on both backends and every arm, and is what a driver that honours
|
||||
// layerCount would fail.
|
||||
// * A 1D DEPTH chain with no native blit. The depth shader arm renders 2D views, which a 1D image
|
||||
// does not admit; the wire arm handed it the chain anyway. It is now the named decline
|
||||
// MipmapShaderFormatOrShape, decided before the chain grows, and the session carries on.
|
||||
// Magma only, and reachable on this host only with MGITEST_MAGMA_FORCE_SHADER_MIPMAP (lavapipe
|
||||
// blits depth natively), which the SERVER reads: split and spawn entries only.
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
#include "../Harness/SplitLane.h"
|
||||
#include "../Harness/SplitRuntimePeek.h"
|
||||
#include "../Harness/WireDeclinePeek.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
#undef GLAPI
|
||||
#endif
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
using Texel = std::array<int, 4>;
|
||||
|
||||
// Is the Khronos validation layer loaded into THIS process? On the inproc arm the server's
|
||||
// Vulkan instance lives here, so this is the proof that a run "under the layer" really was.
|
||||
bool ValidationLayerMapped() {
|
||||
#if defined(__linux__)
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
std::string line;
|
||||
while (std::getline(maps, line)) {
|
||||
if (line.find("VkLayer_khronos_validation") != std::string::npos) return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
class WireMipShapeScenario : public ScenarioTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ScenarioTest::SetUp();
|
||||
if (!Ready()) return;
|
||||
m_magma = Gl().BackendName() == "DirectVulkan";
|
||||
m_transport = PeekSplitRuntime().transportName;
|
||||
}
|
||||
|
||||
// The Magma server's decline tally is readable where the server is this process.
|
||||
bool DeclinesReadable() const { return m_magma && m_transport == "inproc"; }
|
||||
|
||||
bool m_magma = false;
|
||||
std::string m_transport;
|
||||
};
|
||||
|
||||
// A 3D RGBA8 chain, 8x8x8 with its full storage, whose level-0 slices come in PAIRS of one
|
||||
// colour: level 1's slice k is then pair k's colour exactly, and every level below is the
|
||||
// average of two slices of the level above. A blit that loses the z axis (one slice only,
|
||||
// or slices read as array layers of a 2D image) cannot produce those numbers.
|
||||
TEST_F(WireMipShapeScenario, VolumeChainIsGeneratedAlongItsDepth) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
constexpr int kSide = 8, kLevels = 4;
|
||||
const std::array<Texel, 4> pairs = {Texel{200, 40, 80, 255}, Texel{40, 200, 120, 255},
|
||||
Texel{100, 100, 240, 255}, Texel{240, 160, 0, 255}};
|
||||
GLuint volume = 0;
|
||||
glGenTextures(1, &volume);
|
||||
glBindTexture(GL_TEXTURE_3D, volume);
|
||||
glTexStorage3D(GL_TEXTURE_3D, kLevels, GL_RGBA8, kSide, kSide, kSide);
|
||||
std::vector<GLubyte> level0(static_cast<size_t>(kSide * kSide * kSide * 4));
|
||||
for (int z = 0; z < kSide; ++z) {
|
||||
const Texel& texel = pairs[static_cast<size_t>(z / 2)];
|
||||
for (int at = 0; at < kSide * kSide; ++at) {
|
||||
for (int c = 0; c < 4; ++c)
|
||||
level0[static_cast<size_t>((z * kSide * kSide + at) * 4 + c)] = static_cast<GLubyte>(texel[c]);
|
||||
}
|
||||
}
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kSide, kSide, kSide, GL_RGBA, GL_UNSIGNED_BYTE, level0.data());
|
||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "3D texture setup";
|
||||
glGenerateMipmap(GL_TEXTURE_3D);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "glGenerateMipmap(GL_TEXTURE_3D)";
|
||||
|
||||
const auto average = [](const Texel& a, const Texel& b) {
|
||||
return Texel{(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2, (a[3] + b[3]) / 2};
|
||||
};
|
||||
// expected[level][slice]
|
||||
std::vector<std::vector<Texel>> expected(kLevels);
|
||||
expected[1].assign(pairs.begin(), pairs.end());
|
||||
expected[2] = {average(pairs[0], pairs[1]), average(pairs[2], pairs[3])};
|
||||
expected[3] = {average(expected[2][0], expected[2][1])};
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
for (int level = 1; level < kLevels; ++level) {
|
||||
const int side = kSide >> level;
|
||||
GLint width = 0, height = 0, depth = 0;
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_3D, level, GL_TEXTURE_WIDTH, &width);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_3D, level, GL_TEXTURE_HEIGHT, &height);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_3D, level, GL_TEXTURE_DEPTH, &depth);
|
||||
EXPECT_EQ(width, side) << "level " << level;
|
||||
EXPECT_EQ(height, side) << "level " << level;
|
||||
EXPECT_EQ(depth, side) << "level " << level;
|
||||
std::vector<GLubyte> pixels(static_cast<size_t>(side * side * side * 4), 0x5a);
|
||||
glGetTexImage(GL_TEXTURE_3D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "readback of level " << level;
|
||||
for (int z = 0; z < side; ++z) {
|
||||
const Texel& want = expected[static_cast<size_t>(level)][static_cast<size_t>(z)];
|
||||
size_t wrong = 0;
|
||||
Texel first{};
|
||||
for (int at = 0; at < side * side; ++at) {
|
||||
const size_t base = static_cast<size_t>((z * side * side + at) * 4);
|
||||
bool ok = true;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
const int got = pixels[base + static_cast<size_t>(c)];
|
||||
if (got < want[c] - 2 || got > want[c] + 2) ok = false;
|
||||
}
|
||||
if (!ok && wrong++ == 0) {
|
||||
for (int c = 0; c < 4; ++c) first[c] = pixels[base + static_cast<size_t>(c)];
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(wrong, 0u) << "level " << level << " slice " << z << ": expected (" << want[0] << ","
|
||||
<< want[1] << "," << want[2] << "," << want[3] << "), first wrong texel ("
|
||||
<< first[0] << "," << first[1] << "," << first[2] << "," << first[3] << ")";
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_3D, 0);
|
||||
glDeleteTextures(1, &volume);
|
||||
|
||||
// The `.Mip3DValidation.` entry: the blit above ran under the Khronos validation layer,
|
||||
// whose VUID lines the entry's FAIL_REGULAR_EXPRESSION reads. Without the layer actually
|
||||
// loaded, a clean output would prove nothing, so the entry requires it.
|
||||
if (SplitLane::MarkerIsOne("MGITEST_REQUIRE_VK_VALIDATION_LAYER")) {
|
||||
ASSERT_TRUE(m_magma && m_transport == "inproc")
|
||||
<< "the validation entry reads the server's layer output in-process: Magma, inproc";
|
||||
EXPECT_TRUE(ValidationLayerMapped())
|
||||
<< "VK_LAYER_KHRONOS_validation is not loaded in this process, so the absence of "
|
||||
"VUID lines in this entry's output says nothing";
|
||||
}
|
||||
}
|
||||
|
||||
// A 1D DEPTH chain with the native blit forced off: no mip arm renders a 1D image, so the
|
||||
// wire arm declines by name (MipmapShaderFormatOrShape) before growing the chain. Pinned:
|
||||
// no GL error, the session answering (two readback round trips), level 0 as uploaded, level
|
||||
// 1 still the application's own sentinel - and, where the server is this process, exactly
|
||||
// one MipmapShaderFormatOrShape decline counted.
|
||||
TEST_F(WireMipShapeScenario, Depth1DChainWithoutANativeBlitDeclinesByName) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
if (!m_magma)
|
||||
GTEST_SKIP() << "Magma wire-arm decline (P8-SV): the knob and the depth shader arm it forces are "
|
||||
"Magma's; Espryt (" << Gl().BackendName() << ") generates this chain itself";
|
||||
const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP");
|
||||
if (tier.empty())
|
||||
GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits depth natively, "
|
||||
"so the depth shader arm this case refuses is not entered";
|
||||
constexpr int kWidth = 16;
|
||||
GLuint line = 0;
|
||||
glGenTextures(1, &line);
|
||||
glBindTexture(GL_TEXTURE_1D, line);
|
||||
glTexStorage1D(GL_TEXTURE_1D, 5, GL_DEPTH_COMPONENT32F, kWidth);
|
||||
const std::vector<GLfloat> level0(kWidth, 0.25f), level1(kWidth / 2, 0.75f);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, kWidth, GL_DEPTH_COMPONENT, GL_FLOAT, level0.data());
|
||||
glTexSubImage1D(GL_TEXTURE_1D, 1, 0, kWidth / 2, GL_DEPTH_COMPONENT, GL_FLOAT, level1.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "1D depth texture setup";
|
||||
unsigned long long declinesBefore = 0;
|
||||
const bool counted =
|
||||
DeclinesReadable() && PeekWireDeclineCount("MipmapShaderFormatOrShape", &declinesBefore);
|
||||
glGenerateMipmap(GL_TEXTURE_1D);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "glGenerateMipmap(GL_TEXTURE_1D) on a depth chain";
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
for (const int level : {0, 1}) {
|
||||
const int width = kWidth >> level;
|
||||
std::vector<GLfloat> depths(static_cast<size_t>(width), -1.0f);
|
||||
glGetTexImage(GL_TEXTURE_1D, level, GL_DEPTH_COMPONENT, GL_FLOAT, depths.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "readback of level " << level;
|
||||
const GLfloat want = level == 0 ? 0.25f : 0.75f;
|
||||
size_t wrong = 0;
|
||||
GLfloat first = 0.0f;
|
||||
for (const GLfloat depth : depths) {
|
||||
if (depth < want - 0.0001f || depth > want + 0.0001f) {
|
||||
if (wrong++ == 0) first = depth;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(wrong, 0u) << "level " << level << " tier=" << tier << ": a declined mip must leave "
|
||||
<< "every level as the application wrote it (expected " << want
|
||||
<< ", first wrong " << first << ")";
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_1D, 0);
|
||||
glDeleteTextures(1, &line);
|
||||
if (counted) {
|
||||
unsigned long long declinesAfter = 0;
|
||||
ASSERT_TRUE(PeekWireDeclineCount("MipmapShaderFormatOrShape", &declinesAfter));
|
||||
EXPECT_EQ(declinesAfter - declinesBefore, 1u)
|
||||
<< "the 1D depth chain must be refused once, by name, before the chain grows";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace MGITest
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# P8-SV:Magma split 残项
|
||||
|
||||
> 2026-09-30,分支 `p8/sv`(基线 `cbbaf77f`,第一波集成头)。计划:[`PLAN-P8.md`](PLAN-P8.md) 第二波 SV;来源:[`F.md`](F.md)「发现但未修」三条、[`D.md`](D.md) §7 第一条。主机 lavapipe,只动 Magma(Espryt 作对照)。
|
||||
|
||||
## 1. 结论
|
||||
|
||||
| 项 | 改动 | 位置 | 之前 | 之后 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 3D 原生 mip blit 的子资源 `layerCount` 改为 1,深度只走 z 偏移(逐级减半,原本就对) | `WireFramebuffer.inc:1626-1636` | `layerCount = Depth >> level`(`VkTextureManager.cpp:2738-2740` 的切片寻址)当层数,图像 `arrayLayers = 1`;验证层报 `VUID-vkCmdBlitImage-srcImage-00240`、`-srcSubresource-01707`、`-dstSubresource-01708` | 与 monolith 同形(`VulkanRenderer.cpp:12303`、`:12403`);验证层 0 条 |
|
||||
| 2 | 深度模板 mip 拒绝补 `WireDeclineTally` 行 | `WireDeclines.def:124`;站点 `WireFramebuffer.inc:1401` | 只有 `MGLOG_E_ONCE`,`MGWIRE-DECLINES` 看不到 | `MGL_WIRE_DECLINE_AT(MipmapDepthStencilAspect, …)`,日志原文保留 |
|
||||
| 3 | 深度着色器 mip 拒 1D / 1D 数组 / 3D | 生长前判定 `WireFramebuffer.inc:1426`、`:1437`;pass 自身 `WireDepthMipmap.inc:67-74`;调用方传 view 类型 `WireFramebuffer.inc:1491` | 只拒 `is3D`,调用方从不设置;1D 深度链进 pass,对 1D 图像建 2D view(`WireDepthMipmap.inc:244`) | 生长前具名拒绝 `MipmapShaderFormatOrShape`(`WireFramebuffer.inc:1465`);pass 的拒绝是漂移保护(`:1508-1513`) |
|
||||
| 4 | wire 臂 `glDispatchComputeIndirect` 原生化 | `DispatchWireComputeIndirect`(`WireDraw.inc:789`),入口 `VulkanRenderer.cpp:7790-7797`,`DirectVulkan.cpp:938-945` | `ReadWireBuffer` 在 CPU 读 3 个字,shader 写过则整 GPU 等待 | `vkCmdDispatchIndirect`(`WireDraw.inc:844`);不再有 CPU 路径 |
|
||||
|
||||
## 2. 设计
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 1 | `ResolveWireTextureStorage` 不动:Tex3D 的 `layers` 是附件、描述符、回读共用的切片寻址;只在 blit 处按 `resource->viewType == 3D` 改用 `{层 0, 1 层}` |
|
||||
| 2 | 行追加在 `WireDeclines.def` 末尾(只追加);宏同时计数与 `_ONCE` 日志 |
|
||||
| 3 | 两层:生长前 `depthArmTakesShape` 加 `planar`(与颜色臂同一谓词,决定在 `GrowWireTextureMipChain` 之前,`WireFramebuffer.inc:1469`);pass 按 `WireImage::viewType` 再拒一次。monolith 调用方(`VulkanRenderer.cpp:4866`、`:4905`)不设 viewType,行为不变 |
|
||||
| 4 | 形状同 monolith(`VulkanRenderer.cpp:7862`),store 取自 `VerbDispatchIndirectBuffer`(`AcquireWireSlice` 打 `lastUseSerial`) |
|
||||
| 4 屏障 | P8-D 规则:store 被标 shader 写过(`VkBufferManager.cpp:603`)则首个 indirect 读前录一次 `ALL_COMMANDS/MEMORY_WRITE → DRAW_INDIRECT/INDIRECT_COMMAND_READ`(`WireDraw.inc:808`);在绑定本次 dispatch 的 SSBO **之前** 取标,否则本次 dispatch 若把参数 store 也当输出,会吃掉下一个读者的标 |
|
||||
| 4 拒绝 | store 缺失 `DispatchIndirectBufferUnbound`、越界 / 未 4 对齐 / 负偏移 `DispatchIndirectRange`(`WireDeclines.def:127-128`);程序、纹理、管线、绑定四处拒绝复用 `DispatchWireCompute` 的行 |
|
||||
| 4 计数 | 新 `wdsp`(`PipeStats.h:289`、`PipeStats.cpp:706`),屏障计入 D 的 `wibar`,等待仍是 `whw`;`WireIndirectCounters::nativeDispatches` 追加在结构末尾 |
|
||||
| G1 | 全部在 `MOBILEGL_BUILD_DISAGGREGATED` / `MOBILEGL_PIPE_PUSH` 下 |
|
||||
|
||||
## 3. 门
|
||||
|
||||
| 用例 | 断言 | 登记 |
|
||||
|---|---|---|
|
||||
| `WireMipShapeScenario.VolumeChainIsGeneratedAlongItsDepth`(`WireMipShapeScenario.cpp:82`) | 8³ RGBA8,层 0 按切片对着色;1..3 级每片等于上一级两片均值;`GL_TEXTURE_DEPTH` 逐级 | 两后端 monolith + 各 split 臂;Magma 门控 `.Mip3D.` 三臂 |
|
||||
| 同上,`.Mip3DValidation.` | `VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation`,输出含 `VUID-vkCmdBlitImage-` 即红(`FAIL_REGULAR_EXPRESSION`);用例从 `/proc/self/maps` 证实层已加载(`WireMipShapeScenario.cpp:51`、`:156`) | 只 inproc(`MobileGL/MG_IntegrationTest/CMakeLists.txt:4931-4937`),`spawn_lane_parity.py:67-72` 的 `MAGMA_INPROC_ONLY`;CI 跑 `integration-magma-split` 的作业装了验证层(`.github/workflows/test.yml:1700`、`:1986`) |
|
||||
| `WireMipShapeScenario.Depth1DChainWithoutANativeBlitDeclinesByName`(`WireMipShapeScenario.cpp:167`) | 1D `DEPTH_COMPONENT32F`,`MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1`:无 GL 错误、两次回读、层 1 仍是哨兵;inproc 上 `MipmapShaderFormatOrShape` +1 | Magma `.ShaderMipDepth1D.` split + spawn(server 读旋钮),`spawn_lane_parity.py:150` |
|
||||
| `F1WireScenario.GenerateMipmapDepthStencilDeclinesAndKeepsTheSession` | 原像素断言 + inproc 上 `MipmapDepthStencilAspect` +1(`F1WireScenario.cpp:663-683`,经 `WireDeclinePeek.cpp:23`) | 原登记不变 |
|
||||
| `WireIndirectDispatchScenario` 三例(`WireIndirectDispatchScenario.cpp:219`、`:239`、`:267`) | 网格逐 work group 标记;inproc Magma:`wdsp` = 调用数、`whw` = 0、`wibar` = 写次数(读数在结果回读之前取) | 两后端各臂;Magma 门控 `.IndirectDispatch.` 三臂;第三例(无应用屏障)只 Magma split 臂 |
|
||||
|
||||
## 4. red-once(全部执行并还原:拷贝比对 + touch)
|
||||
|
||||
| # | 变异 | 载体 | 观察到的红 |
|
||||
|---|---|---|---|
|
||||
| R1 | blit 改回 `{baseLayer, layerCount}` | `.Mip3D.` 三臂、`.Mip3DValidation.`、两后端 monolith / 各臂 | `.Mip3DValidation.` 红:`Error regular expression found`,三条 VUID 各 6 次;像素用例全绿(lavapipe 3D blit 按 z 偏移、不看 layerCount) |
|
||||
| R2 | 站点改回裸 `MGLOG_E_ONCE` | `F1WireScenario.GenerateMipmapDepthStencilDeclines*` 各臂 | inproc 两例红(`.Split.Fm.`、`.Split.Full.`):计数差 0 ≠ 1;spawn / tcp 绿 |
|
||||
| R2b | 同上,静态 | `wire_declines_audit.py` | rc 1:`row MipmapDepthStencilAspect has NO SITE` |
|
||||
| R3 | 生长前 `planar` 与 pass 的 view 类型拒绝都去掉 | `.ShaderMipDepth1D.` split、spawn | 两例红:层 1 被写成 0.25(哨兵 0.75),无拒绝计数;pass 在 1D 图像上建 2D view 仍产出像素 |
|
||||
| R3b | 只去生长前判定 | 同上 | 绿:pass 拒绝兜住(`the depth pass refused level 1`,计数 1),漂移保护有效 |
|
||||
| R4 | wire 分支恢复 `ReadWireBuffer` + CPU dispatch | `WireIndirectDispatchScenario` 各臂 | inproc 9 例红(3 例 × `.Split.IndirectDispatch.` / `.Split.` / `.Split.Full.`):`wdsp` 0、GPU 写过的 `whw` 2、`wibar` 0;spawn / tcp / monolith 绿(CPU 路径网格正确) |
|
||||
| R5 | 去屏障(`if (false && …)`) | 同上 | inproc 6 例红(两个 shader 写用例 × 3 登记):`wibar` 0 ≠ 2;网格在 lavapipe 上不红(按序执行),spawn / tcp 绿 |
|
||||
| R6 | `wdsp=` 改成 `wds=` | `PipeStatsTest.WireIndirectCountersPrintInTheirOwnBracket` | 红 |
|
||||
| R7a | 删 `.ShaderMipDepth1D.` 例外行 | `spawn_lane_parity.py build-split` | rc 1:tcp 臂缺该例 |
|
||||
| R7b | 删 `.Mip3DValidation.` 例外行 | 同上 | rc 1:spawn、tcp 两臂缺该例 |
|
||||
|
||||
验证层后验:`.Split.IndirectDispatch.` 三例、`.Split.ShaderMipDepth1D.`、`.Split.Mip3D.`、`.Split.Fm.` 深度模板例在层下全绿,VUID 0 条。
|
||||
|
||||
## 5. 整套门(`~/w7/notes/p8/gate.sh`,树外)
|
||||
|
||||
| 项 | 读数 | 相对第一波集成头(ID-P8-12) |
|
||||
|---|---|---|
|
||||
| G1 | 符号增 0 减 0,`.text` `a5ba13` | 不变 |
|
||||
| census / ratchet / parity / 修订 | 78 / 20 / 45;172;0;5 | 不变 |
|
||||
| wire-declines | 61 行、66 站点、0 未记日志 | +3 行(`MipmapDepthStencilAspect`、`DispatchIndirect{BufferUnbound,Range}`),+7 站点(新派发函数另复用 4 个 `Compute*` 行) |
|
||||
| split_coverage | Espryt monolith 706 = 门控 617 + 豁免 89;Magma 706:门控 227、只在信息层 447、豁免 32;missing 0 | +5 例(2 mip + 3 派发),无新豁免行 |
|
||||
| 车道 | unit 2721;split / spawn / tcp 740 / 654 / 666;Magma 277 / 253 / 220 / 673;gpu monolith / inproc 3709 / 3709,全部 100 % | +5 / +5 / +5;Magma +6 / +5 / +4 / +5;gpu +25 |
|
||||
| retrace(`2edb2aa8`,只改测试的提交之前) | 230 / 236,只挂已知 6 例 iterationrp | 同第一波 |
|
||||
|
||||
## 6. monolith 观察(记给 dev / P13,本包不修)
|
||||
|
||||
| 观察 | 位置 | 证据 |
|
||||
|---|---|---|
|
||||
| split 构建的 monolith 臂深度着色器 mip 同样不传 view 类型:设备无深度 BLIT_DST 时,1D 深度链会对 1D 图像建 2D view;Release 下 `MOBILEGL_ASSERT` 是空的 | `VulkanRenderer.cpp:4905`、`:12321` | 推断:lavapipe 原生 blit 深度,主机红不出;monolith 不读 `MGITEST_MAGMA_FORCE_SHADER_MIPMAP` |
|
||||
|
||||
## 7. 发现但未修
|
||||
|
||||
- `mipmap-shader-format-or-shape` 的明细对深度形状也打印 `missing=COLOR_ATTACHMENT`(1D 深度的真实原因是 `view=0`);是 F 的 `MagmaWireShapeDetail` 口径,未改。
|
||||
- create 系列 fixture 没有 `glDispatchComputeIndirect`([`D.md`](D.md) §7),item 4 的真实负载收益未测。
|
||||
- `gen_pipe_dirty_surface.py` 的「根外写者」扫描读整个 `MobileGL/`(`gen_pipe_dirty_surface.py:394`),集成测试源也在内:派发用例的 fixture 成员曾叫 `m_parameters`,让 NEW_PATCH_STATE 的 shutter 看起来在根外被写,自检否定对照 16(`gen_pipe_dirty_surface.py:1770-1778`)不再触发、门红。本包把成员改名 `m_groupCounts`;扫描口径未改,任何新测试 fixture 用到 GLContext 的成员名都会再撞上。
|
||||
@@ -64,7 +64,12 @@ FIXTURE_ENTRIES = {"TcpServer.Start", "TcpServer.Stop"}
|
||||
# answers unavailable, so a copy there checks pixels under a name claiming
|
||||
# the pools. The pixels do run on every arm: the Espryt entries and the
|
||||
# Magma informational and full-suite tiers.
|
||||
MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.", "DescriptorPoolGrowthScenario.")
|
||||
# `.Mip3DValidation.` (P8-SV, a TAIL): WireMipShapeScenario's 3D chain under the Khronos validation
|
||||
# layer, red on a vkCmdBlitImage VUID in the entry's own output and proving the
|
||||
# layer loaded from /proc/self/maps - both need the server's Vulkan instance
|
||||
# in this process. The knob-free `.Mip3D.` entries keep all three arms.
|
||||
MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.", "DescriptorPoolGrowthScenario.",
|
||||
".Mip3DValidation.")
|
||||
|
||||
# P7 wave 2-B2: THE SANCTIONED ASYMMETRY THAT IS NOT INPROC-ONLY - these exist on split AND
|
||||
# spawn and cannot exist on tcp. Every MGITEST_MAGMA_FORCE_* knob is read BY THE SERVER
|
||||
@@ -140,7 +145,9 @@ MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.",
|
||||
f".PrimGenReroute.PrimitivesGeneratedNoXfbScenario.{case}" for case in PRIMGEN_REROUTE_CASES) + tuple(
|
||||
f".SplitRecording64.OversizedRecordingSplitScenario.{case}" for case in SPLIT_RECORDING_CASES) + (
|
||||
# P8-F: the mip shader-shape decline, forced by MGITEST_MAGMA_FORCE_SHADER_MIPMAP (server-read).
|
||||
".ShaderMipDecline.",)
|
||||
".ShaderMipDecline.",
|
||||
# P8-SV: the 1D depth chain's decline, forced by the same server-read knob.
|
||||
".ShaderMipDepth1D.",)
|
||||
|
||||
|
||||
# THE MIRROR SHAPE - entries that exist on tcp ALONE, by construction. Each tail must match at least
|
||||
|
||||
Reference in New Issue
Block a user