mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
[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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ----
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user