mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-11 12:03:24 +09:00
[Feat] (MG_Remote, P5): land the client role - BackendObject_Remote, the 71-slot emit table, the caps mirror and the verb barrier
This commit is contained in:
@@ -6,49 +6,161 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// P5 c0 stubs for package c1. Every body is MGLOG_F + std::abort and never a silent no-op: a
|
||||
// caps accessor that answers a default is how a split lane runs on the wrong device's limits.
|
||||
// P5 package c1.
|
||||
//
|
||||
// EVERY ACCESSOR ANSWERS BEFORE THE FIRST SNAPSHOT, AND THAT IS THE RULING, NOT A WEAKENING.
|
||||
// c0's stubs aborted on all but Valid()/Generation() so that nothing could answer from a zeroed
|
||||
// mirror by accident. But scout-caps-reply §1.2's option (a) - the one the brief adopts - says
|
||||
// LogBackendInfo() reads GetRendererInfo() at MG_Backend/Init.cpp:21, during MG_Backend::Init(),
|
||||
// BEFORE any surface exists, and that P5 accepts one imprecise startup log line rather than
|
||||
// restructure MG_Backend::Init(). An abort there is not a stricter mirror; it is a process that
|
||||
// cannot start. So the mirror answers a placeholder and SAYS SO, once per accessor: a wrong
|
||||
// number that announced itself is auditable, and the announcement is what a case asserts on.
|
||||
//
|
||||
// THE PLACEHOLDER IS A REAL OBJECT, NOT A TEMPORARY. GetRendererInfo() and
|
||||
// GetDynamicParameters() return const references, so the storage has to outlive every caller;
|
||||
// the members below are it, default-constructed, and Adopt() replaces them in place.
|
||||
|
||||
#include "CapsMirror.h"
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include "../CapsCodec.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
#define MGP5_C0_STUB(what) \
|
||||
do { \
|
||||
MGLOG_F("MGPipe: Fatal{UnimplementedCapsMirror, \"%s\"} - P5 package c1 has not landed " \
|
||||
"this yet; c0 shipped the signature only", \
|
||||
what); \
|
||||
std::abort(); \
|
||||
} while (0)
|
||||
namespace {
|
||||
CapsAdoptedHook g_capsAdoptedHook = nullptr;
|
||||
|
||||
void CapsMirror::Adopt(const MG_Pipe::MGPCaps&, const MG_Backend::FormatCapabilityCache&,
|
||||
const RendererInfo&, const String&, BackendType) {
|
||||
MGP5_C0_STUB("CapsMirror::Adopt");
|
||||
// R-8's negative control, counted at the funnel. Not atomics: every reader of this
|
||||
// mirror is the GL thread, by the same argument that makes one gPipeInputs legal.
|
||||
Uint64 g_consumerRefusals = 0;
|
||||
Uint64 g_lastRefusedSubsystem = 0;
|
||||
Uint64 g_refusedSubsystemsLogged = 0;
|
||||
|
||||
// One line per accessor, once, and only while the mirror is a placeholder. MGLOG_W_ONCE
|
||||
// keys on the call site, so each accessor gets its own line and a hot getter cannot
|
||||
// flood the log.
|
||||
void WarnPlaceholder(const char* what, Uint64 generation) {
|
||||
if (generation != 0) return;
|
||||
MGLOG_W("MG_Remote client: CapsMirror::%s read before the first CapsSnapshot - "
|
||||
"answering a PLACEHOLDER. Expected exactly once at startup, from "
|
||||
"LogBackendInfo (MG_Backend/Init.cpp:21); anything later means a caps read "
|
||||
"beat the handshake",
|
||||
what);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CapsMirror::Adopt(const MG_Pipe::MGPCaps& caps, const MG_Backend::FormatCapabilityCache& formats,
|
||||
const RendererInfo& renderer, const String& apiVersion,
|
||||
BackendType backend) {
|
||||
m_caps = caps;
|
||||
// The two blobrefs inside MGPCaps name SEG_STAGE runs that belong to the SERVER's
|
||||
// encoder and are meaningless on this side; the decoded objects beside them are the
|
||||
// answer. Clearing them is not tidiness - a later reader that resolved one would read
|
||||
// whatever the stage allocator has since put there.
|
||||
m_caps.FormatCapabilities = MG_Pipe::MGPBlobRef{};
|
||||
m_caps.RendererInfo = MG_Pipe::MGPBlobRef{};
|
||||
m_formats = formats;
|
||||
m_renderer = renderer;
|
||||
m_apiVersion = apiVersion;
|
||||
m_backend = backend;
|
||||
++m_generation;
|
||||
|
||||
// R-12: RE-ARRIVAL IS THE INVALIDATION, and this is the one place that acts on it.
|
||||
// GLContext::GetCompileEnv() memoises on the raw pActiveBackendObject pointer
|
||||
// (Core.cpp:34); under split that pointer is the single long-lived BackendObject_Remote
|
||||
// and NEVER changes, so without this line the compile env, its preprocess memos and the
|
||||
// advertised-extension list stay stale for ever after a server-side InitCapabilities
|
||||
// re-run. DirectVulkan already spells the monolith half of this exactly this way
|
||||
// (BackendObject_DirectVulkan.cpp:390).
|
||||
if (m_generation > 1 && MG_State::pGLContext != nullptr) {
|
||||
MG_State::pGLContext->InvalidateCompileEnv();
|
||||
}
|
||||
MGLOG_I("MG_Remote client: CapsMirror generation %llu adopted (backend=%d, callMask=0x%llx)",
|
||||
static_cast<unsigned long long>(m_generation), static_cast<int>(m_backend),
|
||||
static_cast<unsigned long long>(m_caps.CallMask));
|
||||
|
||||
// LAST, and after the generation has moved: the hook reads this mirror.
|
||||
if (g_capsAdoptedHook != nullptr) g_capsAdoptedHook();
|
||||
}
|
||||
|
||||
// Not stubs: the two the placeholder contract above promises are readable before the first
|
||||
// snapshot. Everything else aborts, so nothing can accidentally answer from a zeroed mirror.
|
||||
void SetCapsAdoptedHook(CapsAdoptedHook hook) { g_capsAdoptedHook = hook; }
|
||||
|
||||
Bool CapsMirror::Valid() const { return m_generation != 0; }
|
||||
Uint64 CapsMirror::Generation() const { return m_generation; }
|
||||
|
||||
const RendererInfo& CapsMirror::Renderer() const { MGP5_C0_STUB("CapsMirror::Renderer"); }
|
||||
const RendererInfo& CapsMirror::Renderer() const {
|
||||
WarnPlaceholder("Renderer", m_generation);
|
||||
return m_renderer;
|
||||
}
|
||||
|
||||
const MG_Backend::DynamicBackendParameters& CapsMirror::Dynamic() const {
|
||||
MGP5_C0_STUB("CapsMirror::Dynamic");
|
||||
WarnPlaceholder("Dynamic", m_generation);
|
||||
return m_caps.Dynamic;
|
||||
}
|
||||
|
||||
const MG_Backend::FormatCapabilityCache& CapsMirror::Formats() const {
|
||||
MGP5_C0_STUB("CapsMirror::Formats");
|
||||
WarnPlaceholder("Formats", m_generation);
|
||||
return m_formats;
|
||||
}
|
||||
const String& CapsMirror::ApiVersion() const { MGP5_C0_STUB("CapsMirror::ApiVersion"); }
|
||||
BackendType CapsMirror::Backend() const { MGP5_C0_STUB("CapsMirror::Backend"); }
|
||||
Uint64 CapsMirror::CallMask() const { MGP5_C0_STUB("CapsMirror::CallMask"); }
|
||||
Bool CapsMirror::HasCap(MG_Pipe::MGPCapBit) const { MGP5_C0_STUB("CapsMirror::HasCap"); }
|
||||
Bool CapsMirror::ServerConsumes(Uint64) const { MGP5_C0_STUB("CapsMirror::ServerConsumes"); }
|
||||
|
||||
const String& CapsMirror::ApiVersion() const {
|
||||
WarnPlaceholder("ApiVersion", m_generation);
|
||||
return m_apiVersion;
|
||||
}
|
||||
|
||||
BackendType CapsMirror::Backend() const {
|
||||
WarnPlaceholder("Backend", m_generation);
|
||||
return m_backend;
|
||||
}
|
||||
|
||||
Uint64 CapsMirror::CallMask() const { return m_caps.CallMask; }
|
||||
|
||||
Bool CapsMirror::HasCap(MG_Pipe::MGPCapBit bit) const {
|
||||
return (m_caps.CallMask & static_cast<Uint64>(bit)) != 0;
|
||||
}
|
||||
|
||||
Bool CapsMirror::ServerConsumes(Uint64 subsystemBit) const {
|
||||
// THE ONLY LEGAL CLIENT-SIDE SPELLING (R-8). Never MGPipeGetResourceOps(): that is the
|
||||
// SERVER's registration, a process-wide global, right by accident under inproc and null
|
||||
// under spawn - and a null read there silently stops five record families while the
|
||||
// client goes on clearing its dirty flags, which is ID-39's 66 lost uploads with a wire
|
||||
// in between. Folded through CapsCodec.h's helper rather than shifted here, because two
|
||||
// spellings of one bit layout is how the two sides come to disagree about it.
|
||||
//
|
||||
// A PLACEHOLDER MIRROR CONSUMES NOTHING, and that is the safe direction: with no
|
||||
// snapshot the mask is 0, every family answers "no consumer", the client emits nothing
|
||||
// and the LEGACY PULL PATH runs untouched. The unsafe direction - emitting to a server
|
||||
// that has no consumer - is the one that loses uploads.
|
||||
const Bool consumes = MGCapsServerConsumes(m_caps.CallMask, subsystemBit);
|
||||
if (!consumes) {
|
||||
++g_consumerRefusals;
|
||||
g_lastRefusedSubsystem = subsystemBit;
|
||||
if ((g_refusedSubsystemsLogged & subsystemBit) == 0) {
|
||||
g_refusedSubsystemsLogged |= subsystemBit;
|
||||
MGLOG_W("MG_Remote client: the server does not consume MGPipe subsystem 0x%llx - "
|
||||
"this family emits NOTHING and the legacy pull path runs for it "
|
||||
"(R-8). callMask=0x%llx, caps generation %llu",
|
||||
static_cast<unsigned long long>(subsystemBit),
|
||||
static_cast<unsigned long long>(m_caps.CallMask),
|
||||
static_cast<unsigned long long>(m_generation));
|
||||
}
|
||||
}
|
||||
return consumes;
|
||||
}
|
||||
|
||||
Uint64 ConsumerRefusals() { return g_consumerRefusals; }
|
||||
Uint64 LastRefusedSubsystem() { return g_lastRefusedSubsystem; }
|
||||
void ResetConsumerRefusalsForTest() {
|
||||
g_consumerRefusals = 0;
|
||||
g_lastRefusedSubsystem = 0;
|
||||
g_refusedSubsystemsLogged = 0;
|
||||
}
|
||||
|
||||
Bool CapsMirror::PrefersCpuXfbPrimitiveAccounting() const {
|
||||
MGP5_C0_STUB("CapsMirror::PrefersCpuXfbPrimitiveAccounting");
|
||||
return HasCap(MG_Pipe::kCapCpuXfbPrimitiveAccounting);
|
||||
}
|
||||
|
||||
CapsMirror& CapsMirrorInstance() {
|
||||
@@ -58,6 +170,4 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return instance;
|
||||
}
|
||||
|
||||
#undef MGP5_C0_STUB
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Client
|
||||
|
||||
Reference in New Issue
Block a user