diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 198eb889..e4f099fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1424,7 +1424,7 @@ jobs: working-directory: build-split run: bash "${GITHUB_WORKSPACE}/scripts/ci/dualblock_negative_control.sh" - # ---- P5e (gl), CONTRACT-P5E §7: MAGMA'S OWN LANE, EXPECTED RED ----------------------- + # ---- MAGMA'S OWN WIRE LANE: FRAMEBUFFER, TEXTURE AND APPLICATION BUFFERS ------------ # # P5f fm: Magma's framebuffer, copy and mipmap verbs consume server records. # The label remains separate so both backends' coverage stays explicit. @@ -1438,6 +1438,40 @@ jobs: run: | ctest -L integration-magma-split --no-tests=error -j 2 --output-on-failure + - name: Magma application-buffer wire cases must all execute + working-directory: build-split + run: | + ctest -L integration-magma-buffers --show-only=json-v1 > "${RUNNER_TEMP}/magma-buffer-lane.json" + ctest -L integration-magma-buffers --no-tests=error -j 2 --output-on-failure \ + --output-junit "${RUNNER_TEMP}/magma-buffers.xml" + python3 - "${GITHUB_WORKSPACE}/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py" \ + "${RUNNER_TEMP}/magma-buffer-lane.json" "${RUNNER_TEMP}/magma-buffers.xml" <<'PY' + import importlib.util, json, sys + spec = importlib.util.spec_from_file_location("slp", sys.argv[1]) + slp = importlib.util.module_from_spec(spec); spec.loader.exec_module(slp) + cases = [ + "F1WireScenario.EnabledVertexBufferDrawKeepsNamedP7Fatal", + "F1WireScenario.UniformBufferRangeAndRebindPixels", + "F1WireScenario.ShortUniformBufferRangePadsMissingBytes", + "F1WireScenario.ComputeWrittenVertexAndIndexBuffersDrawAndReadBack", + "IndexedDrawFamilyScenario.AnElementBufferDrawElementsOffsetSelectsTheRightQuad", + "IndexedDrawFamilyScenario.DrawElementsBaseVertexMovesTheSameIndicesToTheOtherQuad", + "IndexedDrawFamilyScenario.DrawRangeElementsDrawsTheRangedRun", + "IndexedDrawFamilyScenario.DrawElementsInstancedBaseVertexPaintsOneQuadPerInstance", + "IndexedDrawFamilyScenario.MultiDrawElementsBaseVertexPaintsEverySubDraw", + "IndexedDrawFamilyScenario.MultiDrawElementsIndirectDrawsFromTheIndirectBuffer", + "CrossFrameBufferScenario.VertexBufferSubData", + "CrossFrameBufferScenario.IndexBufferSubData", + "CrossFrameBufferScenario.VertexOrphanAndReupload", + "CrossFrameBufferScenario.IndexOrphanAndReupload", + "PersistentCoherentMapScenario.TwoWritesThroughTheCoherentPointerEachReachTheirOwnDraw", + "PersistentCoherentMapScenario.AWriteAfterAFrameBoundaryReachesTheNextFramesDraw", + "StorageBufferRegrowScenario.AGrownStoreIsVisibleThroughItsExistingIndexedBinding", + ] + slp.require_green(json.load(open(sys.argv[2])), sys.argv[3], + ["DirectVulkan.Split.Buffers." + name for name in cases]) + PY + - name: Magma P7 native mip format debt stays a named refusal working-directory: build-split env: diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index 3730bba8..8c7ee090 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -97,18 +97,10 @@ namespace MobileGL::MG_Backend { // rather than aborting. constexpr Bool kMGPipeP5eRunAheadReady = true; - // P5e (sb, ID-106). DirectGLES GAINS BIT 13 - the indexed buffer binding points - and - // Magma deliberately does not. c0e landed the phase constant and the dirty-bit map with - // this row still reading P4a's mask, because withholding a consumer bit is the safe - // direction while no emitter exists: the client's R-8 gate then keeps the whole family - // on the legacy pull path. The moment ShaderBufferEmit.h's wired constant leaves 0 the - // two have to move together, so they are one commit. - // - // DIRECTVULKAN STAYS ON P4a's MASK. The bit says "this server reads set_shader_buffers - // instead of walking the client's binding-point table", and Magma's UniformManager does - // no such thing - it has no binding-point records at all, and P5e leaves it lockstep - // (CONTRACT-P5E.md §6). Claiming the bit there would make the client emit a family - // nothing consumes, which is ID-39's failure with a different family's name on it. + // The Magma transport now owns buffer stores and consumes their vertex, + // index and shader binding records. Both backends publish the resource + // family only alongside a real op table (checked below). This does not + // publish kCapRunAheadApply: Magma keeps its existing lockstep contract. Uint64 ConsumedSubsystemsFor(BackendType type) { switch (type) { case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP5e; diff --git a/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt b/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt index a8109804..8220c9a7 100644 --- a/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt +++ b/MobileGL/MG_IntegrationTest/Harness/dualblock-expected-fatals.txt @@ -40,6 +40,8 @@ # and storage-block program-name validation. fm retired Clear on Magma through # server framebuffer/resource records. The shared copy-image seam also retires # GetTextureObject in either package in isolation; it is one removal at integration. -# The expanded Magma lane includes real bufferless shader/image pixel scenarios. +# The expanded Magma lane also covers application VBO/EBO/UBO/SSBO and persistent +# map consumers. The former VBO fatal case now asserts rendered pixels; no new +# field-Fatal expectations or skip exemptions accompany that retirement. # Its named P7 native-format probe has a separate, enforced known-red CI lane. # Every new Fatal or Admitted in this census remains a regression. diff --git a/MobileGL/MG_Test/Pipe/MagmaPipeIdentityTest.cpp b/MobileGL/MG_Test/Pipe/MagmaPipeIdentityTest.cpp index 229e58a3..1b4235ab 100644 --- a/MobileGL/MG_Test/Pipe/MagmaPipeIdentityTest.cpp +++ b/MobileGL/MG_Test/Pipe/MagmaPipeIdentityTest.cpp @@ -218,20 +218,9 @@ namespace { EXPECT_EQ(mint.Count(), 1u); } - // ---- P5e (MG_Remote/CONTRACT-P5E.md §1, §6, ruling 12) ------------------------------- - // - // MAGMA DOES NOT RUN AHEAD, AND THIS IS WHERE THAT IS A TEST RATHER THAN A COMMENT. - // kCapRunAheadApply is the client's whole permission to publish a record and move on: the - // moment a server sets it, the apply thread promises it reads nothing of the client's. That - // promise is FALSE for Magma for the whole of P5e - the four apply-thread allocator sites - // inside MagmaP7AllocatorDebtScope are real debt P7 retires, and - // MGPipeApplierCurrentRecordIsBarriered() answering true for every record on a server - // without the bit is exactly what keeps them inside P5C's semantics and keeps rsp honest. - // - // The arm is a pure function precisely so this case can reach it: InitSplitRoles needs a - // live session, a backend and a handshake, and none of those belong in a unit lane. RED - // ONCE by making MGPipeRunAheadCapBitsFor answer for DirectVulkan too (the exact - // perturbation the phase's red-once list names) - both EXPECTs below fail, by name. + // Application buffer record consumers do not imply run-ahead readiness. + // Magma retains its lockstep lifetime/submission contract; the capability + // must remain absent even when the Espryt readiness constant is true. TEST_F(MagmaPipeIdentityTest, AMagmaServerNeverPublishesTheRunAheadCapBit) { // Whatever the integration constant says. `true` is what the P5e integration commit // will pass, so the Magma answer is pinned on BOTH sides of that flip and the case diff --git a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp index 60e41fc2..d279cef4 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp +++ b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -592,8 +593,9 @@ TEST(CapsMirrorTest, APlaceholderMirrorConsumesNothing) { TEST(CapsMirrorTest, ObjectFamilyEmissionTracksIndependentConsumerCapsWithoutBufferOps) { #if MGTEST_HAVE_FORK - // Isolate every process global: transport, push mask, caps generation, refusal - // counters and the cached supply environment must not escape into another case. + // Deliberately synthetic masks with no buffer consumer: production Magma now + // publishes one, but object-family independence must survive a restricted peer. + // Isolate transport, push mask, caps generation and cached supply environment. const ChildResult result = RunInChild([] { MG_Config::Transport = MG_Config::TransportMode::InProcess; MG_Config::Features.PipePush = kMGPipeSubsystemsMigratedAtP5e; @@ -619,9 +621,8 @@ TEST(CapsMirrorTest, ObjectFamilyEmissionTracksIndependentConsumerCapsWithoutBuf AdoptSnapshot(snapshot); EXPECT_TRUE(emits(kMGPipeSubsystemTextureResources)); EXPECT_FALSE(emits(kMGPipeSubsystemFramebuffer)); - // SuppliedFieldMask's object rows remain BARRIER_PULLED until the final - // ownership retirement. They cannot honestly expose a mask change yet; - // this checks the actual emitter gate, not a synthetic memo-only answer. + // Object accessors were retired by P5f rather than made record-supplied; + // this checks the actual per-family emitter gate, not a synthetic field mask. std::fflush(nullptr); ::_exit(::testing::Test::HasFailure() ? 1 : 0); }); @@ -633,6 +634,55 @@ TEST(CapsMirrorTest, ObjectFamilyEmissionTracksIndependentConsumerCapsWithoutBuf #endif } +TEST(CapsMirrorTest, MagmaTransportPublishesRealBufferConsumersWithoutRunAhead) { +#if MGTEST_HAVE_FORK + const ChildResult result = RunInChild([] { + ::alarm(15); + MG_Config::Transport = MG_Config::TransportMode::InProcess; + MG_Config::ActiveBackendType = BackendType::DirectVulkan; + MG_Config::Features.PipePush = kMGPipeSubsystemsMigratedAtP5e; + MG_Config::Ipc.RunAhead = 1; + MG_Pipe::MGPipeSetResourceOps(nullptr); + // The production bootstrap creates the server backend and publishes its + // real mask before any native EGL/Vulkan context exists. No invented caps + // snapshot or fake resource table can make this test pass. + MG_Backend::Init(); + auto& server = MG_Remote::Server::ServerSessionInstance(); + EXPECT_TRUE(server.Accepted()); + EXPECT_TRUE(server.CallMaskIsSet()); + if (server.CallMaskIsSet()) { + const Uint64 mask = server.CallMask(); + EXPECT_TRUE(MGCapsServerConsumes(mask, kMGPipeSubsystemResources)); + EXPECT_TRUE(MGCapsServerConsumes(mask, kMGPipeSubsystemBufferBindings)); + EXPECT_EQ(mask & static_cast(kCapRunAheadApply), 0u); + } + const auto* ops = MG_Pipe::MGPipeGetResourceOps(); + EXPECT_NE(ops, nullptr); + if (ops) { + EXPECT_NE(ops->Create, nullptr); + EXPECT_NE(ops->Respecify, nullptr); + EXPECT_NE(ops->SubData, nullptr); + EXPECT_NE(ops->FlushRange, nullptr); + EXPECT_NE(ops->Readback, nullptr); + EXPECT_NE(ops->Destroy, nullptr); + EXPECT_NE(ops->MapPersistent, nullptr); + if (ops->MapPersistent) EXPECT_EQ(ops->MapPersistent({7, 1}, 64, nullptr), nullptr) + << "Magma must not donate a server address as a client persistent map"; + } + EXPECT_FALSE(ClientSessionInstance().RunAheadArmed()); + ClientSessionInstance().Stop(); + MG_Remote::Server::ServerLoopInstance().Stop(); + std::fflush(nullptr); + ::_exit(::testing::Test::HasFailure() ? 1 : 0); + }); + ASSERT_GE(result.Status, 0) << "fork/waitpid failed"; + ASSERT_TRUE(WIFEXITED(result.Status)) << DescribeStatus(result) << result.Log; + EXPECT_EQ(WEXITSTATUS(result.Status), 0) << result.Log; +#else + GTEST_SKIP() << "process-global backend bootstrap isolation requires fork"; +#endif +} + TEST(CapsMirrorTest, TheConsumerBlockDoesNotCollideWithTheFeatureBits) { // The two halves of CallMask, asserted against each other rather than against a constant: // bits 0..8 are MGPCapBit and bits 32..47 are the consumer mask, and the whole reason R-8