[Feat] (MG_Pipe, MG_Remote, P5b/t2): add kCapBackendOwnsXfbCapture, publish it from the server table EndTransformFeedback slot, and answer the capture-ownership probe from it

This commit is contained in:
2026-09-16 12:23:57 -04:00
parent f567610339
commit c240557fe0
5 changed files with 69 additions and 6 deletions
+17 -1
View File
@@ -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.
+15 -1
View File
@@ -15,6 +15,12 @@
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Remote/Client/GpuWritePending.h>
#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 <MG_Remote/Client/SlotCaps.h>
#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) {
+12
View File
@@ -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 {
+5 -3
View File
@@ -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<Uint64>(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<Uint64>(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),
+20 -1
View File
@@ -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, "<slot>"} 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.