diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index e762606b..dcf4d075 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -292,7 +292,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } // P4a (D-F3, [correction]). THE RAW-DEPTH-FETCH SUBSTITUTION STAYS ON THE SERVER, and it - // keeps constructing a frontend SamplerObject inside MG_Backend to do it. That is + // keeps constructing a frontend SamplerObject inside MG_Backend on MONOLITH to do it. That is // deliberate and it is not this phase's to change: ARCHITECTURE.md assigns the two // backend-specific post-processings of the resolved sampler set - this one and Magma's // feedback-loop detection - to the server, acting ON the already-resolved set, and the @@ -305,6 +305,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // MG_State-type usage anywhere under MG_Backend/DirectGLES, and its owner is P3b/P4b - so // the include-graph gate at P13 meets a known item rather than a surprise. The purity // gates are unaffected either way: they grep MG_Backend for the pull arm's live-GLContext + // P5f fs's transport arm below uses only a native sampler with fixed values. The legacy + // pair is not constructed or consulted by the server. The purity gate's live-context // pointer token (G13 - deliberately not spelled here, because that grep is a BARE TOKEN // grep and a comment naming it is a hit) and for an MG_State type inside // MGPipeResourceOps, and this is neither. diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index eebc405d..815e4370 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -19,6 +19,7 @@ #include "WireTables.h" #include +#include #include #include #include @@ -116,6 +117,10 @@ namespace MobileGL::MG_Remote::Client { // thread inside the applier" - see ClientSession::InBarrierWait's note. thread_local Bool g_inBarrierWait = false; std::atomic g_applyThreadInsideApplier{false}; + // A split client cannot observe a server's process-local diagnostic. The + // role-local value remains useful to the applier itself; ordering between + // roles is carried by the ring watermarks, not by this instrumentation. + thread_local Bool g_roleInsideApplier = false; struct BarrierWaitScope { BarrierWaitScope() { g_inBarrierWait = true; } @@ -1280,10 +1285,11 @@ namespace MobileGL::MG_Remote::Client { // THE CLIENT'S FLAG IS THREAD-LOCAL AND THE SERVER'S IS NOT, and the asymmetry is the // point: "am I inside a barrier wait" is a question about the calling thread, while "is the // apply thread inside the applier" is a question the GL thread asks about a DIFFERENT - // thread - so the second has to be a shared atomic and the first must not be, or a second - // GL thread would see the first one's wait as its own. + // thread in the shared-block control. Under role split each thread instead observes only + // its own diagnostic; the client writes a different block and waits through appliedSeq. Bool ClientSession::InBarrierWait() { return g_inBarrierWait; } Bool ClientSession::ApplyThreadIsInsideApplier() { + if (MG_Pipe::MGPipeRoleSplitActive()) return g_roleInsideApplier; return g_applyThreadInsideApplier.load(std::memory_order_acquire); } @@ -1379,10 +1385,14 @@ namespace MobileGL::MG_Remote::Client { std::abort(); } void ClientSession::NoteApplyThreadEnteredApplier() { - g_applyThreadInsideApplier.store(true, std::memory_order_release); + g_roleInsideApplier = true; + if (!MG_Pipe::MGPipeRoleSplitActive()) + g_applyThreadInsideApplier.store(true, std::memory_order_release); } void ClientSession::NoteApplyThreadLeftApplier() { - g_applyThreadInsideApplier.store(false, std::memory_order_release); + g_roleInsideApplier = false; + if (!MG_Pipe::MGPipeRoleSplitActive()) + g_applyThreadInsideApplier.store(false, std::memory_order_release); } Uint32 ClientSession::PumpControlPlane() { diff --git a/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp index 1a093c18..7419f0fe 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include namespace MobileGL::MG_Backend::DirectGLES { SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler(); @@ -149,3 +152,35 @@ TEST(ContextEpochTest, ServerLivenessDoesNotFollowTheClientContextOrAVerbStamp) GTEST_SKIP() << "server liveness exists only in the disaggregated build"; #endif } + +TEST(ContextEpochTest, DualBlockApplierDiagnosticIsRoleLocalWhileSharedControlRemainsArmed) { +#if MOBILEGL_BUILD_DISAGGREGATED + DriverScope scope; + const Bool previous = MG_Config::Ipc.RoleSplitState; + using Session = MG_Remote::Client::ClientSession; + const auto observe = [&](Bool split) { + MG_Config::Ipc.RoleSplitState = split; + std::atomic entered{false}, leave{false}; + Bool serverSawItself = false; + std::thread server([&] { + Session::NoteApplyThreadEnteredApplier(); + serverSawItself = Session::ApplyThreadIsInsideApplier(); + entered.store(true, std::memory_order_release); + while (!leave.load(std::memory_order_acquire)) std::this_thread::yield(); + Session::NoteApplyThreadLeftApplier(); + }); + while (!entered.load(std::memory_order_acquire)) std::this_thread::yield(); + const Bool clientSawServer = Session::ApplyThreadIsInsideApplier(); + leave.store(true, std::memory_order_release); + server.join(); + EXPECT_TRUE(serverSawItself); + EXPECT_EQ(clientSawServer, !split); + EXPECT_FALSE(Session::ApplyThreadIsInsideApplier()); + }; + observe(false); + observe(true); + MG_Config::Ipc.RoleSplitState = previous; +#else + GTEST_SKIP() << "role-local observer requires the disaggregated build"; +#endif +}