[Feat] (MG_Remote, P5b): decode every P5b row to a WireVerbSink method, stub each ServerVerbSink body with a named UnmigratedVerb Fatal, and partition the emit table class-C list per migration package

This commit is contained in:
2026-09-16 11:15:45 -04:00
parent 4dd4027e80
commit a164113029
6 changed files with 485 additions and 107 deletions
+102 -39
View File
@@ -470,11 +470,30 @@ namespace MobileGL::MG_Remote::Client {
// CLASS C - Fatal{UnmigratedVerb}. 64 slots: 63 in GLFunctionsTable + SetSwapInterval.
// =============================================================================
//
// The list is an X-macro so the DEFINITION and the ASSIGNMENT cannot drift apart, and
// so the count is arithmetic rather than a comment. Two of them carry a pre-verb hook
// before the Fatal - see the note on DispatchCompute.
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
// ~/w7/notes/p5b/BRIEF-P5B.md), so that four packages migrating in parallel edit four
// DISJOINT lists and four DISJOINT counts rather than one list and one number. To flip a
// slot a package (1) removes its X row from ITS list, (2) assigns the real emitter in
// BuildRemoteEmitTable's class-B block, (3) raises ITS kEmittedSlots* by one. The
// per-package ownership assertions below then still hold, the totals stay arithmetic,
// and a slot that changes class without changing the arithmetic is a build break. The
// X-macro shape is kept so the DEFINITION and the ASSIGNMENT cannot drift apart. Three
// slots carry a body the macro cannot (DispatchCompute, DispatchComputeIndirect,
// SetSwapInterval) and are written out by hand below.
//
// d1 indexed / instanced / multi-draw / indirect draws -> draw_vbo (59), its
// kDrawIsIndirect tail and its kDrawHasUserIndices span
// i1 image bind, compute, barriers, copy-image, storage block -> bind_shader_image
// (72), launch_grid (60), memory_barrier (61), resource_copy_region (53),
// set_storage_block_binding (75)
// t2 the XFB spans and object bind, the patch parameter -> begin/end/pause/resume_
// stream_output (62..65), bind_stream_output (74), patch_parameter (73)
// f1 the clear family, the framebuffer-sourced copies, mips -> clear (57),
// copy_framebuffer_to_texture (76), generate_mipmap (54)
// tail the wave-3 remainder nothing measured: queries, syncs, the texture readbacks,
// the DSA blit, the swap interval (census-classC.md "static cross")
#define MGR_UNMIGRATED_GL_SLOTS(X) \
#define MGR_UNMIGRATED_D1_SLOTS(X) \
X(DrawElements, void, (GLenum, GLsizei, GLenum, const void*)) \
X(DrawElementsBaseVertex, void, (GLenum, GLsizei, GLenum, const void*, GLint)) \
X(MultiDrawArrays, void, (GLenum, const GLint*, const GLsizei*, GLsizei)) \
@@ -497,7 +516,30 @@ namespace MobileGL::MG_Remote::Client {
X(DrawArraysInstancedBaseInstance, void, (GLenum, GLint, GLsizei, GLsizei, GLuint)) \
X(DrawArraysInstanced, void, (GLenum, GLint, GLsizei, GLsizei)) \
X(DrawElementsIndirect, void, (GLenum, GLenum, const void*)) \
X(DrawArraysIndirect, void, (GLenum, const void*)) \
X(DrawArraysIndirect, void, (GLenum, const void*))
// DispatchCompute and DispatchComputeIndirect are i1's too; they are hand-written below
// because they carry b1's dispatch hook before the Fatal.
#define MGR_UNMIGRATED_I1_SLOTS(X) \
X(BindImageTexture, void, (GLuint, GLuint, GLint, GLboolean, GLint, GLenum, GLenum)) \
X(CopyImageSubData, void, \
(const MG_Backend::CopyImageEndpoint&, GLenum, GLint, GLint, GLint, GLint, \
const MG_Backend::CopyImageEndpoint&, GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, \
GLsizei)) \
X(MemoryBarrier, void, (GLbitfield)) \
X(MemoryBarrierByRegion, void, (GLbitfield)) \
X(ShaderStorageBlockBinding, void, (GLuint, const GLchar*, GLuint))
#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))
#define MGR_UNMIGRATED_F1_SLOTS(X) \
X(ClearBufferfi, void, (GLenum, GLint, GLfloat, GLint)) \
X(ClearBufferfv, void, (GLenum, GLint, const GLfloat*)) \
X(ClearBufferuiv, void, (GLenum, GLint, const GLuint*)) \
@@ -510,44 +552,33 @@ namespace MobileGL::MG_Remote::Client {
(const SharedPtr<MG_State::GLState::FramebufferObject>&, GLenum, GLint, const GLint*)) \
X(ClearNamedFramebufferuiv, void, \
(const SharedPtr<MG_State::GLState::FramebufferObject>&, GLenum, GLint, const GLuint*)) \
X(CopyTexImage2D, void, (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint)) \
X(CopyTexSubImage2D, void, (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei)) \
X(GenerateMipmap, void, (GLenum))
// The wave-3 tail. SetSwapInterval is hand-written below (it is not a GL.* slot).
#define MGR_UNMIGRATED_TAIL_SLOTS(X) \
X(BlitNamedFramebuffer, void, \
(const SharedPtr<MG_State::GLState::FramebufferObject>&, \
const SharedPtr<MG_State::GLState::FramebufferObject>&, GLint, GLint, GLint, GLint, GLint, \
GLint, GLint, GLint, GLbitfield, GLenum)) \
X(CopyTexImage2D, void, (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint)) \
X(CopyTexSubImage2D, void, (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei)) \
X(CopyImageSubData, void, \
(const MG_Backend::CopyImageEndpoint&, GLenum, GLint, GLint, GLint, GLint, \
const MG_Backend::CopyImageEndpoint&, GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, \
GLsizei)) \
X(GenerateMipmap, void, (GLenum)) \
X(GetTexImage, void, (GLenum, GLint, GLenum, GLenum, GLvoid*)) \
X(GetTextureImage, void, \
(const SharedPtr<MG_State::GLState::ITextureObject>&, TextureUploadTarget, GLint, GLenum, \
GLenum, GLsizei, GLvoid*)) \
X(MemoryBarrier, void, (GLbitfield)) \
X(MemoryBarrierByRegion, void, (GLbitfield)) \
X(BindImageTexture, void, (GLuint, GLuint, GLint, GLboolean, GLint, GLenum, GLenum)) \
X(ShaderStorageBlockBinding, void, (GLuint, const GLchar*, GLuint)) \
X(WaitSync, void, (MG_Backend::BackendSyncHandle, GLbitfield, GLuint64)) \
X(DeleteSync, void, (MG_Backend::BackendSyncHandle)) \
X(EndTimeElapsedQuery, void, (MG_Backend::BackendQueryHandle)) \
X(DeleteBackendQuery, void, (MG_Backend::BackendQueryHandle)) \
X(EndOcclusionQuery, void, (MG_Backend::BackendQueryHandle)) \
X(EndXfbPrimitivesQuery, void, (MG_Backend::BackendQueryHandle)) \
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(EndXfbPrimitivesQuery, void, (MG_Backend::BackendQueryHandle))
// The non-void ones, kept apart only because the macro body differs: a [[noreturn]]
// call is a complete body for a void slot and for a value-returning one alike, but a
// compiler that does not see UnmigratedVerbFatal's attribute through the macro would
// warn on the second. It does see it; they are split for readability.
#define MGR_UNMIGRATED_GL_VALUE_SLOTS(X) \
// warn on the second. It does see it; they are split for readability. All ten are the
// wave-3 tail.
#define MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X) \
X(FenceSync, MG_Backend::BackendSyncHandle, ()) \
X(ClientWaitSync, GLenum, (MG_Backend::BackendSyncHandle, GLbitfield, GLuint64)) \
X(GetSyncStatus, Bool, (MG_Backend::BackendSyncHandle)) \
@@ -559,6 +590,16 @@ namespace MobileGL::MG_Remote::Client {
X(BeginXfbPrimitivesQuery, MG_Backend::BackendQueryHandle, (Bool)) \
X(GetGpuTimestampNs, Int64, ())
// The union, for the places that want every class-C row at once (the definitions and
// the assignments). A package never edits THIS; it edits its own list above.
#define MGR_UNMIGRATED_GL_SLOTS(X) \
MGR_UNMIGRATED_D1_SLOTS(X) \
MGR_UNMIGRATED_I1_SLOTS(X) \
MGR_UNMIGRATED_T2_SLOTS(X) \
MGR_UNMIGRATED_F1_SLOTS(X) \
MGR_UNMIGRATED_TAIL_SLOTS(X)
#define MGR_UNMIGRATED_GL_VALUE_SLOTS(X) MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X)
#define MGR_DEFINE_UNMIGRATED(Name, Ret, Sig) \
Ret Name##_Unmigrated Sig { UnmigratedVerbFatal(#Name); }
@@ -568,11 +609,11 @@ namespace MobileGL::MG_Remote::Client {
// THE TWO COMPUTE SLOTS CARRY b1's DISPATCH HOOK BEFORE THE FATAL, and this is stated
// rather than hidden. MarkGpuWritesForDispatch() belongs immediately before the
// dispatch record, and the dispatch record is class C in P5 - so the call site is here,
// in the right place, and is UNREACHABLE-IN-EFFECT: the abort follows it. There is no
// gate on it and this file says so; the phase that moves DispatchCompute into class B
// replaces the Fatal and inherits a call site that is already correct rather than
// discovering that the mark walk was never wired.
// dispatch record, and the dispatch record is class C until i1 lands - so the call site
// is here, in the right place, and is UNREACHABLE-IN-EFFECT: the abort follows it. The
// package that moves DispatchCompute into class B (i1: launch_grid, opcode 60) replaces
// the Fatal and inherits a call site that is already correct rather than discovering
// that the mark walk was never wired.
void DispatchCompute_Unmigrated(GLuint, GLuint, GLuint) {
PushPersistentMapsBeforeVerb();
MarkGpuWritesForDispatch();
@@ -588,16 +629,38 @@ namespace MobileGL::MG_Remote::Client {
// The counts, as arithmetic. MGR_COUNT_ONE expands to `+ 1` per row.
#define MGR_COUNT_ONE(Name, Ret, Sig) +1
constexpr Uint32 kUnmigratedListedSlots =
0 MGR_UNMIGRATED_GL_SLOTS(MGR_COUNT_ONE) MGR_UNMIGRATED_GL_VALUE_SLOTS(MGR_COUNT_ONE);
constexpr Uint32 kUnmigratedD1 = 0 MGR_UNMIGRATED_D1_SLOTS(MGR_COUNT_ONE);
// + DispatchCompute, DispatchComputeIndirect, written out by hand.
constexpr Uint32 kUnmigratedI1 = 0 MGR_UNMIGRATED_I1_SLOTS(MGR_COUNT_ONE) + 2;
constexpr Uint32 kUnmigratedT2 = 0 MGR_UNMIGRATED_T2_SLOTS(MGR_COUNT_ONE);
constexpr Uint32 kUnmigratedF1 = 0 MGR_UNMIGRATED_F1_SLOTS(MGR_COUNT_ONE);
// + SetSwapInterval, written out by hand.
constexpr Uint32 kUnmigratedTail =
0 MGR_UNMIGRATED_TAIL_SLOTS(MGR_COUNT_ONE) MGR_UNMIGRATED_TAIL_VALUE_SLOTS(MGR_COUNT_ONE) + 1;
#undef MGR_COUNT_ONE
// + DispatchCompute, DispatchComputeIndirect, SetSwapInterval, written out by hand
// because they carry a body the macro cannot.
constexpr Uint32 kUnmigratedSlots = kUnmigratedListedSlots + 3;
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
constexpr Uint32 kEmittedSlots = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
constexpr Uint32 kUnmigratedSlots =
kUnmigratedD1 + kUnmigratedI1 + kUnmigratedT2 + kUnmigratedF1 + kUnmigratedTail;
static_assert(kUnmigratedSlots == 64, "CONTRACT-P5.md §7 class C is 64 slots");
// The emitted counts, PER OWNER. P5's five are c1's; each P5b package raises its own.
constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
constexpr Uint32 kEmittedSlotsD1 = 0;
constexpr Uint32 kEmittedSlotsI1 = 0;
constexpr Uint32 kEmittedSlotsT2 = 0;
constexpr Uint32 kEmittedSlotsF1 = 0;
constexpr Uint32 kEmittedSlots =
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1;
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
// EACH PACKAGE'S OWNERSHIP, PINNED. A package that flips a slot removes one row and
// adds one to its emitted count; a package that touches another's list breaks the
// other's line, not its own. The four numbers are the census's package tables plus the
// unmeasured companions that share a wire row (BRIEF-P5B.md file-ownership table).
static_assert(kUnmigratedD1 + kEmittedSlotsD1 == 19, "d1 owns the 19 draw slots");
static_assert(kUnmigratedI1 + kEmittedSlotsI1 == 7, "i1 owns the 7 image/compute/barrier/copy/SSBO 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(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");
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
"the three classes no longer partition the 71 slots");
+14 -15
View File
@@ -58,25 +58,24 @@
#include <Includes.h>
#include <MG_Backend/BackendObject.h>
// P5b: the clear discriminants below alias MGPipeTypes.h's (CONTRACT-P5B.md f1). The header
// was already in this file's closure through BackendObject.h's neighbours; naming it makes
// the dependency the aliases have explicit.
#include <MG_Pipe/MGPipeTypes.h>
#include <MG_Pipe/MGPipeValueTypes.h>
namespace MobileGL::MG_Remote::Client {
// MGPClear::Kind. MGPipeTypes.h:1273 states the list as a COMMENT - "Whole | Color | Depth
// | Stencil | DepthStencil" - and mints no enumerator, because until P5 the record had no
// producer. These are the values, in that comment's own order, and they are here rather
// than in MGPipeTypes.h because that file is c0's and this phase produces exactly ONE of
// them: glClear is the only entry point that reaches the Clear slot (the four
// glClearBuffer* and the four glClearNamedFramebuffer* are class C). v1's
// WireVerbSink::OnClear must therefore Fatal on anything but Whole rather than guess, and
// the phase that migrates the other eight moves these into the contract.
enum MGRemoteClearKind : Uint32 {
kRemoteClearWhole = 0,
kRemoteClearColor = 1,
kRemoteClearDepth = 2,
kRemoteClearStencil = 3,
kRemoteClearDepthStencil = 4,
};
// MGPClear::Kind and ::ValueClass. P5b MOVED THE NUMBERS INTO MGPipeTypes.h
// (kMGPipeClearKind* / kMGPipeClearValueClass*, CONTRACT-P5B.md f1) - the single spelling
// this header and the server's PipeApplier.h each said they were waiting for. These are
// aliases so c1's EmitClear reads unchanged; f1's four ClearBuffer* emitters and the DSA
// form name the MG_Pipe constants directly.
inline constexpr Uint32 kRemoteClearWhole = MG_Pipe::kMGPipeClearKindWhole;
inline constexpr Uint32 kRemoteClearColor = MG_Pipe::kMGPipeClearKindColor;
inline constexpr Uint32 kRemoteClearDepth = MG_Pipe::kMGPipeClearKindDepth;
inline constexpr Uint32 kRemoteClearStencil = MG_Pipe::kMGPipeClearKindStencil;
inline constexpr Uint32 kRemoteClearDepthStencil = MG_Pipe::kMGPipeClearKindDepthStencil;
// The table MG_Backend::Init() installs into gBackendFunctionsTable for the remote role.
// A reference to a never-destroyed block, like every other MG_Remote singleton (ID-8).
+111 -19
View File
@@ -269,21 +269,36 @@ namespace MobileGL::MG_Remote::Server {
return true;
}
// P5b's server-side stub shape (CONTRACT-P5B.md): the same line the client's class-C table
// raises (EmitTables.cpp UnmigratedVerbFatal) and the same family the census greps, so a
// slot flipped on the client ahead of its server half aborts BY NAME on the apply thread
// rather than rendering nothing. Named "(server sink)" in the message so a log reader can
// tell which half is missing.
[[noreturn]] static void ServerUnmigratedVerbFatal(const char* slot) {
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"%s\"} (server sink: the record crossed and "
"ServerVerbSink has no body for it yet - CONTRACT-P5B.md names the package)",
slot);
std::abort();
}
Bool ServerVerbSink::OnDrawVbo(const MG_Pipe::MGPDrawInfo& info,
const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices) {
const MG_Pipe::MGHostSpan* userIndices,
const MG_Pipe::MGPDrawIndirect* indirect) {
const MG_Backend::GlobalBackendFunctionsTable* table = Table("draw_vbo");
if (table == nullptr) return false;
// P5b d1 (CONTRACT-P5B.md): the three arms the contract gives this sink and P5 did not
// implement are DECLINED BY NAME until d1 lands them - the same names the census greps,
// so the lane's first-blocker table reads the server's gap as the slot it is.
if (indirect != nullptr) {
ServerUnmigratedVerbFatal(info.IndexSize == 0 ? "MultiDrawArraysIndirect"
: "MultiDrawElementsIndirect");
}
if (userIndices != nullptr) {
// kCapNeedsHostIndexBytes is 0 for the whole of P5 by ruling (table 0's cap-bit
// row) precisely so this tail never appears; a span that arrived anyway means the
// client's cap gate did not hold, and filling one is P8's.
MGLOG_E_ONCE("MG_Remote server: draw_vbo carries an MGHostSpan of user indices. P5 "
"rules kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes to 0 so that "
"no host span reaches the first IPC frame (contract table 0); filling "
"one under split is P8's. The draw is DECLINED rather than drawn from "
"a pointer that does not belong to this process");
return false;
// The span is validated and names a SEG_STAGE run the client staged (d1's rule for
// client-side index arrays); resolving it is MG_Pipe::MGPipeHostBytes and passing
// the pointer to gl.DrawElements is d1's body. Declined by name until then.
ServerUnmigratedVerbFatal("DrawElements+CLIENT_INDICES");
}
if (ranges == nullptr || info.NumDraws == 0) return false;
@@ -291,17 +306,16 @@ namespace MobileGL::MG_Remote::Server {
// draw_vbo collapses all twenty draw entry points, and picking the right one needs the
// instancing / base-vertex / base-instance / multi-draw cross product. TriangleScenario
// is a single non-instanced array draw and OpenRA's are single indexed draws from a
// bound element buffer; the rest are P8's, together with the MGPDrawIndirect record
// that has no producer yet.
// bound element buffer; the rest are d1's (CONTRACT-P5B.md d1 says which GL entry each
// shape of the record dispatches to).
const MG_Backend::GLFunctionsTable& gl = table->GL;
const Bool instanced = info.InstanceCount > 1 || info.StartInstance != 0;
if (info.NumDraws != 1 || instanced) {
MGLOG_E_ONCE("MG_Remote server: draw_vbo with NumDraws=%u InstanceCount=%u "
"StartInstance=%u is DECLINED - P5's reduced path is the single "
"non-instanced draw (BRIEF 4); the multi-draw and instanced arms are "
"P8's",
info.NumDraws, info.InstanceCount, info.StartInstance);
return false;
if (info.NumDraws != 1) {
ServerUnmigratedVerbFatal(info.IndexSize == 0 ? "MultiDrawArrays" : "MultiDrawElements");
}
if (instanced) {
ServerUnmigratedVerbFatal(info.IndexSize == 0 ? "DrawArraysInstanced"
: "DrawElementsInstanced");
}
const MG_Pipe::MGPDrawRange& range = ranges[0];
if (info.IndexSize == 0) {
@@ -331,6 +345,84 @@ namespace MobileGL::MG_Remote::Server {
return true;
}
// -----------------------------------------------------------------------------------
// P5b: the stubs the four migration packages replace (MG_Remote/CONTRACT-P5B.md).
//
// Each dies by the GL slot's own name. The record has crossed and been validated by the
// codec by the time one of these runs, so the only thing missing is the backend call, and
// the package that owns the row writes it here: `Table("<row>")`, the null-slot check
// (a backend that leaves the slot null DECLINES, which is the monolith's null-slot answer
// in the same words), the call, and a tally the lane can assert moved.
// -----------------------------------------------------------------------------------
// ---- i1 ----
Bool ServerVerbSink::OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
(void)grid;
ServerUnmigratedVerbFatal(grid.IsIndirect ? "DispatchComputeIndirect" : "DispatchCompute");
}
Bool ServerVerbSink::OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) {
ServerUnmigratedVerbFatal(barrier.ByRegion ? "MemoryBarrierByRegion" : "MemoryBarrier");
}
Bool ServerVerbSink::OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) {
(void)copy;
ServerUnmigratedVerbFatal("CopyImageSubData");
}
Bool ServerVerbSink::OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) {
(void)bind;
ServerUnmigratedVerbFatal("BindImageTexture");
}
Bool ServerVerbSink::OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
const char* name) {
(void)binding;
(void)name;
ServerUnmigratedVerbFatal("ShaderStorageBlockBinding");
}
// ---- t2 ----
Bool ServerVerbSink::OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) {
(void)begin;
ServerUnmigratedVerbFatal("BeginTransformFeedback");
}
Bool ServerVerbSink::OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) {
(void)accounting;
ServerUnmigratedVerbFatal("EndTransformFeedback");
}
Bool ServerVerbSink::OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control;
ServerUnmigratedVerbFatal("PauseTransformFeedback");
}
Bool ServerVerbSink::OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control;
ServerUnmigratedVerbFatal("ResumeTransformFeedback");
}
Bool ServerVerbSink::OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) {
(void)bind;
ServerUnmigratedVerbFatal("BindTransformFeedback");
}
Bool ServerVerbSink::OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) {
(void)patch;
ServerUnmigratedVerbFatal("PatchParameteri");
}
// ---- f1 ----
Bool ServerVerbSink::OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) {
(void)plan;
ServerUnmigratedVerbFatal("GenerateMipmap");
}
Bool ServerVerbSink::OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) {
ServerUnmigratedVerbFatal(copy.SubImage ? "CopyTexSubImage2D" : "CopyTexImage2D");
}
// -----------------------------------------------------------------------------------
// PipeApplier
// -----------------------------------------------------------------------------------
+47 -22
View File
@@ -72,27 +72,19 @@ namespace MobileGL::MG_Remote::Server {
// ---- MGPClear's two discriminants ---------------------------------------------------
//
// MGPClear (MGPipeTypes.h:1271) names `Kind` "Whole | Color | Depth | Stencil |
// DepthStencil" and `ValueClass` "Float | Int | Uint" IN A COMMENT AND NOWHERE ELSE: the
// catalogue ships no enum for either, and the record has no producer or consumer in the
// tree, so P5 writes both halves and the two halves have to agree on a number. Declaring
// them here rather than open-coding 0..4 on each side is table 0's own rule for exactly
// this shape ("a decoder that open-codes it is the class-1 defect"), applied to a field
// table 0 did not reach.
//
// THE ORDER IS THE COMMENT'S, LEFT TO RIGHT, and ValueClass reuses the numbering
// MG_State/GLState/Core.h:39-41 already gives the identical three-way split on
// MGPAttribValue::ValueClass. c1 encodes against these constants; a disagreement is a
// clear of the wrong attachment with the wrong value type, which renders plausibly.
// FLAGGED FOR THE INTEGRATOR: this belongs in MGPipeTypes.h, which is c0's file.
inline constexpr Uint32 kMGPClearKindWhole = 0; // glClear(mask)
inline constexpr Uint32 kMGPClearKindColor = 1; // glClearBuffer{f,i,ui}v(GL_COLOR, i, v)
inline constexpr Uint32 kMGPClearKindDepth = 2; // glClearBufferfv(GL_DEPTH, 0, &d)
inline constexpr Uint32 kMGPClearKindStencil = 3; // glClearBufferiv(GL_STENCIL, 0, &s)
inline constexpr Uint32 kMGPClearKindDepthStencil = 4; // glClearBufferfi(GL_DEPTH_STENCIL,...)
inline constexpr Uint32 kMGPClearValueClassFloat = 0;
inline constexpr Uint32 kMGPClearValueClassInt = 1;
inline constexpr Uint32 kMGPClearValueClassUint = 2;
// P5b MOVED THEM INTO MGPipeTypes.h (kMGPipeClearKind* / kMGPipeClearValueClass*), which is
// where this file said they belonged: through P5 the numbers lived here and in the client's
// EmitTables.h as two hand-minted copies, and a disagreement between them is a clear of the
// wrong attachment with the wrong value type, which renders plausibly. These are ALIASES so
// v1's bodies read unchanged; new code names the MG_Pipe constants directly.
inline constexpr Uint32 kMGPClearKindWhole = MG_Pipe::kMGPipeClearKindWhole;
inline constexpr Uint32 kMGPClearKindColor = MG_Pipe::kMGPipeClearKindColor;
inline constexpr Uint32 kMGPClearKindDepth = MG_Pipe::kMGPipeClearKindDepth;
inline constexpr Uint32 kMGPClearKindStencil = MG_Pipe::kMGPipeClearKindStencil;
inline constexpr Uint32 kMGPClearKindDepthStencil = MG_Pipe::kMGPipeClearKindDepthStencil;
inline constexpr Uint32 kMGPClearValueClassFloat = MG_Pipe::kMGPipeClearValueClassFloat;
inline constexpr Uint32 kMGPClearValueClassInt = MG_Pipe::kMGPipeClearValueClassInt;
inline constexpr Uint32 kMGPClearValueClassUint = MG_Pipe::kMGPipeClearValueClassUint;
// ---- the five class-B verbs' consumer ------------------------------------------------
//
@@ -116,7 +108,40 @@ namespace MobileGL::MG_Remote::Server {
Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq,
Wire::ReplySink* replies) override;
Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices) override;
const MG_Pipe::MGHostSpan* userIndices,
const MG_Pipe::MGPDrawIndirect* indirect) override;
// ---- P5b (MG_Remote/CONTRACT-P5B.md): one override per row a migration package owns.
// At the contract commit EVERY BODY BELOW IS A STUB that dies
// Fatal{UnmigratedVerb, "<GL slot>"} by the slot's own name - the same line the client's
// class-C table raises and the census greps - so a client flipped ahead of its server
// half aborts by name rather than rendering nothing, and the census on this head is
// unchanged (the client refuses first). The owning package replaces the body.
//
// i1 OnLaunchGrid ("DispatchCompute"), OnMemoryBarrier, OnResourceCopyRegion
// ("CopyImageSubData"), OnBindShaderImage ("BindImageTexture"),
// OnSetStorageBlockBinding ("ShaderStorageBlockBinding")
// t2 OnBeginStreamOutput / OnEndStreamOutput / OnPauseStreamOutput /
// OnResumeStreamOutput ("*TransformFeedback"), OnBindStreamOutput
// ("BindTransformFeedback"), OnPatchParameter ("PatchParameteri")
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture ("CopyTexImage2D" /
// "CopyTexSubImage2D"), and OnClear's four non-Whole kinds (live already)
// d1 OnDrawVbo above: the indirect tail, the user-index span, NumDraws > 1 and the
// instanced arms (declined by name today)
Bool OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) override;
Bool OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) override;
Bool OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) override;
Bool OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) override;
Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
const char* name) override;
Bool OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) override;
Bool OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) override;
Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override;
Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) override;
Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) override;
Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) override;
Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) override;
// Per-verb tallies. The lane asserts these moved, because "the scenario passed" on a
// split build is also what a scenario that ran entirely on the monolith path looks
+124 -9
View File
@@ -234,7 +234,12 @@ namespace MobileGL::MG_Remote::Wire {
X(SetSwapInterval, MGPSwapInterval) \
X(QueryTimestamp, MGPTimestampRequest) \
X(QueryCounter, MGPQueryDesc) \
X(FenceWaitServer, MGPFenceWait)
X(FenceWaitServer, MGPFenceWait) \
X(BindShaderImage, MGPImageBind) \
X(PatchParameter, MGPPatchParameter) \
X(BindStreamOutput, MGPStreamOutputBind) \
X(SetStorageBlockBinding, MGPStorageBlockBinding) \
X(CopyFramebufferToTexture, MGPCopyFromFramebuffer)
namespace {
@@ -311,6 +316,9 @@ namespace MobileGL::MG_Remote::Wire {
case MGPWireOp::ResourceSubData:
case MGPWireOp::BufferSubDataResident:
return {static_cast<Uint32>(offsetof(MGPSubData, Blob)), 1};
// P5b: the block NAME rides SEG_STAGE (CONTRACT-P5B.md i1).
case MGPWireOp::SetStorageBlockBinding:
return {static_cast<Uint32>(offsetof(MGPStorageBlockBinding, Name)), 1};
default:
return {};
}
@@ -625,6 +633,7 @@ namespace MobileGL::MG_Remote::Wire {
tail0 = TailBytesFor(p.Count, sizeof(MGPBufferRange));
tail1 = TailBytesFor(p.HostSpanCount, sizeof(MGHostSpan));
tails = p.HostSpanCount != 0 ? 2 : 1;
out.SecondTailIsHostSpans = true;
break;
}
case MGPWireOp::SetStreamOutputTargets: {
@@ -677,9 +686,27 @@ namespace MobileGL::MG_Remote::Wire {
// header comment.
const auto& p = *static_cast<const MGPDrawInfo*>(payload);
tail0 = TailBytesFor(p.NumDraws, sizeof(MGPDrawRange));
if ((p.Flags & kDrawHasUserIndices) != 0) {
const Bool userIndices = (p.Flags & kDrawHasUserIndices) != 0;
const Bool indirect = (p.Flags & kDrawIsIndirect) != 0;
// P5b (CONTRACT-P5B.md d1): the second tail is the user-index span OR the indirect
// block, never both - an indirect draw takes its indices from the bound element
// buffer by GL rule - and an indirect draw declares no ranges, because the server
// never reads the indirect buffer to learn a count and the client has none to send.
if (userIndices && indirect) {
WireProtocolFatal("DrawVbo.Flags",
"kDrawHasUserIndices and kDrawIsIndirect are exclusive; an "
"indirect draw's indices come from the bound element buffer");
}
if (indirect && p.NumDraws != 0) {
WireProtocolFatalAt("DrawVbo.NumDraws", p.NumDraws, 0);
}
if (userIndices) {
tail1 = sizeof(MGHostSpan);
tails = 2;
out.SecondTailIsHostSpans = true;
} else if (indirect) {
tail1 = sizeof(MGPDrawIndirect);
tails = 2;
} else {
tails = 1;
}
@@ -953,7 +980,7 @@ namespace MobileGL::MG_Remote::Wire {
}
}
if ((callFlags & static_cast<Uint32>(kHostSpan)) != 0 && layout.TailCount == 2 &&
layout.TailBytes[1] != 0 && m_segments != nullptr) {
layout.SecondTailIsHostSpans && layout.TailBytes[1] != 0 && m_segments != nullptr) {
const Uint64 at = layout.TailOffset[1] - sizeof(MGPWireRecHeader);
const Uint64 spans = layout.TailBytes[1] / sizeof(MGHostSpan);
for (Uint64 i = 0; i < spans; ++i) {
@@ -1785,11 +1812,22 @@ namespace MobileGL::MG_Remote::Wire {
PostReply(op, seq, ReplySink::kStatusOk, nullptr, 0);
return true;
// P5b (CONTRACT-P5B.md): the two transfer verbs with no applier reach the sink like the
// class-B five. resource_copy_region = glCopyImageSubData (i1), generate_mipmap =
// glGenerateMipmap (f1). The payloads are plain PODs with no blob and no tail, so the
// bounds gate above is the whole validation; what the sink does with a renderbuffer
// endpoint or an emulation site is the contract's, not the codec's.
case MGPWireOp::ResourceCopyRegion:
return m_verbs != nullptr &&
m_verbs->OnResourceCopyRegion(*static_cast<const MGPCopyRegion*>(payload));
case MGPWireOp::GenerateMipmap:
return m_verbs != nullptr &&
m_verbs->OnGenerateMipmap(*static_cast<const MGPMipPlan*>(payload));
case MGPWireOp::GetTextureImage:
// Off the reduced path (BRIEF §4), and GetTextureImage's emit-table slot is
// Fatal{UnmigratedVerb} on the client anyway (contract §7 class C).
// Off the reduced path (BRIEF §4) and outside P5b's measured 25: its emit-table slot
// is Fatal{UnmigratedVerb} on the client (contract §7 class C, wave 3 / P9).
return false;
// ---- the five class-B verbs: no MGPipeApply* exists, so v1's sink or nothing -------
@@ -1823,27 +1861,104 @@ namespace MobileGL::MG_Remote::Wire {
std::memcpy(&span, tailAt(1), sizeof(span));
// ALL FOUR ARMS. WireVerbSink's header promises OnDrawVbo "a DECODED,
// VALIDATED argument list"; without the segment-range arm a span whose run
// left SEG_STAGE reached the sink and that promise was false. P8 is what arms
// this path, which is exactly when nobody will be reading this code.
// left SEG_STAGE reached the sink and that promise was false. P5b's d1 is what
// arms this path (client-side index arrays staged whole, CONTRACT-P5B.md d1).
CheckHostSpanIsHonest(span, *m_segments);
userIndices = &span;
}
// P5b d1: the indirect block, in the span's place. The layout already refused a
// record that sets both flags or that declares ranges alongside it.
const MGPDrawIndirect* indirect = nullptr;
MGPDrawIndirect indirectBlock{};
if ((info.Flags & kDrawIsIndirect) != 0) {
if (layout.TailCount != 2 || layout.TailBytes[1] != sizeof(MGPDrawIndirect)) {
WireProtocolFatalAt("DrawVbo.indirect", layout.TailBytes[1],
sizeof(MGPDrawIndirect));
}
std::memcpy(&indirectBlock, tailAt(1), sizeof(indirectBlock));
indirect = &indirectBlock;
}
return m_verbs != nullptr &&
m_verbs->OnDrawVbo(info, reinterpret_cast<const MGPDrawRange*>(tailAt(0)),
userIndices);
userIndices, indirect);
}
case MGPWireOp::Present:
return m_verbs != nullptr && m_verbs->OnPresent(*static_cast<const MGPPresent*>(payload));
// ---- compute, XFB, barriers, flush, swap interval: off the reduced path ------------
// ---- compute, barriers, XFB: P5b's i1 and t2 rows, to the sink -----------------------
//
// Six existing rows that returned false through P5 ("off the reduced path"). None has
// an MGPipeApply* and none gains one (CONTRACT-P5B.md): the payloads are plain PODs the
// bounds gate has already proved, so each arm hands over and stops.
case MGPWireOp::LaunchGrid:
return m_verbs != nullptr &&
m_verbs->OnLaunchGrid(*static_cast<const MGPGridInfo*>(payload));
case MGPWireOp::MemoryBarrier:
return m_verbs != nullptr &&
m_verbs->OnMemoryBarrier(*static_cast<const MGPMemoryBarrier*>(payload));
case MGPWireOp::BeginStreamOutput:
return m_verbs != nullptr &&
m_verbs->OnBeginStreamOutput(*static_cast<const MGPStreamOutputBegin*>(payload));
case MGPWireOp::EndStreamOutput:
return m_verbs != nullptr &&
m_verbs->OnEndStreamOutput(*static_cast<const MGPXfbAccounting*>(payload));
case MGPWireOp::PauseStreamOutput:
return m_verbs != nullptr &&
m_verbs->OnPauseStreamOutput(*static_cast<const MGPStreamOutputControl*>(payload));
case MGPWireOp::ResumeStreamOutput:
return m_verbs != nullptr &&
m_verbs->OnResumeStreamOutput(*static_cast<const MGPStreamOutputControl*>(payload));
// ---- the five P5b-appended verbs, opcodes 72..76 ---------------------------------------
case MGPWireOp::BindShaderImage:
return m_verbs != nullptr &&
m_verbs->OnBindShaderImage(*static_cast<const MGPImageBind*>(payload));
case MGPWireOp::PatchParameter:
return m_verbs != nullptr &&
m_verbs->OnPatchParameter(*static_cast<const MGPPatchParameter*>(payload));
case MGPWireOp::BindStreamOutput:
return m_verbs != nullptr &&
m_verbs->OnBindStreamOutput(*static_cast<const MGPStreamOutputBind*>(payload));
case MGPWireOp::SetStorageBlockBinding: {
// The block name is the ONE string on the wire (CONTRACT-P5B.md i1): a kHasBlob
// record whose blob is the NUL-terminated name, Size = strlen + 1, staged whole in
// SEG_STAGE like every other client -> server blob. Copied into a bounded local
// and re-terminated, so a record whose staged bytes forgot the NUL cannot make the
// backend read past the run, and so the pointer the sink sees dies with this call
// (rule C).
const auto& rec = *static_cast<const MGPStorageBlockBinding*>(payload);
constexpr Uint64 kMaxBlockNameBytes = 4096;
RequireDeclaredBlob(op, rec.Name, *m_segments);
if (rec.Name.Size > kMaxBlockNameBytes) {
WireProtocolFatalAt("SetStorageBlockBinding.Name", rec.Name.Size, kMaxBlockNameBytes);
}
const void* bytes = ResolveOrFatal(op, rec.Name);
char name[kMaxBlockNameBytes + 1];
std::memcpy(name, bytes, static_cast<SizeT>(rec.Name.Size));
name[rec.Name.Size] = '\0';
if (name[rec.Name.Size - 1] != '\0') {
WireProtocolFatal("SetStorageBlockBinding.Name",
"the staged block name is not NUL-terminated; Size is strlen + 1");
}
return m_verbs != nullptr && m_verbs->OnSetStorageBlockBinding(rec, name);
}
case MGPWireOp::CopyFramebufferToTexture:
return m_verbs != nullptr &&
m_verbs->OnCopyFramebufferToTexture(
*static_cast<const MGPCopyFromFramebuffer*>(payload));
case MGPWireOp::SetSwapInterval:
// Class C, wave 3 (census-classC.md "static cross"); not a verb (FillPoints.def:21).
return false;
case MGPWireOp::Flush:
+87 -3
View File
@@ -170,6 +170,12 @@ namespace MobileGL::MG_Remote::Wire {
Uint64 TailBytes[2] = {0, 0};
Uint32 TailCount = 0;
Uint64 TotalBytes = 0; // header + payload + gaps + tails, rounded up to 8
// P5b: WHAT THE SECOND TAIL IS. SetShaderBuffers' second tail is MGHostSpan[HostSpanCount]
// and DrawVbo's is EITHER the user-index MGHostSpan (kDrawHasUserIndices) OR one
// MGPDrawIndirect (kDrawIsIndirect, CONTRACT-P5B.md d1). The encoder's host-span
// honesty pass reads this rather than "tail 2 exists", because a 40-byte indirect
// block read as spans is one span and a quarter of garbage.
Bool SecondTailIsHostSpans = false;
};
// `payload` must already be known to hold at least the op's payload struct - that is what
@@ -377,13 +383,91 @@ namespace MobileGL::MG_Remote::Wire {
return false;
}
// `ranges` is info.NumDraws entries. `userIndices` is null unless the record set
// kDrawHasUserIndices - which P5 never does, because the reduced path draws from a
// VBO precisely so no MGHostSpan is produced (table 0's cap-bit row).
// kDrawHasUserIndices; `indirect` is null unless it set kDrawIsIndirect (P5b d1,
// CONTRACT-P5B.md). The layout refuses a record that sets both, so at most one of the
// two is non-null. The span is VALIDATED (all four R-2 arms, the segment-range one
// included) and names a SEG_STAGE run the client staged; the sink resolves it through
// MG_Pipe::MGPipeHostBytes and never holds the pointer past its return (rule C).
virtual Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices) {
const MG_Pipe::MGHostSpan* userIndices,
const MG_Pipe::MGPDrawIndirect* indirect) {
(void)info;
(void)ranges;
(void)userIndices;
(void)indirect;
return false;
}
// ---- P5b (MG_Remote/CONTRACT-P5B.md): the rows the four migration packages consume.
//
// Every one below is a GLFunctionsTable verb with NO MGPipeApply* entry point - the
// census's correction - so, exactly like the five above, the codec validates and hands
// over and the SERVER'S sink (Server/PipeApplier.cpp's ServerVerbSink) makes the
// backend call. The default bodies return false ("this build does not implement it");
// ServerVerbSink's stubs die Fatal{UnmigratedVerb, "<GL slot>"} by name until the owning
// package lands the real body, so a client that flips a slot ahead of its server half
// aborts with the same line the census greps rather than rendering nothing.
//
// i1 OnLaunchGrid, OnMemoryBarrier, OnResourceCopyRegion, OnBindShaderImage,
// OnSetStorageBlockBinding
// t2 OnBeginStreamOutput, OnEndStreamOutput, OnPauseStreamOutput,
// OnResumeStreamOutput, OnBindStreamOutput, OnPatchParameter
// f1 OnGenerateMipmap, OnCopyFramebufferToTexture (and OnClear's non-Whole kinds)
// d1 OnDrawVbo's indirect tail and user-index span (above)
virtual Bool OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
(void)grid;
return false;
}
virtual Bool OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) {
(void)barrier;
return false;
}
virtual Bool OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) {
(void)copy;
return false;
}
virtual Bool OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) {
(void)bind;
return false;
}
// `name` is the NUL-terminated block name the decoder copied out of the record's
// SEG_STAGE blob; valid for the call only.
virtual Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
const char* name) {
(void)binding;
(void)name;
return false;
}
virtual Bool OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) {
(void)begin;
return false;
}
virtual Bool OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) {
(void)accounting;
return false;
}
virtual Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control;
return false;
}
virtual Bool OnResumeStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) {
(void)control;
return false;
}
virtual Bool OnBindStreamOutput(const MG_Pipe::MGPStreamOutputBind& bind) {
(void)bind;
return false;
}
virtual Bool OnPatchParameter(const MG_Pipe::MGPPatchParameter& patch) {
(void)patch;
return false;
}
virtual Bool OnGenerateMipmap(const MG_Pipe::MGPMipPlan& plan) {
(void)plan;
return false;
}
virtual Bool OnCopyFramebufferToTexture(const MG_Pipe::MGPCopyFromFramebuffer& copy) {
(void)copy;
return false;
}
};