diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index cb9c9edf..6af52e8e 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -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&, GLenum, GLint, const GLint*)) \ X(ClearNamedFramebufferuiv, void, \ (const SharedPtr&, 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&, \ const SharedPtr&, 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&, 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"); diff --git a/MobileGL/MG_Remote/Client/EmitTables.h b/MobileGL/MG_Remote/Client/EmitTables.h index cb791e46..434ede91 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.h +++ b/MobileGL/MG_Remote/Client/EmitTables.h @@ -58,25 +58,24 @@ #include #include +// 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 #include 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). diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 7b2e8c0c..cae264b6 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -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("")`, 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 // ----------------------------------------------------------------------------------- diff --git a/MobileGL/MG_Remote/Server/PipeApplier.h b/MobileGL/MG_Remote/Server/PipeApplier.h index c11ca7e5..7114c1d6 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.h +++ b/MobileGL/MG_Remote/Server/PipeApplier.h @@ -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, ""} 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 diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp index 1f901c27..77f4e982 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp @@ -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(offsetof(MGPSubData, Blob)), 1}; + // P5b: the block NAME rides SEG_STAGE (CONTRACT-P5B.md i1). + case MGPWireOp::SetStorageBlockBinding: + return {static_cast(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(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(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(payload)); + case MGPWireOp::GenerateMipmap: + return m_verbs != nullptr && + m_verbs->OnGenerateMipmap(*static_cast(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(tailAt(0)), - userIndices); + userIndices, indirect); } case MGPWireOp::Present: return m_verbs != nullptr && m_verbs->OnPresent(*static_cast(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(payload)); + case MGPWireOp::MemoryBarrier: + return m_verbs != nullptr && + m_verbs->OnMemoryBarrier(*static_cast(payload)); + case MGPWireOp::BeginStreamOutput: + return m_verbs != nullptr && + m_verbs->OnBeginStreamOutput(*static_cast(payload)); + case MGPWireOp::EndStreamOutput: + return m_verbs != nullptr && + m_verbs->OnEndStreamOutput(*static_cast(payload)); + case MGPWireOp::PauseStreamOutput: + return m_verbs != nullptr && + m_verbs->OnPauseStreamOutput(*static_cast(payload)); + case MGPWireOp::ResumeStreamOutput: + return m_verbs != nullptr && + m_verbs->OnResumeStreamOutput(*static_cast(payload)); + + // ---- the five P5b-appended verbs, opcodes 72..76 --------------------------------------- + case MGPWireOp::BindShaderImage: + return m_verbs != nullptr && + m_verbs->OnBindShaderImage(*static_cast(payload)); + + case MGPWireOp::PatchParameter: + return m_verbs != nullptr && + m_verbs->OnPatchParameter(*static_cast(payload)); + + case MGPWireOp::BindStreamOutput: + return m_verbs != nullptr && + m_verbs->OnBindStreamOutput(*static_cast(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(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(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(payload)); + case MGPWireOp::SetSwapInterval: + // Class C, wave 3 (census-classC.md "static cross"); not a verb (FillPoints.def:21). return false; case MGPWireOp::Flush: diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.h b/MobileGL/MG_Remote/Wire/PipeWireCodec.h index ea2360bb..d3c1dff1 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.h +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.h @@ -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, ""} 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; } };