diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 1cc58a8c..0c25b5e1 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -522,31 +522,10 @@ namespace MobileGL::MG_Backend::DirectGLES { // TODO: deletion for deleted objects namespace BufferImpl { - // P5e (vi), CONTRACT-P5E §5.1 + §5.8 (ruling 1 / ID-81): THE ARM SELECTOR for this - // family, and it is a conjunction on purpose. - // - // Transport != Monolith the record arm exists because there is no frontend VAO on - // this side of a real split. Under Transport=monolith the - // push build keeps its frontend arms token for token - that - // is what the verify comparator compares against, and what - // makes MOBILEGL_IPC_RUN_AHEAD=0 a pure wait-rule A/B on - // identical server code rather than an arm swap. - // the family bit `0x0ff` (bit 7 on, bit 8 off) is a supported A/B and must - // keep running the legacy vertex-input walk; the bit is - // already the gate the rest of this family reads. - // - // It is NOT gated on run-ahead. The records carry the whole family either way, so a - // lockstep split session reads them too and the wait rule changes nothing here - which - // is the only reason ra can flip one constant at the end of the phase and change no - // backend code at all. - inline Bool VertexInputReadsRecords() { -#if MOBILEGL_BUILD_DISAGGREGATED - return MG_Config::Transport != MG_Config::TransportMode::Monolith && - VertexInputSubsystemEnabled(); -#else - return false; -#endif - } + // P5e (mv): THE ARM SELECTOR of this family - VertexInputReadsRecords() - moved to + // Managers.h, unchanged. MultiDraw.cpp is a second translation unit on the same draw + // path and has to select the same arm; a copy of the conjunction there would be two + // selectors for one family. Its rationale travelled with it. #if MOBILEGL_BUILD_DISAGGREGATED // P5e (vi): "does this draw fetch any attribute out of the application's own memory", diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 8105d415..60ce40db 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -1094,6 +1094,38 @@ namespace MobileGL::MG_Backend::DirectGLES { Uint CurrentBufferContextGeneration(); #endif + // P5e (vi), CONTRACT-P5E §5.1 + §5.8 (ruling 1 / ID-81): THE ARM SELECTOR for this + // family, and it is a conjunction on purpose. + // + // Transport != Monolith the record arm exists because there is no frontend VAO on + // this side of a real split. Under Transport=monolith the + // push build keeps its frontend arms token for token - that + // is what the verify comparator compares against, and what + // makes MOBILEGL_IPC_RUN_AHEAD=0 a pure wait-rule A/B on + // identical server code rather than an arm swap. + // the family bit `0x0ff` (bit 7 on, bit 8 off) is a supported A/B and must + // keep running the legacy vertex-input walk; the bit is + // already the gate the rest of this family reads. + // + // It is NOT gated on run-ahead. The records carry the whole family either way, so a + // lockstep split session reads them too and the wait rule changes nothing here - which + // is the only reason ra can flip one constant at the end of the phase and change no + // backend code at all. + // + // P5e (mv): it lives in the HEADER rather than in DirectGLES.cpp because the multi-draw + // path is a second translation unit that has to select the SAME arm - and the addendum's + // rule is that a site states the transport test it relies on, which a copy of the + // conjunction in MultiDraw.cpp would satisfy in letter while giving the family two + // selectors that can drift apart. One definition, spelled at every site that reads it. + inline Bool VertexInputReadsRecords() { +#if MOBILEGL_BUILD_DISAGGREGATED + return MG_Config::Transport != MG_Config::TransportMode::Monolith && + VertexInputSubsystemEnabled(); +#else + return false; +#endif + } + // Registered as the frontend's BufferBackendOps at backend init and on // every MakeCurrent (the ES context can be destroyed and recreated, e.g. // by the trace replayer's probe context). diff --git a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp index 7c993c75..7229ec79 100644 --- a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp +++ b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -91,11 +92,151 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { return resource ? resource->id : 0; } - const SharedPtr& BoundIndexBuffer() { - static const SharedPtr none; + // --------------------------------------------------------------------------- + // The bound index buffer, and WHICH SIDE ANSWERS FOR IT + // + // P5e (mv), CONTRACT-P5E §5.1 + §5.8 (ruling ID-81): this file asks the bound element + // array buffer exactly three questions - "is one bound at all", "what GL name did + // PrepareForDraw leave on GL_ELEMENT_ARRAY_BUFFER (and how big is its store)", and + // "give me its bytes on the CPU" - and until now it asked all three of the FRONTEND + // VAO, on the apply thread, once per indexed multi-draw. That was the last unguarded + // `MGB_CTX->GetBoundVertexArray()` in the backend: package vi retired the ordinary + // draw path's copy (DirectGLES.cpp's SyncCurrentVertexAttributeValues / PrepareForDraw) + // and this one survived only because these batches used to die earlier, on the + // framebuffer row fb has since retired. + // + // THE ARM IS STATED, NEVER INFERRED. BufferImpl::VertexInputReadsRecords() is the + // family's own selector and reduces to `Transport != Monolith && the vertex-input bit`; + // it is the same conjunction RefuseElementArrayBufferFromTheFrontend guards the + // single-draw path with. Nothing below decides an arm by noticing that a handle or a + // pointer happens to be null - conflating "a handle was noted" with "the record arm is + // selected" is what ID-107 cost this phase 137 scenarios. + // --------------------------------------------------------------------------- + + // How much of the index buffer a caller needs. DELIBERATELY not nested levels: each + // value is exactly the work its original call site did, in its original order, so the + // monolith arm makes the same calls in the same sequence it made before this package. + enum class IndexBufferQuestion { + Presence, // is an element array buffer bound at all + DriverName, // + the GL name currently bound to GL_ELEMENT_ARRAY_BUFFER + DriverNameAndSize,// + the store's size in bytes (the compute tier's source) + HostBytes, // a CPU-readable copy of the whole store, and its size + }; + + struct BoundIndexBufferView { + Bool Present = false; + Uint Id = 0; // DriverName*: 0 when there is no backend store yet + SizeT Size = 0; // DriverNameAndSize / HostBytes + const Uint8* HostBytes = nullptr; // HostBytes: null when there is no CPU copy + }; + +#if MOBILEGL_BUILD_DISAGGREGATED + // A missing record on the handle arm is a NAMED refusal and never a quiet fall-back to + // the frontend (TASK-mv requirement 2; the shape is Managers.cpp's + // RefuseNullFrontendTextureOffTheHandleArm). The frontend element slot is not a second + // opinion on this side: it answers for whatever the CLIENT has bound NOW, which is a + // later draw than the one being applied. Drawing a batch from it would be a silently + // wrong picture, which is precisely what MultiDrawScenario's batch-matches-unrolled + // assertions exist to catch and what a fall-back would hide from them. + [[noreturn]] void RefuseMissingIndexBufferRecord(const char* entry, MG_Pipe::MGPipeHandle res, + const char* missing) { + MGLOG_F("MGPipe: Fatal{RoleViolation, \"multidraw-index-buffer-arm\"} - %s found no %s for " + "the index buffer {%u, %u} this batch's record named. The index buffer of this " + "family is MGPipeApplier().IndexBuffer.Res (CONTRACT-P5E §5.1) and the arm is " + "selected by Transport != Monolith AND the vertex-input subsystem bit (§5.8, " + "ID-81); falling back to the frontend VAO's element slot here would draw this " + "multi-draw from whatever the CLIENT has bound now", + entry, missing, res.Slot, res.Gen); + std::abort(); + } + + // Does the applier's record say the application SUPPLIED this store's content? The M-3 + // rule ScopedRestartIndexSubstitution spells: for a supplied store a coverage gap in the + // server shadow is a missing record and Fatal by name; for one the application ORPHANED + // the gap is its own undefined content, and reading the shadow's zero-fill is exactly + // what the monolith arm's MappedData() hands back. + Bool IndexBufferRecordHasDefinedContent(const MG_Pipe::MGPipeApplierState& st, + MG_Pipe::MGPipeHandle res) { + if (res.Slot >= st.Resources.size()) return false; + const auto& candidate = st.Resources[res.Slot]; + if (!candidate.Live || candidate.Gen != res.Gen) return false; + return candidate.Desc.HasDefinedContent != 0; + } +#endif + + BoundIndexBufferView ResolveBoundIndexBuffer(IndexBufferQuestion question, const char* entry) { + BoundIndexBufferView view; +#if MOBILEGL_BUILD_DISAGGREGATED + if (BufferImpl::VertexInputReadsRecords()) { + const auto& st = MG_Pipe::MGPipeApplier(); + const MG_Pipe::MGPipeHandle res = BufferImpl::ResolveDrawIndexBufferFromRecord(st).Res; + // A null Res is "no element array buffer bound", exactly as a null frontend slot + // is on the arm below - the batch's indices are a client array, and every caller + // already has an arm for that. It is NOT the arm test; the arm was decided above. + if (MG_Pipe::MGPipeHandleIsNull(res)) return view; + view.Present = true; + if (question == IndexBufferQuestion::Presence) return view; + + if (question == IndexBufferQuestion::HostBytes) { + // No Sync* pair here and none is missing: on this side the applier has + // already consumed the persistent-map blocks and the shader writebacks for + // this resource before the draw verb replayed, so the server's staged shadow + // IS the synced copy. That is also why this arm cannot be expressed as "find + // the object and run the monolith body". + auto* resource = BufferImpl::FindBufferResourceForHandle(res); + if (resource == nullptr) RefuseMissingIndexBufferRecord(entry, res, "backend resource"); + view.Size = BufferImpl::ResourceWidthForHandle(res); + view.HostBytes = resource->hostBytes; + if (view.HostBytes != nullptr && IndexBufferRecordHasDefinedContent(st, res)) { + BufferImpl::RequireStagedCoverage(*resource, view.HostBytes, 0, view.Size, + "multidraw_index_rebase"); + } + // A null HostBytes is not a refusal: MappedData() on the monolith arm is + // equally allowed to be null (an adopted coherent map keeps its bytes + // elsewhere), and the one caller that asks declines the tier for it. + return view; + } + + auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, res); + if (resource == nullptr) RefuseMissingIndexBufferRecord(entry, res, "backend resource"); + view.Id = resource->id; + if (question == IndexBufferQuestion::DriverNameAndSize) { + view.Size = BufferImpl::ResourceWidthForHandle(res); + } + return view; + } +#endif + // MONOLITH GLUE from here down, token for token what each call site did before. const auto& vao = MGB_CTX->GetBoundVertexArray(); - if (!vao) return none; - return vao->GetIndexBufferBindingSlot().GetBoundObject(); + if (!vao) return view; + const auto& ibo = vao->GetIndexBufferBindingSlot().GetBoundObject(); + if (!ibo) return view; + view.Present = true; + switch (question) { + case IndexBufferQuestion::Presence: + break; + case IndexBufferQuestion::DriverName: { + const auto* resource = BufferImpl::EnsureBufferResource(ibo); + view.Id = resource ? resource->id : 0; + break; + } + case IndexBufferQuestion::DriverNameAndSize: { + const auto* resource = BufferImpl::EnsureBufferResource(ibo); + view.Id = resource ? resource->id : 0; + view.Size = ibo->GetSize(); + break; + } + case IndexBufferQuestion::HostBytes: + // The shadow is the source of truth for CPU reads, but a persistent map or + // a shader write may have moved past it since the last sync. + ibo->SyncPersistentMappedRange(); + ibo->SyncGpuWrites(); + view.HostBytes = ibo->MappedData(); + view.Size = ibo->GetSize(); + break; + } + (void)entry; + return view; } // The GL name PrepareForDraw left on GL_ELEMENT_ARRAY_BUFFER, i.e. what a tier @@ -103,10 +244,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { // matters beyond tidiness: the VAO twin memoises that it already synced this // index binding and will not re-issue it on the next draw. Uint BoundIndexBufferId() { - const auto& ibo = BoundIndexBuffer(); - if (!ibo) return 0; - const auto* resource = BufferImpl::EnsureBufferResource(ibo); - return resource ? resource->id : 0; + return ResolveBoundIndexBuffer(IndexBufferQuestion::DriverName, "BoundIndexBufferId").Id; } // --------------------------------------------------------------------------- @@ -361,13 +499,18 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { } } - // CPU-readable bytes of one sub-draw's indices, from the frontend shadow of the - // bound index buffer or straight from the client array. Null when the sub-draw - // would read outside the buffer. - const Uint8* ResolveSubDrawIndices(const SharedPtr& indexBuffer, - const Uint8* indexBufferBytes, SizeT indexBufferSize, const void* indices, - SizeT indexCount, SizeT indexSize) { - if (!indexBuffer) { + // CPU-readable bytes of one sub-draw's indices, from the CPU copy of the bound index + // buffer (the frontend shadow on the monolith arm, the server's staged shadow on the + // record arm) or straight from the client array. Null when the sub-draw would read + // outside the buffer. + // + // P5e (mv): `hasIndexBuffer` is a Bool and not the frontend object it used to be, + // because presence is the only thing this function ever asked of it - and on the record + // arm there is no such object on this side to hand it. + const Uint8* ResolveSubDrawIndices(Bool hasIndexBuffer, const Uint8* indexBufferBytes, + SizeT indexBufferSize, const void* indices, SizeT indexCount, + SizeT indexSize) { + if (!hasIndexBuffer) { return static_cast(indices); } if (!indexBufferBytes) return nullptr; @@ -410,8 +553,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { if (indexSize == 0) return false; // Indirect commands address indices as an element offset into the bound element // array buffer, and an indirect draw is not defined without one. - const auto& indexBuffer = BoundIndexBuffer(); - if (!indexBuffer) return false; + if (!ResolveBoundIndexBuffer(IndexBufferQuestion::Presence, "RunIndirect").Present) return false; g_commandStaging.resize(static_cast(drawcount)); for (GLsizei i = 0; i < drawcount; ++i) { @@ -502,17 +644,10 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { if (total == 0) return true; if (total > kMaxFlattenedIndices) return false; - const auto& indexBuffer = BoundIndexBuffer(); - const Uint8* indexBufferBytes = nullptr; - SizeT indexBufferSize = 0; - if (indexBuffer) { - // The shadow is the source of truth for CPU reads, but a persistent map or - // a shader write may have moved past it since the last sync. - indexBuffer->SyncPersistentMappedRange(); - indexBuffer->SyncGpuWrites(); - indexBufferBytes = indexBuffer->MappedData(); - indexBufferSize = indexBuffer->GetSize(); - } + const BoundIndexBufferView indexBuffer = + ResolveBoundIndexBuffer(IndexBufferQuestion::HostBytes, "RunRebasedDrawElements"); + const Uint8* const indexBufferBytes = indexBuffer.HostBytes; + const SizeT indexBufferSize = indexBuffer.Size; const Bool restartActive = RestartActive(); const Uint32 restartSentinel = RestartSentinelFor(type); @@ -531,8 +666,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { for (GLsizei i = 0; i < drawcount; ++i) { if (count[i] <= 0) continue; const SizeT subDrawCount = static_cast(count[i]); - const Uint8* source = ResolveSubDrawIndices(indexBuffer, indexBufferBytes, indexBufferSize, indices[i], - subDrawCount, indexSize); + const Uint8* source = ResolveSubDrawIndices(indexBuffer.Present, indexBufferBytes, indexBufferSize, + indices[i], subDrawCount, indexSize); if (!source) { MGLOG_E_ONCE("DirectGLES multi-draw (drawelements tier): sub-draw %d reads outside the bound index " "buffer; skipping the batch", @@ -714,16 +849,18 @@ void main() { // The shader reads the source indices as a storage buffer, so there has to be // a real buffer to read - a client-memory index array has none. - const auto& indexBuffer = BoundIndexBuffer(); - if (!indexBuffer) return; + if (!ResolveBoundIndexBuffer(IndexBufferQuestion::Presence, "FlattenWithCompute").Present) return; // A dispatch inside an open capture span is not legal, and the span would also // observe one merged draw rather than the batch it asked for. if (XfbImpl::IsCaptureSpanOpen()) return; - auto* sourceResource = BufferImpl::EnsureBufferResource(indexBuffer); - if (!sourceResource || sourceResource->id == 0) return; - const SizeT sourceSize = indexBuffer->GetSize(); + // Asked a second time, and one question later: the ensure below may do GL work, so + // it stays BEHIND the capture-span check exactly as it was before P5e (mv). + const BoundIndexBufferView source = + ResolveBoundIndexBuffer(IndexBufferQuestion::DriverNameAndSize, "FlattenWithCompute"); + if (source.Id == 0) return; + const SizeT sourceSize = source.Size; // std430 addresses the source as uint[]; a tail shorter than a word is not // reachable, so a narrow index type needs a word-multiple buffer. if (indexSize < 4 && (sourceSize % 4) != 0) return; @@ -762,7 +899,7 @@ void main() { return; } - BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, 0, sourceResource->id); + BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, 0, source.Id); BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, 1, g_drawInfo.id); BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, 2, g_flattenedIndices.id); @@ -906,7 +1043,8 @@ void main() { const Bool arbitraryRestart = restartKind == RestartSubstitutionKind::RewriteIndices; const ScopedSuppressedPrimitiveRestart restartCapOverride(restartKind); - const Bool hasIndexBuffer = BoundIndexBuffer() != nullptr; + const Bool hasIndexBuffer = + ResolveBoundIndexBuffer(IndexBufferQuestion::Presence, "DrawElementsBatch").Present; // The compute tier dispatches BEFORE the draw state is established: doing it // afterwards would mean unpicking the program, SSBO and index bindings diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index f72d467a..23e13c74 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2014,6 +2014,79 @@ if (MOBILEGL_BUILD_DISAGGREGATED) ) endforeach() + # ---- P5e mv: THE MULTI-DRAW TIERS, INSIDE THE GATED SET (ID-109's rule, one level down) -- + # + # WHY FIVE MORE LANES FOR ONE FILE. MultiDrawImpl asks the bound index buffer three questions + # and P5e (mv) moved all three onto the applier's record. Five of this driver's six tiers ask + # them from a DIFFERENT place in the batch - `RunIndirect` before any GL work, the rebased + # tier from the CPU shadow, `FlattenWithCompute` before PrepareForDraw has even run - and the + # resolved tier is ONE process-wide read of MOBILEGL_ESPRYT_MULTIDRAW_MODE (MultiDraw.cpp's + # ResolveTierOnce). The d1 block above leaves it at `auto`, and on every driver this gate has + # run against, `auto` lands on `ext` - a real multi-draw entry point that asks the index + # buffer nothing at all. Three of the five migrated sites therefore had no gated entry. + # + # That is ID-107's shape exactly one level down: code on a real arm, with no entry in the set + # the gate runs. The phase already paid 137 SEGFAULTs across 38 scenarios for the version of + # this that lived at the transport level, and the lesson ID-109 drew - "the gate runs the SET, + # so put the second arm in the set" - does not stop being true because the selector is an env + # knob rather than MG_Config::Transport. A manual sweep run once by one package decays to + # nothing by the next one. + # + # MOBILEGL_ESPRYT_MULTIDRAW_MODE IS A ctest ENVIRONMENT PROPERTY, not a job-level export, for + # review finding N-6's reason and the PushMonolithArm block's: the property overrides the job + # environment, so a gate that exported a mode of its own could not silently collapse these + # five lanes into five more copies of the `auto` one - the single failure mode a lane whose + # whole purpose is to pin a tier cannot survive. + # + # `ext` IS NOT REGISTERED HERE, deliberately: it is what `auto` already resolves to, so the + # d1 block above IS the ext lane and a sixth block would only run it twice. Every other tier + # was confirmed to resolve to ITSELF on this driver rather than falling back + # (`ResolveTier`'s "MOBILEGL_ESPRYT_MULTIDRAW_MODE= -> (driver supports: ext, + # basevertex, multiindirect, indirect, drawelements, compute (opt-in))"); a tier the driver + # refuses would resolve to `ext` and register an entry that quietly proves nothing, so if this + # list ever runs somewhere poorer, drop the refused tier rather than keeping a green that + # means "ext again". + # + # THE FILTER IS THE TIER-SENSITIVE WORKLOAD and nothing else, which is this file's own rule + # for an armed Split entry: MultiDrawScenario (its whole point is that a batch matches the + # unrolled draws, which is precisely what a wrong index buffer breaks quietly) and + # IndexedDrawFamilyScenario's three MultiDraw cases. The client-side-indices case keeps the + # d1 block's exclusion - it is refused by name under split before any tier is chosen. + # + # NOTE FOR THE STRICT-MARKER CENSUS (PREAMBLE-ADDENDUM-WAVE3 §4): these entries carry a THIRD + # name segment, so `strict_census2.sh` re-derives their gtest filter from the last two + # components and re-runs them WITHOUT the mode this block pins - which for these lanes is the + # whole point of the entry. They join Ct. / F1. / SmallRing. / PersistentMapArm. / NamedBlit. + # in that blind spot, so SplitLogPaths.cmake.in gives each of them a private log path: read + # the marker out of `build-split/MobileGL/MG_IntegrationTest/split-logs/.log`, which + # the LANE wrote under its own pin, instead of out of a re-run that lost it. + set(MGL_SPLIT_MULTIDRAW_TIERS basevertex multiindirect indirect drawelements compute) + set(MGL_SPLIT_MULTIDRAW_TIER_PREFIX_basevertex "BaseVertex") + set(MGL_SPLIT_MULTIDRAW_TIER_PREFIX_multiindirect "MultiIndirect") + set(MGL_SPLIT_MULTIDRAW_TIER_PREFIX_indirect "Indirect") + set(MGL_SPLIT_MULTIDRAW_TIER_PREFIX_drawelements "DrawElements") + set(MGL_SPLIT_MULTIDRAW_TIER_PREFIX_compute "Compute") + set(MGL_SPLIT_MULTIDRAW_TIER_FILTER + "MultiDrawScenario.*:IndexedDrawFamilyScenario.MultiDraw*-MultiDrawScenario.ClientSideIndicesBatchMatchesUnrolledDraws") + foreach(mglItestMultiDrawTier IN LISTS MGL_SPLIT_MULTIDRAW_TIERS) + mgl_itest_join_environment(MGL_ITEST_GLES_SPLIT_MULTIDRAW_ENVIRONMENT + "MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_TRANSPORT=inproc" + "MOBILEGL_IPC_SERVER_PATH=${MGL_ITEST_SPLIT_SERVER_PATH}" "MGITEST_SPLIT_LANE=1" + "MOBILEGL_ESPRYT_MULTIDRAW_MODE=${mglItestMultiDrawTier}" + ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV}) + gtest_discover_tests(MobileGLIntegrationTest + TEST_PREFIX + "DirectGLES.Split.MultiDrawTier${MGL_SPLIT_MULTIDRAW_TIER_PREFIX_${mglItestMultiDrawTier}}." + TEST_LIST "MGL_SPLIT_MULTIDRAW_${mglItestMultiDrawTier}_TESTS" + TEST_FILTER "${MGL_SPLIT_MULTIDRAW_TIER_FILTER}" + DISCOVERY_TIMEOUT 30 + PROPERTIES + LABELS "integration-gpu\;integration-split" + TIMEOUT ${MGL_ITEST_TIMEOUT} + ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_MULTIDRAW_ENVIRONMENT}" + ) + endforeach() + # ---- P5b package i1 (MG_Remote/CONTRACT-P5B.md §2 i1) -------------------------------- # # THE LANE CASE THAT PROVES THE RECORDS CROSSED, and it is a RESULT and not a probe (R-16: diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitLogPaths.cmake.in b/MobileGL/MG_IntegrationTest/Harness/SplitLogPaths.cmake.in index 3bc1c1f6..d4117c39 100644 --- a/MobileGL/MG_IntegrationTest/Harness/SplitLogPaths.cmake.in +++ b/MobileGL/MG_IntegrationTest/Harness/SplitLogPaths.cmake.in @@ -26,6 +26,17 @@ foreach(scenario @MGL_SPLIT_D1_SCENARIOS@) endforeach() endforeach() +# P5e mv's five tier-pinned lanes, the same rule for the same reason - and here it also repairs +# the addendum §4 blind spot for them: these entries carry a third name segment, so the strict +# census cannot re-derive their MOBILEGL_ESPRYT_MULTIDRAW_MODE, but a private log per entry means +# its marker can be read where the LANE wrote it instead of from a re-run that lost the pin. +foreach(tier @MGL_SPLIT_MULTIDRAW_TIERS@) + foreach(entry IN LISTS MGL_SPLIT_MULTIDRAW_${tier}_TESTS) + set_tests_properties("${entry}" PROPERTIES ENVIRONMENT + "MOBILEGL_LOG_FILE_PATH=@CMAKE_CURRENT_BINARY_DIR@/split-logs/${entry}.log") + endforeach() +endforeach() + # P5b package i1's four scenarios, the same shape as the small-ring loop above: every # DirectGLES.Split. entry owns exactly one absolute log path and shares it with nobody, which # is what SplitLogPaths.PrivateAndDistinct checks over the discovered set.