[Feat] (MG_Remote, P5b/t2): emit the four stream-output span rows, the XFB object bind and the patch parameter, and give each ServerVerbSink body its backend call

This commit is contained in:
2026-09-16 12:23:57 -04:00
parent c240557fe0
commit dd293203ca
3 changed files with 254 additions and 22 deletions
+156 -8
View File
@@ -16,6 +16,12 @@
// changes class without changing the arithmetic is a build break rather than a behaviour // changes class without changing the arithmetic is a build break rather than a behaviour
// change nobody reviewed. // change nobody reviewed.
// //
// P5b MOVES SLOTS FROM C TO B, ONE PACKAGE AT A TIME (MG_Remote/CONTRACT-P5B.md §7). The three
// numbers above are the partition AT THE P5b CONTRACT COMMIT and they are the ones the contract
// states; the arithmetic below is what the tree currently has, and the per-package ownership
// assertions say which package moved which slot. On this head t2 has landed: class B is 5 + 6
// and class C is 58.
//
// THE PRE-VERB HOOKS RUN BEFORE THE RECORD, NEVER AFTER (b1, ID-18). PushPersistentMapsBeforeVerb // THE PRE-VERB HOOKS RUN BEFORE THE RECORD, NEVER AFTER (b1, ID-18). PushPersistentMapsBeforeVerb
// publishes the bytes an application wrote through a coherent map with no API call at all, and // publishes the bytes an application wrote through a coherent map with no API call at all, and
// MarkGpuWritesForDraw builds the conservative GPU-write set the client now owns. Both describe // MarkGpuWritesForDraw builds the conservative GPU-write set the client now owns. Both describe
@@ -428,6 +434,132 @@ namespace MobileGL::MG_Remote::Client {
session.PumpControlPlane(); session.PumpControlPlane();
} }
// =============================================================================
// CLASS B - P5b package t2: the transform-feedback spans, the XFB object bind and the
// tessellation patch parameter (MG_Remote/CONTRACT-P5B.md §2 t2).
// =============================================================================
//
// SIX SLOTS, SIX ROWS, AND NOT ONE OF THEM STARTS A SHADER. Every one of the six is a
// control call - it opens, closes, pauses, resumes or re-targets a capture span, or sets
// the patch size the next tessellation draw uses - so each takes BeforeReadOnlyVerb(),
// which is CONTRACT-P5B.md §4's rule for "a verb that reads buffers but starts no
// shader" naming the XFB and patch controls by hand. The GPU-WRITE MARK FOR THE CAPTURE
// TARGETS IS NOT TAKEN HERE and that is deliberate: it belongs at the END of the span,
// after the record and before GLContext::EndTransformFeedback clears the live bindings,
// which is exactly where b1 already put it (GL_Drawing.cpp's
// MarkEndTransformFeedbackCaptureTargets). Taking it here as well would mark the same
// buffers twice and taking it INSTEAD of there would mark nothing.
//
// RULE D (CONTRACT-P5B.md §0): each record carries the GL arguments the frontend handed
// the backend slot and nothing that is a READING of them. The capture program, the
// capture-buffer bindings, the patch state and the bound XFB object all stay
// BARRIER-PULLED - the server's backend reads the client's gPipeInputs fill of the
// moment, which MGP_FILL at each call site has just written and the verb barrier holds
// still (R-1). That is why these are six two-line emitters and not an XFB protocol.
//
// WHAT MUST HAVE CROSSED BEFORE begin_stream_output, since it is the ordering question
// this package was asked: the capture buffers' own resource records (emitted at their
// own call sites through the resource family, long before this point), the program
// (the CSO/program family, likewise), and the buffer BINDINGS - which do not cross as a
// record at all, because set_stream_output_targets (39) has no producer and no consumer
// and CONTRACT-P5B.md §2 rules it NOT required for t2: under the barrier the server's
// StartPendingTransformFeedback reads them through the kXfbSpan/kDraw pulls
// (GetTransformFeedbackProgram, GetBufferBindingPoint). Producing that row is P9's.
void EmitBeginTransformFeedback(GLenum primitiveMode) {
ClientSession& session = RequireSession("BeginTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPStreamOutputBegin record{};
// The GL token verbatim (contract table 0's "GL enums on the wire"): the sink hands
// it to the backend slot that takes it, and nothing between here and there reads it.
record.PrimitiveMode = static_cast<Uint32>(primitiveMode);
session.EmitAndWait(MG_Pipe::MGPWireOp::BeginStreamOutput, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitEndTransformFeedback() {
ClientSession& session = RequireSession("EndTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPXfbAccounting record{};
// THE ACCOUNTING IS THE CLIENT'S OWN AND IT IS INFORMATIONAL ON THIS SIDE OF THE
// WIRE: end_stream_output's backend call takes no arguments, and the three numbers
// are what the frontend has counted over this span (CONTRACT-P5B.md §2 t2, the
// companions row). They travel because the row has carried them since P4a and
// because they are what a server-side scatter would need when P9 lands one; the
// sink today calls GL.EndTransformFeedback() and reads none of them. Read here,
// BEFORE GLContext::EndTransformFeedback resets the counters at the call site.
const auto& context = *MG_State::pGLContext;
record.CapturedVertices = context.GetTransformFeedbackCapturedVertices();
record.PrimitivesWritten = context.GetTransformFeedbackPrimitiveCounter();
record.PrimitiveMode = static_cast<Uint32>(context.GetTransformFeedbackPrimitiveMode());
session.EmitAndWait(MG_Pipe::MGPWireOp::EndStreamOutput, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitPauseTransformFeedback() {
ClientSession& session = RequireSession("PauseTransformFeedback");
BeforeReadOnlyVerb();
// Reserved IS zero and the contract says so (MGPStreamOutputControl{Reserved = 0}).
// The row exists to BE the verb boundary - the stamp the server puts up before the
// sink runs - not to carry anything.
MG_Pipe::MGPStreamOutputControl record{};
record.Reserved = 0;
session.EmitAndWait(MG_Pipe::MGPWireOp::PauseStreamOutput, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitResumeTransformFeedback() {
ClientSession& session = RequireSession("ResumeTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPStreamOutputControl record{};
record.Reserved = 0;
session.EmitAndWait(MG_Pipe::MGPWireOp::ResumeStreamOutput, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitBindTransformFeedback(GLuint name) {
ClientSession& session = RequireSession("BindTransformFeedback");
BeforeReadOnlyVerb();
MG_Pipe::MGPStreamOutputBind record{};
// THE GL NAME IS NOT AN IDENTITY (ARCHITECTURE 4.2.1) and is carried anyway, because
// it is the key the backend has always used: Espryt indexes its driver objects by it
// (XfbImpl::g_xfbObjects[name], DirectGLES.cpp:1401) and generates the ES object on
// first bind. Name 0 is the default object, which is why the field is not a handle.
record.GlName = static_cast<Uint32>(name);
// Beside it, the identity that WILL dispatch: the frontend's per-object lifetime id,
// process-wide and never reused, which is what survives glGenTransformFeedbacks
// recycling a name. Read AFTER GLContext::BindTransformFeedbackObject at the call
// site, so it is the id of the object being bound and not of the previous one.
record.LifetimeId = MG_State::pGLContext->GetBoundTransformFeedbackLifetimeId();
session.EmitAndWait(MG_Pipe::MGPWireOp::BindStreamOutput, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
void EmitPatchParameteri(GLenum pname, GLint value) {
ClientSession& session = RequireSession("PatchParameteri");
BeforeReadOnlyVerb();
MG_Pipe::MGPPatchParameter record{};
// GL_PATCH_VERTICES is the only pname that reaches a backend slot - the frontend
// answers GL_PATCH_DEFAULT_*_LEVEL itself and bakes those into the synthesized
// control stage - and the frontend has already rejected every other pname with
// INVALID_ENUM before this call (GL_Drawing.cpp's PatchParameteri). Carried verbatim
// so the sink reproduces the call rather than a reading of it.
record.Pname = static_cast<Uint32>(pname);
record.Value = static_cast<Int32>(value);
// set_patch_state (43) STILL TRAVELS, at the next validate, and that is not a
// duplicate: it is the applier's working-block copy, this is the driver push Espryt
// does AT THE CALL (DirectGLES.cpp:8760), and both pushes happen today on the
// monolith path too (CONTRACT-P5B.md §2 t2).
session.EmitAndWait(MG_Pipe::MGPWireOp::PatchParameter, &record, sizeof(record),
nullptr, 0, nullptr, 0, nullptr);
}
// ============================================================================= // =============================================================================
// CLASS A - answered locally from the caps mirror (R-15). NO RECORD, EVER. // CLASS A - answered locally from the caps mirror (R-15). NO RECORD, EVER.
// ============================================================================= // =============================================================================
@@ -530,13 +662,14 @@ namespace MobileGL::MG_Remote::Client {
X(MemoryBarrierByRegion, void, (GLbitfield)) \ X(MemoryBarrierByRegion, void, (GLbitfield)) \
X(ShaderStorageBlockBinding, void, (GLuint, const GLchar*, GLuint)) X(ShaderStorageBlockBinding, void, (GLuint, const GLchar*, GLuint))
// t2 LANDED (CONTRACT-P5B.md §2 t2): the three measured slots - BeginTransformFeedback
// (95 lane entries), PatchParameteri (43), BindTransformFeedback (2) - and the three
// companions that share their rows are class B now and live in the block above.
// DeleteTransformFeedback is the one that stays: it has NO ROW in P5b, by ruling and
// not by omission (unmeasured; the driver object leaks on the server until P9's XFB
// namespace work, and a bind of name 0 is what the backend does on delete of the bound
// one, DirectGLES.cpp:1422). It therefore still aborts by its own name.
#define MGR_UNMIGRATED_T2_SLOTS(X) \ #define MGR_UNMIGRATED_T2_SLOTS(X) \
X(PatchParameteri, void, (GLenum, GLint)) \
X(BeginTransformFeedback, void, (GLenum)) \
X(EndTransformFeedback, void, ()) \
X(PauseTransformFeedback, void, ()) \
X(ResumeTransformFeedback, void, ()) \
X(BindTransformFeedback, void, (GLuint)) \
X(DeleteTransformFeedback, void, (GLuint)) X(DeleteTransformFeedback, void, (GLuint))
#define MGR_UNMIGRATED_F1_SLOTS(X) \ #define MGR_UNMIGRATED_F1_SLOTS(X) \
@@ -645,7 +778,9 @@ namespace MobileGL::MG_Remote::Client {
constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
constexpr Uint32 kEmittedSlotsD1 = 0; constexpr Uint32 kEmittedSlotsD1 = 0;
constexpr Uint32 kEmittedSlotsI1 = 0; constexpr Uint32 kEmittedSlotsI1 = 0;
constexpr Uint32 kEmittedSlotsT2 = 0; // t2: BeginTransformFeedback, EndTransformFeedback, PauseTransformFeedback,
// ResumeTransformFeedback, BindTransformFeedback, PatchParameteri.
constexpr Uint32 kEmittedSlotsT2 = 6;
constexpr Uint32 kEmittedSlotsF1 = 0; constexpr Uint32 kEmittedSlotsF1 = 0;
constexpr Uint32 kEmittedSlots = constexpr Uint32 kEmittedSlots =
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1; kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1;
@@ -660,7 +795,10 @@ namespace MobileGL::MG_Remote::Client {
static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots"); static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots");
static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots"); static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots");
static_assert(kUnmigratedTail == 20, "the wave-3 tail is 20 slots and no P5b package owns one"); static_assert(kUnmigratedTail == 20, "the wave-3 tail is 20 slots and no P5b package owns one");
static_assert(kUnmigratedSlots == 64, "CONTRACT-P5.md §7 class C is 64 slots at the P5b contract commit"); // 64 at the P5b contract commit, minus the six t2 flipped. CONTRACT-P5.md §7's number
// is the one above, not this one; this is the arithmetic after t2 and it moves again
// for every package that lands.
static_assert(kUnmigratedSlots == 58, "class C is 58 slots after t2's six");
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount, static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
"the three classes no longer partition the 71 slots"); "the three classes no longer partition the 71 slots");
@@ -697,6 +835,16 @@ namespace MobileGL::MG_Remote::Client {
table.GL.ReadPixels = &EmitReadPixels; table.GL.ReadPixels = &EmitReadPixels;
table.GL.BlitFramebuffer = &EmitBlitFramebuffer; table.GL.BlitFramebuffer = &EmitBlitFramebuffer;
table.Present = &EmitPresent; table.Present = &EmitPresent;
// ---- class B, P5b t2. Assigned AFTER the class-C block above, which is what makes
// the flip a single-line change per slot: the Fatal thunk is overwritten, and a slot
// whose row is removed from MGR_UNMIGRATED_T2_SLOTS but not assigned here would be
// NULL and caught by RemoteEmitTable.NoSlotIsNull rather than silently skipped.
table.GL.BeginTransformFeedback = &EmitBeginTransformFeedback;
table.GL.EndTransformFeedback = &EmitEndTransformFeedback;
table.GL.PauseTransformFeedback = &EmitPauseTransformFeedback;
table.GL.ResumeTransformFeedback = &EmitResumeTransformFeedback;
table.GL.BindTransformFeedback = &EmitBindTransformFeedback;
table.GL.PatchParameteri = &EmitPatchParameteri;
return table; return table;
} }
+80 -11
View File
@@ -382,35 +382,104 @@ namespace MobileGL::MG_Remote::Server {
ServerUnmigratedVerbFatal("ShaderStorageBlockBinding"); ServerUnmigratedVerbFatal("ShaderStorageBlockBinding");
} }
// ---- t2 ---- // ---- t2 ---- (MG_Remote/CONTRACT-P5B.md §2 t2)
//
// SIX BODIES, SIX BACKEND CALLS, NO STATE OF THEIR OWN. Rule D: the record IS the call, and
// everything the backend reads around it - the capture program, the capture-buffer
// bindings, the bound XFB object, the patch state - it reads from gPipeInputs through its
// verb class's BARRIER-PULLED fields, which the client filled at the call site and the
// verb barrier holds still (R-1). That is why none of these touches m_backend beyond
// Table() and why not one of them caches anything across records.
//
// A NULL SLOT DECLINES, AND THE DECLINE IS THE MONOLITH'S ANSWER IN THE SAME WORDS. Magma
// (DirectVulkan) registers NO XFB slot and no PatchParameteri at all
// (BackendObject_DirectVulkan.cpp), and under monolith the frontend's own
// `if (const auto f = table.GL.X)` guard simply skips the call; `return false` here is that
// same skip, reported to DecodeAndApply as "this build did not apply it" rather than as a
// crash or as a silent success. Contract §2 t2 says so for PatchParameteri by name.
Bool ServerVerbSink::OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) { Bool ServerVerbSink::OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) {
(void)begin; const MG_Backend::GlobalBackendFunctionsTable* table = Table("begin_stream_output");
ServerUnmigratedVerbFatal("BeginTransformFeedback"); if (table == nullptr) return false;
if (table->GL.BeginTransformFeedback == nullptr) return false;
// Espryt's Begin only ARMS the span (DirectGLES.cpp:1212-1220: primitiveMode, pending,
// targets cleared); the driver glBeginTransformFeedback happens in the tail of the next
// PrepareForDraw (StartPendingTransformFeedback, :1224), where the capture program and
// the buffer bindings are read through the pulls. So this record's effect is not
// visible until a DRAW crosses - which is why an XFB scenario whose draw is still class
// C moves its first blocker to that draw rather than rendering.
table->GL.BeginTransformFeedback(static_cast<GLenum>(begin.PrimitiveMode));
++m_streamOutputSpans;
return true;
} }
Bool ServerVerbSink::OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) { Bool ServerVerbSink::OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) {
const MG_Backend::GlobalBackendFunctionsTable* table = Table("end_stream_output");
if (table == nullptr) return false;
if (table->GL.EndTransformFeedback == nullptr) return false;
// THE THREE ACCOUNTING FIELDS ARE NOT READ, AND THAT IS THE RULING RATHER THAN AN
// OMISSION. glEndTransformFeedback takes no arguments; the numbers are the CLIENT's own
// per-span accounting (contract §2 t2's companions row) and the client is where they are
// consumed - by the primitive queries and by the capture-capacity clamp. A server that
// second-guessed them from its own driver would be publishing a second answer to a
// question the frontend already answers, and the second answer is the one that goes
// stale. They cross because the row has carried them since P4a and because P9's
// server-side scatter is what will need them.
(void)accounting; (void)accounting;
ServerUnmigratedVerbFatal("EndTransformFeedback"); table->GL.EndTransformFeedback();
++m_streamOutputSpans;
return true;
} }
Bool ServerVerbSink::OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) { Bool ServerVerbSink::OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control; const MG_Backend::GlobalBackendFunctionsTable* table = Table("pause_stream_output");
ServerUnmigratedVerbFatal("PauseTransformFeedback"); if (table == nullptr) return false;
if (table->GL.PauseTransformFeedback == nullptr) return false;
(void)control; // Reserved, and the contract says it is 0.
table->GL.PauseTransformFeedback();
++m_streamOutputControls;
return true;
} }
Bool ServerVerbSink::OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) { Bool ServerVerbSink::OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
const MG_Backend::GlobalBackendFunctionsTable* table = Table("resume_stream_output");
if (table == nullptr) return false;
if (table->GL.ResumeTransformFeedback == nullptr) return false;
(void)control; (void)control;
ServerUnmigratedVerbFatal("ResumeTransformFeedback"); table->GL.ResumeTransformFeedback();
++m_streamOutputControls;
return true;
} }
Bool ServerVerbSink::OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) { Bool ServerVerbSink::OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) {
(void)bind; const MG_Backend::GlobalBackendFunctionsTable* table = Table("bind_stream_output");
ServerUnmigratedVerbFatal("BindTransformFeedback"); if (table == nullptr) return false;
if (table->GL.BindTransformFeedback == nullptr) return false;
// THE GL NAME IS THE ARGUMENT, NOT THE LifetimeId BESIDE IT. Espryt keys its driver
// objects by the GL name (XfbImpl::g_xfbObjects[name], DirectGLES.cpp:1401) and creates
// the ES object on first bind; passing the lifetime id would index a map that has never
// heard of it and silently create a second driver object per bind. The lifetime id
// travels as the identity P7/P9 will dispatch on once the XFB namespace has a wire
// lifetime of its own - it has no reader on this side today, and pretending otherwise
// by folding it into the key is exactly the "a GL name is never an identity" confusion
// the contract's GlName row is written against.
table->GL.BindTransformFeedback(static_cast<GLuint>(bind.GlName));
++m_streamOutputBinds;
return true;
} }
Bool ServerVerbSink::OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) { Bool ServerVerbSink::OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) {
(void)patch; const MG_Backend::GlobalBackendFunctionsTable* table = Table("patch_parameter");
ServerUnmigratedVerbFatal("PatchParameteri"); if (table == nullptr) return false;
// Magma registers no PatchParameteri: it compiles the patch size into its synthesized
// control stage from set_patch_state instead, so the DECLINE below is the whole of the
// right answer for that backend and not a gap (contract §2 t2).
if (table->GL.PatchParameteri == nullptr) return false;
// Pname is GL_PATCH_VERTICES and the frontend has already rejected every other spelling
// with INVALID_ENUM before the record was built, so this is a forward and not a switch.
table->GL.PatchParameteri(static_cast<GLenum>(patch.Pname), static_cast<GLint>(patch.Value));
++m_patchParameters;
return true;
} }
// ---- f1 ---- // ---- f1 ----
+18 -3
View File
@@ -121,9 +121,9 @@ namespace MobileGL::MG_Remote::Server {
// i1 OnLaunchGrid ("DispatchCompute"), OnMemoryBarrier, OnResourceCopyRegion // i1 OnLaunchGrid ("DispatchCompute"), OnMemoryBarrier, OnResourceCopyRegion
// ("CopyImageSubData"), OnBindShaderImage ("BindImageTexture"), // ("CopyImageSubData"), OnBindShaderImage ("BindImageTexture"),
// OnSetStorageBlockBinding ("ShaderStorageBlockBinding") // OnSetStorageBlockBinding ("ShaderStorageBlockBinding")
// t2 OnBeginStreamOutput / OnEndStreamOutput / OnPauseStreamOutput / // t2 LANDED. OnBeginStreamOutput / OnEndStreamOutput / OnPauseStreamOutput /
// OnResumeStreamOutput ("*TransformFeedback"), OnBindStreamOutput // OnResumeStreamOutput / OnBindStreamOutput / OnPatchParameter are real bodies
// ("BindTransformFeedback"), OnPatchParameter ("PatchParameteri") // now: the backend call the contract names, the null-slot DECLINE, a tally.
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture ("CopyTexImage2D" / // f1 OnGenerateMipmap, OnCopyFramebufferToTexture ("CopyTexImage2D" /
// "CopyTexSubImage2D"), and OnClear's four non-Whole kinds (live already) // "CopyTexSubImage2D"), and OnClear's four non-Whole kinds (live already)
// d1 OnDrawVbo above: the indirect tail, the user-index span, NumDraws > 1 and the // d1 OnDrawVbo above: the indirect tail, the user-index span, NumDraws > 1 and the
@@ -158,6 +158,17 @@ namespace MobileGL::MG_Remote::Server {
// DstSize is exactly the heap overflow codex 1 found, one field over. // DstSize is exactly the heap overflow codex 1 found, one field over.
Uint64 ReadbackScratchBytes() const { return static_cast<Uint64>(m_readbackScratch.size()); } Uint64 ReadbackScratchBytes() const { return static_cast<Uint64>(m_readbackScratch.size()); }
// P5b t2's tallies, for the same reason the five above exist (R-16): under split "the
// scenario passed" is also what a scenario that ran entirely on the monolith path looks
// like, so a lane that wants to say the XFB spans CROSSED has to read a counter the
// server moved. Spans counts Begin and End together - they are one span and a lane that
// saw only one of them has a bug the two-counter version would have hidden behind a
// sum; controls counts Pause and Resume; binds and patch parameters count their own.
Uint64 StreamOutputSpans() const { return m_streamOutputSpans; }
Uint64 StreamOutputControls() const { return m_streamOutputControls; }
Uint64 StreamOutputBinds() const { return m_streamOutputBinds; }
Uint64 PatchParameters() const { return m_patchParameters; }
private: private:
const MG_Backend::GlobalBackendFunctionsTable* Table(const char* verb) const; const MG_Backend::GlobalBackendFunctionsTable* Table(const char* verb) const;
@@ -169,6 +180,10 @@ namespace MobileGL::MG_Remote::Server {
Uint64 m_presents = 0; Uint64 m_presents = 0;
Uint64 m_lastPresentSerial = 0; Uint64 m_lastPresentSerial = 0;
Uint64 m_readbackBytes = 0; Uint64 m_readbackBytes = 0;
Uint64 m_streamOutputSpans = 0;
Uint64 m_streamOutputControls = 0;
Uint64 m_streamOutputBinds = 0;
Uint64 m_patchParameters = 0;
// ReadPixels' destination. The pixels go into the reply slot, but GLFunctionsTable:: // ReadPixels' destination. The pixels go into the reply slot, but GLFunctionsTable::
// ReadPixels writes into a caller buffer, so one staging vector per session sits // ReadPixels writes into a caller buffer, so one staging vector per session sits
// between them. Grown, never shrunk, and never handed out past the call. // between them. Grown, never shrunk, and never handed out past the call.