mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 00:28:31 +09:00
[Feat] (MG_Backend, MobileGL/Init): the single split hook in MG_Backend::Init - server backend, CallMask halves, ClientSession::Start, remote object - and ShutdownSplitRoles at the top of Destroy
This commit is contained in:
@@ -43,6 +43,11 @@ namespace MobileGL {
|
|||||||
if (logLifecycle) {
|
if (logLifecycle) {
|
||||||
MGLOG_I("MobileGL closing...");
|
MGLOG_I("MobileGL closing...");
|
||||||
}
|
}
|
||||||
|
// P5: the split roles come down FIRST - ARCHITECTURE.md:537's order puts the whole
|
||||||
|
// of it before MobileGL::Destroy(), and this function IS MobileGL::Destroy. A no-op
|
||||||
|
// in a monolith build and in a monolith run; see BackendObjects.h for why the
|
||||||
|
// position rather than the call is the load-bearing part.
|
||||||
|
MG_Backend::ShutdownSplitRoles();
|
||||||
// Before any subsystem the counters name goes away, and before the last frame's
|
// Before any subsystem the counters name goes away, and before the last frame's
|
||||||
// numbers can be lost: emits the final summary line and, when
|
// numbers can be lost: emits the final summary line and, when
|
||||||
// MOBILEGL_PIPE_STATS_FILE is set, the JSON dump. A no-op when the counters are
|
// MOBILEGL_PIPE_STATS_FILE is set, the JSON dump. A no-op when the counters are
|
||||||
|
|||||||
@@ -15,4 +15,27 @@
|
|||||||
namespace MobileGL::MG_Backend {
|
namespace MobileGL::MG_Backend {
|
||||||
extern UniquePtr<BackendObject>& pActiveBackendObject;
|
extern UniquePtr<BackendObject>& pActiveBackendObject;
|
||||||
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
extern GlobalBackendFunctionsTable gBackendFunctionsTable;
|
||||||
|
|
||||||
|
// P5 v1. The counterpart of Init()'s single split hook, and a NO-OP in every build and
|
||||||
|
// every run that is not split - which is why MobileGL/Init.cpp can call it unconditionally
|
||||||
|
// without a second #if in a file no package owns.
|
||||||
|
//
|
||||||
|
// IT MUST RUN FIRST, BEFORE ANYTHING ELSE IN DestroyImpl. ARCHITECTURE.md:537's order is
|
||||||
|
// publish -> the server drains and acks -> stop the apply thread (Kill, then join) -> close
|
||||||
|
// the transport -> the client drains the compile pool -> MobileGL::Destroy() -> release the
|
||||||
|
// sync/query handles; DestroyImpl IS MobileGL::Destroy, so everything before that arrow has
|
||||||
|
// to happen at its top. Two consequences are load-bearing rather than tidy:
|
||||||
|
//
|
||||||
|
// * the apply thread is JOINED before PipeStats::Shutdown() dumps, so nothing is writing
|
||||||
|
// a counter while the final line is produced;
|
||||||
|
// * the apply thread is joined before pActiveBackendObject.reset(), so the server's own
|
||||||
|
// BackendObject - which is NOT that global (table 3) - is destroyed on the thread that
|
||||||
|
// owns the context, by ServerLoop::Stop, rather than on the app thread.
|
||||||
|
//
|
||||||
|
// The sync/query registries stay where they are, drained at MobileGL/Init.cpp:62 and :67
|
||||||
|
// BEFORE pActiveBackendObject.reset(). ARCHITECTURE.md:537 puts them after
|
||||||
|
// MobileGL::Destroy(); the two are only reconcilable if a split sync handle is
|
||||||
|
// client-minted and needs no backend call, which is P10's. CONTRACT-P5 4 flags this as a
|
||||||
|
// KNOWN OPEN ITEM and asks v1 to record which way it went: P5 keeps today's order.
|
||||||
|
void ShutdownSplitRoles();
|
||||||
} // namespace MobileGL::MG_Backend
|
} // namespace MobileGL::MG_Backend
|
||||||
|
|||||||
@@ -11,6 +11,26 @@
|
|||||||
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
||||||
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.h>
|
#include <MG_Util/Converters/MGToStr/GLExtensionConverter.h>
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
#include <MG_Pipe/MGPipe.h>
|
||||||
|
#include <MG_Pipe/PipeApply.h>
|
||||||
|
#include <MG_Remote/Client/ClientSession.h>
|
||||||
|
#include <MG_Remote/Server/ServerLoop.h>
|
||||||
|
#include <MG_Remote/Server/ServerSession.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
namespace MobileGL::MG_Remote::Client {
|
||||||
|
// PACKAGE c1's, DECLARED HERE RATHER THAN INCLUDED. The client's BackendObject_Remote is
|
||||||
|
// c1's file and does not exist while v1 is written, so v1 ships a WEAK definition of this
|
||||||
|
// beside ServerLoop that aborts by name. c1's strong definition displaces it at link time
|
||||||
|
// with no edit to this file - and until then the hook cannot silently succeed, which is the
|
||||||
|
// only property that matters: a split lane that installed a WORKING monolith backend object
|
||||||
|
// here would render correctly for entirely the wrong reason (ARCHITECTURE.md 10.3).
|
||||||
|
UniquePtr<MG_Backend::BackendObject> CreateRemoteBackendObject();
|
||||||
|
} // namespace MobileGL::MG_Remote::Client
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend {
|
namespace MobileGL::MG_Backend {
|
||||||
void LogBackendInfo() {
|
void LogBackendInfo() {
|
||||||
if (!pActiveBackendObject) {
|
if (!pActiveBackendObject) {
|
||||||
@@ -45,9 +65,150 @@ namespace MobileGL::MG_Backend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
namespace {
|
||||||
|
// WHICH MGPipe SUBSYSTEMS THIS SERVER HAS A CONSUMER FOR - CallMask bits 32..47 (R-8 /
|
||||||
|
// C-4). It is stated from what the server's own backend IS, and NOT derived from
|
||||||
|
// MGPipeGetResourceOps(): that is a PROCESS-WIDE global, so under inproc a derivation
|
||||||
|
// would answer with whatever the client half of the same process registered and under
|
||||||
|
// spawn it would collapse to P2's 0x7f. Either way CapsMirror::ServerConsumes would
|
||||||
|
// then answer a client-side liveness gate with a guess, the client would stop emitting
|
||||||
|
// whole record families, clear its dirty flags on acceptance anyway, and the lane would
|
||||||
|
// go green with the uploads lost - ID-39's 66 lost uploads, reflected (ServerSession.h).
|
||||||
|
//
|
||||||
|
// DirectGLES consumes all thirteen migrated families (P2's 0..6, P3a's 7..8, P4a's
|
||||||
|
// 9..12): it registers the resource op table in Initialize()
|
||||||
|
// (BackendObject_DirectGLES.cpp:849) and reads every other family out of gPipeInputs.
|
||||||
|
// DirectVulkan registers NO resource ops - MGPipeSetResourceOps has exactly one caller
|
||||||
|
// in the whole tree and it is Managers.cpp:2594 - so bit 7 is CLEAR for it, which is
|
||||||
|
// the same fact ObjectSubsystemControlScenario already pins from the client side.
|
||||||
|
Uint64 ConsumedSubsystemsFor(BackendType type) {
|
||||||
|
switch (type) {
|
||||||
|
case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a;
|
||||||
|
case BackendType::DirectVulkan:
|
||||||
|
return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a & ~MG_Pipe::kMGPipeSubsystemResources;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE CROSS-CHECK THAT MAKES A WRONG ANSWER LOUD. Claiming bit 7 while no resource op
|
||||||
|
// table is registered is the exact failure the mask exists to prevent, one level down:
|
||||||
|
// the client would keep emitting the resource family and the server would drop every
|
||||||
|
// record of it. The check runs AFTER Initialize(), which is where DirectGLES registers
|
||||||
|
// the table, so it can see the real answer rather than a promise.
|
||||||
|
void AssertConsumerMaskIsHonest(Uint64 mask) {
|
||||||
|
const Bool claimsResources = (mask & MG_Pipe::kMGPipeSubsystemResources) != 0;
|
||||||
|
const Bool hasResourceOps = MG_Pipe::MGPipeGetResourceOps() != nullptr;
|
||||||
|
if (claimsResources && !hasResourceOps) {
|
||||||
|
MGLOG_F("MGPipe: Fatal{ConsumerMaskLie, \"kMGPipeSubsystemResources\"} - the "
|
||||||
|
"server published a consumer bit for the resource family while "
|
||||||
|
"MGPipeGetResourceOps() is null. The client's R-8 liveness gate would "
|
||||||
|
"keep emitting resource_create / resource_subdata records that this "
|
||||||
|
"server drops on the floor, and the client clears its dirty flags on "
|
||||||
|
"acceptance anyway (ID-39). A mask is a statement about this backend, "
|
||||||
|
"not a hope");
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
if (!claimsResources && hasResourceOps) {
|
||||||
|
// The safe direction: the legacy pull path keeps running. Said out loud anyway,
|
||||||
|
// because it silently costs the whole P3a family its migration.
|
||||||
|
MGLOG_W("MG_Remote server: a resource op table is registered but the consumer "
|
||||||
|
"mask withholds kMGPipeSubsystemResources; the buffer family will fall "
|
||||||
|
"back to the legacy path for this session");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The single hook (ARCHITECTURE.md:29). Returns false when the split could not be
|
||||||
|
// brought up, and the caller then REFUSES TO CONTINUE rather than falling back to the
|
||||||
|
// switch below - a fallback here is "the split lane ran monolith and went green".
|
||||||
|
Bool InitSplitRoles() {
|
||||||
|
using namespace MobileGL::MG_Remote;
|
||||||
|
|
||||||
|
// 1. the SERVER role's private backend object, on the app thread, with no GL and no
|
||||||
|
// EGL. The context is created and made current later, on mgl-srv-apply, when the
|
||||||
|
// client's first eglMakeCurrent crosses as a blocking control request.
|
||||||
|
Server::ServerLoop& loop = Server::ServerLoopInstance();
|
||||||
|
const MobileGLResult created = loop.CreateBackend(MG_Config::ActiveBackendType);
|
||||||
|
if (created != MOBILEGL_OK) return false;
|
||||||
|
|
||||||
|
// 2. the two CallMask halves. NEITHER HAS A DEFAULT and CallMask() is a named Fatal
|
||||||
|
// on an unset one (s1's BLOCKER fix), so this is the "somebody" that block names.
|
||||||
|
Server::ServerSession& session = Server::ServerSessionInstance();
|
||||||
|
const Uint64 consumed = ConsumedSubsystemsFor(MG_Config::ActiveBackendType);
|
||||||
|
AssertConsumerMaskIsHonest(consumed);
|
||||||
|
session.SetConsumedSubsystems(consumed);
|
||||||
|
// ZERO IS THE EXPLICIT ANSWER FOR P5, not an omission (ServerSession.h's block):
|
||||||
|
// every optional capability bit belongs to the package that owns its question, and
|
||||||
|
// withholding one leaves the legacy path running, which is the safe direction.
|
||||||
|
// kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes must be 0 for the whole of P5
|
||||||
|
// by ruling - they are the only two things that ask for an MGHostSpan, and 0 is
|
||||||
|
// what keeps every one of them out of the first IPC frame (contract table 0).
|
||||||
|
session.SetCapabilityBits(0);
|
||||||
|
session.SetBackend(loop.Backend());
|
||||||
|
|
||||||
|
// 3. the handshake, the four segments, and - at its end - the apply thread.
|
||||||
|
const MobileGLResult started =
|
||||||
|
Client::ClientSessionInstance().Start(MG_Config::Transport, MG_Config::TransportEndpoint);
|
||||||
|
if (started != MOBILEGL_OK) {
|
||||||
|
MGLOG_E("MG_Remote: the split session failed to start (rc=%d); MobileGL will not "
|
||||||
|
"fall back to monolith - a lane named split that ran monolith is the one "
|
||||||
|
"failure this phase is built to make impossible",
|
||||||
|
static_cast<int>(started));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. and only now the CLIENT's backend object in the one global that holds it.
|
||||||
|
// Table 3: pActiveBackendObject holds BackendObject_Remote and the server's
|
||||||
|
// BackendObject_DirectGLES stays private to ServerLoop.
|
||||||
|
pActiveBackendObject = MG_Remote::Client::CreateRemoteBackendObject();
|
||||||
|
if (!pActiveBackendObject) {
|
||||||
|
MGLOG_E("MG_Remote: CreateRemoteBackendObject returned null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void ShutdownSplitRoles() {
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||||
|
// ClientSession::Stop IS table 3's whole order and it is idempotent: publish and wait
|
||||||
|
// for the server to drain (bounded - a lost record must be a red lane, not a hung
|
||||||
|
// exit), Doorbell::Kill through the transport's Shutdown, ServerLoop::Stop's bounded
|
||||||
|
// join - which also destroys the server's private BackendObject ON the apply thread
|
||||||
|
// while it still owns the context - the transport, and only THEN anything an emitter
|
||||||
|
// owns. A var-tail still named by an unapplied record is a use-after-free the join is
|
||||||
|
// what prevents, which is why the order is not a preference.
|
||||||
|
MG_Remote::Client::ClientSessionInstance().Stop();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void Init() {
|
void Init() {
|
||||||
MGLOG_D("Initializing MobileGL Backend...");
|
MGLOG_D("Initializing MobileGL Backend...");
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// THE SINGLE HOOK. In a build without MOBILEGL_BUILD_DISAGGREGATED, MG_Config::Transport
|
||||||
|
// is a `constexpr Monolith` (Config.h) and this whole statement is discarded, so the
|
||||||
|
// pull build gains no symbol, no branch and no byte - which is what G1 measures.
|
||||||
|
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||||
|
if (!InitSplitRoles()) {
|
||||||
|
// NOT a fallback to the switch. pActiveBackendObject stays null and the next GL
|
||||||
|
// call fails loudly, which is the only honest outcome: the operator asked for a
|
||||||
|
// transport this process could not bring up.
|
||||||
|
pActiveBackendObject = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Bool remoteResult = InitSpecificBackendLibs();
|
||||||
|
if (!remoteResult) {
|
||||||
|
MGLOG_W("Failed to initialize MobileGL backend libraries for the remote object");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogBackendInfo();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (MG_Config::ActiveBackendType) {
|
switch (MG_Config::ActiveBackendType) {
|
||||||
case BackendType::DirectGLES:
|
case BackendType::DirectGLES:
|
||||||
pActiveBackendObject = MakeUnique<DirectGLES::BackendObject_DirectGLES>();
|
pActiveBackendObject = MakeUnique<DirectGLES::BackendObject_DirectGLES>();
|
||||||
|
|||||||
Reference in New Issue
Block a user