mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +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:
@@ -31,6 +31,11 @@
|
||||
#include <MG_Util/ShaderTranspiler/Types.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
// CONTRACT-P5.md §7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the
|
||||
// client under split - it becomes a caps-mirror read. SlotCaps.h carries the rule and the test
|
||||
// that decides which of its two spellings a site takes; in a pull build both expand to exactly
|
||||
// the check they replaced.
|
||||
#include <MG_Remote/Client/SlotCaps.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Declared rather than #included from GL_RenderState.h on purpose: that header also declares
|
||||
@@ -1354,7 +1359,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// full 64-bit GPU timestamp survives; LWJGL reads it this way.
|
||||
Int64 timestamp = 0;
|
||||
if (!MG_Config::Features.DisableTimerQuery) {
|
||||
if (const auto getGpuTimestampNs = MG_Backend::gBackendFunctionsTable.GL.GetGpuTimestampNs) {
|
||||
// glGetInteger64v(GL_TIMESTAMP). GetGpuTimestampNs is class C - a LIVE GPU
|
||||
// timestamp is not a static property, so R-15 does not reach it - and the
|
||||
// documented answer when it is unavailable is 0 (BackendObject.h:192), which
|
||||
// is correct rather than merely quiet. kCapTimerQuery is the published bit.
|
||||
//
|
||||
// The POINTER-valued macro, so the init-statement below is unchanged in a pull
|
||||
// build and G1 cannot see this edit: the Bool-valued spelling moved this
|
||||
// function by -150 bytes for no behavioural reason at all.
|
||||
if (const auto getGpuTimestampNs =
|
||||
MGL_BACKEND_SLOT_PTR_CAP(GetGpuTimestampNs, MG_Pipe::kCapTimerQuery)) {
|
||||
MGP_FILL(GetGpuTimestampNs);
|
||||
timestamp = getGpuTimestampNs();
|
||||
}
|
||||
@@ -2265,7 +2279,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_TIMESTAMP: {
|
||||
Int64 timestamp = 0;
|
||||
if (!MG_Config::Features.DisableTimerQuery) {
|
||||
if (const auto getGpuTimestampNs = MG_Backend::gBackendFunctionsTable.GL.GetGpuTimestampNs) {
|
||||
// glGetInteger64v(GL_TIMESTAMP). GetGpuTimestampNs is class C - a LIVE GPU
|
||||
// timestamp is not a static property, so R-15 does not reach it - and the
|
||||
// documented answer when it is unavailable is 0 (BackendObject.h:192), which
|
||||
// is correct rather than merely quiet. kCapTimerQuery is the published bit.
|
||||
//
|
||||
// The POINTER-valued macro, so the init-statement below is unchanged in a pull
|
||||
// build and G1 cannot see this edit: the Bool-valued spelling moved this
|
||||
// function by -150 bytes for no behavioural reason at all.
|
||||
if (const auto getGpuTimestampNs =
|
||||
MGL_BACKEND_SLOT_PTR_CAP(GetGpuTimestampNs, MG_Pipe::kCapTimerQuery)) {
|
||||
MGP_FILL(GetGpuTimestampNs);
|
||||
timestamp = getGpuTimestampNs();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
// CONTRACT-P5.md §7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the
|
||||
// client under split - it becomes a caps-mirror read, whatever class the slot itself is in.
|
||||
// The two macros carry that rule; in a pull build each expands to exactly the check it replaced.
|
||||
#include <MG_Remote/Client/SlotCaps.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace {
|
||||
@@ -478,7 +482,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const Bool isOcclusionQuery =
|
||||
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||
MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery);
|
||||
const Bool isPipelineStatisticsQuery = IsPipelineStatisticsQueryTarget(target);
|
||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery &&
|
||||
!isPipelineStatisticsQuery) {
|
||||
@@ -528,10 +532,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
} else if (isTransformFeedbackQuery) {
|
||||
// Prefer real GPU transform-feedback queries (exact with geometry shaders);
|
||||
// the CPU accounting delta stays as the fallback when the backend lacks them.
|
||||
const Bool xfbQuerySupported =
|
||||
MGL_BACKEND_SLOT_CAP(BeginXfbPrimitivesQuery, MG_Pipe::kCapXfbPrimitivesQuery);
|
||||
const auto beginXfbPrimitivesQuery = MG_Backend::gBackendFunctionsTable.GL.BeginXfbPrimitivesQuery;
|
||||
MGP_FILL(BeginXfbPrimitivesQuery);
|
||||
queryObject->backendHandle =
|
||||
beginXfbPrimitivesQuery ? beginXfbPrimitivesQuery(target == GL_PRIMITIVES_GENERATED) : nullptr;
|
||||
xfbQuerySupported ? beginXfbPrimitivesQuery(target == GL_PRIMITIVES_GENERATED) : nullptr;
|
||||
queryObject->counterSnapshot = TransformFeedbackCounterForTarget(target);
|
||||
queryObject->accountedCaptureDrawSnapshot =
|
||||
MG_State::pGLContext->GetTransformFeedbackAccountedCaptureDraws();
|
||||
@@ -541,10 +547,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MGP_FILL(BeginOcclusionQuery);
|
||||
queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery();
|
||||
} else {
|
||||
const Bool timerQuerySupported =
|
||||
MGL_BACKEND_SLOT_CAP(BeginTimeElapsedQuery, MG_Pipe::kCapTimerQuery);
|
||||
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||
MGP_FILL(BeginTimeElapsedQuery);
|
||||
queryObject->backendHandle =
|
||||
(!TimerQueryDisabled() && beginTimeElapsedQuery) ? beginTimeElapsedQuery() : nullptr;
|
||||
(!TimerQueryDisabled() && timerQuerySupported) ? beginTimeElapsedQuery() : nullptr;
|
||||
}
|
||||
activeQueryId = id;
|
||||
}
|
||||
@@ -555,7 +563,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const Bool isOcclusionQuery =
|
||||
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||
MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery);
|
||||
const Bool isPipelineStatisticsQuery = IsPipelineStatisticsQueryTarget(target);
|
||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery &&
|
||||
!isPipelineStatisticsQuery) {
|
||||
@@ -657,7 +665,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
ResetQueryObjectLocked(queryObject); // discard any previous result
|
||||
queryObject->target = target;
|
||||
const auto queryCounterTimestamp = MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp;
|
||||
const Bool timerQuerySupported =
|
||||
MGL_BACKEND_SLOT_CAP(QueryCounterTimestamp, MG_Pipe::kCapTimerQuery);
|
||||
const auto queryCounterTimestamp =
|
||||
timerQuerySupported ? MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp : nullptr;
|
||||
MGP_FILL(QueryCounterTimestamp);
|
||||
queryObject->backendHandle =
|
||||
(!TimerQueryDisabled() && queryCounterTimestamp) ? queryCounterTimestamp() : nullptr;
|
||||
@@ -782,7 +793,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
if (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||
const Bool occlusionSupported = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||
const Bool occlusionSupported = MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery);
|
||||
*params = occlusionSupported ? (target == GL_SAMPLES_PASSED ? 32 : 1) : 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
// CONTRACT-P5.md §7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the
|
||||
// client under split - it becomes a caps-mirror read. SlotCaps.h carries the rule and the test
|
||||
// that decides which of its two spellings a site takes; in a pull build both expand to exactly
|
||||
// the check they replaced.
|
||||
#include <MG_Remote/Client/SlotCaps.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace {
|
||||
@@ -56,7 +61,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
auto* syncObject = new SyncObject;
|
||||
syncObject->condition = condition;
|
||||
syncObject->flags = flags;
|
||||
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||
// The family's ONE gate. FenceSync is class C under split, and "absent" is the
|
||||
// answer the whole fallback chain below is written against: every later site already
|
||||
// checks syncObject->backendHandle, which stays null from here. The POINTER-valued
|
||||
// macro keeps the init-statement byte-identical in a pull build (G1).
|
||||
if (const auto backendFenceSync = MGL_BACKEND_SLOT_PTR_LOCAL(FenceSync)) {
|
||||
MGP_FILL(FenceSync);
|
||||
syncObject->backendHandle = backendFenceSync();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
#include <MG_Util/Math/FixedPointConversion.h>
|
||||
#include <MG_State/GLState/TextureState/TextureObjectBuffer.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
// CONTRACT-P5.md §7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the
|
||||
// client under split - it becomes a caps-mirror read. SlotCaps.h carries the rule and the test
|
||||
// that decides which of its two spellings a site takes; in a pull build both expand to exactly
|
||||
// the check they replaced.
|
||||
#include <MG_Remote/Client/SlotCaps.h>
|
||||
// P4a, ID-18 M2. The ONE door MG_State and MG_Impl have into the client's emitters; the three
|
||||
// call sites below are declarations only, exactly as the frontend's mutators are.
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
@@ -6534,7 +6539,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLenum type, GLsizei bufSize, void* pixels, const char* caller) {
|
||||
if (MG_Backend::pActiveBackendObject != nullptr &&
|
||||
MG_Backend::pActiveBackendObject->GetBackendType() == BackendType::DirectVulkan &&
|
||||
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage != nullptr) {
|
||||
MGL_BACKEND_SLOT_LOCAL(GetTextureImage)) {
|
||||
MGP_FILL(GetTextureImage);
|
||||
MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type,
|
||||
bufSize, pixels);
|
||||
@@ -6796,7 +6801,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
|
||||
void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {
|
||||
if (!GetTexImage_State(target, level, format, type, pixels)) return;
|
||||
if (MG_Backend::gBackendFunctionsTable.GL.GetTexImage != nullptr) {
|
||||
if (MGL_BACKEND_SLOT_LOCAL(GetTexImage)) {
|
||||
GetTexImage_Backend(target, level, format, type, pixels);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,15 @@
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
#include <Config.h>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// R-8 (c1): the client's liveness gates read the caps mirror, never MGPipeGetResourceOps().
|
||||
// Behind the build option for G1's reason - nothing under MG_Remote may be reachable from a
|
||||
// pull build - and every use below is additionally gated on the resolved TRANSPORT, because
|
||||
// build-split runs MOBILEGL_TRANSPORT=monolith in every unit and integration-gpu lane and those
|
||||
// lanes must keep answering exactly what they answered before.
|
||||
#include <MG_Remote/Client/CapsMirror.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -609,12 +618,38 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// R-8 (c1). THE SECOND CONJUNCT MOVES UNDER SPLIT, AND ONLY UNDER SPLIT.
|
||||
//
|
||||
// `MGPipeGetResourceOps() != nullptr` asks "has a backend registered the consumer". That
|
||||
// table is the SERVER's registration and it is a PROCESS-WIDE global (PipeApply.cpp:402):
|
||||
// under inproc a client reading it answers correctly BY ACCIDENT, and under spawn the
|
||||
// client process has no backend at all, so the read answers null and five record families
|
||||
// stop emitting - silently, while the emitters go on clearing their per-level dirty flags
|
||||
// on the acceptance they never asked for. That is ID-39's 66 lost DirectVulkan uploads with
|
||||
// a wire in between. The client asks the caps mirror instead, which carries the answer the
|
||||
// SERVER gave at the handshake (CallMask bits 32..47).
|
||||
//
|
||||
// s1 made the server end Fatal when nobody sets the mask; this is the client end.
|
||||
Bool MGPipeResourceSubsystemEnabled() {
|
||||
return (MG_Config::Features.PipePush & kMGPipeSubsystemResources) != 0 &&
|
||||
MGPipeGetResourceOps() != nullptr;
|
||||
if ((MG_Config::Features.PipePush & kMGPipeSubsystemResources) == 0) return false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
return MG_Remote::Client::CapsMirrorInstance().ServerConsumes(kMGPipeSubsystemResources);
|
||||
}
|
||||
#endif
|
||||
return MGPipeGetResourceOps() != nullptr;
|
||||
}
|
||||
|
||||
Bool MGPipeResourceOpsHaveSubDataResident() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
// CONTRACT-P5.md §7's THIRD NAMED CAPABILITY PROBE. `ops->SubDataResident != nullptr`
|
||||
// is not a safety check - it decides whether the resident-upload path EXISTS - and
|
||||
// under split there is no op table here to probe. kCapResidentSubData is the bit the
|
||||
// server publishes for exactly this question.
|
||||
return MG_Remote::Client::CapsMirrorInstance().HasCap(kCapResidentSubData);
|
||||
}
|
||||
#endif
|
||||
const MGPipeResourceOps* ops = MGPipeGetResourceOps();
|
||||
return ops != nullptr && ops->SubDataResident != nullptr;
|
||||
}
|
||||
@@ -936,8 +971,21 @@ namespace MobileGL::MG_Pipe {
|
||||
// MGPipeEmitResourceRespecify above uses for buffers) publishes it. Nothing here needs
|
||||
// to remember the window.
|
||||
Bool P4aFamilyHasItsConsumer(Uint64 subsystem) {
|
||||
return (subsystem & kMGPipeP4aFamilySubsystems) == 0 ||
|
||||
MGPipeGetResourceOps() != nullptr;
|
||||
if ((subsystem & kMGPipeP4aFamilySubsystems) == 0) return true;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// R-8 (c1), the same move as MGPipeResourceSubsystemEnabled's and for the same
|
||||
// reason. ALL FOUR FAMILIES RIDE THE ONE SIGNAL, exactly as they do in monolith:
|
||||
// the paragraph above explains why the resource consumer IS the texture family's
|
||||
// consumer, and the split spelling of "a backend registered the resource op table"
|
||||
// is "the server published the resource subsystem's consumer bit". Asking per
|
||||
// family here would be a NEW rule, and a client that withheld more than the server
|
||||
// refuses leaves the server's handle arm live with no records to read.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
return MG_Remote::Client::CapsMirrorInstance().ServerConsumes(
|
||||
kMGPipeSubsystemResources);
|
||||
}
|
||||
#endif
|
||||
return MGPipeGetResourceOps() != nullptr;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
|
||||
Reference in New Issue
Block a user