diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index ba4d648b..8a8fb04f 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -150,7 +150,23 @@ namespace MobileGL::MG_Backend { // kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes must be 0 for the whole of P5 // by ruling - they are the only two things that ask for an MGHostSpan, and 0 is // what keeps every one of them out of the first IPC frame (contract table 0). - session.SetCapabilityBits(0); + // + // P5b t2 (CONTRACT-P5B.md §6.5) PUBLISHES THE ONE BIT P5b ADDS, and this is the + // only place that can: the question kCapBackendOwnsXfbCapture answers is "does the + // SERVER's backend own the transform-feedback capture", and the server's table is + // visible here and nowhere on the client. It is read straight off the table + // ServerLoop::CreateBackend just built - Espryt registers XfbImpl::EndTransformFeedback + // (BackendObject_DirectGLES.cpp:1458) and Magma registers no XFB slot at all - so the + // bit is a statement about THIS backend rather than about a build option, which is + // what makes it survive a backend switch. The client reads it through + // MGL_BACKEND_SLOT_CAP at GL_Drawing.cpp's FixupGsStripCaptureOrder. + Uint64 capBits = 0; + if (const MG_Backend::BackendObject* serverBackend = loop.Backend(); + serverBackend != nullptr && + serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) { + capBits |= MG_Pipe::kCapBackendOwnsXfbCapture; + } + session.SetCapabilityBits(capBits); session.SetBackend(loop.Backend()); // 3. the handshake, the four segments, and - at its end - the apply thread. diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 4b289545..232d80fb 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -15,6 +15,12 @@ #if MOBILEGL_BUILD_DISAGGREGATED #include #endif +// CONTRACT-P5.md §7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the +// client under split - it becomes a caps-mirror read. SlotCaps.h carries the rule and the test +// that decides which of its two spellings a site takes; in a pull build both expand to exactly +// the check they replaced. P5b t2 converts ONE site in this file - the capture-ownership probe +// in FixupGsStripCaptureOrder - for the reason CONTRACT-P5B.md §6.5 gives. +#include #include "../Getter/GL_Getter.h" namespace MobileGL::MG_Impl::GLImpl { @@ -1287,7 +1293,15 @@ namespace MobileGL::MG_Impl::GLImpl { // Only Vulkan-order captures need this. A backend that runs the capture on its // own GL/ES driver (it owns the span, hence the EndTransformFeedback entry) has // already produced GL's vertex order, and reordering it again would corrupt it. - if (MG_Backend::gBackendFunctionsTable.GL.EndTransformFeedback != nullptr) { + // + // P5b t2 (CONTRACT-P5B.md §6.5): UNDER SPLIT THE TABLE THIS USED TO ASK IS THE CLIENT'S + // EMIT TABLE, whose EndTransformFeedback slot t2 just made non-null for every server - + // so the raw null check would answer "the backend owns the capture" even against Magma, + // which registers no XFB slot at all, and would skip a reorder Magma needs. The question + // is about the SERVER's table, so it is answered from the bit the server publishes. + // Under monolith (and in a pull build) this expands to the null check it replaced, + // character for character. + if (MGL_BACKEND_SLOT_CAP(EndTransformFeedback, MG_Pipe::kCapBackendOwnsXfbCapture)) { return; } if (program == nullptr || !program->HasGsTriangleStripCaptureFixup() || inputPrimitives == 0) { diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index 0c3ec042..c64e919f 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -121,6 +121,18 @@ namespace MobileGL::MG_Pipe { // The server packs named uniform blocks into its own ring and therefore needs the // host bytes of a set_shader_buffers(Uniform) range (D-B8). kCapNeedsHostUboBytes = 1ull << 8, + // P5b t2 (CONTRACT-P5B.md §6.5), the one cap bit P5b adds. The SERVER's backend owns + // the transform-feedback capture, i.e. its own table registers EndTransformFeedback. + // FixupGsStripCaptureOrder (GL_Drawing.cpp:1290) asks that question to decide whether + // the CLIENT must reorder the captured records into GL's vertex order, and it used to + // ask it of gBackendFunctionsTable.GL.EndTransformFeedback - which under split is the + // client's EMIT table, where the slot is non-null the moment t2 installs an emitter, + // for every server. A client talking to Espryt would then be right by accident and a + // client talking to Magma (which registers no XFB slot at all, so the sink DECLINES) + // would skip a reorder Magma needs and hand the application a silently corrupt capture + // buffer. So the answer is the SERVER's table, published as a bit and read through + // MGL_BACKEND_SLOT_CAP. The first of the "XFB span family" bits SlotCaps.h predicted. + kCapBackendOwnsXfbCapture = 1ull << 9, }; struct MGPCaps { diff --git a/MobileGL/MG_Remote/CapsCodec.cpp b/MobileGL/MG_Remote/CapsCodec.cpp index 051bd501..b6d841e7 100644 --- a/MobileGL/MG_Remote/CapsCodec.cpp +++ b/MobileGL/MG_Remote/CapsCodec.cpp @@ -44,9 +44,11 @@ namespace MobileGL::MG_Remote { - // The consumer mask may not collide with the MGPCapBits below it. kCapNeedsHostUboBytes - // is 1<<8 today; this asserts the gap stays a gap rather than trusting the comment. - static_assert((static_cast(MG_Pipe::kCapNeedsHostUboBytes) & kMGCapsConsumerMask) == 0, + // The consumer mask may not collide with the MGPCapBits below it. The HIGHEST allocated + // feature bit is kCapBackendOwnsXfbCapture, 1<<9 (P5b t2 raised it from + // kCapNeedsHostUboBytes' 1<<8); this asserts the gap stays a gap rather than trusting the + // comment, so it has to name whichever bit is currently the top one. + static_assert((static_cast(MG_Pipe::kCapBackendOwnsXfbCapture) & kMGCapsConsumerMask) == 0, "an MGPCapBit has grown into CallMask's consumer block (bits 32..47)"); static_assert(MGCapsServerConsumes(MGCapsConsumerBits(MG_Pipe::kMGPipeSubsystemResources), MG_Pipe::kMGPipeSubsystemResources), diff --git a/MobileGL/MG_Remote/Client/SlotCaps.h b/MobileGL/MG_Remote/Client/SlotCaps.h index 63bcf4b0..ca50ba04 100644 --- a/MobileGL/MG_Remote/Client/SlotCaps.h +++ b/MobileGL/MG_Remote/Client/SlotCaps.h @@ -75,11 +75,30 @@ // GL_Drawing.cpp:844 PatchParameteri. "Absent" means the patch size is never set and every // tessellation draw silently uses the previous one. Class C; it aborts by name. // +// P5b t2 CLOSED THE FIRST OF THOSE TWO, AND THE OTHER SIX SITES NEEDED NOTHING (CONTRACT-P5B.md +// §2 t2, §6.5). The six SPAN sites - :1274 Begin, :1371 End, :1420 Pause, :1435 Resume, :1641 +// Delete, :1673 Bind - are guards over a slot that is now class B in the client's table, so they +// simply call the emitter; their `if (const auto f = ...)` shape is left exactly as it was, +// because under split the slot is non-null and under monolith nothing moved. THE PROBE AT :1290 +// IS THE ONE THAT HAD TO CHANGE, and it is the reason this header said the XFB span family would +// be the first to need a bit: it is not a guard on a call, it is a QUESTION ABOUT THE BACKEND +// asked of a table that under split belongs to the client. It now reads +// MGL_BACKEND_SLOT_CAP(EndTransformFeedback, kCapBackendOwnsXfbCapture), the bit the server sets +// from ITS table in MG_Backend/Init.cpp's InitSplitRoles. Espryt registers the slot and answers +// yes; Magma registers no XFB slot, answers no, and the client keeps reordering for it exactly +// as it does under monolith. GL_Drawing.cpp:844's PatchParameteri stays a plain guard for the +// same reason as the six: the slot is class B now, so "absent" never arises. +// +// The one XFB slot still class C is DeleteTransformFeedback, which CONTRACT-P5B.md gives no row +// (unmeasured); :1641's guard therefore still reaches Fatal{UnmigratedVerb} by name, which is +// the outcome R-4 asks for. +// // WHAT THIS HEADER DELIBERATELY DOES NOT DO. It does not touch the 28 unguarded slots: those // have no probe to convert, and calling one reaches Fatal{UnmigratedVerb, ""} by name, // which is R-4's intent. And it does not invent a cap bit - a new MGPCapBit is an // MGPipeTypes.h edit and that file is c0's, so a family that needs one goes through the -// integrator (the XFB span family is the first that will). +// integrator (the XFB span family is the first that will). It did: P5b's contract granted t2 +// exactly that one bit, kCapBackendOwnsXfbCapture (CONTRACT-P5B.md §6.5, §8), and t2 added it. // // G1: in a build without MOBILEGL_BUILD_DISAGGREGATED both macros expand to the null check the // site already had, so the pull build's code generation is unchanged.