mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
Merge branch 'p5e/c0e' into feat/disaggregated (P5e c0e: the contract, the wait-class column, opcode 80, the knobs and the cross-package seams)
This commit is contained in:
+32
-3
@@ -322,9 +322,9 @@ namespace MobileGL::MG_Config {
|
||||
// 0 - the only shipped value until the migration lands - is "pull everything",
|
||||
// i.e. exactly today's behaviour, and is the default of a PULL build, where the
|
||||
// knob is meaningless anyway. A PUSH build defaults to every subsystem migrated so
|
||||
// far (MG_Pipe::kMGPipeSubsystemsMigratedAtP4a), so MOBILEGL_PIPE_PUSH=0 in the
|
||||
// environment is the all-pull control and 0x1ff (kMGPipeSubsystemsMigratedAtP3a) is
|
||||
// the "everything before P4a" control P4a's A/B is run against - each phase's
|
||||
// far (MG_Pipe::kMGPipeSubsystemsMigratedAtP5e = 0x3fff), so MOBILEGL_PIPE_PUSH=0 in
|
||||
// the environment is the all-pull control and 0x1fff (kMGPipeSubsystemsMigratedAtP4a)
|
||||
// is the "everything before P5e" control P5e's A/B is run against - each phase's
|
||||
// constant survives as the next phase's control, which is why none of them is ever
|
||||
// edited. Accepts decimal or 0x-prefixed hex, and operators pass it as hex, so the
|
||||
// bits are listed here (MG_Pipe/MGPipe.h owns them):
|
||||
@@ -342,6 +342,9 @@ namespace MobileGL::MG_Config {
|
||||
// 0x800 samplers (sampler CSO, sampler view, set_sampler_views /
|
||||
// bind_sampler_states / set_shader_images) - requires 0x400
|
||||
// 0x1000 programs (shader CSO, set_draw/dispatch_program, global constants)
|
||||
// 0x2000 buffer binding points (set_shader_buffers, the three indexed binding-point
|
||||
// classes and the dirty bits 15/16/17) - requires 0x80
|
||||
// (every MGPBufferRange::Res names a Buffer handle; P5e)
|
||||
// A dependency that is not met is REFUSED with one ERROR naming both bits and the
|
||||
// family runs its legacy arm; it is never half-run.
|
||||
// 1<<63 NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of
|
||||
@@ -502,6 +505,32 @@ namespace MobileGL::MG_Config {
|
||||
// GLContext by the client's residual fill and a free-running queue lets the server
|
||||
// read a FUTURE value of them.
|
||||
Uint32 VerbBarrier = 1;
|
||||
// MOBILEGL_IPC_RUN_AHEAD (P5e, MG_Remote/CONTRACT-P5E.md §1): 1 = after publishing an
|
||||
// UNBARRIERED record the client returns immediately instead of waiting for its apply.
|
||||
// It is one half of a conjunction and never a switch on its own - the client arms
|
||||
// run-ahead only when the server also publishes kCapRunAheadApply, so on Magma, and on
|
||||
// Espryt before the P5e integration commit, 1 means exactly what 0 means and logs once
|
||||
// saying so.
|
||||
//
|
||||
// 0 IS THE A/B CONTROL AND NOT A NEGATIVE ONE: the server code, the fill decision and
|
||||
// every record are identical on both arms, and the only difference is whether the
|
||||
// client waits. That is what makes "is the picture the same" a question about the wait
|
||||
// rule alone. MOBILEGL_IPC_VERB_BARRIER=0 keeps its own meaning and stays the
|
||||
// lockstep arm's negative control; under run-ahead it is the one that must go red, at
|
||||
// the first barriered row's stale pull.
|
||||
//
|
||||
// Forced to 0 by MOBILEGL_PIPE_VERIFY, beside BatchWaits: the comparator needs a
|
||||
// client-filled gPipeInputs block for every verb, and run-ahead is precisely the
|
||||
// arm that stops filling it.
|
||||
Uint32 RunAhead = 1;
|
||||
// MOBILEGL_IPC_PRESENT_CREDIT (P5e, ruling 4 / ID-92): how many presents the client may
|
||||
// have in flight before it waits for a swap to come back. 1 = the client publishes
|
||||
// frame N+1's records while the server applies and swaps frame N - one frame of
|
||||
// overlap, at most one frame of added latency - and the CREDIT, never the ring's bytes,
|
||||
// is what paces a run-ahead client. 2 is a device MEASUREMENT arm: it buys no CPU on a
|
||||
// client that is already CPU-bound and costs a frame of latency, which is why the
|
||||
// default is 1 and not "as deep as the ring".
|
||||
Uint32 PresentCredit = 1;
|
||||
// MOBILEGL_IPC_STRICT_ERRORS: promote a BARRIER-PULLED field read - and, in a split
|
||||
// build, the seven sticky forwards that are otherwise exempt - from "count it in
|
||||
// rsp" to Fatal (R-7.3).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "Config.h"
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// For kMGPipeSubsystemsMigratedAtP4a, the push build's PipePush default (the P2 and P3a
|
||||
// For kMGPipeSubsystemsMigratedAtP5e, the push build's PipePush default (the P2, P3a and P4a
|
||||
// constants beside it are the phase-by-phase controls, not the default). Push-only, so the
|
||||
// pull build's translation unit is unchanged.
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
@@ -263,7 +263,7 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// environment is the all-subsystems-pull control that reproduces P1 exactly, and
|
||||
// kMGPipeSubsystemsMigratedAtP3a (0x1ff) is the phase-by-phase control - P4a's four
|
||||
// subsystems off, everything P3a landed still on.
|
||||
features.PipePush = QueryEnvUint64("MOBILEGL_PIPE_PUSH", MG_Pipe::kMGPipeSubsystemsMigratedAtP4a);
|
||||
features.PipePush = QueryEnvUint64("MOBILEGL_PIPE_PUSH", MG_Pipe::kMGPipeSubsystemsMigratedAtP5e);
|
||||
#else
|
||||
// Meaningless in a pull build: there is nothing to push. Config.h documents 0 as
|
||||
// "pull everything" and that stays literally true.
|
||||
@@ -379,6 +379,17 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// point of use, which is where the "P11" in the message belongs.
|
||||
ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", 2, 0, 2);
|
||||
ipc.VerbBarrier = QueryEnvUint32("MOBILEGL_IPC_VERB_BARRIER", 1, 0, 1);
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1). The wait rule's A/B, parsed here like every
|
||||
// other IPC knob and armed only where the server publishes kCapRunAheadApply.
|
||||
ipc.RunAhead = QueryEnvUint32("MOBILEGL_IPC_RUN_AHEAD", 1, 0, 1);
|
||||
// ... and forced OFF by the verify harness for BatchWaits' reason, one step further:
|
||||
// run-ahead's whole point is that the client stops filling gPipeInputs for an
|
||||
// unbarriered record, and the comparator has nothing left to compare when it does.
|
||||
if (MG_Config::Features.PipeVerify) ipc.RunAhead = 0;
|
||||
// The present credit (ruling 4). 1 is one frame of overlap; 8 is the ceiling because a
|
||||
// deeper queue buys nothing on a CPU-bound client and pays for it in latency. 0 is NOT
|
||||
// admitted: a credit of zero would mean "publish no present at all".
|
||||
ipc.PresentCredit = QueryEnvUint32("MOBILEGL_IPC_PRESENT_CREDIT", 1, 1, 8);
|
||||
ipc.StrictErrors = QueryEnvFlag("MOBILEGL_IPC_STRICT_ERRORS");
|
||||
ipc.Audit = QueryEnvFlag("MOBILEGL_IPC_AUDIT");
|
||||
QueryEnvVariable("MOBILEGL_IPC_SERVER_AFFINITY", ipc.ServerAffinity, "auto");
|
||||
@@ -387,9 +398,11 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// One line, on the arm where these numbers decide behaviour, because every one of
|
||||
// them is a number a bug report has to quote.
|
||||
MGLOG_I("Config: IPC ring=%uMiB stage=%uMiB spin=%uus persistent-block=%uKiB "
|
||||
"adopt-tier=%u verb-barrier=%u strict=%d audit=%d affinity='%s'",
|
||||
"adopt-tier=%u verb-barrier=%u run-ahead=%u present-credit=%u strict=%d "
|
||||
"audit=%d affinity='%s'",
|
||||
ipc.RingMb, ipc.StageMb, ipc.SpinUs, ipc.PersistentBlockKb, ipc.AdoptTier,
|
||||
ipc.VerbBarrier, static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
||||
ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit,
|
||||
static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
|
||||
ipc.ServerAffinity.c_str());
|
||||
if (ipc.VerbBarrier == 0) {
|
||||
MGLOG_W("Config: MOBILEGL_IPC_VERB_BARRIER=0 is the R-1 NEGATIVE CONTROL and is "
|
||||
|
||||
@@ -13697,4 +13697,88 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
TwinRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject, MG_Pipe::MGPipeKind::Renderbuffer>
|
||||
g_backendRenderbufferObjects;
|
||||
} // namespace RenderbufferImpl
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// =====================================================================================
|
||||
// P5e SEAMS: DECLARED AND REFUSED HERE, BODIED BY THE FAMILY PACKAGES
|
||||
// (MG_Remote/CONTRACT-P5E.md §4.2; BRIEF-P5E §1's "each such seam is a declared signature
|
||||
// in c0e so both sides compile from day one")
|
||||
// =====================================================================================
|
||||
//
|
||||
// WHY THEY ARE ALL IN ONE BLOCK AND WHY THE BODIES ABORT. P5e lands as eight packages in
|
||||
// eight parallel worktrees, and five of them are on opposite sides of these calls: tx2
|
||||
// writes SyncTextureToBackendByHandle and SyncMipmapsToBackendByHandle while fb's
|
||||
// attachment sync and image sweep CALL them; fb writes the two SyncToBackendByHandle
|
||||
// overloads while tx2's detach walk needs the reverse index behind them; id rekeys the
|
||||
// registries the three resolvers read. A package that had to add its own declaration would
|
||||
// collide with the package that added the other half, which is the merge trap P4a's
|
||||
// contract package was written to avoid.
|
||||
//
|
||||
// So the signature is fixed HERE, once, and each family's commit replaces a body. The body
|
||||
// ABORTS BY NAME rather than returning null or doing nothing: a resolver that answered null
|
||||
// would render a blank draw and a sync that did nothing would render stale pixels, and both
|
||||
// would be green lanes. Nothing calls any of them at this commit - every call site is still
|
||||
// on the frontend overload beside it - so the abort is a link-time seam, not a runtime one.
|
||||
//
|
||||
// A package REPLACES the body in place and deletes the matching comment; it does not add a
|
||||
// second definition elsewhere, or the linker's answer depends on link order.
|
||||
namespace {
|
||||
[[noreturn]] void MGPipeP5eSeamNotLanded(const char* name, const char* owner,
|
||||
MG_Pipe::MGPipeHandle handle) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"%s\"} - the P5e by-handle seam is declared by "
|
||||
"package c0e and bodied by package %s; it was called for handle {%u, %u} "
|
||||
"before that package landed",
|
||||
name, owner, handle.Slot, handle.Gen);
|
||||
std::abort();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace VertexArrayImpl {
|
||||
BackendVertexArrayObject* ResolveVaoTwin(MG_Pipe::MGPipeHandle vertexElements) {
|
||||
MGPipeP5eSeamNotLanded("VertexArrayImpl::ResolveVaoTwin", "id/vi", vertexElements);
|
||||
}
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
SharedPtr<BackendTextureObject>& SyncTextureToBackendByHandle(MG_Pipe::MGPipeHandle texture,
|
||||
Bool imageBindableStorageRequired) {
|
||||
(void)imageBindableStorageRequired;
|
||||
MGPipeP5eSeamNotLanded("TextureImpl::SyncTextureToBackendByHandle", "tx2", texture);
|
||||
}
|
||||
|
||||
BackendTextureObject* ResolveTextureTwin(MG_Pipe::MGPipeHandle texture) {
|
||||
MGPipeP5eSeamNotLanded("TextureImpl::ResolveTextureTwin", "id/tx2", texture);
|
||||
}
|
||||
|
||||
void BackendTextureObject::SyncMipmapsToBackendByHandle(MG_Pipe::MGPipeHandle texture) {
|
||||
MGPipeP5eSeamNotLanded("BackendTextureObject::SyncMipmapsToBackendByHandle", "tx2",
|
||||
texture);
|
||||
}
|
||||
} // namespace TextureImpl
|
||||
|
||||
namespace FramebufferImpl {
|
||||
void BackendFramebufferObject::SyncToBackendByHandle(MG_Pipe::MGPipeHandle fbo,
|
||||
FramebufferTarget asTarget) {
|
||||
(void)asTarget;
|
||||
MGPipeP5eSeamNotLanded("BackendFramebufferObject::SyncToBackendByHandle", "fb", fbo);
|
||||
}
|
||||
} // namespace FramebufferImpl
|
||||
|
||||
namespace PrgramImpl {
|
||||
BackendProgramObjectImpl* ResolveProgramTwin(MG_Pipe::MGPipeHandle cso) {
|
||||
MGPipeP5eSeamNotLanded("PrgramImpl::ResolveProgramTwin", "id/pg", cso);
|
||||
}
|
||||
|
||||
void BackendProgramObjectImpl::SyncToBackendByHandle(MG_Pipe::MGPipeHandle cso) {
|
||||
MGPipeP5eSeamNotLanded("BackendProgramObjectImpl::SyncToBackendByHandle", "pg", cso);
|
||||
}
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace RenderbufferImpl {
|
||||
void BackendRenderbufferObject::SyncToBackendByHandle(MG_Pipe::MGPipeHandle renderbuffer) {
|
||||
MGPipeP5eSeamNotLanded("BackendRenderbufferObject::SyncToBackendByHandle", "fb",
|
||||
renderbuffer);
|
||||
}
|
||||
} // namespace RenderbufferImpl
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
|
||||
@@ -1466,6 +1466,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// the attribute-offset emulation must stay out of the way. Applied to whatever the
|
||||
// applier stored, so the answer is the same whichever side resolved it first.
|
||||
Bool BackendUsesNativeBaseInstance();
|
||||
|
||||
// ---- P5e SEAM (MG_Remote/CONTRACT-P5E.md §4.2; declared by c0e, bodied by id/vi) ----
|
||||
//
|
||||
// THE VAO TWIN, RESOLVED FROM THE HANDLE THE RECORD CARRIED - MGPipeApplierState::
|
||||
// BoundVertexElements - instead of from the frontend VertexArrayObject the draw's
|
||||
// BARRIER_PULLED row hands over. The frontend overload above it stays as the
|
||||
// monolith-glue half, in the shape ResolveSamplerCsoTwin already established for the
|
||||
// sampler CSO family: two overloads, not an #if inside one body, so which arm a caller
|
||||
// is on is visible at the call site.
|
||||
//
|
||||
// It is declared HERE, with a body that aborts by name, because the packages that fill
|
||||
// it in land in parallel worktrees: id rekeys the registry under it, vi moves
|
||||
// PrepareForDraw's call onto it, and neither may edit the other's file. A missing
|
||||
// declaration would make that a merge conflict; a declaration with a quiet body would
|
||||
// make it a null twin and a blank draw.
|
||||
BackendVertexArrayObject* ResolveVaoTwin(MG_Pipe::MGPipeHandle vertexElements);
|
||||
#endif
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
@@ -1590,6 +1606,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BackendTextureObject(const BackendTextureObject&) = delete;
|
||||
BackendTextureObject& operator=(const BackendTextureObject&) = delete;
|
||||
void SyncMipmapsToBackend(const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e SEAM (declared by c0e, bodied by tx2): the same storage sync keyed on the
|
||||
// texture HANDLE, reading the applier's resource record and the server's staged
|
||||
// store instead of the frontend object's levels and pending uploads. fb's
|
||||
// attachment sync and the image sweep both call it, which is why it is declared
|
||||
// once here rather than twice in two packages' worktrees.
|
||||
void SyncMipmapsToBackendByHandle(MG_Pipe::MGPipeHandle texture);
|
||||
#endif
|
||||
// The storage half of the sync for a texture created by glTextureView. Instead of
|
||||
// allocating storage and replaying uploads, it makes this object's ES name BE a view
|
||||
// of the storage texture's ES name (EXT/OES_texture_view), which is what gives the
|
||||
@@ -1820,6 +1844,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SharedPtr<BackendTextureObject>& SyncTextureObjectToBackend(
|
||||
const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||
Bool imageBindableStorageRequired = false);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// ---- P5e SEAM (MG_Remote/CONTRACT-P5E.md §4.2, §5.2; declared by c0e, bodied by
|
||||
// id/tx2) --------------------------------------------------------------------------
|
||||
//
|
||||
// EVERY DRAW-PATH TEXTURE ENTRY, BY HANDLE. The caller holds a Texture handle - a
|
||||
// sampler view's Texture, an image unit's Res, a framebuffer surface's Res, VerbMipRes,
|
||||
// VerbCopyTexDst, a copy-image endpoint - and the sync resolves the RECORD first and the
|
||||
// twin by GetOrCreateByHandle, so nothing on the apply thread dereferences a frontend
|
||||
// ITextureObject. The frontend overload above stays as the monolith-glue half.
|
||||
//
|
||||
// ResolveTextureTwin is the lookup without the sync, for the callers that need the twin
|
||||
// to answer a shape question (an image bind's target and storage format come off the
|
||||
// twin, not off a new wire field) after tx2's sync has already run this frame.
|
||||
SharedPtr<BackendTextureObject>& SyncTextureToBackendByHandle(
|
||||
MG_Pipe::MGPipeHandle texture, Bool imageBindableStorageRequired = false);
|
||||
BackendTextureObject* ResolveTextureTwin(MG_Pipe::MGPipeHandle texture);
|
||||
#endif
|
||||
// Brings every texture the next draw reads - the touched units' bindings and the draw
|
||||
// FBO's texture attachments - onto the backend, through the two borrowed-pair memos
|
||||
// documented at their definitions. Declared here so tests can drive those memos directly.
|
||||
@@ -1844,6 +1885,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BackendFramebufferObject& operator=(const BackendFramebufferObject&) = delete;
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject,
|
||||
FramebufferTarget asTarget);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e SEAM (MG_Remote/CONTRACT-P5E.md §5.4; declared by c0e, bodied by fb): the same
|
||||
// sync keyed on the framebuffer HANDLE. The record's eleven surfaces ARE the point
|
||||
// set - the emitter refuses a point at or above the wire width - so the attachment
|
||||
// walk needs no frontend FramebufferObject and no m_pushedSyncHandle handshake: the
|
||||
// handle is the argument. An OVERLOAD rather than a changed signature, so the
|
||||
// monolith arm and the pull build see no token move.
|
||||
void SyncToBackendByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget asTarget);
|
||||
#endif
|
||||
// Apply only this FBO's read buffer (glReadBuffer) to the backend. Split out so it can
|
||||
// still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is
|
||||
// bound as both draw and read (otherwise glReadBuffer changes would be silently dropped).
|
||||
@@ -2315,6 +2365,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BackendProgramObjectImpl();
|
||||
~BackendProgramObjectImpl();
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e SEAM (MG_Remote/CONTRACT-P5E.md §5.5; declared by c0e, bodied by pg): the same
|
||||
// sync keyed on the ShaderCso HANDLE and answered from the record - the archive the
|
||||
// create carries and the three binding tails set_program_bindings carries. It is an
|
||||
// OVERLOAD beside the frontend one, which stays as the monolith-glue half, so the
|
||||
// pull build's mangled names do not move.
|
||||
void SyncToBackendByHandle(MG_Pipe::MGPipeHandle cso);
|
||||
#endif
|
||||
void Use();
|
||||
void SetBaseInstance(Uint32 baseInstance) const;
|
||||
void SetBaseInstanceWordIndex(Int32 wordIndex) const;
|
||||
@@ -2609,6 +2667,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
};
|
||||
ImageFormatBakeInputs CollectImageFormatBakeInputs(
|
||||
const MG_State::GLState::ProgramObject& stateProgramObject);
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// ---- P5e SEAM (MG_Remote/CONTRACT-P5E.md §4.2, §5.5; declared by c0e, bodied by
|
||||
// id/pg) ---------------------------------------------------------------------------
|
||||
//
|
||||
// THE PROGRAM TWIN, RESOLVED FROM MGPipeApplierState::DrawProgram / DispatchProgram /
|
||||
// BoundShaderCso instead of from GetProgramForDraw()'s frontend SharedPtr - the second
|
||||
// unconditional pointer read of every draw, and one of the two rows whose retirement is
|
||||
// what P5e is for. Composite pipeline programs resolve through the same call: their
|
||||
// slots come out of the allocator's composite band, which the server's slot table gains
|
||||
// a band for so the ordinary table does not grow to a million entries.
|
||||
//
|
||||
// Null, loudly, when the handle names no record or cannot be adopted - the shape
|
||||
// ResolveSamplerCsoTwin set - and null silently for the null handle, which is the legal
|
||||
// "nothing bound".
|
||||
BackendProgramObjectImpl* ResolveProgramTwin(MG_Pipe::MGPipeHandle cso);
|
||||
#endif
|
||||
} // namespace PrgramImpl
|
||||
|
||||
namespace SamplerImpl {
|
||||
@@ -2789,6 +2864,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BackendRenderbufferObject(const BackendRenderbufferObject&) = delete;
|
||||
BackendRenderbufferObject& operator=(const BackendRenderbufferObject&) = delete;
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::RenderbufferObject>& stateRBOObject);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P5e SEAM (declared by c0e, bodied by fb): the renderbuffer twin of the
|
||||
// framebuffer overload above. MGPSurface::Res names the renderbuffer and the
|
||||
// resource record already carries its format, extent and sample count (P4a), so the
|
||||
// attachment sync needs no frontend RenderbufferObject - which is what deletes the
|
||||
// one live identity probe left in the attachment path (Managers.cpp's renderbuffer
|
||||
// cross-check, the sibling that never got the Transport == Monolith gate its
|
||||
// texture counterpart has).
|
||||
void SyncToBackendByHandle(MG_Pipe::MGPipeHandle renderbuffer);
|
||||
#endif
|
||||
Uint GetBackendRenderbufferId() const { return m_backendRBOId; }
|
||||
void Bind() const;
|
||||
|
||||
|
||||
@@ -81,6 +81,22 @@ namespace MobileGL::MG_Backend {
|
||||
// DirectVulkan registers NO resource ops - MGPipeSetResourceOps has exactly one caller
|
||||
// in the whole tree and it is Managers.cpp:2594 - so bit 7 is CLEAR for it, which is
|
||||
// the same fact ObjectSubsystemControlScenario already pins from the client side.
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §6.2). THE ONE CONSTANT THE WHOLE PHASE HANGS ON.
|
||||
//
|
||||
// kCapRunAheadApply says "this server applies an unbarriered record without reading
|
||||
// client memory". That is only true once EVERY per-draw family reads records instead
|
||||
// of the frontend - vi, sb, pg, tx2 and fb all land before it is - so the caps arm
|
||||
// below is gated on this constant, which the P5e INTEGRATION COMMIT flips to true
|
||||
// after the last of them. Until then every P5e package lands with the wait rule, the
|
||||
// static WaitClass column and the barriered predicate compiled and INERT: the client
|
||||
// never latches RunAheadArmed, so it runs today's lockstep path byte for byte.
|
||||
//
|
||||
// It is a constant and not a knob on purpose. An operator cannot turn a half-migrated
|
||||
// server into a run-ahead one, because the failure mode is not a slow frame - it is
|
||||
// the apply thread reading client memory that has already moved, which renders wrong
|
||||
// rather than aborting.
|
||||
constexpr Bool kMGPipeP5eRunAheadReady = false;
|
||||
|
||||
Uint64 ConsumedSubsystemsFor(BackendType type) {
|
||||
switch (type) {
|
||||
case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a;
|
||||
@@ -166,6 +182,21 @@ namespace MobileGL::MG_Backend {
|
||||
serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) {
|
||||
capBits |= MG_Pipe::kCapBackendOwnsXfbCapture;
|
||||
}
|
||||
// P5e (CONTRACT-P5E.md §1, §6): kCapRunAheadApply, THE DIRECTGLES ARM AND ONLY IT.
|
||||
//
|
||||
// Magma is deliberately absent and is not an omission: it keeps the lockstep for
|
||||
// the whole of P5e, its four apply-thread allocator sites are real debt P7 retires,
|
||||
// and MGPipeApplierCurrentRecordIsBarriered() answers true for every record on a
|
||||
// server that does not publish this bit - which is exactly what keeps those probes
|
||||
// and its BARRIER_PULLED reads inside P5C's semantics and its rsp accounting
|
||||
// honest. Publishing the bit here for DirectVulkan would turn accounted pulls into
|
||||
// torn ones, so MagmaPipeIdentityTest pins its absence.
|
||||
//
|
||||
// The Espryt arm is gated on kMGPipeP5eRunAheadReady, which is false until the
|
||||
// integration commit: the bit is what ARMS the client, so every package before it
|
||||
// lands inert.
|
||||
capBits |= MG_Pipe::MGPipeRunAheadCapBitsFor(MG_Config::ActiveBackendType,
|
||||
kMGPipeP5eRunAheadReady);
|
||||
session.SetCapabilityBits(capBits);
|
||||
session.SetBackend(loop.Backend());
|
||||
|
||||
|
||||
@@ -48,6 +48,31 @@
|
||||
|
||||
namespace MobileGL::MG_Pipe {
|
||||
|
||||
// GL_READ_ONLY / GL_WRITE_ONLY / GL_READ_WRITE folded into the one byte the wire carries.
|
||||
// A value the enum does not name would otherwise truncate silently into a Uint8, which is
|
||||
// the class of bug the descriptors exist to close.
|
||||
//
|
||||
// P5e (CONTRACT-P5E.md §1, ruling 16) MOVED THE NUMBERS OUT FROM UNDER THIS FUNCTION and
|
||||
// left the GLenum half here, where the GLenum is. The three values are
|
||||
// MGPipeValueTypes.h's MGPipeImageAccess, which the server decodes through, so the encode
|
||||
// and the decode are one table instead of two literals that happened to agree. It is also
|
||||
// why the function is a free function now rather than a private static: the unit case that
|
||||
// pins the three constants has to reach BOTH halves (MG_Test/Pipe/ImageEmitTest.cpp).
|
||||
inline Uint32 MGPipeEncodeImageAccess(GLenum access) {
|
||||
switch (access) {
|
||||
case GL_READ_ONLY:
|
||||
return kMGPipeImageAccessReadOnly;
|
||||
case GL_WRITE_ONLY:
|
||||
return kMGPipeImageAccessWriteOnly;
|
||||
case GL_READ_WRITE:
|
||||
return kMGPipeImageAccessReadWrite;
|
||||
default:
|
||||
MOBILEGL_ASSERT(false, "glBindImageTexture access 0x%x is not one of the three GL names",
|
||||
static_cast<Uint>(access));
|
||||
return kMGPipeImageAccessReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
// D-G3. Over the tail with Start and Count mixed in, the same shape the two sampler sets
|
||||
// use - and it covers InternalFormat and Access because those are live glBindImageTexture
|
||||
// state that the format-less image bake keys on, not decoration.
|
||||
@@ -157,24 +182,6 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint32 Window() const { return m_window; }
|
||||
|
||||
private:
|
||||
// GL_READ_ONLY / GL_WRITE_ONLY / GL_READ_WRITE folded into the one byte the wire
|
||||
// carries. A value the enum does not name would otherwise truncate silently into a
|
||||
// Uint8, which is the class of bug the descriptors exist to close.
|
||||
static Uint32 MGPipeEncodeImageAccess(GLenum access) {
|
||||
switch (access) {
|
||||
case GL_READ_ONLY:
|
||||
return 0;
|
||||
case GL_WRITE_ONLY:
|
||||
return 1;
|
||||
case GL_READ_WRITE:
|
||||
return 2;
|
||||
default:
|
||||
MOBILEGL_ASSERT(false, "glBindImageTexture access 0x%x is not one of the three GL names",
|
||||
static_cast<Uint>(access));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Array<MGPImageView, kMGPipeMaxImageUnits> m_entries{};
|
||||
MGPShaderImages m_lastImages{};
|
||||
Uint32 m_window = 0;
|
||||
|
||||
@@ -2023,6 +2023,15 @@ namespace MobileGL::MG_Pipe {
|
||||
// emission gate is the subsystem bit plus the whole-record hash and nothing else.
|
||||
case MGPipeFieldEmitter::SetContextValues:
|
||||
return kMGPipeSubsystemResidualValues;
|
||||
// P5e's one emitted row (MG_Remote/CONTRACT-P5E.md §5.6): the indexed buffer
|
||||
// binding points. Written HERE at the contract commit, with the Coverage.def row
|
||||
// and EmittedCallSuppliesTheWholeField's arm beside it, for the reason
|
||||
// kMGPipeWiredSubsystems' block states one paragraph down - this file belongs to
|
||||
// the contract package for the whole phase, and an emitter enumerator whose
|
||||
// dispatch arm lived in the family's own worktree is the merge trap that block
|
||||
// exists to close. It is inert until that family's wired constant leaves 0.
|
||||
case MGPipeFieldEmitter::SetShaderBuffers:
|
||||
return kMGPipeSubsystemBufferBindings;
|
||||
case MGPipeFieldEmitter::kNone:
|
||||
break;
|
||||
}
|
||||
@@ -2091,6 +2100,15 @@ namespace MobileGL::MG_Pipe {
|
||||
MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS,
|
||||
"the MGPipe vertex-attribute capacity and the frontend's have drifted");
|
||||
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1, ruling 10), pinned here for the same reason and in
|
||||
// the same place: this translation unit sees the frontend constant and the MG_Pipe one,
|
||||
// and nothing else does. 84 is what the emitter may describe and what the wire carries;
|
||||
// the BACKEND clamps to the device's real GL_MAX_UNIFORM_BUFFER_BINDINGS, as it does
|
||||
// today, because a client-side clamp would read a device capability from the wrong side.
|
||||
static_assert(kMGPipeMaxBufferBindingPoints ==
|
||||
MG_State::GLState::BufferBindingPointCount,
|
||||
"the MGPipe buffer-binding-point capacity and the frontend's have drifted");
|
||||
|
||||
// ---- P4a's SEVEN pairings, and EVERY ONE OF THEM COMPARES AGAINST
|
||||
// SubsystemForEmitter RATHER THAN AGAINST A CONSTANT. That is the lesson written out
|
||||
// twenty lines above and it is not a style preference: naming the subsystem constant
|
||||
@@ -2128,6 +2146,23 @@ namespace MobileGL::MG_Pipe {
|
||||
SubsystemForEmitter(MGPipeFieldEmitter::SetDispatchProgram),
|
||||
"set_draw_program and set_dispatch_program are one family and one A/B");
|
||||
|
||||
// P5e's pairing, in the same shape and for the same reason as P4a's seven: the two maps
|
||||
// answer different questions - one takes a field's EMITTER, the other a dirty BIT - and
|
||||
// the emission is gated on one while the residual-fill skip is gated on the other, so a
|
||||
// divergence would push a call whose field is still pulled, or skip a field whose call
|
||||
// was never emitted. Compared against SubsystemForEmitter rather than against the
|
||||
// constant, because pinning the two MAPS to each other is the statement that cannot
|
||||
// drift.
|
||||
static_assert(MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers) ==
|
||||
SubsystemForEmitter(MGPipeFieldEmitter::SetShaderBuffers),
|
||||
"set_shader_buffers and NEW_SHADER_BUFFERS must name one subsystem");
|
||||
static_assert(MGPipeSubsystemForDirty(MGPipeDirty::NewConstBuffers) ==
|
||||
MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers),
|
||||
"the three binding-point bits are one family and one A/B");
|
||||
static_assert(MGPipeSubsystemForDirty(MGPipeDirty::NewSoTargets) ==
|
||||
MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers),
|
||||
"the three binding-point bits are one family and one A/B");
|
||||
|
||||
// THE TEXTURE-RESOURCE SUBSYSTEM HAS NO DIRTY BIT, and that has to be asserted rather
|
||||
// than left as an absence: its calls are dispatched from the GL entry points that
|
||||
// cause them, so a bit that started naming it would gate the emission twice - once at
|
||||
@@ -2283,6 +2318,13 @@ namespace MobileGL::MG_Pipe {
|
||||
case MGPipeInputField::GetTextureUnitObject:
|
||||
case MGPipeInputField::GetProgramForDraw:
|
||||
case MGPipeInputField::GetProgramForDispatch:
|
||||
// P5e (CONTRACT-P5E.md §5.6): the same answer for the same reason. The field is
|
||||
// four raw bases into the frontend's binding-point table and set_shader_buffers
|
||||
// carries resolved {handle, offset, size} ranges; the applier cannot produce a
|
||||
// pointer, so skipping the pull would leave the mirror null on every draw of every
|
||||
// push build. What retires it is the four Espryt consumers reading the applier's
|
||||
// BoundShaderBuffers, not this row.
|
||||
case MGPipeInputField::GetBufferBindingPoint:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
|
||||
@@ -62,8 +62,23 @@ namespace MobileGL::MG_Pipe {
|
||||
SetSamplerViews, // P4a - wired (backend debounce deletion: P3b/P4b)
|
||||
BindSamplerStates, // P4a - wired (backend debounce deletion: P3b/P4b)
|
||||
SetShaderImages, // P4a - wired (backend debounce deletion: P3b/P4b)
|
||||
SetShaderBuffers, // P4b
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1, ruling 11): THREE SLOTS, ONE PER CLASS, not one
|
||||
// for the call. set_shader_buffers is emitted per Class (Uniform / ShaderStorage /
|
||||
// AtomicCounter) because the record's own Class field says which binding-point array
|
||||
// it describes - so a single slot would make every emission of one class cancel the
|
||||
// previous emission of another, and the shader-storage set would be suppressed as
|
||||
// "unchanged" by a uniform set that happened to hash the same way. Three slots also
|
||||
// keep the per-family fire tally and the A/B meaningful, which one keyed on
|
||||
// (slot, Class) would not.
|
||||
SetShaderBuffersUniform, // P5e (sb)
|
||||
SetShaderBuffersShaderStorage, // P5e (sb)
|
||||
SetShaderBuffersAtomicCounter, // P5e (sb)
|
||||
SetStreamOutputTargets, // P4b
|
||||
// P5e (CONTRACT-P5E.md §1): set_program_bindings, the post-link binding record. Not
|
||||
// kVarTail-only - it has three tails - but the same rule applies: the emitter latches
|
||||
// on (Cso, backendStateVersion, blockBindingVersion) and the whole-record hash is what
|
||||
// says "nothing moved".
|
||||
SetProgramBindings, // P5e (pg)
|
||||
SetVertexAttribDefaults, // P2 - the one consumer that is wired
|
||||
SetFramebufferState, // P4a - wired
|
||||
// P5c rv (CONTRACT-P5C.md §5.3). NOT kVarTail either - the same shape as
|
||||
|
||||
@@ -136,6 +136,19 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeDirtyBit(MGPipeDirty::NewFramebuffer) | MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) |
|
||||
MGPipeDirtyBit(MGPipeDirty::NewSamplers) | MGPipeDirtyBit(MGPipeDirty::NewShaderImages);
|
||||
|
||||
// The THREE P5e adds, all one subsystem (kMGPipeSubsystemBufferBindings): the indexed
|
||||
// buffer binding points, whose three bits have been computed and counted since P2 and have
|
||||
// named no subsystem since. Added rather than edited in, for the reason above.
|
||||
//
|
||||
// NewSoTargets rides the same bit even though set_stream_output_targets stays UNEMITTED
|
||||
// for the whole of P5e (XFB is lockstep by escalation, CONTRACT-P5E.md §5.7): the bit is
|
||||
// "which A/B switch owns this family's legacy arm", and an operator clearing bit 13 has to
|
||||
// get the whole binding-point family's frontend walk back rather than two thirds of it -
|
||||
// the rule P3a's three and P4a's two triples already state.
|
||||
inline constexpr Uint32 kMGPipeDirtyEmittedAtP5e =
|
||||
kMGPipeDirtyEmittedAtP4a | MGPipeDirtyBit(MGPipeDirty::NewConstBuffers) |
|
||||
MGPipeDirtyBit(MGPipeDirty::NewShaderBuffers) | MGPipeDirtyBit(MGPipeDirty::NewSoTargets);
|
||||
|
||||
inline constexpr const char* kMGPipeDirtyNames[kMGPipeDirtyCount] = {
|
||||
"NEW_RENDER_STATE",
|
||||
"NEW_PIPELINE_STATE",
|
||||
@@ -198,6 +211,22 @@ namespace MobileGL::MG_Pipe {
|
||||
case MGPipeDirty::NewSamplers:
|
||||
case MGPipeDirty::NewShaderImages:
|
||||
return kMGPipeSubsystemSamplers;
|
||||
// P5e's three, one subsystem (MG_Remote/CONTRACT-P5E.md §1): the indexed buffer
|
||||
// binding points. Bits 15/16/17 have been computed and counted since P2 and have named
|
||||
// no subsystem since - "the remaining bits have no call of their own until P4b", which
|
||||
// the default arm below used to say for them. set_shader_buffers is the call, and
|
||||
// set_stream_output_targets stays unemitted while riding the same A/B bit, for the
|
||||
// reason kMGPipeDirtyEmittedAtP5e states.
|
||||
//
|
||||
// NAMING THE SUBSYSTEM IS NOT THE SAME AS EMITTING. The emission gate is five
|
||||
// conjuncts (PipeFill.cpp's `wants()`), one of which is kMGPipeWiredSubsystems - and
|
||||
// the buffer-binding family's wired constant is 0 until the package that gives the
|
||||
// emitter its body sets it, exactly as P4a's four families were. So this map moves
|
||||
// here, inert, and nothing is emitted and no field is skipped on its account yet.
|
||||
case MGPipeDirty::NewConstBuffers:
|
||||
case MGPipeDirty::NewShaderBuffers:
|
||||
case MGPipeDirty::NewSoTargets:
|
||||
return kMGPipeSubsystemBufferBindings;
|
||||
// NO BIT NAMES kMGPipeSubsystemTextureResources, and that is deliberate rather than an
|
||||
// omission: the texture and renderbuffer resource_* calls and set_texture_params are
|
||||
// dispatched from the GL entry points that cause them - a constructor, a storage
|
||||
@@ -205,8 +234,8 @@ namespace MobileGL::MG_Pipe {
|
||||
// is. Bit 10 gates those dispatch sites; there is no dirty bit to map onto it and
|
||||
// there must not be one, or the emission would be gated twice and disagree with itself.
|
||||
default:
|
||||
// The remaining bits have no call of their own until P4b, so there is no
|
||||
// subsystem to switch and the residual fill keeps supplying their fields.
|
||||
// Every dirty bit now names a subsystem; the arm stays because the switch is over
|
||||
// a value cast from an index and a future bit must not fall off the end.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +236,23 @@
|
||||
// And GetTextureObject / GetProgramObject are STICKY (see MGP_COVERAGE_STICKY_LIST): they are
|
||||
// keyed by GL name, they are object lookups rather than verb state, and a forwarded field has
|
||||
// no storage for an emitted call to supply.
|
||||
//
|
||||
// P5e ADDS ONE ROW, GetBufferBindingPoint -> SetShaderBuffers (MG_Remote/CONTRACT-P5E.md §5.6),
|
||||
// and it is SHAPE-ONLY like P3a's and P4a's six: the field's storage is four raw bases into the
|
||||
// frontend's Array<Array<BindingSlotRange1D, 84>, 4>, and set_shader_buffers carries RESOLVED
|
||||
// {handle, offset, size} ranges per class. The applier has no way to produce a pointer and P5e
|
||||
// deliberately does not give it one, so the row lands in PipeFill.cpp's
|
||||
// EmittedCallSuppliesTheWholeField FALSE arm and the decision is taken THERE. What retires the
|
||||
// pull is not a better applier, it is the four Espryt consumers reading the applier's
|
||||
// BoundShaderBuffers instead of the frontend's binding points - at which point the
|
||||
// FieldOwnership.def row flips to RECORD_SUPPLIED.
|
||||
//
|
||||
// THE ROW IS INERT AT THIS COMMIT for GetBoundVertexArray's reason one phase on: the emission
|
||||
// and the fill skip both test kMGPipeWiredSubsystems, and the buffer-binding family's wired
|
||||
// constant is 0 until the package that gives MG_Impl/Pipe/ShaderBufferEmit.h its body sets it.
|
||||
// It lands here, with its two PipeFill.cpp switch arms, because this file and that one belong
|
||||
// to the contract package for the whole phase - a family package that had to add an enumerator
|
||||
// AND the arm that dispatches on it would be editing the one file the merge trap lives in.
|
||||
#define MGP_COVERAGE_EMITTED_LIST(X) \
|
||||
X(GetActiveTextureUnit, SetContextValues) \
|
||||
X(GetBlendColor, SetDynamicState) \
|
||||
@@ -243,6 +260,7 @@
|
||||
X(GetBlendFuncIndexed, CreateRenderState) \
|
||||
X(GetBoundTransformFeedbackLifetimeId, SetContextValues) \
|
||||
X(GetBoundVertexArray, BindVertexElements) \
|
||||
X(GetBufferBindingPoint, SetShaderBuffers) \
|
||||
X(GetClampReadColor, SetDynamicState) \
|
||||
X(GetClearColor, SetDynamicState) \
|
||||
X(GetClearDepth, SetDynamicState) \
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
/* no record fallback, and PipeFill.cpp:1902-1905 says outright that what retires the pull */ \
|
||||
/* is P8, not a better applier: the storage is a SharedPtr<VertexArrayObject> and */ \
|
||||
/* bind_vertex_elements carries an eight-byte {slot, gen}. */ \
|
||||
X(GetBoundVertexArray, BARRIER_PULLED, "P8", \
|
||||
X(GetBoundVertexArray, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend heap reference; the record carries a handle, the mirror is a pointer") \
|
||||
/* 18 Espryt sites + 11 Magma. Coverage.def:37-70 splits the 15 BufferTargets across three */ \
|
||||
/* calls and leaves SEVEN with no carrier at all (CopyRead, CopyWrite, PixelPack, */ \
|
||||
@@ -77,19 +77,19 @@
|
||||
/* though eight targets are covered. */ \
|
||||
X(GetBufferBindingSlot, BARRIER_PULLED, "P8 (indirect), P9 (readback), P13 (transfer)", \
|
||||
"7 of 15 BufferTargets have no call; the field is one array over all 15") \
|
||||
X(GetBufferBindingPoint, BARRIER_PULLED, "P3b/P4b, P7", \
|
||||
X(GetBufferBindingPoint, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend BindingSlotRange1D pointer") \
|
||||
/* 8 Espryt sites through GetFramebufferBindingSlotChecked + 13 Magma. SyncCurrentFBO */ \
|
||||
/* (:2995) is SELF-DECLARED monolith glue (DirectGLES.cpp:2961-2965) while BindCurrentFBO */ \
|
||||
/* (:4303-4353) is already split-clean - the bind is migrated, the sync is not. */ \
|
||||
X(GetFramebufferBindingSlot, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
||||
X(GetFramebufferBindingSlot, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend BindingSlot<FramebufferObject> pointer") \
|
||||
X(GetImageTextureBinding, BARRIER_PULLED, "P3b/P4b, P7", \
|
||||
X(GetImageTextureBinding, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend ImageTextureBinding base pointer") \
|
||||
X(GetTextureUnitObject, BARRIER_PULLED, "P3b/P4b, P7", \
|
||||
X(GetTextureUnitObject, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend TextureUnit base pointer; 13 Espryt + 8 Magma sites") \
|
||||
/* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \
|
||||
X(GetProgramForDraw, BARRIER_PULLED, "P8 (Espryt), P7 (Magma)", \
|
||||
X(GetProgramForDraw, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
||||
/* P5b package i1 (CONTRACT-P5B.md §6.9, the one row the contract grants a package). This */ \
|
||||
/* was FATAL "reachable only from kDispatch; there is no compute on the reduced path" - and */ \
|
||||
@@ -97,7 +97,7 @@
|
||||
/* PrepareForCompute pulls this inside it (DirectGLES.cpp:5779, VulkanRenderer.cpp:7327). */ \
|
||||
/* It is GetProgramForDraw's twin in every respect, so it takes its class and its retiring */ \
|
||||
/* phases. Overturned by: nothing in P5b; P7/P8 retire both rows together. */ \
|
||||
X(GetProgramForDispatch, BARRIER_PULLED, "P7 (Magma), P8 (Espryt)", \
|
||||
X(GetProgramForDispatch, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
||||
/* The XFB family's one OBJECT row. XFB itself is off the reduced path, but kDraw's */ \
|
||||
/* may-read mask carries it and the draw walk reads it regardless - which is exactly the */ \
|
||||
|
||||
@@ -54,6 +54,30 @@ namespace MobileGL::MG_Pipe {
|
||||
kOptional = 1u << 5,
|
||||
};
|
||||
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §2.2). THE FIFTH COLUMN OF PipeCalls.def: what the client
|
||||
// does after publishing a record of this call, on a server that publishes
|
||||
// kCapRunAheadApply. Unscoped for MGPipeCallClass's reason - PipeCalls.def spells these as
|
||||
// bare tokens so the same file can be read by the C++ preprocessor and by
|
||||
// scripts/gen_pipe.py, which generates MGPipeWaitClassFor(op) from it.
|
||||
//
|
||||
// IT IS A VALUE, NOT AN OPCODE AND NOT A FLAG. The wire carries the opcode and both roles
|
||||
// recover the class from it, exactly as they recover the flags - which is the whole reason
|
||||
// MGPipeBarriered(op, payload, applierState) can be one function computed identically by
|
||||
// the emit table and by the sink instead of two copies that agree until they do not.
|
||||
//
|
||||
// kWaitNone is 0 so that an uninitialised read is the SAFE direction in exactly one sense
|
||||
// and the dangerous one in the other, which is why nothing reads this table without an
|
||||
// opcode that came out of the catalogue: MGPipeWaitClassFor answers kWaitClassCount for an
|
||||
// opcode it does not know, and a caller holding bytes off a stream must reach its own
|
||||
// Fatal{ProtocolCorruption} rather than a wait decision.
|
||||
enum MGPipeWaitClass : Uint8 {
|
||||
kWaitNone = 0,
|
||||
kWaitApplied,
|
||||
kWaitReply,
|
||||
kWaitPresent,
|
||||
kWaitClassCount,
|
||||
};
|
||||
|
||||
// The pipeline/dynamic split of RenderStateParameters, defined exactly once (section
|
||||
// 4.5.2): MG_Pipe/MGPipeRenderStateSpans.{h,cpp}, which landed with P2 and computes
|
||||
// every chunk boundary with offsetof. Include that header to use it; what stays here
|
||||
@@ -106,7 +130,17 @@ namespace MobileGL::MG_Pipe {
|
||||
// the three unit sets
|
||||
inline constexpr Uint64 kMGPipeSubsystemPrograms = 1ull << 12; // shader CSO, draw/dispatch
|
||||
// program, global constants
|
||||
// bits 13..62 reserved for the later phases, allocated in ROADMAP order.
|
||||
// P5e's one (MG_Remote/CONTRACT-P5E.md §1). The INDEXED BUFFER BINDING POINTS: the three
|
||||
// dirty bits NewConstBuffers / NewShaderBuffers / NewSoTargets, the set_shader_buffers
|
||||
// emitter, and the backend's four binding-point walks. It is its own bit and not part of
|
||||
// bit 7's resource family for the reason every other split here has: a UBO/SSBO binding
|
||||
// path that regressed and a buffer path that regressed are different findings, and
|
||||
// clearing one must not disarm the other.
|
||||
//
|
||||
// BIT 13 REQUIRES BIT 7, and for bit 11's reason: every MGPBufferRange::Res names a Buffer
|
||||
// handle, and only bit 7 puts one in the slot table.
|
||||
inline constexpr Uint64 kMGPipeSubsystemBufferBindings = 1ull << 13;
|
||||
// bits 14..62 reserved for the later phases, allocated in ROADMAP order.
|
||||
// NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of CSOs, so
|
||||
// every pipeline-version change mints a fresh CSO and the map is never probed. This is
|
||||
// the negative control the whole CSO design is measured against (ROADMAP.md P2).
|
||||
@@ -124,6 +158,13 @@ namespace MobileGL::MG_Pipe {
|
||||
kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers |
|
||||
kMGPipeSubsystemPrograms),
|
||||
"the P4a phase constant and P4a's four subsystem bits have drifted");
|
||||
// P5e's, and P4a's is NOT edited: 0x1fff stays P5e's "everything P4a had and nothing of
|
||||
// mine" control, exactly as 0x1ff was P4a's and 0x7f was P3a's. The push default becomes
|
||||
// 0x3fff, so the A/B that reproduces today's picture is one bit cleared.
|
||||
inline constexpr Uint64 kMGPipeSubsystemsMigratedAtP5e = 0x3fffull; // bits 0..13
|
||||
static_assert(kMGPipeSubsystemsMigratedAtP5e ==
|
||||
(kMGPipeSubsystemsMigratedAtP4a | kMGPipeSubsystemBufferBindings),
|
||||
"the P5e phase constant and P5e's subsystem bit have drifted");
|
||||
|
||||
// The catalogue itself. Only macros, so it is safe to expand inside the namespace, and
|
||||
// consumers (the unit test, later the transport) get MGP_CALL_LIST from this header.
|
||||
|
||||
@@ -133,8 +133,38 @@ namespace MobileGL::MG_Pipe {
|
||||
// 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,
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1, §6). THE SERVER APPLIES AN UNBARRIERED RECORD
|
||||
// WITHOUT READING CLIENT MEMORY: every object it needs is resolved from a handle the
|
||||
// record or the applier state carries, so the client may publish and move on instead
|
||||
// of waiting for appliedSeq at every verb (rule F).
|
||||
//
|
||||
// It is set in the DirectGLES arm of the server's CallMask and NEVER in the
|
||||
// DirectVulkan one - Magma keeps the lockstep for the whole of P5e (§6), its
|
||||
// apply-thread allocator debt is real, and a bit published there would turn that debt
|
||||
// into torn reads rather than into the accounted pulls it is today. On Espryt the arm
|
||||
// is gated on kMGPipeP5eRunAheadReady (MG_Backend/Init.cpp), false until the P5e
|
||||
// integration commit, so every package lands with the wait rule inert.
|
||||
//
|
||||
// The client latches RunAheadArmed() = m_barrierArmed && Ipc.RunAhead &&
|
||||
// Caps().HasCap(kCapRunAheadApply) at the first caps adoption after Start; a later
|
||||
// snapshot may only turn it OFF, because a caps re-run must not be able to make a
|
||||
// lockstep server run-ahead in the middle of a frame.
|
||||
kCapRunAheadApply = 1ull << 10,
|
||||
};
|
||||
|
||||
// THE RUN-AHEAD ARM, AS A PURE FUNCTION, so that "Magma never" is something a unit case can
|
||||
// hold rather than a line inside MG_Backend/Init.cpp's InitSplitRoles that only a live
|
||||
// split session reaches. `ready` is Init.cpp's kMGPipeP5eRunAheadReady - false until the
|
||||
// P5e integration commit - and the BACKEND TYPE is the half that must not depend on it:
|
||||
// whatever `ready` says, a DirectVulkan server publishes no bit 10, because Magma keeps the
|
||||
// lockstep for the whole of P5e (CONTRACT-P5E.md §6) and its apply thread really does read
|
||||
// client memory inside MagmaP7AllocatorDebtScope. MG_Test/Pipe/MagmaPipeIdentityTest.cpp
|
||||
// pins both halves; reverting the type test there is the phase's named red.
|
||||
inline constexpr Uint64 MGPipeRunAheadCapBitsFor(BackendType type, Bool ready) {
|
||||
return (ready && type == BackendType::DirectGLES) ? static_cast<Uint64>(kCapRunAheadApply)
|
||||
: static_cast<Uint64>(kCapNone);
|
||||
}
|
||||
|
||||
struct MGPCaps {
|
||||
// The ~90 flat scalars the backends already publish, by inclusion rather than by
|
||||
// restatement: a caps field added there must not need a second edit here. This is
|
||||
@@ -591,10 +621,26 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint8 NativeFloat64;
|
||||
Uint8 PointSizeDemoted;
|
||||
Uint8 EnableSpirvValidation;
|
||||
// P5e (CONTRACT-P5E.md §1). 1 = the link this record describes SUCCEEDED. Today
|
||||
// "linked" is only implied by the create existing, which is not the same statement:
|
||||
// create_shader_state is re-issued on the same handle at every link that moves
|
||||
// GetLinkVersion, and a FAILED relink of a bound program moves it too. The server's
|
||||
// clean condition needs the difference spelled, because a draw on a program the
|
||||
// frontend reports unlinked DECLINES (the frontend raises INVALID_OPERATION) and a
|
||||
// record that cannot say so leaves the twin rebuilding from empty artefacts.
|
||||
//
|
||||
// THE DESCRIPTOR GREW 192 -> 200 AND THE DRAFT'S "in the descriptor's existing pad"
|
||||
// WAS WRONG: the four bytes above end at offset 24 and MGPBlobRef is 8-aligned, so
|
||||
// there is no spare byte and a fifth one costs the alignment slack below. It is paid
|
||||
// once, on a record that fires at most once per link, and it is confined to the push
|
||||
// build (this payload is only ever instantiated under MOBILEGL_PIPE_PUSH), which is
|
||||
// why G1 does not see it. PipeCatalogueTest pins the new size.
|
||||
Uint8 LinkStatus;
|
||||
Uint8 Pad0[7];
|
||||
MGPBlobRef Spirv[6]; // per stage
|
||||
MGPBlobRef Reflection;
|
||||
};
|
||||
MGP_ASSERT_POD(MGPProgramDesc, 192);
|
||||
MGP_ASSERT_POD(MGPProgramDesc, 200);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// set_*
|
||||
@@ -802,6 +848,17 @@ namespace MobileGL::MG_Pipe {
|
||||
inline constexpr Uint32 kMGPipeMaxTextureUnits = 192;
|
||||
inline constexpr Uint32 kMGPipeMaxImageUnits = 192;
|
||||
|
||||
// P5e (CONTRACT-P5E.md §1, ruling 10). How many INDEXED BUFFER BINDING POINTS one
|
||||
// set_shader_buffers record may describe per class. 84 is the frontend's capacity
|
||||
// (MG_State/GLState/BufferState/BufferState.h's BufferBindingPointCount, itself the GL 4.5
|
||||
// core minimum for GL_MAX_UNIFORM_BUFFER_BINDINGS) and it is what travels: the BACKEND
|
||||
// clamps to the device's real ceiling, as it does today, because a client-side clamp would
|
||||
// read a device capability from the wrong side of the wire.
|
||||
//
|
||||
// Pinned against the frontend constant in MG_Impl/Pipe/PipeFill.cpp, the one translation
|
||||
// unit that sees both, exactly as the two above are.
|
||||
inline constexpr Uint32 kMGPipeMaxBufferBindingPoints = 84;
|
||||
|
||||
struct MGPVertexBuffer {
|
||||
MGPipeHandle Res;
|
||||
Uint64 Offset;
|
||||
@@ -907,8 +964,23 @@ namespace MobileGL::MG_Pipe {
|
||||
// Var-tail header: MGPBufferRange[Count], then MGHostSpan[HostSpanCount]. HostSpanCount is
|
||||
// 0, or Count for the Uniform class under kCapNeedsHostUboBytes (a range with nothing to
|
||||
// ship carries an empty span, so the two arrays stay index-aligned).
|
||||
// MGPShaderBuffers::Class's three values, and the ONLY spelling of them (P5e,
|
||||
// MG_Remote/CONTRACT-P5E.md §1). The payload's comment has named them since P4a and
|
||||
// nothing numbered them, so the emitter and the applier were one literal each away from
|
||||
// disagreeing. Uniform is 0 so a zeroed record describes the uniform binding points, which
|
||||
// is the class every workload has.
|
||||
//
|
||||
// XFB IS NOT ONE OF THEM: set_stream_output_targets carries a Generation this payload has
|
||||
// no field for and its capture points are span-scoped state latched at Begin, so the
|
||||
// catalogue's split between the two rows stays (§5.7).
|
||||
inline constexpr Uint32 kMGPipeShaderBufferClassUniform = 0;
|
||||
inline constexpr Uint32 kMGPipeShaderBufferClassShaderStorage = 1;
|
||||
inline constexpr Uint32 kMGPipeShaderBufferClassAtomicCounter = 2;
|
||||
inline constexpr Uint32 kMGPipeShaderBufferClassCount = 3;
|
||||
|
||||
struct MGPShaderBuffers {
|
||||
Uint32 Class; // Uniform | ShaderStorage | AtomicCounter
|
||||
Uint32 Class; // MGPipeTypes.h's kMGPipeShaderBufferClass* - Uniform | ShaderStorage |
|
||||
// AtomicCounter
|
||||
Uint32 Start;
|
||||
Uint32 Count;
|
||||
Uint32 WritableMask;
|
||||
@@ -927,6 +999,60 @@ namespace MobileGL::MG_Pipe {
|
||||
};
|
||||
MGP_ASSERT_POD(MGPStreamOutputTargets, 24);
|
||||
|
||||
// ---- set_program_bindings (P5e, opcode 80; CONTRACT-P5E.md §1 and §5.5) --------------
|
||||
//
|
||||
// THE THREE POST-LINK MUTABLE REFLECTION FIELDS. create_shader_state carries the archive
|
||||
// as LINKED; glUniformBlockBinding, glUniform1i on a sampler and glShaderStorageBlockBinding
|
||||
// all move a binding AFTER the link, inside the frontend's LinkArtifacts, so an archive
|
||||
// snapshot alone is wrong and the server would answer a draw with the bindings the program
|
||||
// was linked with rather than the ones it is bound with.
|
||||
//
|
||||
// Three tails, in this order, because the three live in three different index spaces:
|
||||
// Int32 BlockBindings[BlockBindingCount] dense, GL uniform-block index
|
||||
// MGPProgramSamplerUnit SamplerUnits[SamplerUnitCount] sparse, ascending Location
|
||||
// MGPProgramStorageOverride StorageOverrides[StorageOverrideCount]
|
||||
// The storage-override map is NAME-keyed BY DESIGN (ProgramObject.h: three index spaces,
|
||||
// one coordinate), so its key travels as a host span staged whole, exactly as
|
||||
// set_storage_block_binding's single name does.
|
||||
//
|
||||
// Emitted BEFORE create_shader_state at the same validate point: a rebuild inside the verb
|
||||
// must already see the bindings. A re-issued create CLEARS all three tails, for the reason
|
||||
// GlobalConstants is cleared - a relink replaces the archive the tails index into.
|
||||
struct MGPProgramSamplerUnit {
|
||||
Uint32 Location; // the frontend uniform location
|
||||
Int32 Unit; // the texture image unit glUniform1i put there
|
||||
};
|
||||
MGP_ASSERT_POD(MGPProgramSamplerUnit, 8);
|
||||
|
||||
struct MGPProgramStorageOverride {
|
||||
MGHostSpan Name; // the block name, NUL included, staged whole
|
||||
Int32 Binding;
|
||||
Uint32 Pad0;
|
||||
};
|
||||
MGP_ASSERT_POD(MGPProgramStorageOverride, 40);
|
||||
|
||||
// The three declared bounds. A program that exceeds one is a COUNTED REFUSAL and never a
|
||||
// truncation: a sampler array larger than 256 locations is a program this record cannot
|
||||
// describe, and silently describing 256 of them is the class of bug the descriptors exist
|
||||
// to close. The codec raises Fatal{ProtocolCorruption, "SetProgramBindings.<field>"}.
|
||||
inline constexpr Uint32 kMGPipeMaxProgramBlockBindings = 64;
|
||||
inline constexpr Uint32 kMGPipeMaxProgramSamplerUnits = 256;
|
||||
inline constexpr Uint32 kMGPipeMaxProgramStorageOverrides = 64;
|
||||
|
||||
struct MGPProgramBindings {
|
||||
MGPipeHandle Cso;
|
||||
// The client's commutative hash of the override MAP, the same function the backend
|
||||
// computes today (Managers.cpp's ComputeShaderStorageBlockBindingSignature), so the
|
||||
// server's "do I have to rebuild" clause is one Uint64 compare and the name tail is
|
||||
// touched only on a rebuild.
|
||||
Uint64 Signature;
|
||||
Uint32 BlockBindingCount;
|
||||
Uint32 SamplerUnitCount;
|
||||
Uint32 StorageOverrideCount;
|
||||
Uint32 Pad0;
|
||||
};
|
||||
MGP_ASSERT_POD(MGPProgramBindings, 32);
|
||||
|
||||
// Covers the DEFAULT UNIFORM BLOCK only (D6).
|
||||
struct MGPGlobalConstants {
|
||||
MGPipeHandle ShaderCso;
|
||||
@@ -1413,6 +1539,18 @@ namespace MobileGL::MG_Pipe {
|
||||
// server never reads the indirect buffer to learn a count and the client never sends
|
||||
// ranges it does not have.
|
||||
kDrawIsIndirect = 1u << 5,
|
||||
// P5e (CONTRACT-P5E.md §1, §5.1, ruling 2). AT LEAST ONE ENABLED ATTRIBUTE OF THE
|
||||
// BOUND VAO HAS NO BUFFER: the draw's vertex bytes live in client memory and the
|
||||
// server uploads them today by dereferencing the raw client pointer the record's
|
||||
// Offset carries (Managers.cpp's SyncClientSideAttributesForDrawArrays). That is
|
||||
// exactly the read rule F forbids, and it is NOT refused under split at this head -
|
||||
// only client INDEX arrays are. So the flag exists to make the case VISIBLE on the
|
||||
// wire: the barriered predicate escalates on it, and under run-ahead the client
|
||||
// refuses the draw by name (Fatal{UnmigratedVerb, "DrawArrays+CLIENT_ARRAYS"}) rather
|
||||
// than publishing bytes that may move before they are read. The staged form - a
|
||||
// per-attribute {BindingIndex, MGHostSpan} tail, shaped like kDrawHasUserIndices - is
|
||||
// P8's; the lockstep arm is unchanged.
|
||||
kDrawClientArrays = 1u << 6,
|
||||
};
|
||||
|
||||
// = pipe_draw_info. Today's twenty draw entry points collapse onto this one call, with
|
||||
|
||||
@@ -599,6 +599,55 @@ namespace MobileGL {
|
||||
Uint32 Divisor; // 12
|
||||
};
|
||||
|
||||
// ---- MGPImageView::Access, P5e (CONTRACT-P5E.md §1, ruling 16) -----------------------
|
||||
//
|
||||
// ONE TABLE, BOTH ROLES. The CLIENT has encoded this byte since P4a
|
||||
// (MG_Impl/Pipe/ImageEmit.h's MGPipeEncodeImageAccess, which folds the three GL names into
|
||||
// 0/1/2 and asserts on anything else); the SERVER open-coded the reverse mapping at its one
|
||||
// reader and carried a comment saying the encoding "does not exist", which was stale. The
|
||||
// numbers now live HERE, in the value header both sides already include, so the encode and
|
||||
// the decode cannot drift - the client emitter names these enumerators instead of literals
|
||||
// and the backend decodes through the function below.
|
||||
//
|
||||
// The enum is NOT the GLenum: this header is pure (no MG_State, MG_Impl, MG_Backend or
|
||||
// MG_Remote - scripts/check_include_closure.py probe "value-header") and a GL token is a
|
||||
// frontend spelling. The GLenum <-> enumerator half stays at the one client site that has
|
||||
// a GLenum in its hand.
|
||||
enum class MGPipeImageAccess : Uint8 {
|
||||
ReadOnly = 0, // GL_READ_ONLY
|
||||
WriteOnly = 1, // GL_WRITE_ONLY
|
||||
ReadWrite = 2, // GL_READ_WRITE
|
||||
Count = 3,
|
||||
};
|
||||
|
||||
// 0 IS READ-ONLY AND THAT IS WHY THE ORDER IS THIS ONE, not the low byte of the GL tokens:
|
||||
// a zeroed MGPImageView must decode to the most restrictive access, so a record that forgot
|
||||
// to set the field can only ever lose a write, never invent one.
|
||||
inline constexpr Uint8 kMGPipeImageAccessReadOnly = static_cast<Uint8>(MGPipeImageAccess::ReadOnly);
|
||||
inline constexpr Uint8 kMGPipeImageAccessWriteOnly = static_cast<Uint8>(MGPipeImageAccess::WriteOnly);
|
||||
inline constexpr Uint8 kMGPipeImageAccessReadWrite = static_cast<Uint8>(MGPipeImageAccess::ReadWrite);
|
||||
|
||||
// The decoder's own precondition. Any other value is Fatal{ProtocolCorruption,
|
||||
// "ImageView.Access"} at the READER - this header may not log, so it answers the question
|
||||
// and the caller owns the refusal.
|
||||
inline constexpr Bool MGPipeImageAccessIsValid(Uint8 encoded) {
|
||||
return encoded < static_cast<Uint8>(MGPipeImageAccess::Count);
|
||||
}
|
||||
|
||||
inline constexpr MGPipeImageAccess MGPipeDecodeImageAccess(Uint8 encoded) {
|
||||
return static_cast<MGPipeImageAccess>(encoded);
|
||||
}
|
||||
|
||||
// "May the shader read / write through this binding." Stated here rather than at each
|
||||
// reader because the image sweep's writable set and the backend's barrier plan ask the
|
||||
// same question of the same three values.
|
||||
inline constexpr Bool MGPipeImageAccessReads(MGPipeImageAccess access) {
|
||||
return access == MGPipeImageAccess::ReadOnly || access == MGPipeImageAccess::ReadWrite;
|
||||
}
|
||||
inline constexpr Bool MGPipeImageAccessWrites(MGPipeImageAccess access) {
|
||||
return access == MGPipeImageAccess::WriteOnly || access == MGPipeImageAccess::ReadWrite;
|
||||
}
|
||||
|
||||
// ---- trip wires (P0.5). Sizes are what every ABI MobileGL ships on produces: every
|
||||
// member is a fixed-width scalar, an enum of one, or an array of those - no pointer, no
|
||||
// SizeT - except the vertex types, which carry SharedPtr<BufferObject> by design and are
|
||||
|
||||
@@ -1537,6 +1537,13 @@ namespace MobileGL::MG_Pipe {
|
||||
PipeInputs::kBufferTargetCount,
|
||||
"the record's per-target array and PipeInputs' have drifted");
|
||||
MGPipeApplyAccess::SetContextValues(gPipeInputs, values);
|
||||
// P5e (CONTRACT-P5E.md §2.1, escalation (i)): mirror the XFB-active flag into the
|
||||
// applier state as well. The barriered predicate has to be computable on the SERVER
|
||||
// from applier state alone - gPipeInputs is the block the run-ahead client stops
|
||||
// filling, so a predicate that read the answer out of THERE would be reading the very
|
||||
// memory the rule exists to stop it reading. One store, inert until the caps bit is
|
||||
// published, and it is what makes the two roles' third clause the same clause.
|
||||
g_applier.IsTransformFeedbackActive = values.IsTransformFeedbackActive != 0;
|
||||
}
|
||||
|
||||
void MGPipeApplySetPatchState(const MGPPatchState& patch) {
|
||||
@@ -2822,6 +2829,90 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeApplierNoteTextureStateMoved();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P5e's two entry points, DECLARED AND REFUSED HERE (MG_Remote/CONTRACT-P5E.md §1)
|
||||
// ---------------------------------------------------------------------------------
|
||||
//
|
||||
// Package sb writes the first body and package pg the second. Until then a record that
|
||||
// reached either one would be a binding set the server accepted and dropped, so the answer
|
||||
// is a named abort rather than a quiet return: the picture would be wrong and the lane
|
||||
// would be green, which is the one failure mode this campaign spends its refusals on.
|
||||
//
|
||||
// Neither is reachable today - no route row installs the table slot and the wire decoder's
|
||||
// arms for both opcodes validate and decline (PipeCatalogueTest pins both slots null) - so
|
||||
// this is a link-time seam, not a runtime one.
|
||||
void MGPipeApplySetShaderBuffers(const MGPShaderBuffers& hdr, const MGPBufferRange* tail) {
|
||||
(void)tail;
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"set_shader_buffers\"} - the applier entry point "
|
||||
"is declared by P5e package c0e and bodied by package sb; a record for class %u "
|
||||
"with %u range(s) reached it, which means a route was installed ahead of its "
|
||||
"consumer",
|
||||
hdr.Class, hdr.Count);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void MGPipeApplySetProgramBindings(const MGPProgramBindings& hdr, const Int32* blockBindings,
|
||||
const MGPProgramSamplerUnit* samplerUnits,
|
||||
const MGPProgramStorageOverride* storageOverrides,
|
||||
const char* const* storageOverrideNames) {
|
||||
(void)blockBindings;
|
||||
(void)samplerUnits;
|
||||
(void)storageOverrides;
|
||||
(void)storageOverrideNames;
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"set_program_bindings\"} - the applier entry point "
|
||||
"is declared by P5e package c0e and bodied by package pg; a record for shader CSO "
|
||||
"{%u, %u} with %u/%u/%u tail entries reached it, which means a route was installed "
|
||||
"ahead of its consumer",
|
||||
hdr.Cso.Slot, hdr.Cso.Gen, hdr.BlockBindingCount, hdr.SamplerUnitCount,
|
||||
hdr.StorageOverrideCount);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P5e: the barriered predicate and the applier's current-record flag (§2.1, §4.4)
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
Bool MGPipeBarriered(MGPWireOp op, const void* payload, const MGPipeApplierState& st) {
|
||||
// 1. The static column. Every reply row, present, and the rows whose apply still pulls
|
||||
// a BARRIER_PULLED field or probes the client allocator.
|
||||
if (MGPipeWaitClassFor(op) != kWaitNone) return true;
|
||||
// 2. Escalation (i): an open transform-feedback span makes every verb inside it
|
||||
// barriered. XFB is not migrated by P5e - StartPendingTransformFeedback holds
|
||||
// frontend SharedPtrs across two verbs, which rule C forbids an applier entry point
|
||||
// outright - so the span keeps P5C's semantics and the cost on the steady Espryt
|
||||
// path is one bool test per draw.
|
||||
if (MGPipeCallClassFor(op) == kCtxVerb && st.IsTransformFeedbackActive) return true;
|
||||
// 3. Escalation (ii): a draw whose enabled attributes include a client-memory array.
|
||||
// The server uploads those bytes by dereferencing a raw client pointer, so the draw
|
||||
// cannot run ahead. Under run-ahead the client refuses it before it is published
|
||||
// (§5.1), so this clause is what the LOCKSTEP arm - where the draw is legal - uses.
|
||||
if (op == MGPWireOp::DrawVbo && payload != nullptr) {
|
||||
const auto& draw = *static_cast<const MGPDrawInfo*>(payload);
|
||||
if ((draw.Flags & static_cast<Uint8>(kDrawClientArrays)) != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Server-private and deliberately NOT a member of MGPipeApplierState: it is not per-context
|
||||
// state the applier owns, it is "which record is the apply thread inside right now", and a
|
||||
// reader (the allocator guard) that had to find the right context's applier first would be
|
||||
// asking a harder question than the one it needs answered.
|
||||
//
|
||||
// TRUE IS THE DEFAULT AND THE LOCKSTEP ANSWER. On a server that does not publish
|
||||
// kCapRunAheadApply every record is barriered, the client is parked in its own wait for
|
||||
// each of them, and the two named exemption scopes of CONTRACT-P5C §3.1 keep working
|
||||
// exactly as they do today. Nothing writes this until package id sets it from
|
||||
// MGPipeBarriered in the sink's ApplyOne.
|
||||
namespace {
|
||||
thread_local Bool g_currentRecordBarriered = true;
|
||||
} // namespace
|
||||
|
||||
Bool MGPipeApplierCurrentRecordIsBarriered() { return g_currentRecordBarriered; }
|
||||
|
||||
void MGPipeApplierSetCurrentRecordBarriered(Bool barriered) {
|
||||
g_currentRecordBarriered = barriered;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// w3: the shader CSO, the two program bindings and the default uniform block
|
||||
//
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
#include "MGPipeRenderStateSpans.h"
|
||||
#include "MGPipeTypes.h"
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §2.1): MGPipeBarriered takes an MGPWireOp and reads the
|
||||
// generated wait-class and call-class tables, all of which live in MGPipe.h. It is a sibling
|
||||
// header in the same layer - MG_Pipe includes MG_Pipe - so the closure does not move.
|
||||
#include "MGPipe.h"
|
||||
|
||||
// The in-process applier: the SERVER half of the calls P2 emits. Under split this file is
|
||||
// MG_Remote/Server/PipeApplier (ARCHITECTURE.md 8.3); in the monolith it writes
|
||||
@@ -367,6 +371,16 @@ namespace MobileGL::MG_Pipe {
|
||||
// GlobalConstants is the one allocation P4a adds per program, it is bounded by
|
||||
// Desc.GlobalUboSize, and it is NOT on the hot path: set_global_constants is
|
||||
// (ShaderCso, Version) keyed and fires at most once per program per frame.
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1): one entry of set_program_bindings' third tail as the
|
||||
// RECORD owns it. The wire carries the block name as an MGHostSpan into SEG_STAGE, which
|
||||
// retires with the record that named it (rule C), so the applier copies the bytes here
|
||||
// rather than keeping the span: a rebuild happens at the next bind, which may be a frame
|
||||
// later, and by then the staged run is somebody else's.
|
||||
struct MGPipeProgramStorageOverride {
|
||||
String Name;
|
||||
Int32 Binding = 0;
|
||||
};
|
||||
|
||||
struct MGPipeShaderCsoRecord {
|
||||
Uint32 Gen = 0;
|
||||
Bool Live = false;
|
||||
@@ -377,6 +391,31 @@ namespace MobileGL::MG_Pipe {
|
||||
Vector<Uint8> GlobalConstants;
|
||||
Uint64 GlobalConstantsSerial = 0;
|
||||
Uint64 Serial = 0;
|
||||
|
||||
// ---- P5e's set_program_bindings tails (CONTRACT-P5E.md §1, §5.5) -------------------
|
||||
//
|
||||
// DECLARED BY c0e, FILLED BY PACKAGE pg. THE THREE POST-LINK MUTABLE REFLECTION FIELDS
|
||||
// the archive cannot answer: glUniformBlockBinding, glUniform1i on a sampler uniform and
|
||||
// glShaderStorageBlockBinding all move a binding INSIDE the frontend's LinkArtifacts
|
||||
// after the link, so a twin that answered a draw from the archive alone would bind the
|
||||
// uniform blocks the program was LINKED with rather than the ones it is BOUND with.
|
||||
//
|
||||
// A RE-ISSUED create_shader_state CLEARS ALL OF THEM, for the reason it clears
|
||||
// GlobalConstants: a relink replaces the archive these index into, so a surviving tail
|
||||
// would name block indices and uniform locations of a program that no longer exists.
|
||||
// The client re-emits set_program_bindings before the create that describes the relink.
|
||||
Vector<Int32> BlockBindings; // dense, GL uniform-block index order
|
||||
Vector<MGPProgramSamplerUnit> SamplerUnits; // sparse, ascending Location
|
||||
Vector<MGPipeProgramStorageOverride> StorageOverrides; // name-keyed by design
|
||||
// The client's commutative hash of the override MAP, so the twin's "must I rebuild the
|
||||
// storage-block bindings" clause is one Uint64 compare and the name tail is touched only
|
||||
// on a rebuild. 0 is "no overrides", which is what an untouched program has.
|
||||
Uint64 Signature = 0;
|
||||
// Server-owned MGGen, ++ on every applied set_program_bindings for this record. It is
|
||||
// what the program twin's clean condition and both texture-unit memos key on (ruling 6),
|
||||
// so it obeys VertexBuffersSerial's rule: ADVANCED, never returned to a value it has
|
||||
// already handed out.
|
||||
Uint64 BindingsSerial = 0;
|
||||
};
|
||||
|
||||
// The vertex-elements CSO as the applier holds it: the unpacked blob, both views, plus
|
||||
@@ -693,6 +732,42 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeHandle BoundShaderCso = kMGPipeNullHandle;
|
||||
Uint64 ProgramBindingSerial = 0;
|
||||
|
||||
// ---- P5e: THE INDEXED BUFFER BINDING POINTS (CONTRACT-P5E.md §5.6, rulings 10/11) ---
|
||||
//
|
||||
// DECLARED BY c0e, FILLED BY PACKAGE sb. One window per CLASS, because the record is
|
||||
// per class - a set_shader_buffers names Uniform, ShaderStorage or AtomicCounter and
|
||||
// describes THAT target's array. XFB does NOT ride this state: its targets are span-
|
||||
// scoped and carry a Generation this payload has no field for, so set_stream_output_
|
||||
// targets keeps its own row and XFB stays lockstep (§5.7).
|
||||
//
|
||||
// THE VAR-TAIL WINDOW IS THE BOUND, exactly as it is for the three unit sets above:
|
||||
// Start is 0 by contract, Count is the touched high-water mark, and a binding at or
|
||||
// above Count means "nothing bound" - which is what the frontend array's default says
|
||||
// too, so the server does not have to clear entries outside the window to be right.
|
||||
Array<MGPBufferRange, kMGPipeMaxBufferBindingPoints>
|
||||
BoundShaderBuffers[kMGPipeShaderBufferClassCount]{};
|
||||
Uint32 ShaderBufferStart[kMGPipeShaderBufferClassCount] = {0, 0, 0};
|
||||
Uint32 ShaderBufferCount[kMGPipeShaderBufferClassCount] = {0, 0, 0};
|
||||
// Which entries of classes 1 and 2 the shader may WRITE through. It is what replaces
|
||||
// the backend's own GPU-write marking walk (DirectGLES.cpp's three MarkBufferGpuWritten
|
||||
// sites), which sb deletes under a transport because the client already owns the
|
||||
// GPU-write set and the server's walk is over client memory.
|
||||
Uint32 ShaderBufferWritableMask[kMGPipeShaderBufferClassCount] = {0, 0, 0};
|
||||
// ONE serial for all three classes, ++ on every applied record and ADVANCED (never
|
||||
// zeroed) by MGPipeApplierReset, for VertexBuffersSerial's reason - and the emitter's
|
||||
// latch resets with it, or the first emission after a make-current is suppressed as
|
||||
// unchanged.
|
||||
Uint64 ShaderBuffersSerial = 0;
|
||||
|
||||
// ---- P5e: the applied value the barriered predicate's XFB escalation reads ---------
|
||||
//
|
||||
// MGPContextValues::IsTransformFeedbackActive as set_context_values last delivered it.
|
||||
// It is mirrored HERE, beside the applier's other working state, and not only written
|
||||
// into gPipeInputs, because MGPipeBarriered must be computable on the SERVER from
|
||||
// applier state alone: the record that carries it precedes the verb on the ring, so
|
||||
// both roles evaluate the same predicate over the same value (§2.1, escalation (i)).
|
||||
Bool IsTransformFeedbackActive = false;
|
||||
|
||||
// ---- P5c (rv, CONTRACT-P5C.md §5.3): the server-side answer for the three texture
|
||||
// SHUTTERS. FieldOwnership.def moves GetSamplingResolutionGeneration /
|
||||
// GetTextureBindGeneration / GetTextureContextId to APPLIER_DERIVED - "a shutter, not
|
||||
@@ -782,6 +857,53 @@ namespace MobileGL::MG_Pipe {
|
||||
// The monolith's single applier. Under split there is one per served context.
|
||||
MGPipeApplierState& MGPipeApplier();
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P5e: THE BARRIERED PREDICATE (MG_Remote/CONTRACT-P5E.md §2.1, ruling 3)
|
||||
// ---------------------------------------------------------------------------------
|
||||
//
|
||||
// ONE FUNCTION, TWO CALLERS, AND THAT IS THE WHOLE POINT. The client calls it at the emit
|
||||
// table to decide whether to wait after publishing; the sink calls it in ApplyOne to decide
|
||||
// whether the record it is about to apply may read client memory. If the two answers ever
|
||||
// differ the failure is silent: the client runs on while the server reads a value that has
|
||||
// already moved. So it is a PURE FUNCTION OF WIRE-VISIBLE DATA - the static wait-class
|
||||
// column, the record's own payload, and applier state that a record earlier on the ring put
|
||||
// there - and both roles evaluate the same three clauses:
|
||||
//
|
||||
// 1. MGPipeWaitClassFor(op) != kWaitNone the static column (§2.2)
|
||||
// 2. kCtxVerb with an open transform-feedback span escalation (i): XFB stays lockstep
|
||||
// 3. draw_vbo carrying kDrawClientArrays escalation (ii): client vertex arrays
|
||||
//
|
||||
// NO OTHER RUNTIME ESCALATION EXISTS. Adding one is an integrator ruling and a row in the
|
||||
// contract, not a condition somebody adds at a call site - because a fourth clause that only
|
||||
// one side computes is the same silent failure one level down.
|
||||
//
|
||||
// Escalation (ii) is a REFUSAL under run-ahead (§5.1), so on a run-ahead server it never
|
||||
// reaches the sink at all; it is here so the predicate is TOTAL and so the lockstep arm,
|
||||
// where such a draw is legal, gets the barrier it needs.
|
||||
//
|
||||
// `payload` may be null, which answers "the fixed payload is not available here" and makes
|
||||
// clause 3 fall through - a caller that has an opcode but no bytes (the emit table before it
|
||||
// has built the record) must not be told a draw is unbarriered on that account, so DrawVbo
|
||||
// with a null payload is treated as carrying no client arrays and the emit site passes the
|
||||
// real MGPDrawInfo it is about to publish.
|
||||
Bool MGPipeBarriered(MGPWireOp op, const void* payload, const MGPipeApplierState& st);
|
||||
|
||||
// Whether the record the apply thread is INSIDE is a barriered one. Server-private: written
|
||||
// by the sink's ApplyOne from MGPipeBarriered before it dispatches, read by
|
||||
// MGPipeRefuseAllocatorFromApplyThread (§4.4) to decide whether a frontend-keyed registry
|
||||
// probe is a named, accounted debt or a role violation.
|
||||
//
|
||||
// IT ANSWERS `true` BY DEFAULT AND THAT IS THE LANDED LOCKSTEP ANSWER, not a stub: on a
|
||||
// server that does not publish kCapRunAheadApply - Magma always, and Espryt until the
|
||||
// integration commit - EVERY record is barriered, the client is parked in its own wait for
|
||||
// each of them, and P5C's semantics hold unchanged. An asserting body here would abort the
|
||||
// monolith and the lockstep split alike, which is why this one seam is implemented rather
|
||||
// than declared: the safe answer and the correct answer are the same answer.
|
||||
Bool MGPipeApplierCurrentRecordIsBarriered();
|
||||
// The one writer, called by the sink at the top of every record's dispatch. Package id owns
|
||||
// the call site (it is the line ra rebases onto); c0e owns the storage so both compile.
|
||||
void MGPipeApplierSetCurrentRecordBarriered(Bool barriered);
|
||||
|
||||
// P5c (rv): the two serials the PipeInputs texture-shutter accessors answer with under a
|
||||
// server-stamped verb (FieldOwnership.def, APPLIER_DERIVED). Free functions rather than
|
||||
// member reads so PipeInputs.h needs this header's DECLARATIONS only... and because the
|
||||
@@ -1145,6 +1267,37 @@ namespace MobileGL::MG_Pipe {
|
||||
void MGPipeApplyBindSamplerStates(const MGPSamplerStates& hdr, const MGPipeHandle* tail);
|
||||
void MGPipeApplySetShaderImages(const MGPShaderImages& hdr, const MGPImageView* tail);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P5e's two entry points: DECLARED BY c0e, BODIED BY sb AND pg
|
||||
// ---------------------------------------------------------------------------------
|
||||
//
|
||||
// Both are declared here, with bodies in PipeApply.cpp that ABORT BY NAME, so that every
|
||||
// P5e package compiles and links against the same signatures from day one and the family
|
||||
// that lands one of them changes a body rather than adding a symbol. An abort and not a
|
||||
// silent no-op: a record that reached an unimplemented applier and returned quietly is a
|
||||
// dropped binding set, which renders wrong rather than stopping - the exact failure mode
|
||||
// ID-39's sixty-six lost uploads had. Nothing routes either opcode until its package lands
|
||||
// (PipeCatalogueTest pins both table slots null), so the abort is unreachable today.
|
||||
|
||||
// set_shader_buffers, per Class. `tail` is hdr.Count MGPBufferRanges starting at hdr.Start;
|
||||
// Class >= kMGPipeShaderBufferClassCount and Start + Count above the capacity are
|
||||
// Fatal{ProtocolCorruption, "SetShaderBuffers.Class"} / "SetShaderBuffers.Count". Entries
|
||||
// outside the declared window are NOT cleared - the record is "the last set as received",
|
||||
// exactly as set_vertex_buffers is.
|
||||
void MGPipeApplySetShaderBuffers(const MGPShaderBuffers& hdr, const MGPBufferRange* tail);
|
||||
|
||||
// set_program_bindings. The three tails in the order the record declares them, plus the
|
||||
// RESOLVED block names for the override tail: the names ride SEG_STAGE as host spans and
|
||||
// SEG_STAGE retires with the record that named them, so the applier must copy them into the
|
||||
// record before this call returns (rule C) and the decoder hands over pointers that are
|
||||
// valid only for the duration of the call. `storageOverrideNames[i]` belongs to
|
||||
// `storageOverrides[i]`, NUL-terminated, exactly as set_storage_block_binding's single name
|
||||
// is. Any of the four pointers may be null when its count is 0.
|
||||
void MGPipeApplySetProgramBindings(const MGPProgramBindings& hdr, const Int32* blockBindings,
|
||||
const MGPProgramSamplerUnit* samplerUnits,
|
||||
const MGPProgramStorageOverride* storageOverrides,
|
||||
const char* const* storageOverrideNames);
|
||||
|
||||
// create_shader_state. THE ARTEFACTS TRAVEL BESIDE THE RECORD, by pointer: all seven of
|
||||
// desc.Spirv[] and desc.Reflection are declared with Size 0 ("this record does not declare
|
||||
// its blob"), which is what a monolith emission is, and the codec is NOT called - zero
|
||||
|
||||
+136
-84
@@ -11,11 +11,42 @@
|
||||
// (scripts/gen_pipe.py -> MG_Pipe/generated/*.inc) and one unit test
|
||||
// (MG_Test/Pipe/PipeCatalogueTest.cpp) pins the arithmetic.
|
||||
//
|
||||
// X(Name, PayloadStruct, Class, Flags)
|
||||
// X(Name, PayloadStruct, Class, Flags, WaitClass)
|
||||
// Class : kScreen | kCtxCso | kCtxState | kCtxObject | kCtxVerb | kCtxQuery
|
||||
// kScreen lands in struct MGPipeScreen, every other class in struct
|
||||
// MGPipeContext (plan section 4.3).
|
||||
// Flags : kNone | kNeedsAck | kHasBlob | kVarTail | kHostSpan | kReplySlot | kOptional
|
||||
// WaitClass : kWaitNone | kWaitApplied | kWaitReply | kWaitPresent
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md section 2.2). WHAT THE CLIENT DOES AFTER IT HAS
|
||||
// PUBLISHED A RECORD OF THIS CALL, on a server that publishes kCapRunAheadApply.
|
||||
// It is A VALUE AND NOT A NEW OP: the opcode order and the flag column are
|
||||
// untouched, and gen_pipe.py emits the column beside kMGPipeCallFlags as
|
||||
// MGPipeWaitClassFor(op) in generated/PipeWire.inc, so the emit table and the
|
||||
// sink compute the barriered predicate from one table instead of two copies.
|
||||
//
|
||||
// kWaitReply the answer is not derivable and the client acts on it (R-5), so
|
||||
// it blocks on the reply slot. EVERY kReplySlot ROW CARRIES IT and
|
||||
// gen_pipe.py refuses a row where the two disagree - a reply-slot
|
||||
// row that did not wait would be a waiter that hangs, and a waiting
|
||||
// row with no slot would block on an answer nobody posts.
|
||||
// resource_subdata is here because the row OWNS a slot; the buffer
|
||||
// half's wantReply=false is a per-record ROUTE decision one level
|
||||
// down (CONTRACT-P5E.md section 2.5), not a second wait class.
|
||||
// kWaitPresent present, whose pacing is the present CREDIT taken BEFORE the
|
||||
// encode rather than a wait after the publish (section 2.4).
|
||||
// kWaitApplied the record's apply still reads a BARRIER_PULLED row or probes the
|
||||
// client allocator, so the client parks in its own wait and the row
|
||||
// keeps CONTRACT-P5C's semantics exactly: its pulls count into rsp
|
||||
// and its probes are legal inside a scope. This column is the
|
||||
// static half of MGPipeBarriered(op, payload, applierState); the
|
||||
// other two escalations (an open XFB span, a draw with client
|
||||
// vertex arrays) are payload-derived and live in that function.
|
||||
// kWaitNone rule F holds for this row's sink: its apply names no client
|
||||
// memory at all, so the client publishes and moves on.
|
||||
//
|
||||
// THE COLUMN IS INERT UNTIL THE INTEGRATION COMMIT publishes kCapRunAheadApply
|
||||
// (MG_Backend/Init.cpp's kMGPipeP5eRunAheadReady): with the bit unpublished the
|
||||
// client runs today's lockstep path and never reads the table.
|
||||
// kNeedsAck on a call means records of this call MAY require an ack; a
|
||||
// per-record predicate decides. resource_respecify carries it for
|
||||
// glBufferStorage - a real synchronous allocation, and the only entry point
|
||||
@@ -45,15 +76,15 @@
|
||||
// appended server-side fence wait 1 and P5c's applier_reset 1
|
||||
// kCtxQuery 8 query object namespace 6, plus the appended timestamp pair 2
|
||||
// kCtxCso 13 CSO create/bind/delete
|
||||
// kCtxState 18 16 of the 18 set_* calls + the temporary set_residual_value_state
|
||||
// + P5c rv's set_context_values
|
||||
// kCtxState 19 16 of the 18 set_* calls + the temporary set_residual_value_state
|
||||
// + P5c rv's set_context_values + P5e's set_program_bindings
|
||||
// kCtxObject 10 set_texture_params (the 17th set_*) + 8 object-scoped transfers, plus
|
||||
// P5c's object_death 1
|
||||
// kCtxVerb 18 3 context-reading transfer calls + the 10 commands, plus the five
|
||||
// P5b-appended verbs (bind_shader_image, patch_parameter,
|
||||
// bind_stream_output, set_storage_block_binding,
|
||||
// copy_framebuffer_to_texture - MG_Remote/CONTRACT-P5B.md)
|
||||
// total 79
|
||||
// total 80
|
||||
//
|
||||
// Reconciliation with the plan's headline numbers (section 4.4 / appendix A), because they
|
||||
// do not add up to a set of UNIQUE records and this file has to hold unique records:
|
||||
@@ -101,9 +132,17 @@
|
||||
// routed through gMGPipeContext like its seventeen siblings, and under monolith it has no
|
||||
// producer at all - the fields keep coming through the residual fill, byte for byte as
|
||||
// before (G1). 79 unique records.
|
||||
// - P5e (MG_Remote/CONTRACT-P5E.md section 1) appended ONE more (opcode 80):
|
||||
// set_program_bindings, the three POST-LINK mutable reflection fields - the uniform-block
|
||||
// bindings, the sampler uniform units and the name-keyed storage-block override map. They
|
||||
// live inside the LinkArtifacts the reflection archive snapshots, and all three move AFTER
|
||||
// the link, so no existing row can carry them: create_shader_state describes the link and
|
||||
// this one describes what happened to it since. It lands with a NULL ROUTE and a sink that
|
||||
// refuses it by name until package pg wires the emitter, exactly as set_shader_buffers has
|
||||
// sat catalogued-and-dead since P4a. 80 unique records.
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
#define MGP_CALL_LIST_DOCUMENTED_COUNT 79
|
||||
#define MGP_CALL_LIST_DOCUMENTED_COUNT 80
|
||||
|
||||
// clang-format off
|
||||
#define MGP_CALL_LIST(X) \
|
||||
@@ -112,7 +151,7 @@
|
||||
/* RendererInfo (MGPipeTypes.h). It carried none until P5 R-13.1; the flag is a */ \
|
||||
/* property of the payload, so its absence was simply wrong. Adding a flag does not */ \
|
||||
/* move an opcode: the opcode is this line's 1-based position. */ \
|
||||
X(GetCaps, MGPCaps, kScreen, kReplySlot|kHasBlob) \
|
||||
X(GetCaps, MGPCaps, kScreen, kReplySlot|kHasBlob, kWaitReply) \
|
||||
/* THE FOUR ACCEPTANCE ROWS (P5 R-5, R-16). These four and map_persistent are the only */ \
|
||||
/* applier entry points that RETURN something the client acts on destructively - clearing */ \
|
||||
/* per-level dirty flags, latching parameters, advancing its own bookkeeping - so the */ \
|
||||
@@ -120,7 +159,7 @@
|
||||
/* until P5, because in monolith the answer is the return value of a direct call and there */ \
|
||||
/* was nothing to declare. kMGPipeCallFlags is now what sizes the reply pool and what the */ \
|
||||
/* decoder posts against, so a row that answers without the flag is a waiter that hangs. */ \
|
||||
X(ResourceCreate, MGPResourceDesc, kScreen, kReplySlot) \
|
||||
X(ResourceCreate, MGPResourceDesc, kScreen, kReplySlot, kWaitReply) \
|
||||
/* NO kHasBlob, BY RULING (P5 R-13.3; CONTRACT-P5.md table 1 row 19). MGPResourceDesc */ \
|
||||
/* owns no MGPBlobRef and gains none: under split `initialBytes` is always nullptr and */ \
|
||||
/* the initial content of a glBufferData(size, data) / glTexImage*(..., data) arrives */ \
|
||||
@@ -129,65 +168,65 @@
|
||||
/* cost is one extra record. NOTE the SECOND uncarried companion on this call, the */ \
|
||||
/* MGPRespecifiedLevel* scope (PipeApply.h:792) - it is NOT bytes and NOT covered by */ \
|
||||
/* this ruling; table 1 row 19 gives it the two pad fields of MGPResourceDesc. */ \
|
||||
X(ResourceRespecify, MGPResourceDesc, kScreen, kNeedsAck|kReplySlot) \
|
||||
X(ResourceDestroy, MGPHandleOnly, kScreen, kNone) \
|
||||
X(MapPersistent, MGPHandleOnly, kScreen, kReplySlot|kOptional) \
|
||||
X(UnmapPersistent, MGPHandleOnly, kScreen, kOptional) \
|
||||
X(FenceCreate, MGPHandleOnly, kScreen, kNone) \
|
||||
X(FenceStatus, MGPHandleOnly, kScreen, kReplySlot) \
|
||||
X(FenceWait, MGPFenceWait, kScreen, kReplySlot) \
|
||||
X(FenceDestroy, MGPHandleOnly, kScreen, kNone) \
|
||||
X(ResourceRespecify, MGPResourceDesc, kScreen, kNeedsAck|kReplySlot, kWaitReply) \
|
||||
X(ResourceDestroy, MGPHandleOnly, kScreen, kNone, kWaitNone) \
|
||||
X(MapPersistent, MGPHandleOnly, kScreen, kReplySlot|kOptional, kWaitReply) \
|
||||
X(UnmapPersistent, MGPHandleOnly, kScreen, kOptional, kWaitNone) \
|
||||
X(FenceCreate, MGPHandleOnly, kScreen, kNone, kWaitNone) \
|
||||
X(FenceStatus, MGPHandleOnly, kScreen, kReplySlot, kWaitReply) \
|
||||
X(FenceWait, MGPFenceWait, kScreen, kReplySlot, kWaitReply) \
|
||||
X(FenceDestroy, MGPHandleOnly, kScreen, kNone, kWaitNone) \
|
||||
/* ---- context: query objects (plan 4.3 gives the namespace to the context) ---- */ \
|
||||
X(QueryCreate, MGPQueryDesc, kCtxQuery, kNone) \
|
||||
X(QueryBegin, MGPQueryDesc, kCtxQuery, kNone) \
|
||||
X(QueryEnd, MGPQueryDesc, kCtxQuery, kNone) \
|
||||
X(QueryAvailable, MGPHandleOnly, kCtxQuery, kReplySlot) \
|
||||
X(QueryResult, MGPQueryResultRequest, kCtxQuery, kReplySlot) \
|
||||
X(QueryDestroy, MGPHandleOnly, kCtxQuery, kNone) \
|
||||
X(QueryCreate, MGPQueryDesc, kCtxQuery, kNone, kWaitNone) \
|
||||
X(QueryBegin, MGPQueryDesc, kCtxQuery, kNone, kWaitNone) \
|
||||
X(QueryEnd, MGPQueryDesc, kCtxQuery, kNone, kWaitNone) \
|
||||
X(QueryAvailable, MGPHandleOnly, kCtxQuery, kReplySlot, kWaitReply) \
|
||||
X(QueryResult, MGPQueryResultRequest, kCtxQuery, kReplySlot, kWaitReply) \
|
||||
X(QueryDestroy, MGPHandleOnly, kCtxQuery, kNone, kWaitNone) \
|
||||
/* ---- context: CSO create/bind/delete (plan 4.4.2, 4.5.2-4.5.5) ---- */ \
|
||||
X(CreateRenderState, MGPRenderStateDesc, kCtxCso, kHasBlob) \
|
||||
X(BindRenderState, MGPBindRenderState, kCtxCso, kNone) \
|
||||
X(DeleteRenderState, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(CreateVertexElements, MGPVertexElements, kCtxCso, kHasBlob) \
|
||||
X(BindVertexElements, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(DeleteVertexElements, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(CreateRenderState, MGPRenderStateDesc, kCtxCso, kHasBlob, kWaitNone) \
|
||||
X(BindRenderState, MGPBindRenderState, kCtxCso, kNone, kWaitNone) \
|
||||
X(DeleteRenderState, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
X(CreateVertexElements, MGPVertexElements, kCtxCso, kHasBlob, kWaitNone) \
|
||||
X(BindVertexElements, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
X(DeleteVertexElements, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
/* kHasBlob because MGPSamplerDesc owns an MGPBlobRef member, `Parameters` - the */ \
|
||||
/* SamplerParameters POD byte for byte, borderColorForm included (P5 R-13.1). The */ \
|
||||
/* companion pointer beside the record today is a TYPED frontend pointer */ \
|
||||
/* (const SamplerParameters*), which is exactly what split may not carry. */ \
|
||||
X(CreateSamplerState, MGPSamplerDesc, kCtxCso, kHasBlob) \
|
||||
X(DeleteSamplerState, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(CreateSamplerView, MGPSamplerView, kCtxCso, kNone) \
|
||||
X(DeleteSamplerView, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(CreateShaderState, MGPProgramDesc, kCtxCso, kHasBlob) \
|
||||
X(BindShaderState, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(DeleteShaderState, MGPHandleOnly, kCtxCso, kNone) \
|
||||
X(CreateSamplerState, MGPSamplerDesc, kCtxCso, kHasBlob, kWaitNone) \
|
||||
X(DeleteSamplerState, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
X(CreateSamplerView, MGPSamplerView, kCtxCso, kNone, kWaitNone) \
|
||||
X(DeleteSamplerView, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
X(CreateShaderState, MGPProgramDesc, kCtxCso, kHasBlob, kWaitNone) \
|
||||
X(BindShaderState, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
X(DeleteShaderState, MGPHandleOnly, kCtxCso, kNone, kWaitNone) \
|
||||
/* ---- context: set_* (plan 4.4.3) ---- */ \
|
||||
X(SetDynamicState, MGPDynamicState, kCtxState, kHasBlob) \
|
||||
X(SetFramebufferState, MGPFramebufferState, kCtxState, kNone) \
|
||||
X(SetVertexBuffers, MGPVertexBuffers, kCtxState, kVarTail) \
|
||||
X(SetIndexBuffer, MGPIndexBuffer, kCtxState, kNone) \
|
||||
X(SetIndirectBuffers, MGPIndirectBuffers, kCtxState, kNone) \
|
||||
X(SetSamplerViews, MGPSamplerViews, kCtxState, kVarTail) \
|
||||
X(BindSamplerStates, MGPSamplerStates, kCtxState, kVarTail) \
|
||||
X(SetShaderImages, MGPShaderImages, kCtxState, kVarTail) \
|
||||
X(SetShaderBuffers, MGPShaderBuffers, kCtxState, kVarTail|kHostSpan) \
|
||||
X(SetStreamOutputTargets, MGPStreamOutputTargets, kCtxState, kVarTail) \
|
||||
X(SetGlobalConstants, MGPGlobalConstants, kCtxState, kHasBlob) \
|
||||
X(SetVertexAttribDefaults, MGPVertexAttribDefaults, kCtxState, kVarTail) \
|
||||
X(SetPixelPackState, MGPPixelPackState, kCtxState, kNone) \
|
||||
X(SetPatchState, MGPPatchState, kCtxState, kNone) \
|
||||
X(SetDrawProgram, MGPHandleOnly, kCtxState, kNone) \
|
||||
X(SetDispatchProgram, MGPHandleOnly, kCtxState, kNone) \
|
||||
X(SetDynamicState, MGPDynamicState, kCtxState, kHasBlob, kWaitNone) \
|
||||
X(SetFramebufferState, MGPFramebufferState, kCtxState, kNone, kWaitNone) \
|
||||
X(SetVertexBuffers, MGPVertexBuffers, kCtxState, kVarTail, kWaitNone) \
|
||||
X(SetIndexBuffer, MGPIndexBuffer, kCtxState, kNone, kWaitNone) \
|
||||
X(SetIndirectBuffers, MGPIndirectBuffers, kCtxState, kNone, kWaitNone) \
|
||||
X(SetSamplerViews, MGPSamplerViews, kCtxState, kVarTail, kWaitNone) \
|
||||
X(BindSamplerStates, MGPSamplerStates, kCtxState, kVarTail, kWaitNone) \
|
||||
X(SetShaderImages, MGPShaderImages, kCtxState, kVarTail, kWaitNone) \
|
||||
X(SetShaderBuffers, MGPShaderBuffers, kCtxState, kVarTail|kHostSpan, kWaitNone) \
|
||||
X(SetStreamOutputTargets, MGPStreamOutputTargets, kCtxState, kVarTail, kWaitNone) \
|
||||
X(SetGlobalConstants, MGPGlobalConstants, kCtxState, kHasBlob, kWaitNone) \
|
||||
X(SetVertexAttribDefaults, MGPVertexAttribDefaults, kCtxState, kVarTail, kWaitNone) \
|
||||
X(SetPixelPackState, MGPPixelPackState, kCtxState, kNone, kWaitNone) \
|
||||
X(SetPatchState, MGPPatchState, kCtxState, kNone, kWaitNone) \
|
||||
X(SetDrawProgram, MGPHandleOnly, kCtxState, kNone, kWaitNone) \
|
||||
X(SetDispatchProgram, MGPHandleOnly, kCtxState, kNone, kWaitNone) \
|
||||
/* Migration-only carrier for Track V, retired field by field across P2..P13. Its */ \
|
||||
/* retirement is a compile error: MGL_RESIDUAL_BLOCK_SIZE only ever goes DOWN and the */ \
|
||||
/* final step asserts sizeof(ResidualValueBlock) == 0 (plan 6.3). */ \
|
||||
X(SetResidualValueState, MGPResidualValueState, kCtxState, kHasBlob) \
|
||||
X(SetResidualValueState, MGPResidualValueState, kCtxState, kHasBlob, kWaitNone) \
|
||||
/* ---- context: per-object state and transfer (plan 4.4.3 set_texture_params, 4.4.4) ---- */ \
|
||||
X(SetTextureParams, MGPTextureParams, kCtxObject, kReplySlot) \
|
||||
X(ResourceSubData, MGPSubData, kCtxObject, kHasBlob|kVarTail|kReplySlot) \
|
||||
X(BufferSubDataResident, MGPSubData, kCtxObject, kHasBlob|kOptional) \
|
||||
X(ResourceSubDataComplete, MGPSubDataComplete, kCtxObject, kNone) \
|
||||
X(SetTextureParams, MGPTextureParams, kCtxObject, kReplySlot, kWaitReply) \
|
||||
X(ResourceSubData, MGPSubData, kCtxObject, kHasBlob|kVarTail|kReplySlot, kWaitReply) \
|
||||
X(BufferSubDataResident, MGPSubData, kCtxObject, kHasBlob|kOptional, kWaitNone) \
|
||||
X(ResourceSubDataComplete, MGPSubDataComplete, kCtxObject, kNone, kWaitNone) \
|
||||
/* NO kHasBlob AND NO MGPBlobRef, BY RULING (P5 R-13.2; CONTRACT-P5.md table 1 row 21). */ \
|
||||
/* It takes a content pointer today, but under split it CARRIES NO BYTES AT ALL: it is */ \
|
||||
/* a {range, AccessFlags} control record, and the bytes of [Offset, Offset+Size) arrive */ \
|
||||
@@ -195,36 +234,36 @@
|
||||
/* drives rewrites its range "from the authoritative shadow" (Managers.cpp:1047-1076), */ \
|
||||
/* and under split the authoritative shadow is server-owned (R-11), so subdata is the */ \
|
||||
/* only way bytes reach it - a blobref here would be a SECOND way to say the same thing. */ \
|
||||
X(ResourceFlushRange, MGPFlushRange, kCtxObject, kNone) \
|
||||
X(ResourceReadback, MGPReadback, kCtxObject, kReplySlot) \
|
||||
X(ResourceCopyRegion, MGPCopyRegion, kCtxObject, kNone) \
|
||||
X(GenerateMipmap, MGPMipPlan, kCtxObject, kNone) \
|
||||
X(GetTextureImage, MGPReadbackInfo, kCtxObject, kReplySlot) \
|
||||
X(ResourceFlushRange, MGPFlushRange, kCtxObject, kNone, kWaitNone) \
|
||||
X(ResourceReadback, MGPReadback, kCtxObject, kReplySlot, kWaitReply) \
|
||||
X(ResourceCopyRegion, MGPCopyRegion, kCtxObject, kNone, kWaitNone) \
|
||||
X(GenerateMipmap, MGPMipPlan, kCtxObject, kNone, kWaitApplied) \
|
||||
X(GetTextureImage, MGPReadbackInfo, kCtxObject, kReplySlot, kWaitReply) \
|
||||
/* ---- context: transfer calls that read whole-context state, and the commands ---- */ \
|
||||
X(Blit, MGPBlit, kCtxVerb, kNone) \
|
||||
X(Clear, MGPClear, kCtxVerb, kNone) \
|
||||
X(ReadPixels, MGPReadbackInfo, kCtxVerb, kReplySlot) \
|
||||
X(DrawVbo, MGPDrawInfo, kCtxVerb, kHostSpan|kVarTail) \
|
||||
X(LaunchGrid, MGPGridInfo, kCtxVerb, kNone) \
|
||||
X(MemoryBarrier, MGPMemoryBarrier, kCtxVerb, kNone) \
|
||||
X(BeginStreamOutput, MGPStreamOutputBegin, kCtxVerb, kNone) \
|
||||
X(EndStreamOutput, MGPXfbAccounting, kCtxVerb, kNone) \
|
||||
X(PauseStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone) \
|
||||
X(ResumeStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone) \
|
||||
X(Flush, MGPFlush, kCtxVerb, kNone) \
|
||||
X(Present, MGPPresent, kCtxVerb, kNone) \
|
||||
X(SetSwapInterval, MGPSwapInterval, kCtxVerb, kOptional) \
|
||||
X(Blit, MGPBlit, kCtxVerb, kNone, kWaitNone) \
|
||||
X(Clear, MGPClear, kCtxVerb, kNone, kWaitNone) \
|
||||
X(ReadPixels, MGPReadbackInfo, kCtxVerb, kReplySlot, kWaitReply) \
|
||||
X(DrawVbo, MGPDrawInfo, kCtxVerb, kHostSpan|kVarTail, kWaitNone) \
|
||||
X(LaunchGrid, MGPGridInfo, kCtxVerb, kNone, kWaitNone) \
|
||||
X(MemoryBarrier, MGPMemoryBarrier, kCtxVerb, kNone, kWaitNone) \
|
||||
X(BeginStreamOutput, MGPStreamOutputBegin, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(EndStreamOutput, MGPXfbAccounting, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(PauseStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(ResumeStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(Flush, MGPFlush, kCtxVerb, kNone, kWaitNone) \
|
||||
X(Present, MGPPresent, kCtxVerb, kNone, kWaitPresent) \
|
||||
X(SetSwapInterval, MGPSwapInterval, kCtxVerb, kOptional, kWaitNone) \
|
||||
/* ---- APPENDED. Opcodes are positional, so a late arrival goes at the END, never into ---- */ \
|
||||
/* ---- its group: three live GLFunctionsTable entries the catalogue had no carrier for. ---- */ \
|
||||
/* glGetInteger64v(GL_TIMESTAMP) - GetGpuTimestampNs, a synchronous server answer, which */ \
|
||||
/* the reply slot carries. The query namespace is the context's (plan 4.3). */ \
|
||||
X(QueryTimestamp, MGPTimestampRequest, kCtxQuery, kReplySlot) \
|
||||
X(QueryTimestamp, MGPTimestampRequest, kCtxQuery, kReplySlot, kWaitReply) \
|
||||
/* glQueryCounter(GL_TIMESTAMP) - QueryCounterTimestamp, a one-shot stamp into a query */ \
|
||||
/* object, NOT a begin/end pair. Kind carries GL_TIMESTAMP. */ \
|
||||
X(QueryCounter, MGPQueryDesc, kCtxQuery, kNone) \
|
||||
X(QueryCounter, MGPQueryDesc, kCtxQuery, kNone, kWaitNone) \
|
||||
/* glWaitSync - WaitSync, the GPU-side wait, distinct from FenceWait's client-side one. */ \
|
||||
/* TimeoutNs is GL_TIMEOUT_IGNORED by contract. */ \
|
||||
X(FenceWaitServer, MGPFenceWait, kScreen, kNone) \
|
||||
X(FenceWaitServer, MGPFenceWait, kScreen, kNone, kWaitNone) \
|
||||
/* ---- APPENDED BY P5b (MG_Remote/CONTRACT-P5B.md): five GLFunctionsTable verbs the ---- */ \
|
||||
/* ---- class-C census measured as first blockers and that no existing row could carry. ---- */ \
|
||||
/* ---- Opcodes 72..76. Every one reaches WireVerbSink, none has an MGPipeApply* entry ---- */ \
|
||||
@@ -232,18 +271,18 @@
|
||||
/* ---- directly, as today. The payload comments in MGPipeTypes.h say why each exists. ---- */ \
|
||||
/* glBindImageTexture - Espryt syncs the unit's binding to the driver AT THE CALL */ \
|
||||
/* (SyncImageTextureBinding), so the validate-time set_shader_images cannot stand in. */ \
|
||||
X(BindShaderImage, MGPImageBind, kCtxVerb, kNone) \
|
||||
X(BindShaderImage, MGPImageBind, kCtxVerb, kNone, kWaitNone) \
|
||||
/* glPatchParameteri(GL_PATCH_VERTICES) - Espryt pushes glPatchParameteri at the call. */ \
|
||||
X(PatchParameter, MGPPatchParameter, kCtxVerb, kNone) \
|
||||
X(PatchParameter, MGPPatchParameter, kCtxVerb, kNone, kWaitNone) \
|
||||
/* glBindTransformFeedback - the driver object rebind and the capture-binding shadow */ \
|
||||
/* invalidation happen at the call; the XFB namespace has no wire lifetime. */ \
|
||||
X(BindStreamOutput, MGPStreamOutputBind, kCtxVerb, kNone) \
|
||||
X(BindStreamOutput, MGPStreamOutputBind, kCtxVerb, kNone, kWaitApplied) \
|
||||
/* glShaderStorageBlockBinding - the block NAME is the coordinate all three sides share */ \
|
||||
/* (BackendObject.h), and a name is bytes: kHasBlob, staged whole in SEG_STAGE, NUL included. */ \
|
||||
X(SetStorageBlockBinding, MGPStorageBlockBinding, kCtxVerb, kHasBlob) \
|
||||
X(SetStorageBlockBinding, MGPStorageBlockBinding, kCtxVerb, kHasBlob, kWaitApplied) \
|
||||
/* glCopyTexImage2D / glCopyTexSubImage2D - a copy whose SOURCE is the read framebuffer, */ \
|
||||
/* which resource_copy_region (resource -> resource) cannot express. */ \
|
||||
X(CopyFramebufferToTexture, MGPCopyFromFramebuffer, kCtxVerb, kNone) \
|
||||
X(CopyFramebufferToTexture, MGPCopyFromFramebuffer, kCtxVerb, kNone, kWaitApplied) \
|
||||
/* ---- APPENDED BY P5c (MG_Remote/CONTRACT-P5C.md §5): the two control records. Opcodes ---- */ \
|
||||
/* ---- 77..78. Neither has an MGPipeApply* entry point and neither gains one: both reach ---- */ \
|
||||
/* ---- WireVerbSink, and under monolith neither has a producer - the reset is a direct ---- */ \
|
||||
@@ -255,14 +294,14 @@
|
||||
/* against every verb that follows. ContextSerial is the client context's make-current */ \
|
||||
/* serial at the edge - ASSERTED against the session's, never dispatched on (P5c: one */ \
|
||||
/* context per session). */ \
|
||||
X(ApplierReset, MGPApplierReset, kScreen, kNone) \
|
||||
X(ApplierReset, MGPApplierReset, kScreen, kNone, kWaitApplied) \
|
||||
/* A frontend state object died. The record carries the HANDLE (and kind) the client's own */ \
|
||||
/* allocator resolves for it - nothing is emitted for an object that never crossed (the */ \
|
||||
/* server never saw it, so there is no twin to kill) - and the sink releases the kind's */ \
|
||||
/* twin table by handle. The framebuffer family's FIRST wire delete opcode: its death used */ \
|
||||
/* to hop to the apply thread through a stack-struct mailbox and a lifetime-id probe of */ \
|
||||
/* the client's allocator, both rule-E surfaces. */ \
|
||||
X(ObjectDeath, MGPHandleOnly, kCtxObject, kNone) \
|
||||
X(ObjectDeath, MGPHandleOnly, kCtxObject, kNone, kWaitNone) \
|
||||
/* ---- P5c rv (CONTRACT-P5C.md §5.3), opcode 79: the residual-value record. One POD that ---- */ \
|
||||
/* ---- carries every value-class BARRIER_PULLED field left after the set_* catalogue: the ---- */ \
|
||||
/* ---- two texture-unit counters, the 15 per-target touched-buffer-binding counts and the ---- */ \
|
||||
@@ -270,7 +309,20 @@
|
||||
/* ---- (whole-record hash suppression, D11's shape), ACTIVE ONLY with an active transport ---- */ \
|
||||
/* ---- (PipeFill.cpp gates both the emission and the residual-fill skip on it): under ---- */ \
|
||||
/* ---- monolith there is no producer and the fields keep being pulled, byte for byte (G1). ---- */ \
|
||||
X(SetContextValues, MGPContextValues, kCtxState, kNone)
|
||||
X(SetContextValues, MGPContextValues, kCtxState, kNone, kWaitNone) \
|
||||
/* ---- P5e (MG_Remote/CONTRACT-P5E.md §1), opcode 80: the post-link binding record. ------ */ \
|
||||
/* ---- THREE POST-LINK MUTABLE REFLECTION FIELDS LIVE INSIDE THE FRONTEND'S LinkArtifacts ---- */ \
|
||||
/* ---- and therefore inside the archive create_shader_state carries - but glUniformBlock- ---- */ \
|
||||
/* ---- Binding, glUniform1i on a sampler and glShaderStorageBlockBinding all move one ---- */ \
|
||||
/* ---- AFTER the link. So an archive snapshot alone answers a draw with the bindings the ---- */ \
|
||||
/* ---- program was LINKED with rather than the ones it is BOUND with, and the server's ---- */ \
|
||||
/* ---- twin rebuilds against the wrong uniform-block points. Three tails, three index ---- */ \
|
||||
/* ---- spaces (dense block index, sparse uniform location, and a NAME span for the storage ---- */ \
|
||||
/* ---- override map, which is name-keyed by design). Emitted BEFORE create_shader_state at ---- */ \
|
||||
/* ---- the same validate point: a rebuild inside the verb must already see the bindings. ---- */ \
|
||||
/* ---- kWaitNone: nothing about it is read back, and under run-ahead a kCtxState row that ---- */ \
|
||||
/* ---- names only handles and values is exactly what rule F admits. ---- */ \
|
||||
X(SetProgramBindings, MGPProgramBindings, kCtxState, kVarTail|kHostSpan, kWaitNone)
|
||||
// clang-format on
|
||||
|
||||
// Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"):
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
#define MGP_FIELDS_MGPProgramDesc(F) \
|
||||
F(Cso) F(StageMask) F(GlobalUboSize) F(ReservedNumSamplesOffset) F(SpirvStatus) F(NativeFloat64) \
|
||||
F(PointSizeDemoted) F(EnableSpirvValidation) F(Spirv) F(Reflection)
|
||||
F(PointSizeDemoted) F(EnableSpirvValidation) F(LinkStatus) F(Spirv) F(Reflection)
|
||||
|
||||
// P4a, ID-12 / esprytobj DV-5: Pad0 became Uint16 TextureTarget. Same trip wire as
|
||||
// MGPFramebufferState's Target below - PADDING_MEMBER_RE only excludes a member still NAMED
|
||||
@@ -145,6 +145,19 @@
|
||||
#define MGP_FIELDS_MGPStreamOutputTargets(F) \
|
||||
F(Count) F(Generation) F(ContentHash)
|
||||
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1): set_program_bindings and its two tail element types. The
|
||||
// third tail's element embeds an MGHostSpan, which has a list of its own below, so the
|
||||
// comparator sees the NAME SPAN's four members rather than memcmp'ing a struct with a pointer
|
||||
// and a pad word in it - the exact false-difference MGPSamplerDesc's blob used to produce.
|
||||
#define MGP_FIELDS_MGPProgramBindings(F) \
|
||||
F(Cso) F(Signature) F(BlockBindingCount) F(SamplerUnitCount) F(StorageOverrideCount)
|
||||
|
||||
#define MGP_FIELDS_MGPProgramSamplerUnit(F) \
|
||||
F(Location) F(Unit)
|
||||
|
||||
#define MGP_FIELDS_MGPProgramStorageOverride(F) \
|
||||
F(Name) F(Binding)
|
||||
|
||||
#define MGP_FIELDS_MGPGlobalConstants(F) \
|
||||
F(ShaderCso) F(Version) F(Blob)
|
||||
|
||||
@@ -394,6 +407,7 @@
|
||||
P(MGPImageBind) P(MGPPatchParameter) P(MGPStreamOutputBind) P(MGPStorageBlockBinding) \
|
||||
P(MGPCopyFromFramebuffer) \
|
||||
P(MGPApplierReset) P(MGPContextValues) \
|
||||
P(MGPProgramBindings) P(MGPProgramSamplerUnit) P(MGPProgramStorageOverride) \
|
||||
P(RenderStateParameters) P(PixelStoreParameters) P(SamplerParameters) P(PerBufferBlendState) \
|
||||
P(StencilFaceState) \
|
||||
P(DynamicBackendParameters) P(MGHostSpan) \
|
||||
|
||||
@@ -106,9 +106,9 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetBlendEquationIndexed
|
||||
"-", // GetBlendFuncIndexed
|
||||
"-", // GetBoundTransformFeedbackName
|
||||
"P8", // GetBoundVertexArray
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetBoundVertexArray
|
||||
"P8 (indirect), P9 (readback), P13 (transfer)", // GetBufferBindingSlot
|
||||
"P3b/P4b, P7", // GetBufferBindingPoint
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetBufferBindingPoint
|
||||
"P7/P13", // GetBufferBindingPointCount
|
||||
"-", // GetTouchedBufferBindingPointCount
|
||||
"-", // GetClampReadColor
|
||||
@@ -121,8 +121,8 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetDepthFunc
|
||||
"-", // GetDepthMask
|
||||
"-", // GetDepthRangeIndexed
|
||||
"P3b/P4b (Espryt), P7 (Magma)", // GetFramebufferBindingSlot
|
||||
"P3b/P4b, P7", // GetImageTextureBinding
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetFramebufferBindingSlot
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetImageTextureBinding
|
||||
"-", // GetLineWidth
|
||||
"-", // GetLogicOp
|
||||
"-", // GetMaxTouchedTextureUnit
|
||||
@@ -136,8 +136,8 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetPolygonOffsetFactor
|
||||
"-", // GetPolygonOffsetUnits
|
||||
"-", // GetPrimitiveRestartIndex
|
||||
"P7 (Magma), P8 (Espryt)", // GetProgramForDispatch
|
||||
"P8 (Espryt), P7 (Magma)", // GetProgramForDraw
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetProgramForDispatch
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetProgramForDraw
|
||||
"P9", // GetProgramObject
|
||||
"-", // GetProvokingVertexMode
|
||||
"-", // GetRenderStateParameters
|
||||
@@ -148,7 +148,7 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetTextureBindGeneration
|
||||
"-", // GetTextureContextId
|
||||
"P7", // GetTextureObject
|
||||
"P3b/P4b, P7", // GetTextureUnitObject
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetTextureUnitObject
|
||||
"-", // GetTransformFeedbackCapturedVertices
|
||||
"-", // GetTransformFeedbackGeneration
|
||||
"-", // GetTransformFeedbackPausedPrimitiveCounter
|
||||
|
||||
@@ -315,6 +315,7 @@ enum class MGPipeFieldEmitter : Uint8 {
|
||||
SetFramebufferState,
|
||||
SetPatchState,
|
||||
SetSamplerViews,
|
||||
SetShaderBuffers,
|
||||
SetShaderImages,
|
||||
SetVertexAttribDefaults,
|
||||
};
|
||||
@@ -331,6 +332,7 @@ inline constexpr const char* kMGPipeFieldEmitterNames[] = {
|
||||
"SetFramebufferState",
|
||||
"SetPatchState",
|
||||
"SetSamplerViews",
|
||||
"SetShaderBuffers",
|
||||
"SetShaderImages",
|
||||
"SetVertexAttribDefaults",
|
||||
};
|
||||
@@ -343,7 +345,7 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount
|
||||
MGPipeFieldEmitter::kNone, // GetBoundTransformFeedbackName
|
||||
MGPipeFieldEmitter::BindVertexElements, // GetBoundVertexArray
|
||||
MGPipeFieldEmitter::kNone, // GetBufferBindingSlot
|
||||
MGPipeFieldEmitter::kNone, // GetBufferBindingPoint
|
||||
MGPipeFieldEmitter::SetShaderBuffers, // GetBufferBindingPoint
|
||||
MGPipeFieldEmitter::kNone, // GetBufferBindingPointCount
|
||||
MGPipeFieldEmitter::SetContextValues, // GetTouchedBufferBindingPointCount
|
||||
MGPipeFieldEmitter::SetDynamicState, // GetClampReadColor
|
||||
@@ -400,7 +402,7 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount
|
||||
MGPipeFieldEmitter::SetContextValues, // GetBoundTransformFeedbackLifetimeId
|
||||
MGPipeFieldEmitter::kNone, // HasOpenTransformFeedbackSpan
|
||||
};
|
||||
inline constexpr SizeT kMGPipeEmittedFieldCount = 47;
|
||||
inline constexpr SizeT kMGPipeEmittedFieldCount = 48;
|
||||
|
||||
struct MGPipeFilledState {
|
||||
Uint64 CurrentVerbSerial;
|
||||
|
||||
@@ -29,7 +29,7 @@ struct MGPipeScreen {
|
||||
void (*ApplierReset)(const MGPApplierReset* payload);
|
||||
};
|
||||
|
||||
// context: 67 calls. A null entry means the backend does not implement this
|
||||
// context: 68 calls. A null entry means the backend does not implement this
|
||||
// call and the frontend keeps its own path (plan B section 4.1).
|
||||
struct MGPipeContext {
|
||||
void (*QueryCreate)(const MGPQueryDesc* payload);
|
||||
@@ -99,11 +99,12 @@ struct MGPipeContext {
|
||||
void (*CopyFramebufferToTexture)(const MGPCopyFromFramebuffer* payload);
|
||||
void (*ObjectDeath)(const MGPHandleOnly* payload);
|
||||
void (*SetContextValues)(const MGPContextValues* payload);
|
||||
void (*SetProgramBindings)(const MGPProgramBindings* payload, const void* varTail, Uint32 varTailCount);
|
||||
};
|
||||
|
||||
inline constexpr SizeT kMGPipeScreenCallCount = 12;
|
||||
inline constexpr SizeT kMGPipeContextCallCount = 67;
|
||||
inline constexpr SizeT kMGPipeCallCount = 79;
|
||||
inline constexpr SizeT kMGPipeContextCallCount = 68;
|
||||
inline constexpr SizeT kMGPipeCallCount = 80;
|
||||
|
||||
// A table that is not exactly its call count of function pointers has grown a
|
||||
// member that no generator knows about.
|
||||
|
||||
@@ -332,3 +332,7 @@ inline void MGP_ObjectDeath(const MGPHandleOnly* payload) {
|
||||
inline void MGP_SetContextValues(const MGPContextValues* payload) {
|
||||
gMGPipeContext.SetContextValues(payload);
|
||||
}
|
||||
|
||||
inline void MGP_SetProgramBindings(const MGPProgramBindings* payload, const void* varTail, Uint32 varTailCount) {
|
||||
gMGPipeContext.SetProgramBindings(payload, varTail, varTailCount);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,9 @@ inline Bool MGPipeVerify(const MGPStorageBlockBinding& a, const MGPStorageBlockB
|
||||
inline Bool MGPipeVerify(const MGPCopyFromFramebuffer& a, const MGPCopyFromFramebuffer& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPApplierReset& a, const MGPApplierReset& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPContextValues& a, const MGPContextValues& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPProgramBindings& a, const MGPProgramBindings& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPProgramSamplerUnit& a, const MGPProgramSamplerUnit& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPProgramStorageOverride& a, const MGPProgramStorageOverride& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const RenderStateParameters& a, const RenderStateParameters& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const PixelStoreParameters& a, const PixelStoreParameters& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const SamplerParameters& a, const SamplerParameters& b, const char** outField);
|
||||
@@ -265,6 +268,12 @@ struct MGPipeHasFieldVerifier<MGPApplierReset> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPContextValues> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPProgramBindings> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPProgramSamplerUnit> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPProgramStorageOverride> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<RenderStateParameters> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<PixelStoreParameters> : std::true_type {};
|
||||
@@ -678,6 +687,21 @@ inline Bool MGPipeVerify(const MGPContextValues& a, const MGPContextValues& b, c
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const MGPProgramBindings& a, const MGPProgramBindings& b, const char** outField) {
|
||||
MGP_FIELDS_MGPProgramBindings(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const MGPProgramSamplerUnit& a, const MGPProgramSamplerUnit& b, const char** outField) {
|
||||
MGP_FIELDS_MGPProgramSamplerUnit(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const MGPProgramStorageOverride& a, const MGPProgramStorageOverride& b, const char** outField) {
|
||||
MGP_FIELDS_MGPProgramStorageOverride(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const RenderStateParameters& a, const RenderStateParameters& b, const char** outField) {
|
||||
MGP_FIELDS_RenderStateParameters(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
@@ -725,4 +749,4 @@ inline Bool MGPipeVerify(const MGPVertexBindingPointWire& a, const MGPVertexBind
|
||||
|
||||
#undef MGP_VERIFY_FIELD
|
||||
|
||||
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 79;
|
||||
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 82;
|
||||
|
||||
@@ -149,7 +149,8 @@ enum class MGPWireOp : Uint16 {
|
||||
ApplierReset = 77,
|
||||
ObjectDeath = 78,
|
||||
SetContextValues = 79,
|
||||
kOpCount = 80,
|
||||
SetProgramBindings = 80,
|
||||
kOpCount = 81,
|
||||
};
|
||||
|
||||
// THE FLAGS, EXPORTED ONCE, INDEXED BY OPCODE (P5 R-13.4). MGPWireRecHeader::Flags is
|
||||
@@ -250,6 +251,7 @@ inline constexpr Uint32 kMGPipeCallFlags[static_cast<SizeT>(MGPWireOp::kOpCount)
|
||||
/* 77 ApplierReset */ static_cast<Uint32>(kNone),
|
||||
/* 78 ObjectDeath */ static_cast<Uint32>(kNone),
|
||||
/* 79 SetContextValues */ static_cast<Uint32>(kNone),
|
||||
/* 80 SetProgramBindings */ static_cast<Uint32>(kVarTail | kHostSpan),
|
||||
};
|
||||
static_assert(sizeof(kMGPipeCallFlags) / sizeof(kMGPipeCallFlags[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
@@ -349,6 +351,7 @@ inline constexpr Uint8 kMGPipeCallClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
||||
/* 77 ApplierReset */ static_cast<Uint8>(kScreen),
|
||||
/* 78 ObjectDeath */ static_cast<Uint8>(kCtxObject),
|
||||
/* 79 SetContextValues */ static_cast<Uint8>(kCtxState),
|
||||
/* 80 SetProgramBindings */ static_cast<Uint8>(kCtxState),
|
||||
};
|
||||
static_assert(sizeof(kMGPipeCallClasses) / sizeof(kMGPipeCallClasses[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
@@ -361,6 +364,126 @@ inline constexpr MGPipeCallClass MGPipeCallClassFor(MGPWireOp op) {
|
||||
: kCallClassCount;
|
||||
}
|
||||
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md 2.2): THE WAIT CLASS OF EVERY OPCODE, indexed the same way.
|
||||
// Index 0 is kWaitClassCount - never a real class - for the flags table's reason: the one
|
||||
// caller that can pass an opcode this catalogue never produced is a decoder holding bytes off
|
||||
// a stream, and it must reach its own Fatal{ProtocolCorruption} rather than a wait decision.
|
||||
//
|
||||
// This is the STATIC HALF of MGPipeBarriered(op, payload, applierState), the predicate the emit
|
||||
// table and the sink compute identically. The other half is payload-derived (an open
|
||||
// transform-feedback span, a draw carrying client vertex arrays) and lives in that function,
|
||||
// because it depends on state a table indexed by opcode cannot see.
|
||||
inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount)] = {
|
||||
/* 0 */ static_cast<Uint8>(kWaitClassCount),
|
||||
|
||||
/* 1 GetCaps */ static_cast<Uint8>(kWaitReply),
|
||||
/* 2 ResourceCreate */ static_cast<Uint8>(kWaitReply),
|
||||
/* 3 ResourceRespecify */ static_cast<Uint8>(kWaitReply),
|
||||
/* 4 ResourceDestroy */ static_cast<Uint8>(kWaitNone),
|
||||
/* 5 MapPersistent */ static_cast<Uint8>(kWaitReply),
|
||||
/* 6 UnmapPersistent */ static_cast<Uint8>(kWaitNone),
|
||||
/* 7 FenceCreate */ static_cast<Uint8>(kWaitNone),
|
||||
/* 8 FenceStatus */ static_cast<Uint8>(kWaitReply),
|
||||
/* 9 FenceWait */ static_cast<Uint8>(kWaitReply),
|
||||
/* 10 FenceDestroy */ static_cast<Uint8>(kWaitNone),
|
||||
/* 11 QueryCreate */ static_cast<Uint8>(kWaitNone),
|
||||
/* 12 QueryBegin */ static_cast<Uint8>(kWaitNone),
|
||||
/* 13 QueryEnd */ static_cast<Uint8>(kWaitNone),
|
||||
/* 14 QueryAvailable */ static_cast<Uint8>(kWaitReply),
|
||||
/* 15 QueryResult */ static_cast<Uint8>(kWaitReply),
|
||||
/* 16 QueryDestroy */ static_cast<Uint8>(kWaitNone),
|
||||
/* 17 CreateRenderState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 18 BindRenderState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 19 DeleteRenderState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 20 CreateVertexElements */ static_cast<Uint8>(kWaitNone),
|
||||
/* 21 BindVertexElements */ static_cast<Uint8>(kWaitNone),
|
||||
/* 22 DeleteVertexElements */ static_cast<Uint8>(kWaitNone),
|
||||
/* 23 CreateSamplerState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 24 DeleteSamplerState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 25 CreateSamplerView */ static_cast<Uint8>(kWaitNone),
|
||||
/* 26 DeleteSamplerView */ static_cast<Uint8>(kWaitNone),
|
||||
/* 27 CreateShaderState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 28 BindShaderState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 29 DeleteShaderState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 30 SetDynamicState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 31 SetFramebufferState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 32 SetVertexBuffers */ static_cast<Uint8>(kWaitNone),
|
||||
/* 33 SetIndexBuffer */ static_cast<Uint8>(kWaitNone),
|
||||
/* 34 SetIndirectBuffers */ static_cast<Uint8>(kWaitNone),
|
||||
/* 35 SetSamplerViews */ static_cast<Uint8>(kWaitNone),
|
||||
/* 36 BindSamplerStates */ static_cast<Uint8>(kWaitNone),
|
||||
/* 37 SetShaderImages */ static_cast<Uint8>(kWaitNone),
|
||||
/* 38 SetShaderBuffers */ static_cast<Uint8>(kWaitNone),
|
||||
/* 39 SetStreamOutputTargets */ static_cast<Uint8>(kWaitNone),
|
||||
/* 40 SetGlobalConstants */ static_cast<Uint8>(kWaitNone),
|
||||
/* 41 SetVertexAttribDefaults */ static_cast<Uint8>(kWaitNone),
|
||||
/* 42 SetPixelPackState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 43 SetPatchState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 44 SetDrawProgram */ static_cast<Uint8>(kWaitNone),
|
||||
/* 45 SetDispatchProgram */ static_cast<Uint8>(kWaitNone),
|
||||
/* 46 SetResidualValueState */ static_cast<Uint8>(kWaitNone),
|
||||
/* 47 SetTextureParams */ static_cast<Uint8>(kWaitReply),
|
||||
/* 48 ResourceSubData */ static_cast<Uint8>(kWaitReply),
|
||||
/* 49 BufferSubDataResident */ static_cast<Uint8>(kWaitNone),
|
||||
/* 50 ResourceSubDataComplete */ static_cast<Uint8>(kWaitNone),
|
||||
/* 51 ResourceFlushRange */ static_cast<Uint8>(kWaitNone),
|
||||
/* 52 ResourceReadback */ static_cast<Uint8>(kWaitReply),
|
||||
/* 53 ResourceCopyRegion */ static_cast<Uint8>(kWaitNone),
|
||||
/* 54 GenerateMipmap */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 55 GetTextureImage */ static_cast<Uint8>(kWaitReply),
|
||||
/* 56 Blit */ static_cast<Uint8>(kWaitNone),
|
||||
/* 57 Clear */ static_cast<Uint8>(kWaitNone),
|
||||
/* 58 ReadPixels */ static_cast<Uint8>(kWaitReply),
|
||||
/* 59 DrawVbo */ static_cast<Uint8>(kWaitNone),
|
||||
/* 60 LaunchGrid */ static_cast<Uint8>(kWaitNone),
|
||||
/* 61 MemoryBarrier */ static_cast<Uint8>(kWaitNone),
|
||||
/* 62 BeginStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 63 EndStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 64 PauseStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 65 ResumeStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 66 Flush */ static_cast<Uint8>(kWaitNone),
|
||||
/* 67 Present */ static_cast<Uint8>(kWaitPresent),
|
||||
/* 68 SetSwapInterval */ static_cast<Uint8>(kWaitNone),
|
||||
/* 69 QueryTimestamp */ static_cast<Uint8>(kWaitReply),
|
||||
/* 70 QueryCounter */ static_cast<Uint8>(kWaitNone),
|
||||
/* 71 FenceWaitServer */ static_cast<Uint8>(kWaitNone),
|
||||
/* 72 BindShaderImage */ static_cast<Uint8>(kWaitNone),
|
||||
/* 73 PatchParameter */ static_cast<Uint8>(kWaitNone),
|
||||
/* 74 BindStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 75 SetStorageBlockBinding */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 76 CopyFramebufferToTexture*/ static_cast<Uint8>(kWaitApplied),
|
||||
/* 77 ApplierReset */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 78 ObjectDeath */ static_cast<Uint8>(kWaitNone),
|
||||
/* 79 SetContextValues */ static_cast<Uint8>(kWaitNone),
|
||||
/* 80 SetProgramBindings */ static_cast<Uint8>(kWaitNone),
|
||||
};
|
||||
static_assert(sizeof(kMGPipeWaitClasses) / sizeof(kMGPipeWaitClasses[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
"the wait-class table and the opcode space disagree");
|
||||
|
||||
inline constexpr MGPipeWaitClass MGPipeWaitClassFor(MGPWireOp op) {
|
||||
const SizeT index = static_cast<SizeT>(op);
|
||||
return index < static_cast<SizeT>(MGPWireOp::kOpCount)
|
||||
? static_cast<MGPipeWaitClass>(kMGPipeWaitClasses[index])
|
||||
: kWaitClassCount;
|
||||
}
|
||||
|
||||
// THE COLUMN'S OWN INVARIANT, stated in C++ as well as in the generator: a reply-slot row
|
||||
// waits for its reply and a waiting row owns a slot. gen_pipe.py refuses a catalogue where the
|
||||
// two disagree, and this is the same statement where a reader of the generated table will look
|
||||
// for it - a kReplySlot row that did not wait would run on and read a slot nobody had posted
|
||||
// into, and a kWaitReply row with no slot would block on an answer nobody posts.
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::kInvalid) == kWaitClassCount,
|
||||
"opcode 0 is not a call and has no wait class");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::Present) == kWaitPresent,
|
||||
"present is paced by the credit, not by a wait after the publish");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::DrawVbo) == kWaitNone,
|
||||
"the steady draw path is what P5e exists to stop waiting on");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::ApplierReset) == kWaitApplied,
|
||||
"a make-current is a fresh server and every verb after it must see the reset");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::ReadPixels) == kWaitReply,
|
||||
"a readback's answer is not derivable (R-5)");
|
||||
|
||||
// Spot checks the generator states about its own output, so that a catalogue edit that
|
||||
// silently drops a flag is a build break here and not a wrong decode six packages away.
|
||||
static_assert(MGPipeCallFlagsFor(MGPWireOp::kInvalid) == static_cast<Uint32>(kNone),
|
||||
@@ -1031,6 +1154,14 @@ static_assert(sizeof(MGPWireRec_SetContextValues) ==
|
||||
((sizeof(MGPWireRecHeader) + sizeof(MGPContextValues) + 7u) & ~SizeT(7u)),
|
||||
"MGPWireRec_SetContextValues gained padding; the wire format moved");
|
||||
|
||||
struct alignas(8) MGPWireRec_SetProgramBindings {
|
||||
MGPWireRecHeader Header;
|
||||
MGPProgramBindings Payload;
|
||||
};
|
||||
static_assert(sizeof(MGPWireRec_SetProgramBindings) ==
|
||||
((sizeof(MGPWireRecHeader) + sizeof(MGPProgramBindings) + 7u) & ~SizeT(7u)),
|
||||
"MGPWireRec_SetProgramBindings gained padding; the wire format moved");
|
||||
|
||||
[[noreturn]] inline void MGPipeWireProtocolFatal(const char* call, Uint64 size, Uint64 remaining) {
|
||||
MGLOG_F("MGPipe: protocol corruption applying %s: size=%llu remaining=%llu", call,
|
||||
static_cast<unsigned long long>(size), static_cast<unsigned long long>(remaining));
|
||||
@@ -1317,6 +1448,9 @@ inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size,
|
||||
case MGPWireOp::SetContextValues:
|
||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetContextValues, "SetContextValues");
|
||||
break;
|
||||
case MGPWireOp::SetProgramBindings:
|
||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetProgramBindings, "SetProgramBindings");
|
||||
break;
|
||||
case MGPWireOp::kInvalid:
|
||||
case MGPWireOp::kOpCount:
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
# CONTRACT-P5E — the client runs ahead of apply on the Espryt draw path
|
||||
|
||||
Authority: this file, beside `CONTRACT-P5.md` (table 0, byte carriers, field ownership, R-1…R-17),
|
||||
`CONTRACT-P5B.md` (the class-C slots) and `CONTRACT-P5C.md` (rule E, the two named exemptions,
|
||||
SEG_EVENT, the guards). Where it disagrees with any of them this file is newer and wins; §8 lists
|
||||
every such place. Base: `feat/disaggregated @ 2fde7034` (code head `1f8de61b`). Every `file:line`
|
||||
was read at that commit and re-resolved at c0e's head; paths are under `MobileGL/` unless they
|
||||
start with `docs/`.
|
||||
|
||||
**How to change it.** Package c0e's file, edited by the integrator first. A P5e package (id / vi /
|
||||
sb / pg / tx2 / fb / ra) that needs a row changed goes through the integrator; the packages compile
|
||||
against the rows below from day one.
|
||||
|
||||
**What c0e actually landed, where it differs from the plan this file was written as.** Four places,
|
||||
each argued where it sits: `MGPProgramDesc` GREW 192 → 200 bytes because it has no spare byte (§1);
|
||||
the applier's per-class binding-point windows are spelled `ShaderBufferStart/Count/WritableMask`
|
||||
rather than bare `Start/Count/WritableMask` (§5.6); the suppressor's three slots are named
|
||||
`SetShaderBuffersUniform/ShaderStorage/AtomicCounter` (§1); and the wire codec's record layout now
|
||||
carries THREE tails rather than two, because `set_program_bindings` has three (§1). Everything else
|
||||
below is the plan as written, and the nineteen rulings of §9 are as the integrator decided them
|
||||
(ID-80…98).
|
||||
|
||||
---
|
||||
|
||||
## §0 What P5e is, and the rule above every row
|
||||
|
||||
P5d made `inproc` CPU-bound on the client thread with the frame still `client work + (per draw)
|
||||
wait for apply + handoff` (R-1, `MG_Remote/Client/ClientSession.cpp:932-959`). The wait exists
|
||||
because the apply of a draw still dereferences client-owned memory: the object-class BARRIER_PULLED
|
||||
rows of `MG_Pipe/FieldOwnership.def:72-106` and the frontend-keyed twin registries
|
||||
(`MG_Backend/DirectGLES/SlotTables.h:411-449`) inside the two P5C §3.1 exemption scopes. **P5e
|
||||
retires the lockstep for every op on the Espryt draw path: the client publishes and moves on; the
|
||||
server resolves every object from a handle a record carried, and the only client memory it may read
|
||||
is the residual fill of a record the client is provably blocked behind.**
|
||||
|
||||
Rules A–E bind unchanged. P5e adds one definition and one rule:
|
||||
|
||||
**Definition — a BARRIERED record.** A record is barriered iff `MGPipeBarriered(op, payload,
|
||||
applierState)` is true, a pure function of wire-visible data computed identically by the emit
|
||||
table and by the sink (§2.1). The client blocks in `WaitForApplied(seq)` after publishing a
|
||||
barriered record; it never blocks after an unbarriered one.
|
||||
|
||||
**Rule F — an unbarriered record's apply names no client memory.** With a live wire on a
|
||||
run-ahead server (§6), the apply of an unbarriered record reads NOTHING from client-owned memory:
|
||||
no frontend object dereference, no `gPipeInputs` BARRIER_PULLED row, no `MGPipeSlots()` probe or
|
||||
mint, no `SharedPtr` to a frontend object taken or held. Every object is resolved from a handle the
|
||||
record or the applier state carries, through a handle-keyed twin table (§4). A violation is
|
||||
`Fatal{UnmigratedPipeInput, "<field>@<verb>"}` (a pulled row, §3.3), `Fatal{RoleViolation,
|
||||
"MGPipeSlots"}` (an allocator probe, §4.4) or `Fatal{RoleViolation, "<surface>"}` (P5C §6 layer 1),
|
||||
**regardless of `MOBILEGL_IPC_STRICT_ERRORS`** — the value would be torn or stale by construction,
|
||||
so there is no "count it" arm. A barriered record's apply keeps P5C's rules exactly: its pulls count
|
||||
into `rsp`, its probes are legal inside a scope, and the client's wait is what makes them legal.
|
||||
|
||||
Refusal vocabulary is P5C's, plus: `Fatal{UnmigratedVerb, "DrawArrays+CLIENT_ARRAYS"}` (§5.1),
|
||||
`Fatal{UnmigratedVerb, "set_global_constants"}` (§5.5), `Fatal{UnmigratedEmulation,
|
||||
"image-bindable-redirty"}` (§5.2), `Fatal{ProtocolCorruption, "SetSamplerViews.Count"}` /
|
||||
`"BindSamplerStates.Count"` / `"SetShaderBuffers.Class"` / `"SetShaderBuffers.Count"` /
|
||||
`"SetProgramBindings.<field>"` / `"Present.FrameSerial"` / `"ImageView.Access"` (§1, §5),
|
||||
`Fatal{PresentCreditTimeout}` (§2.4), `Fatal{RoleViolation, "gPipeInputs"}` (§3.5).
|
||||
`Fatal{EventRingOverflow}` is RETIRED on a run-ahead server (§2.6).
|
||||
|
||||
---
|
||||
|
||||
## §1 Table 0 additions — encodings the P5e rows introduce
|
||||
|
||||
| field | the ruling | zero means | who reads it |
|
||||
|---|---|---|---|
|
||||
| **`MGPCapBit::kCapRunAheadApply = 1ull << 10`** (`MGPipeTypes.h`, bits 0..9 used) | Set in the DirectGLES arm of the server's `CallMask` (`MG_Backend/Init.cpp`), NEVER in the DirectVulkan arm. THE ARM IS A PURE FUNCTION, `MGPipeRunAheadCapBitsFor(backendType, ready)` in `MGPipeTypes.h`, so "Magma never" is a unit case (`MG_Test/Pipe/MagmaPipeIdentityTest.cpp`) rather than a line only a live split session reaches; `ready` is `MG_Backend/Init.cpp`'s `kMGPipeP5eRunAheadReady`. It says "this server applies unbarriered records without reading client memory". The client latches `RunAheadArmed()` = `m_barrierArmed && Ipc.RunAhead && Caps().HasCap(kCapRunAheadApply)` at the first caps adoption after `Start`; a later snapshot may only turn it OFF. | absent = lockstep (Magma; and Espryt until the integration commit flips `kMGPipeP5eRunAheadReady`, §6.2) | `ClientSession::RunAheadArmed`, `MGPipeBarriered` on both sides |
|
||||
| **`MGPipeWaitClass`** — a FIFTH column of `PipeCalls.def`'s X-macro (`MGPipe.h`'s enum, spelled as a bare token like the class column), generated beside `kMGPipeCallFlags` into `generated/PipeWire.inc` as `MGPipeWaitClassFor(op)` | `kWaitReply` (every `kReplySlot` row), `kWaitPresent` (`Present`), `kWaitApplied` (the static barriered rows of §2.2), `kWaitNone` (everything else). Opcode order and the flag column are untouched: the column is a value, not a new op. `gen_pipe.py` refuses a token that is not an `MGPipeWaitClass` enumerator AND a row where the flag column and this one disagree in either direction — a `kReplySlot` row that did not wait is a waiter that hangs, a `kWaitReply` row with no slot blocks on an answer nobody posts. Three negative controls in `--self-test` (twelve now, up from nine). | — | `EmitAndWaitTails`, `ServerVerbSink::ApplyOne`, `MGPipeBarriered` |
|
||||
| **`MGPDrawInfo::Flags` gains `kDrawClientArrays`** = `1u << 6`, the next free bit beside `kDrawIsIndirect` (`MGPipeTypes.h`'s `MGPDrawFlagBit`; `MGPDrawInfo` stays 56 bytes, the flag byte had two bits left) | Set by the client emit arm when any ENABLED attribute of the bound VAO has no buffer. §2.1 escalation (ii). `PipeCatalogueTest` pins the bit's value and its disjointness from the five before it. | no client array in this draw | `MGPipeBarriered`; `EmitDrawRecord`'s refusal (§5.1) |
|
||||
| **`MGPPresent::FrameSerial`** | Minted by the CLIENT, `++m_presentsSent`, 1-based — no longer 0 (`EmitTables.cpp:1040`). The sink calls `ServerSession::ReturnPresentCredit(FrameSerial)` after `table->Present()` returns (today `ReturnPresentCredit` has no production caller, `ServerSession.cpp:677-682`), one credit per swap. | 0 arriving on a run-ahead server is `Fatal{ProtocolCorruption, "Present.FrameSerial"}` | `OnPresent`, `WaitForPresentAck` |
|
||||
| **`MGPShaderBuffers` + `MGPBufferRange[]`** (`MGPipeTypes.h`, wire op 38, catalogued since P4a, no route/sink/emitter today) | One record per **Class** ∈ {Uniform=0, ShaderStorage=1, AtomicCounter=2} — the three values are `kMGPipeShaderBufferClassUniform/ShaderStorage/AtomicCounter` in `MGPipeTypes.h`, which is the ONLY spelling of them: the payload's comment had named them since P4a and nothing numbered them, so the emitter and the applier were one literal each away from disagreeing. `Start = 0` always; `Count = TouchedBufferBindingPointCount[target]` (the rv value already carried) clamped to `kMGPipeMaxBufferBindingPoints = 84` (pinned against `BufferState.h:29` in `PipeFill.cpp`); per entry `Res` = the buffer handle or null, `Offset/Size` from the point's range, **`Size = kMGPipeWholeBuffer` (`MGPipeTypes.h:406`) for a base binding** so the server re-resolves the extent against ITS descriptor at use (a `glBufferData` between emit and apply then binds the new extent, as GL does); `WritableMask` bit per entry for classes 1/2. `HostSpanCount = 0` on Espryt for the whole of P5e (`kCapNeedsHostUboBytes` is 0, `CONTRACT-P5.md:64`); the codec's honesty pass (`PipeWireCodec.cpp:1783-1799`) is the guard. `Class >= 3` → `Fatal{ProtocolCorruption, "SetShaderBuffers.Class"}`; `Start + Count > 84` → `"SetShaderBuffers.Count"`. XFB targets do NOT ride this record (`set_stream_output_targets` keeps its row, still unemitted: XFB stays lockstep, §5.7). | `Res` null = nothing bound at that point | `MGPipeApplySetShaderBuffers`, `SyncBufferBindingPointsByRecord`, the UBO loop, `SyncAtomicCounterBuffers` |
|
||||
| **`set_program_bindings` — opcode 80, appended.** `X(SetProgramBindings, MGPProgramBindings, kCtxState, kVarTail\|kHostSpan, kWaitNone)` | Head `MGPProgramBindings { MGPipeHandle Cso; Uint64 Signature; Uint32 BlockBindingCount, SamplerUnitCount, StorageOverrideCount, Pad0; }` (32 B, `MGP_ASSERT_POD`); three tails in order: `Int32 BlockBindings[BlockBindingCount]` (dense, GL uniform-block index order), `MGPProgramSamplerUnit {Uint32 Location; Int32 Unit;}[SamplerUnitCount]` (8 B, sparse, ascending Location), `MGPProgramStorageOverride {MGHostSpan Name; Int32 Binding; Uint32 Pad0;}[StorageOverrideCount]` (40 B; the storage-block override map is name-keyed BY DESIGN, `ProgramObject.h:1030-1034`; the name travels as a host span staged whole). `Signature` = the client's commutative hash of the override map, the same function as `Managers.cpp:10832-10841`, so the server's rebuild clause is one `Uint64` compare. Bounds: `kMGPipeMaxProgramBlockBindings` 64, `kMGPipeMaxProgramSamplerUnits` 256, `kMGPipeMaxProgramStorageOverrides` 64, else `Fatal{ProtocolCorruption, "SetProgramBindings.<field>"}` — a larger sampler array is a counted refusal, not a truncation. **THE RECORD ALWAYS DECLARES THREE TAILS**, empty ones included: the tails are positional and the third one's offset is derived from the first two, so a record that declared "one tail" when only the block bindings were present would put the same bytes at a different offset than one that declared three, and the two halves of the wire would disagree about where the overrides start. The wire codec's `WireRecordLayout` grew from two tails to three for it (`MG_Remote/Wire/PipeWireCodec.h`), and the override names are walked through `CheckHostSpanIsHonest` in the decode arm rather than by the encoder's blanket second-tail pass — they are MEMBERS of a 40-byte element, and reading that tail as bare spans would see one span and a quarter of garbage. Emitted BEFORE `create_shader_state` at the same validate point (a rebuild inside the verb must already see the bindings). Dirty rule: bit `NewShaderBindings` (`Tracker.h:364-371, 419-448`) already mixes the three counters; the emitter latches on `(Cso, backendStateVersion, blockBindingVersion)`. A re-issued create clears the record's tails (`GlobalConstants`' rule, `PipeApply.cpp:2903-2909`). | all three counts 0 = "defaults" | `MGPipeApplySetProgramBindings`, the program twin's sync and memo keys |
|
||||
| **`MGPProgramDesc::LinkStatus`** (a `Uint8`; **the descriptor grows 192 → 200**, `PipeCatalogueTest` pins the new size) | **THE DRAFT SAID "in the descriptor's existing pad, size unchanged" AND THAT WAS WRONG.** `MGPProgramDesc`'s four trailing `Uint8`s end at offset 24 and `MGPBlobRef` is 8-aligned, so the fixed head is exactly full and a fifth byte costs the alignment slack behind it: 20 bytes of handle and counters + 5 bytes of flags, padded to 32, plus 168 of blob refs. It is paid once, on a record that fires at most once per link, and it is confined to the push build — this payload is only ever instantiated under `MOBILEGL_PIPE_PUSH` (`PipeApply.cpp`, `PipeRoute.cpp` and the emitters are all push-only sources), which is why G1 does not see it. 1 = the link the archive describes succeeded. `create_shader_state` is re-issued on the same handle at every link that changes `GetLinkVersion` (today's rule) and the record's `Serial` moves with it; a failed link re-issues with `LinkStatus = 0` and empty blobs IFF the frontend reports the program unlinked afterwards (§9 ruling 9 pins which). A draw on a `LinkStatus == 0` program DECLINES on the server, as the frontend raises `INVALID_OPERATION`. | not linked | the program twin (`SyncCurrentProgram`) |
|
||||
| **`MGPProgramDesc::Spirv[6]` / `Reflection` blobs are NON-EMPTY** on a live wire | The client encodes the link artefacts once per link with `ProgramArtifactsCodec` into `SEG_STAGE`; the applier decodes into RECORD-OWNED `LinkArtifacts`/`SpirvArtifacts` and the companion pointers (`ProgramEmit.h:230-260`) are deleted under a transport. `Size 0` with no companion is the existing trip wire `Fatal{ProtocolCorruption}` (`PipeApply.cpp:2858-2871`). A `SharedPtr` pin of the client's artefacts was considered and REFUSED: rule B, and `Link()` replaces them in place (`ProgramObject.cpp:348-356`). | — | the program twin's rebuild |
|
||||
| **`MGPImageView::Access`** | 0 = `GL_READ_ONLY`, 1 = `GL_WRITE_ONLY`, 2 = `GL_READ_WRITE` — the encoding `MGPipeEncodeImageAccess` already writes (`MG_Impl/Pipe/ImageEmit.h`). The NUMBERS move to `MG_Pipe/MGPipeValueTypes.h` as `enum class MGPipeImageAccess : Uint8` with `MGPipeDecodeImageAccess` / `MGPipeImageAccessIsValid` / `MGPipeImageAccessReads` / `MGPipeImageAccessWrites` beside them, and the client encode NAMES those enumerators instead of literal 0/1/2 — one table, both roles, moved rather than duplicated. The encode also stops being a private static so the unit case can reach both halves. The server comment at `DirectGLES.cpp:2662-2669` is deleted as stale (fb). Any other value is `Fatal{ProtocolCorruption, "ImageView.Access"}` AT THE READER: the value header may not log, so it answers the question and the caller owns the refusal. | read-only | `SyncImageTextureBinding` |
|
||||
| **`kMGPipeSubsystemBufferBindings = 1ull << 13`** (`MG_Pipe/MGPipe.h`, bits 14..62 now reserved) | Owns dirty bits 15/16/17 (`NewConstBuffers/NewShaderBuffers/NewSoTargets`, `Tracker.h:97-99`) and the `SetShaderBuffers` emitter. Caps-required under a transport like bits 7–12; the default push mask becomes `kMGPipeSubsystemsMigratedAtP5e` = `0x3fff`, and `0x1fff` survives as P5e's "everything P4a had and nothing of mine" control. Bit 13 requires bit 7 (a range names a Buffer handle). `NewSoTargets` rides the bit even though `set_stream_output_targets` stays unemitted for the whole of P5e: the bit answers "which A/B switch owns this family's legacy arm", and an operator clearing it has to get the whole binding-point family's frontend walk back rather than two thirds of it. `TrackerTest.cpp` is rewritten, not deleted, and now also pins that the phase constant is the WHOLE dirty-bit set — after P5e every bit names a subsystem. | bit clear = the frontend walk (monolith / A/B only) | `MGPipeSubsystemForDirty`, `SubsystemForEmitter` |
|
||||
| **`SetHashSuppressor` slots** (`MG_Impl/Pipe/SetHashSuppressor.h`) | `SetShaderBuffers` becomes THREE slots (`SetShaderBuffersUniform` / `SetShaderBuffersShaderStorage` / `SetShaderBuffersAtomicCounter`) — one per Class, so an emission of one class never cancels another's; `SetProgramBindings` gets one. `SetStreamOutputTargets` stays "P4b" (unemitted). | — | the emitters |
|
||||
| **`IpcTable::RunAhead`** = `MOBILEGL_IPC_RUN_AHEAD` (default 1, range 0..1); **`IpcTable::PresentCredit`** = `MOBILEGL_IPC_PRESENT_CREDIT` (default **1**, range 1..8) | Parsed unconditionally like every IPC knob (`ConfigLoader.cpp:356-403`), and both are in the IPC log line, because a knob that silently means nothing on one arm of an A/B is how an A/B stops being one. `PipeVerify` forces `RunAhead = 0` beside `BatchWaits = 0`. `RunAhead=1` on a server without the caps bit logs once "run-ahead requested, server does not publish kCapRunAheadApply" and runs lockstep. The stats line gains `credit-waits`. | `RunAhead=0` is the A/B control: identical server code, only the client's wait differs | `RunAheadArmed`, `EmitPresent` |
|
||||
| **`BufferState` bind-point generations** (client-only, `#if MOBILEGL_PIPE_PUSH`) | One `Uint64` per `BufferBindPointTargets` entry beside `NoteBufferChanged` (`BufferState.h:69-75`), bumped by `glBindBufferBase/Range` (`GL_Buffer.cpp:1501-1520, 1624-1650`) and `SetNamedTransformFeedbackBinding`. Not a wire field; listed because it is the emit rule's input and the pull build's object must not resize (the licence `TextureState::NoteImageUnitTouched` took at P5d r3). | — | bits 15/16/17's shutters |
|
||||
|
||||
---
|
||||
|
||||
## §2 ra — the wait rule
|
||||
|
||||
### 2.1 The barriered predicate (one function, two callers)
|
||||
|
||||
```
|
||||
MGPipeBarriered(op, payload, st) =
|
||||
MGPipeWaitClassFor(op) != kWaitNone // static column, §2.2
|
||||
|| (MGPipeCallClassFor(op) == kCtxVerb && st.IsTransformFeedbackActive) // (i) XFB stays lockstep
|
||||
|| (op == DrawVbo && (payload.Flags & kDrawClientArrays)) // (ii) client arrays
|
||||
```
|
||||
`st.IsTransformFeedbackActive` is `MGPContextValues`' value (`MGPipeTypes.h:1698`): on the client
|
||||
it is `ctx.IsTransformFeedbackActive()` at emit, on the server the applied value — `set_context_values`
|
||||
precedes the verb on the ring, so the two agree. **c0e LANDED THE FUNCTION, all three clauses, in
|
||||
`MG_Pipe/PipeApply.cpp`**, and gave `MGPipeApplierState` its own `IsTransformFeedbackActive` mirror
|
||||
written by `MGPipeApplySetContextValues`: the predicate has to be computable on the server from
|
||||
APPLIER state, and `gPipeInputs` is the block a run-ahead client stops filling, so reading the
|
||||
answer out of there would be reading the very memory rule F exists to stop it reading. `payload`
|
||||
may be null, which makes clause 3 fall through — a caller holding an opcode but not yet the bytes
|
||||
must not be told a draw is unbarriered on that account. Escalation (ii) is a refusal under run-ahead
|
||||
(§5.1), so on a run-ahead server it never reaches the sink; it is listed so the predicate is total.
|
||||
**No other runtime escalation exists**; adding one is an integrator ruling and a row here.
|
||||
|
||||
### 2.2 The static column, row by row
|
||||
|
||||
| class | rows | why |
|
||||
|---|---|---|
|
||||
| `kWaitReply` | every `kReplySlot` row (`PipeCalls.def:115-202, 206, 221`): GetCaps, ResourceCreate/Respecify, MapPersistent, FenceStatus/Wait, QueryAvailable/Result/Timestamp, SetTextureParams, ResourceSubData (texture half, §2.5), ResourceReadback, GetTextureImage, ReadPixels | the answer is not derivable (R-5) |
|
||||
| `kWaitPresent` | `Present` | the credit, §2.4 |
|
||||
| `kWaitApplied` | `ApplierReset`; `GenerateMipmap` (`kCtxObject`, waits today for the reason at `ClientSession.cpp:900-914` — until tx2 lands its `VerbMipRes` arm, then `kWaitNone`); `SetStorageBlockBinding` (`kCtxVerb`, resolves through `GetProgramObject(GlName)`, `PipeApplier.cpp:805-808`; trailing: pg resolves through `MGPStorageBlockBinding::ShaderCso` and flips it); `BeginStreamOutput`, `EndStreamOutput`, `PauseStreamOutput`, `ResumeStreamOutput`, `BindStreamOutput` (§5.7); `CopyFramebufferToTexture` (the CopyTex endpoint still syncs the frontend texture, `DirectGLES.cpp:9714-9735`; trailing, tx2) | each still pulls a row or probes; the wait keeps P5C's semantics for it |
|
||||
| `kWaitNone` | `DrawVbo`, `LaunchGrid`, `Clear`, `Blit`, `MemoryBarrier`, `Flush`, `SetSwapInterval`, `BindShaderImage`, `PatchParameter`; every `kCtxState` / `kCtxCso` / `kCtxObject` row without a reply (incl. `ObjectDeath`, `ResourceSubData` buffer half); `ResourceDestroy`, `UnmapPersistent`, `FenceCreate/Destroy`, `FenceWaitServer`; `QueryCreate/Begin/End/Destroy/Counter` | rule F holds for their sinks after §4/§5 |
|
||||
|
||||
The column is inert until the integration commit publishes the caps bit (§6.2), after vi/sb/pg/tx2/fb.
|
||||
|
||||
### 2.3 `EmitAndWaitTails` after `PublishAndNotify` (`ClientSession.cpp:877`)
|
||||
|
||||
```
|
||||
if (ownsReplySlot) -> WaitForAppliedBudget + DrainEventRing + ReadReply (unchanged)
|
||||
else if (RunAheadArmed())
|
||||
switch (class) kWaitNone: return seq;
|
||||
kWaitApplied: WaitForAppliedBudget + DrainEventRing + ReleaseFillPins(); return seq;
|
||||
kWaitPresent: return seq; // the credit wait ran BEFORE encode, §2.4
|
||||
else -> today's :915-959 shape, byte for byte (lockstep, Magma, verify)
|
||||
```
|
||||
`ReleaseFillPins()` clears the four O-class `SharedPtr` rows of `gPipeInputs` (`PipeInputs.h:801-809`)
|
||||
on the GL thread once the barriered apply returned — under run-ahead the next fill may be a frame
|
||||
away and a pinned VAO/program must not make the apply thread its last owner. `VerbBarrier==0`
|
||||
keeps its lockstep meaning (nothing but reply rows waits); under run-ahead it removes the
|
||||
`kWaitApplied` waits too and is the run-ahead NEGATIVE CONTROL: expected red is
|
||||
`Fatal{UnmigratedPipeInput, ...}` at the first barriered row's stale pull.
|
||||
|
||||
### 2.4 Present pacing
|
||||
|
||||
`EmitPresent` (`EmitTables.cpp:977-1053`): `BeforeReadOnlyVerb(); if (m_presentsSent >= PresentCredit)
|
||||
WaitForPresentAck(m_presentsSent + 1 - PresentCredit, 30 s)` — `ShutDown` returns, `TimedOut` is
|
||||
`Fatal{PresentCreditTimeout}`; then `DrainEventRing`, encode, publish, `PumpControlPlane`. Credit 1
|
||||
= the client publishes frame N+1 while the server applies and swaps frame N: one frame of overlap,
|
||||
at most one frame of added latency. Budget at ~850 draws + ~0.36 MB pmap/frame: SEG_CMD ~70 KB +
|
||||
set/CSO records per frame against 8 MiB, SEG_STAGE ~1 MB/frame against 32 MiB, SEG_REPLY unchanged.
|
||||
The credit, never the bytes, is the steady-state backpressure; `WaitForCmdSpace` and the stage bell
|
||||
(`PipeWireCodec.cpp:836`) are the safety net and both drain events on exit (§2.6).
|
||||
|
||||
### 2.5 Drains and forced waits
|
||||
|
||||
Drain points: every wait exit in `ClientSession` (reply, applied, present-ack, cmd-space, stage
|
||||
bell), the four EGL RPC returns (`BackendObject_Remote.cpp:200-245`), `Stop`. Forced waits:
|
||||
`MapBuffer(READ)` / `GetBufferSubData` / a `CopyBufferSubData` source → `SyncGpuWrites` (reply
|
||||
row, unchanged); `glFinish` → `WaitForApplied(LastPublishedSeq)` + drain (a new client `Finish`
|
||||
slot; `Flush`/`Finish` stay no-ops on the wire, ARCHITECTURE:415); every `Server*` EGL forwarder
|
||||
waits `WaitForApplied(LastPublishedSeq)` BEFORE the RPC (the mailbox is pumped between drain
|
||||
batches, `ServerLoop.cpp:385-390`, so a make-current would otherwise land between two run-ahead
|
||||
records); `ServerSwapEGLBuffers` is not on that list — present is the swap. **`glGetError`
|
||||
relaxation:** a run-ahead verb's `kEventGlError` is observed at the next drain point, i.e. at most
|
||||
one present credit later than the call after it; P5C §4.2 already assigns ordering to P9 and P5e
|
||||
names the window. **`resource_subdata`:** the BUFFER half passes `wantReply = false` through
|
||||
`MGPipeRouteResourceSubData` (the Bool is discarded today, `PipeFill.cpp:839`; the server still
|
||||
posts, the slot is never read — legal, `ReplySlot.h:16, 105`), which is what makes the persistent-map
|
||||
push (`PersistentMapTracker.cpp:735, 850`) fire-and-forget; the TEXTURE half keeps its wait because
|
||||
`DrainTextureSubData` clears the level's dirty flag on the accepted reply (D-D5; trailing).
|
||||
|
||||
### 2.6 Event-ring flow control (ARCHITECTURE §11.7 made real)
|
||||
|
||||
On a run-ahead server `Reserve == nullptr` is NOT `Fatal{EventRingOverflow}`: the producer latches
|
||||
`eventRingFull` (exists, `EventRing.h:135-143`), publishes, rings, and `ApplyThreadMain` parks at
|
||||
the record boundary with `eventRingFull == 0` added to `ready` (`ServerLoop.cpp:380-383`). The
|
||||
client re-checks `m_events.RingIsFull()` at every park exit and drains before re-parking. Deadlock
|
||||
argument: the server blocks only on the event ring; the client blocks only on watermarks the
|
||||
server advances; every client wait drains; so at most one side is parked at any instant. The
|
||||
lockstep arm keeps the Fatal (P5C §4.4).
|
||||
|
||||
### 2.7 Object lifetime under run-ahead
|
||||
|
||||
The client frees a slot only after its death record went out (`PipeFill.cpp:1623-1626`), the ring
|
||||
is in order, every record naming `{s,g}` precedes `destroy{s,g}` which precedes `create{s,g+1}`,
|
||||
and every server twin table Gen-checks (§4.3). **No wait is added at any delete.** Persistent maps:
|
||||
`Forget()` (`PersistentMapTracker.cpp:599-606`) drops tracker state only; pushed bytes were copied
|
||||
into SEG_STAGE at `StageOptional` and reclaim waits on `retiredSeq`, so a map released with blocks
|
||||
in flight is safe. The P5d deferred-destroy queue (`PipeMutation.h:216-236`) STAYS LIVE: a
|
||||
barriered record's apply may still pin a frontend object (its fill's `SharedPtr` rows, XFB
|
||||
`targets`), so the apply thread can still be a last owner. Under `RunAheadArmed` an enqueue from an
|
||||
UNBARRIERED apply is a finding: `MGLOG_E_ONCE` with the kind, Fatal under strict.
|
||||
|
||||
---
|
||||
|
||||
## §3 `gPipeInputs` — server-role memory for unbarriered records
|
||||
|
||||
1. **The client fills only for barriered records.** In `MGPipeValidateForVerb`
|
||||
(`PipeFill.cpp:2962-3306`), when `RunAheadArmed()` and `!MGPipeBarriered(...)`: step 2 (tracker
|
||||
walk) and step 3 (emitters) run unchanged; `:2989` (`++CurrentVerbSerial`), `:2999-3001`
|
||||
(`SetVerb/SetIdentity`), step 4 (`:3252-3270`, `CopyField` + `FilledGen`) and `MGPipeLeaveVerb`'s
|
||||
writes are SKIPPED; `MGPipeNoteFrontendMutation` returns at `:554`. The residual-value trip
|
||||
wire (`:3284-3294`) still emits — it compares on the server against the applier mirror.
|
||||
`RunAhead=0` on a caps-publishing server uses the SAME fill decision (the A/B changes one
|
||||
variable: the wait). `PipeVerify` forces lockstep, a fill for every verb, and every record
|
||||
barriered (both roles read the in-process flag; the comparator `:3304` is inproc-only).
|
||||
2. **The server's stamp is server-private.** `MGPipeServerStampVerbBoundary` writes
|
||||
`m_filled/m_currentVerb/m_serverStampedVerb` into applier-owned storage, never into the shared
|
||||
block; `MGPipeServerClearVerbBoundary()` from the GL thread (`:2997`) is DELETED under a
|
||||
transport. For a barriered record the server reads the client's fill (the client is parked) and
|
||||
its own stamp; for an unbarriered one it does not touch the block. `SetIdentity` (`:3001`) moves
|
||||
to `ApplyOne`, once per session. This removes the E note's hazard (b.4).
|
||||
3. **The detector.** `MGPipeInputUnfreshRead` (`PipeInputs.cpp:191-224`) under an unbarriered
|
||||
record takes `StrictBarrierPullFatal` (`:60-66`) regardless of `StrictErrors`; the seven sticky
|
||||
forwards (`MGPipeStickyForwardPull`, `:251-257`) the same; `CountBarrierPull` stays for barriered
|
||||
records. This is what turns `integration-split-strict` into a hard green lane (§7).
|
||||
4. `VertexAttribDefaultsOf` (`PipeFill.cpp:84-86`) diffs against the emitter's own last header
|
||||
(`g_attribDefaultLastHeader`, `:2949`), never against the block.
|
||||
5. `RefusePipeInputsTouchWhileApplierOwnsIt` (`ClientSession.cpp:1015-1056`) is re-derived: the
|
||||
`BatchWaits` early return at `:1037` goes; under `RunAheadArmed` any GL-thread write to the block
|
||||
outside a barriered fill is `Fatal{RoleViolation, "gPipeInputs"}` by name.
|
||||
`Fatal{BarrierViolation}` (`:840`) stays for `BatchWaits==0 && !RunAhead`.
|
||||
|
||||
---
|
||||
|
||||
## §4 id — handle-keyed twins
|
||||
|
||||
### 4.1 The rule
|
||||
With a live wire, every twin on the apply thread is resolved BY THE HANDLE the applier state or
|
||||
the record carries, never by a frontend object: `GetOrCreate(MGPipeHandle)` (`SlotTables.h:294-342`),
|
||||
`FindByHandle` (`:420-426`), `ReleaseByHandle` (`:394-408`). `HandleOf(stateObj)`,
|
||||
`Find(StateObject*)`, `GetOrCreate(const StatePtr&)`, `NoteStateForHandle/StateForHandle` and
|
||||
`Entry::stateRef` survive as the MONOLITH-GLUE half (§5.8) and are unreachable from an
|
||||
unbarriered apply. The twin sync bodies take `(MGPipeHandle, const <Record>&)` and store
|
||||
`m_handle` (the `m_pushedSyncHandle` pattern, `Managers.h:1857`); the object parameter is deleted
|
||||
from the transport overload, not defaulted to null. The E note's "content-addressed VAO CSO" is
|
||||
corrected: every kind mints its slot off the object's own lifetime id (`VertexInputEmit.h:170-172`,
|
||||
`TextureEmit.h:422`, `FramebufferEmit.h:257`, `ProgramEmit.h:411-422`), so the rekey is a
|
||||
replacement of the lookup ARGUMENT, not of the table.
|
||||
|
||||
### 4.2 Per kind
|
||||
| kind / table | key on the server | the record that names the handle before any verb reads it | twin created | twin dies |
|
||||
|---|---|---|---|---|
|
||||
| Buffer `g_backendBufferResources` (already handle-keyed) | `Res` from `VertexBuffers[]`, `IndexBuffer`, `BoundShaderBuffers[c][i]`, `Desc.BufferForTexBuffer`, `VerbIndirect*` | `resource_create/respecify` | `EnsureBufferResourceForHandle(nullptr, h)` | `resource_destroy` |
|
||||
| VAO `g_backendVertexArrayObjects` | `st.BoundVertexElements` (`PipeApply.h:594`; one CSO per VAO, re-created on the same handle when the config moves) | `create/bind_vertex_elements` | first `ResolveVaoTwin(handle)` at a draw | `object_death{VertexElementsCso}` / `delete_vertex_elements` |
|
||||
| Texture `g_backendTextureObjects` | `BoundSamplerViews[u].Texture`, `BoundShaderImages[u].Res`, `FramebufferRecords[h].State.{Color[i],Depth,Stencil}.Res`, `VerbMipRes`, `VerbCopyTexDst`, `MGPCopyImage` src/dst | `resource_create/respecify` (Texture) | first `SyncTextureToBackendByHandle(h)` | `object_death{Texture}` / `resource_destroy` |
|
||||
| Renderbuffer `g_backendRenderbufferObjects` | `MGPSurface.Res` with `Kind == Renderbuffer` | `resource_create/respecify` (Renderbuffer) | first attach sync | `object_death{Renderbuffer}` |
|
||||
| Framebuffer `g_backendFramebufferObjects` | `st.BoundFramebuffer[t]`, `record.Fbo`, `VerbBlitReadFbo/DrawFbo`; `kMGPipeDefaultFramebuffer` is never a twin | `set_framebuffer_state` | `GetOrCreateByHandle(record.Fbo)` (already, `DirectGLES.cpp:3197`) | `object_death{Framebuffer}` |
|
||||
| Program `g_backendProgramObjects` | `st.DrawProgram` / `DispatchProgram` / `BoundShaderCso`; composites at the band | `create_shader_state` (+ `set_program_bindings`) | first `SyncCurrentProgram(handle)` | `object_death{ShaderCso}` / `delete_shader_state` |
|
||||
| Sampler `g_backendSamplerObjects` | the CSO family ONLY: `st.BoundSamplerStates[u]` or `TextureResources[h].Params.BuiltinSampler` | `create_sampler_state` | `ResolveSamplerCsoTwin` (already, `Managers.cpp:13457-13486`) | `object_death{SamplerCso}` |
|
||||
| SamplerView `g_backendSamplerViews` | `BoundSamplerViews[u].View` (no draw-path resolver today; unchanged) | `create_sampler_view` | — | `object_death{SamplerViewCso}` |
|
||||
|
||||
The identity family of samplers (`ResolveUnitSamplerBackend` `DirectGLES.cpp:4985-5026`, the mint
|
||||
at `:5623-5630`, `Managers.cpp:13264-13280`) is deleted under a transport; a unit whose
|
||||
`BoundSamplerStates[u]` is null while the frontend held a sampler is a missing record and §5.3's
|
||||
window rule makes it unrepresentable. The creation moment is LAZY everywhere (no twin at
|
||||
`create_*`); the applier's Gen check refuses a bind or verb naming an unknown or stale handle
|
||||
(`PipeApply.cpp:2266, 2549, 2625, 2683, 2896`), so a twin is never minted for an object the ring has
|
||||
not introduced. `applier_reset` touches neither twins nor object records (`PipeApply.h:800-832`).
|
||||
|
||||
### 4.3 ABA
|
||||
`Gen` moves only on reuse (`SlotAllocator.cpp:176-189`, and `:240-258` for the composite band —
|
||||
"the one place Gen may move", in the file's own words). At the server every record naming `{s,g}`
|
||||
is applied before `object_death{s,g}`, which is applied before `create{s,g+1}`. `GetOrCreate({s,g+1})`
|
||||
on a live `{s,g}` entry resets it (`SlotTables.h:338`); `FindByHandle({s,g})` after the recycle
|
||||
answers null (`:424`); a backward `{s,g-1}` is refused (`:331`). Even with a death skipped (an
|
||||
object that never crossed emits none, `WireTables.cpp:595-596`) the forward-Gen reset answers.
|
||||
Twin memos key on `{slot,gen}` plus server serials, never on a pointer. `BackendSlotTable` gains
|
||||
the composite band (`m_band`, indexed `slot - base`, as `SlotAllocator.h:144` / `PipeApply.h:511`)
|
||||
so a pipeline program (slot >= 983040) does not grow the ordinary table to `kMaxHandleSlot` entries
|
||||
— a P5e prerequisite, not a follow-up, because the rekey makes it the ordinary path.
|
||||
|
||||
### 4.4 The scopes and the guard
|
||||
`MGPipeRefuseAllocatorFromApplyThread` (`SlotAllocator.cpp:22-38`) exempts a probe iff a scope is
|
||||
active AND `MGPipeApplierCurrentRecordIsBarriered()` (server-private, set by `ApplyOne` from
|
||||
§2.1; `true` for every record on a non-run-ahead server, §6). **c0e landed the storage and the
|
||||
writer** (`MGPipeApplierSetCurrentRecordBarriered`, `MG_Pipe/PipeApply.cpp`) with `true` as the
|
||||
default — the only seam in this phase whose body is implemented rather than aborting, because
|
||||
there the safe answer and the landed lockstep answer are the same answer, and an abort would take
|
||||
down the monolith and the lockstep split alike. id owns the `ApplyOne` call site. **`MGPipeFrontendKeyedRegistryScope`
|
||||
is deleted at its draw-path sites** — `DirectGLES.cpp:1609, 1734, 2588, 3303, 4236, 4329, 4585, 4926,
|
||||
4993, 5328, 5689, 6729, 6775, 9695` and `Managers.cpp:256, 2938 (draw-path callers), 4544, 5882,
|
||||
7087, 8481, 8717, 9256, 9310, 9709, 12949, 13273, 13570` — with the frontend probes they wrap; the
|
||||
class survives only around the barriered-row sites (`DirectGLES.cpp:6166, 8837, 9161, 9216, 10029,
|
||||
11884`, the verify arms `Managers.cpp:10382, 10403`) with their P8/P9 phases re-annotated.
|
||||
`MGPipeReverseAnnouncementScope` is renamed `MagmaP7AllocatorDebtScope`, compiled for the Magma
|
||||
sites only (`VulkanRenderer.cpp:4554, 4634, 8986`, `ResourceTracker.h:723`), exempting only when
|
||||
the server backend is DirectVulkan. The renderbuffer cross-check `Managers.cpp:9709-9720` (not
|
||||
transport-gated today, unlike `:9628-9632`) is deleted by the rekey, not gated. The NoSession
|
||||
server-only fixture hop (`Managers.cpp:224-246`) is deleted; such a fixture's twins die at Stop.
|
||||
|
||||
---
|
||||
|
||||
## §5 Per family — the clean gate, the records, the windows
|
||||
|
||||
### 5.1 VAO / vertex input / index (vi)
|
||||
Twin from `st.BoundVertexElements`; memo key `{BoundVertexElements, rec->ContentSerial,
|
||||
VertexBuffersSerial, IndexBufferSerial}` (all server-owned); the buffer walk is over
|
||||
`st.VertexBuffers[Start..+Count).Res` (dedupe on `{slot,gen}`) with
|
||||
`EnsureBufferResourceForHandle(nullptr, Res)` and `IsBufferDrawCleanByHandle(h, res, nullptr)`;
|
||||
`ResolvedDrawBuffers::Entry::frontend` leaves the split arm; the IBO arm reads `st.IndexBuffer.Res`
|
||||
+ `IndexBufferSerial` (also for the restart substitution, `DirectGLES.cpp:6447-6461`);
|
||||
`SyncCurrentVertexAttributeValues` keys on `{elementsHandle, ContentSerial, activeMask}` and reads
|
||||
`rec->Attributes[i].Enabled`; the dead `GetConfigVersion` read at `:4775` is deleted. **Client-memory
|
||||
vertex arrays are NOT refused under split today** (only `CLIENT_INDICES/CLIENT_COMMANDS/
|
||||
UNBOUND_PARAMETER`, `EmitTables.cpp:651-685`; the server dereferences `attrib.Offset` as a raw
|
||||
pointer, `Managers.cpp:5382`): under `RunAheadArmed` a draw with `kDrawClientArrays` is
|
||||
`Fatal{UnmigratedVerb, "DrawArrays+CLIENT_ARRAYS"}` raised on the GL thread in `EmitDrawRecord`'s
|
||||
array arm; the lockstep arm is unchanged; the staged form (a per-attribute `{BindingIndex,
|
||||
MGHostSpan}` tail like `kDrawHasUserIndices`) is P8's. Two pins by test: `CurrentBufferMutationEpoch()`
|
||||
(`Managers.cpp:794`) is apply-thread-only under a transport; the VAO twin key is 1:1 only while the
|
||||
vertex-elements CSO stays identity-addressed.
|
||||
|
||||
### 5.2 Textures (tx2)
|
||||
Every draw-path entry is `SyncTextureToBackendByHandle(h, imageBindable)`: record first, twin by
|
||||
`GetOrCreateByHandle`, three syncs `(h, *rec)`; the by-value twin copy + second `Find`
|
||||
(`DirectGLES.cpp:1745-1755, 1781-1805`) go with the map arm. Clean = `m_isInitialized &&
|
||||
m_syncedResourceSerial == rec->Serial && rec->PendingUploads.empty() && m_syncedParamsSerial ==
|
||||
rec->ParamsSerial && !rec->Params.ForceResync && !m_forceTextureParamsResync &&
|
||||
m_syncedBuiltinSampler == rec->Params.BuiltinSampler && m_syncedBuiltinSamplerSerial ==
|
||||
SamplerCso(rec->Params.BuiltinSampler)->Serial && !rec->Params.SamplerResync && !m_forceSamplerResync
|
||||
&& rec->Desc.StorageKind == Mipmap` — no new state. The four in-body live reads (`GetTarget` at
|
||||
`Managers.cpp:8876-8877, 8620-8621`; `IsTextureView` `:7060`; the TexBuffer backing `:8223-8231`)
|
||||
read `Desc.Target / Desc.ViewOf / Desc.BufferForTexBuffer`; `GetExternalIndex` in logs becomes
|
||||
`Desc.GlNameForDiag`. The unit work list is `{Res, backend}` keyed `(ContextSerial,
|
||||
SamplerViewsSerial, SamplerViewStart, SamplerViewCount, g_backendContextGeneration)`;
|
||||
`PairingsIntact` is deleted; `ResolveAndBindUnitTextures` is one pass over the window with the
|
||||
target from `SamplerViewCsos[View].View.Target`, then an unbind of every target the window did not
|
||||
claim driven by `g_boundTexturesCache`; the alias arbiter dies (the client arbitrated,
|
||||
`SamplerEmit.h:751-757`). **Named delta (P5e-5):** the unit list syncs the program-resolved texture
|
||||
per unit, not every slot of every touched unit; coverage is unchanged (views ∪ image units ∪
|
||||
attachment lists ∪ the waited texture ops). `RequireImageBindableStorage`'s re-dirty arm
|
||||
(`Managers.cpp:5930-5962`, already aborting three frames deep) becomes a NAMED refusal at its
|
||||
entry under a transport, `Fatal{UnmigratedEmulation, "image-bindable-redirty"}`; `ImageBindableHint`
|
||||
is the prevention, P9 owns the pull. `GenerateMipmap(target)` under a transport resolves the
|
||||
texture from `VerbMipRes` (as `EnsureGenerateMipmapStorageAllocated` already does, `DirectGLES.cpp:8736`),
|
||||
never the active unit (`:9563-9566`), and its row flips to `kWaitNone`. `m_force*Resync` are
|
||||
server-set and never cleared by the wire; the wire bits are never cleared by the server (D-E2, now
|
||||
load-bearing for a gate). The pinned guard list (`MipmapStorage.cpp:39-51`, `RemoteClientTest.cpp:1938-1978`)
|
||||
gains the six shape accessors it exempts today plus `GetSamplerObject/GetTextureParamsVersion/GetContentVersion`.
|
||||
|
||||
### 5.3 Samplers and the two unit windows (tx2; the rule binds the emitters)
|
||||
The effective sampler of unit u is `st.BoundSamplerStates[u]` if non-null else
|
||||
`TextureResources[view.Texture].Params.BuiltinSampler`; its values are the CSO record's
|
||||
(`MGPipeValueTypes.h:462-486`); the raw-depth-fetch substitution stays on the server
|
||||
(ARCHITECTURE §5.5); `texture2D->GetFormat()` becomes `SamplerViewCsos[View].View.InternalFormat`.
|
||||
**Window rule (A8 closed):** `set_sampler_views` and `bind_sampler_states` MUST carry `Start == 0`
|
||||
and `Count >= MaxTouchedTextureUnit + 1` (0 when none touched), as `SamplerEmit.h:733-737` emits
|
||||
today; at every draw/dispatch apply the sink checks the two counts against the applied
|
||||
`MGPContextValues::MaxTouchedTextureUnit` and a narrower window is `Fatal{ProtocolCorruption,
|
||||
"SetSamplerViews.Count"}` / `"BindSamplerStates.Count"` — a server-side re-derivation is refused.
|
||||
The `BindCurrentTextures` memo key is `{DrawProgram.{Slot,Gen}, ShaderCso.Serial, BindingsSerial,
|
||||
SamplerViewsSerial}` (ruling 6). The decline arms `DirectGLES.cpp:5152-5169` and `:2073-2098` become
|
||||
named refusals under a live wire; `CaptureUnitBindings/UnitBindingsUnchanged/g_observedUnitBindings`
|
||||
(`:1867-1957`) are monolith-only text.
|
||||
|
||||
### 5.4 Framebuffers, attachments, images (fb)
|
||||
`SyncCurrentFBO` is `SyncCurrentFBOByRecord` only; a decline under a transport is
|
||||
`Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@<verb>"}`; `FramebufferRecordMatchesBinding`
|
||||
(`:3017-3033`) is monolith-only. `BackendFramebufferObject::SyncToBackend(record, target)`: the
|
||||
attachment walk is over `Color[0..7] + Depth + Stencil` (`Color[0]` alone when `IsDefault`) — the
|
||||
record's 11 surfaces ARE the point set (the emitter refuses a point at or above the wire width,
|
||||
`FramebufferEmit.h:489-501`; FRONT/BACK tokens exist only on the default framebuffer); the empty
|
||||
point is `Kind == None || Res null` for the detach (`Managers.cpp:10310`) and the N-6 refusal
|
||||
(`:9601-9606`) alike; `m_syncedFrontendAttachmentVersions` is replaced by the record hash;
|
||||
`g_attachmentBackendIdGeneration` stays. `SyncAttachmentSurface(target, surface, attachment)` takes
|
||||
no object; storage through `SyncMipmapsToBackendByHandle` / RBO `SyncToBackendByHandle(surface.Res)`.
|
||||
The two attachment texture lists key `(record.Fbo, ContentHash, ContextSerial,
|
||||
g_backendContextGeneration)` with entries `{Res, backend}` and `PairingsIntact` = `entry.Res ==
|
||||
surface.Res`. `ForceBindCurrentFBO` is handle-only and stamps nothing frontend (the stamps at
|
||||
`:4734-4738` and the legacy memo are skipped together under the transport test). The named blit
|
||||
takes two `const MGPFramebufferState&` (draw buffers, `ReadSurface`, `Kind`, layer/level/layered/
|
||||
format from the surface, `Samples` from the record, `kMGPipeDefaultFramebuffer` for the default)
|
||||
and retires `StateForHandle`; the bound arm reads `FramebufferRecordFor(st.BoundFramebuffer[t])`.
|
||||
`ScopedDetachedTextureFramebufferAttachments` walks a reverse index texture → FBO slots maintained
|
||||
at `set_framebuffer_state` apply. `MGPFramebufferState::Complete` is never a "may I draw" gate.
|
||||
`SyncReadFramebufferTextureAttachments` is not gated on the framebuffer bit (`:2227-2230`), so its
|
||||
retirement moves the object A/B baseline in BOTH arms (re-baselined, not a regression).
|
||||
**Default-framebuffer resize:** the surface-changed consumer's `AllocateStorage` (`ClientSession.cpp:210-221`)
|
||||
reaches `MGP_NOTE_AGGREGATE(FramebufferAttachment)` (`TextureObject.cpp:84-102`), so bit
|
||||
`NewFramebuffer` (`Tracker.h:594`) re-emits the record — verified at this head, pinned by a test in fb.
|
||||
Images: `SyncImageTextureBinding(const MGPImageView&)`: twin by `FindByHandle(Res)`, `Access`
|
||||
decoded, target and storage format off the TWIN (no new wire field; `MGPImageView` is 24 B, no
|
||||
pad); `ResolveShaderImageRecord`'s identity test and I5 seam log go. The sweep gate is
|
||||
`(ShaderImagesSerial, TextureShutterSerial, ContextSerial, g_backendContextGeneration)` — never a
|
||||
backend re-mint counter, so the "re-minted inside the sweep" property (`:2890-2901`) holds; the walk
|
||||
stays the UNION of the record window and `g_imageUnitHighWaterMark` (`:2811-2821`), never narrowed
|
||||
to the window. `MarkWritableImageBufferTexturesGpuWritten` is DELETED under a transport (the client
|
||||
owns the GPU-write set, `GpuWritePending.h:9-55`, `:83-115`); `WritableMask` survives as the record
|
||||
of which points P9's narrowing will name.
|
||||
|
||||
### 5.5 Programs (pg)
|
||||
Twin from the ShaderCso handle; `SyncToBackend(h, const MGPipeShaderCsoRecord&)` answers every
|
||||
reflection question from the record-owned archive and the bindings tails (`GetLinkedShaderSnapshot`
|
||||
`:12049` is a debug log, deleted on this arm). The nine-clause clean condition (`DirectGLES.cpp:4437-4507`)
|
||||
keeps its clause count and moves its inputs: `record.Serial` vs `m_syncedShaderCsoSerial` (exists,
|
||||
stamped at `Managers.cpp:12944-12966`, unread today — P5e is its first reader), `BindingsSerial`,
|
||||
`record.Signature`, `Desc.SpirvStatus`, `Desc.LinkStatus`; `ImageUnitFormatsStillMatch` and the
|
||||
patch clauses are fb's / value rows. The stash `g_currentDrawFrontendProgram` (`:4097-4098`)
|
||||
becomes `{MGPipeHandle, twin*}` compared against `st.DrawProgram`; `PrepareForCompute` reads
|
||||
`st.DispatchProgram`. The sampler-pass memo (`:5522`) keys on `BindingsSerial`;
|
||||
`sampledTargetForUnit` reads the sampler-unit tail and the archive's uniform types. **`MapUBO()`
|
||||
has no run-ahead answer:** when `ResolveGlobalConstantsRecord` declines under a transport it is
|
||||
`Fatal{UnmigratedVerb, "set_global_constants"}`, not a torn upload. `GetProgramObject/
|
||||
ValidateProgramName` keep their P9 rows: `GetBackendProgramId(name)` (`:6151`) is a monolith entry;
|
||||
`ShaderStorageBlockBinding` (`:10020`) is reached from the barriered `set_storage_block_binding`
|
||||
(§2.2) until pg's trailing item resolves it by `MGPStorageBlockBinding::ShaderCso`. Codec
|
||||
completeness: `ProgramArtifactsCodec.cpp:242`'s skipped member is named and either encoded or
|
||||
proven unread by the twin before pg claims "every reflection question is answered".
|
||||
|
||||
### 5.6 Buffer binding points (sb)
|
||||
Applier: `BoundShaderBuffers[3][84]`, `ShaderBufferStart[3]` / `ShaderBufferCount[3]` /
|
||||
`ShaderBufferWritableMask[3]` (the draft said bare `Start/Count/WritableMask`; they sit in
|
||||
`MGPipeApplierState` beside `SamplerViewStart`/`SamplerViewCount` and take the same shape), ONE
|
||||
`ShaderBuffersSerial` advanced on every applied record and ADVANCED (never zeroed) by
|
||||
`MGPipeApplierReset` (`VertexBuffersSerial`'s rule, `PipeApply.h`); the emitter's latch resets with
|
||||
it (`PipeFill.cpp:3129-3143`'s list). c0e declares all of it, with
|
||||
`MGPipeApplySetShaderBuffers` declared and aborting by name until sb writes the body.
|
||||
**`WritableMask` IS A `Uint32` AND THE WINDOW IS 84** (`MGPShaderBuffers::WritableMask`, unchanged
|
||||
since P4a): the mask can only describe the first 32 points of a class, so sb either narrows the
|
||||
window it sets bits for, widens the field, or states the bound — an integrator ruling sb must ask
|
||||
for rather than pick, because the field is on the wire. `SyncBufferBindingPointsByRecord(Class, glTarget)`: per entry
|
||||
`EnsureBufferResourceForHandle(nullptr, Res)`; whole-vs-range is `Offset == 0 && Size ==
|
||||
kMGPipeWholeBuffer`; clean = `resource->syncedChangeSerial` vs the resource record's `Serial`
|
||||
(`Managers.cpp:3151`) plus `ShaderBuffersSerial`; no twin key is introduced (a point names an object,
|
||||
it is not one). The UBO loop (`DirectGLES.cpp:5447-5497`) reads `BoundShaderBuffers[Uniform][binding]`
|
||||
where `binding` is the program's block binding (pg's tail); `binding >= Count` = nothing bound;
|
||||
`GetBufferResource(obj)` `:5472` becomes `FindBufferResourceForHandle`. `SyncAtomicCounterBuffers`
|
||||
reads `Count[AtomicCounter]`; `GetBufferBindingPointCount`'s sticky forward becomes FATAL under
|
||||
split (`InvalidateCompileEnv`'s shape, `FieldOwnership.def:163-165`). The three backend GPU-write
|
||||
marks (`DirectGLES.cpp:606, 658, 2775`) are deleted under a transport in ONE edit with fb's.
|
||||
**Client shutter (the P4b hole, verified: bits 15/16/17 read the content aggregate, `Tracker.h:525,
|
||||
636-639`, and `glBindBufferBase` moves only `BindingSlot::m_version`):** bit 15 =
|
||||
`Mix(bindPointGen[Uniform], programIdentity)`, bit 16 = `Mix(gen[ShaderStorage], gen[AtomicCounter])`,
|
||||
bit 17 = `Mix(gen[TransformFeedback], TransformFeedbackGeneration)`; the content aggregate leaves all
|
||||
three. `Touch` joins `gen_pipe_dirty_surface.py`'s `MUTATOR_PREFIXES` (`:87-90`) and `DirtySurface.def`
|
||||
gains the rows the generator then demands. `Coverage.def`'s EMITTED list gains `GetBufferBindingPoint
|
||||
→ SetShaderBuffers` as SHAPE-ONLY — **landed by c0e, with BOTH of its `PipeFill.cpp` switch arms
|
||||
(`SubsystemForEmitter` and `EmittedCallSuppliesTheWholeField`) and the pairing `static_assert`s**,
|
||||
because that file belongs to the contract package for the whole phase and an emitter enumerator
|
||||
whose dispatch arm lived in the family's own worktree is exactly the merge trap the file's own
|
||||
comment was written about. It is inert until the family's wired constant leaves 0.
|
||||
`FieldOwnership.def:80-81` flips to RECORD_SUPPLIED when the four Espryt consumers are gone. `GetBufferBindingSlot`'s 15-pointer copy (`PipeFill.cpp:116-124`) needs no
|
||||
special case: an unbarriered record has no fill at all.
|
||||
|
||||
### 5.7 XFB — stays lockstep
|
||||
`StartPendingTransformFeedback` (`:1381-1510`), `SyncTransformFeedbackBindingPoints` (`:562-592`),
|
||||
`ReadbackCapturedRanges` (`:1118-1160`) and the `xfb.targets` pins are not migrated
|
||||
(`GetTransformFeedbackProgram` is an object row no package retires; the targets hold `SharedPtr`s
|
||||
across two verbs, which rule C forbids an applier entry to do without a barrier). Escalation (i)
|
||||
makes every verb inside an active span barriered; the cost on the Espryt steady path is one bool
|
||||
test per draw (`:1386`).
|
||||
|
||||
### 5.8 Monolith / G1 / the push build under `Transport=monolith` (ruling 1)
|
||||
Every server-side handle arm is selected by `MG_Config::Transport != Monolith` (the idiom at
|
||||
`DirectGLES.cpp:3226, 4725, 6534`, `Managers.cpp:9629`) AND the family bit; it runs under ANY
|
||||
active transport, lockstep or run-ahead, so `RUN_AHEAD=0` is a pure wait-rule A/B on identical
|
||||
server code. The push build under `Transport=monolith` keeps its frontend arms token for token
|
||||
(behaviour and bytes), which the verify comparator needs; the pull build compiles none of it (every
|
||||
new symbol sits under `MOBILEGL_PIPE_PUSH` / `MOBILEGL_BUILD_DISAGGREGATED`; the `TwinRegistry`
|
||||
alias keeps its mangled name, `Managers.h:626-640`; `BackendTextureObject`'s pull-build layout does
|
||||
not move — the admitted-resize set stays empty). Two overloads, not an `#if` inside one body — the
|
||||
frontend one is visibly the monolith-glue half, as `GetOrCreate(const StatePtr&)` is
|
||||
(`SlotTables.h:236-244`). G1 0/0/0/0, G2/G14 name-set rules kept; the new public names are listed in
|
||||
BRIEF-P5E per package. `MOBILEGL_PIPE_VERIFY` forces lockstep (`ConfigLoader.cpp:377`, confirmed).
|
||||
|
||||
---
|
||||
|
||||
## §6 Magma keeps the lockstep
|
||||
|
||||
1. The DirectVulkan arm of `CallMask` never sets `kCapRunAheadApply`; `RunAheadArmed()` is false;
|
||||
the client runs today's `:915-959` path; `MGPipeApplierCurrentRecordIsBarriered()` answers `true`
|
||||
for every record on a server that does not publish the bit, so Magma's probes inside
|
||||
`MagmaP7AllocatorDebtScope` and its BARRIER_PULLED reads keep P5C's semantics and `rsp`
|
||||
accounting. P7 retires them.
|
||||
2. On Espryt the bit is published by `Init.cpp`'s DirectGLES arm gated on one constant
|
||||
(`kMGPipeP5eRunAheadReady`, false until the integration commit), so every package lands with the
|
||||
column inert and nothing changes behaviour until the integrator flips it.
|
||||
3. Negative controls: `MOBILEGL_IPC_RUN_AHEAD=1` on Magma logs once and runs lockstep;
|
||||
`MagmaPipeIdentityTest`/`ServerLoopTest` pin that a DirectVulkan `CallMask` has no bit 10.
|
||||
|
||||
---
|
||||
|
||||
## §7 The strict gate
|
||||
|
||||
`integration-split-strict` (`.github/workflows/test.yml:1140-1206`) becomes a HARD GREEN lane for
|
||||
the Espryt run-ahead server: with `MOBILEGL_IPC_STRICT_ERRORS=1` and the caps bit published, every
|
||||
scenario must complete and every `[BARRIER-PULLED, ...]` marker in a private log is red. Because
|
||||
§3.3 aborts on an unbarriered pull regardless of the knob, strict adds only the barriered rows'
|
||||
pulls (readbacks, GenerateMipmap until tx2, XFB, CopyTex, set_storage_block_binding) — the lane's
|
||||
step lists those rows as the ONLY admitted markers (an allowlist by `<field>@<verb>`) and fails on
|
||||
any other. A revert of any handle arm in vi/sb/pg/tx2/fb goes red HERE, by field and verb, not in
|
||||
a picture. Magma keeps the expected-red step. `rsp` on unbarriered records is 0 in every scenario
|
||||
summary (pinned: a non-zero count would be a Fatal that did not fire). The `PipeSlotPeek` harness
|
||||
asserts zero scope entries per frame on the draw path.
|
||||
|
||||
---
|
||||
|
||||
## §8 Amendments to CONTRACT-P5C (the complete list)
|
||||
|
||||
1. **§3.1's two named exemptions** are superseded by §4.4: the registry scope is deleted at every
|
||||
draw-path site and exempts only a barriered record elsewhere; the announcement scope is Magma's.
|
||||
2. **§4.4 overflow**: lossless stays, but a run-ahead server parks on `Reserve == nullptr` (§2.6).
|
||||
3. **§5.2 `object_death`**: the ring keeps the ordering; the row is `kWaitNone`.
|
||||
4. **§5.1 `applier_reset`** is `kWaitApplied`; its `ContextSerial` assertion stands.
|
||||
5. **§6 CI lane**: the two-sided lane becomes one-sided green for Espryt (§7).
|
||||
6. **§6 layer 2** gains `Fatal{RoleViolation, "gPipeInputs"}` (§3.5); `InBarrierWait()` is
|
||||
re-derived as `MGPipeBarriered`. 7. **§3.3's state note** is retired by fb (§5.4).
|
||||
8. **`FieldOwnership.def`**: `GetBoundVertexArray`, `GetTextureUnitObject`, `GetImageTextureBinding`,
|
||||
`GetFramebufferBindingSlot`, `GetProgramForDraw/Dispatch`, `GetBufferBindingPoint` gain retiring
|
||||
phase "P5e (Espryt unbarriered), P7 (Magma)"; `GetBufferBindingPointCount`'s forward → FATAL under
|
||||
split; `GetTransformFeedbackProgram`, `GetProgramObject`, `GetTextureObject`, `ValidateProgramName`,
|
||||
`HasOpenTransformFeedbackSpan`, `RecordError` keep their rows (barriered-only readers).
|
||||
9. **Table 1** gains `set_program_bindings` (80), appended; `set_shader_buffers` (38) gains its
|
||||
route, sink and emitter (`PipeCatalogueTest.cpp:170-171, 242-243` invert). **Table 0** gains §1's
|
||||
thirteen rows. **Table 3** gains `IpcTable::RunAhead/PresentCredit`.
|
||||
10. **P5C §6's `rsp` paragraph**: the measured object-class pulls (948.5/frame on bsl) go to 0 on
|
||||
unbarriered records; what remains is the barriered allowlist of §7.
|
||||
|
||||
## §9 The rulings, as made (ID-80…98; BRIEF-P5E §6 has the conflicts they resolve)
|
||||
|
||||
All nineteen are ACCEPTED AS WRITTEN. They are restated here rather than referenced, because a
|
||||
package reads this file and not the brief.
|
||||
|
||||
1. Monolith arm selection: transport check + family bit, frontend arms kept for monolith (§5.8) —
|
||||
NOT S7's "handle arm under monolith too" (it would make the monolith push build depend on the
|
||||
new emissions and lose the verify comparator's frontend arm).
|
||||
2. Client vertex arrays: refuse under run-ahead (§5.1), stage in P8; wait-escalation rejected (it
|
||||
would need `Find(vao)` on the apply thread, which §4.4 forbids for a draw).
|
||||
3. XFB / atomic dispatch waits: the wire-derived predicate (§2.1); atomic-counter dispatch needs no
|
||||
escalation because sb lands 4.5.
|
||||
4. Present credit default 1, range 1..8; credit 2 is a measurement arm only.
|
||||
5. `resource_subdata`: buffer half `kWaitNone`, texture half keeps the reply (trailing D-D5).
|
||||
6. `BindCurrentTextures` memo key: both S2's and S4's rows (§5.3).
|
||||
7. Image sweep gate: applier serials, never a backend counter (§5.4); keep the union walk.
|
||||
8. Program archive: encode per link into SEG_STAGE (§1), measured on MC + a shader pack before pg
|
||||
is sized; the `SharedPtr` pin is refused; lazy encode at first bind is the fallback.
|
||||
9. Failed relink: re-issue with `LinkStatus = 0` iff the frontend reports the program unlinked
|
||||
afterwards; pg verifies which at `ProgramObject.cpp:348-356, 518-522`. Never an `object_death`.
|
||||
10. `set_shader_buffers` window: the touched high-water count per target; capacity 84 on the wire,
|
||||
the backend clamps (`DirectGLES.cpp:500-504`). 11. Suppressor: three class slots.
|
||||
12. Magma scope: one renamed scope, backend-kind-keyed exemption (§4.4).
|
||||
13. Deferred-destroy queue: stays live (§2.7). 14. `ResolvedDrawBuffers`: measured after vi.
|
||||
15. `Blit`'s `kWaitNone` is fb's landing, not a separate package; `Clear`'s is tx2+fb.
|
||||
|
||||
---
|
||||
|
||||
## §10 What c0e landed, by name — the surface every other package compiles against
|
||||
|
||||
Inert by construction: the caps bit is not published (`kMGPipeP5eRunAheadReady` is `false`), the
|
||||
new opcode has no route, the new subsystem bit is not in `kMGPipeWiredSubsystems`, and every new
|
||||
applier entry point and by-handle backend signature has a body that aborts by name. Unit,
|
||||
`integration-split` and the generator gates are green with all of it in the tree.
|
||||
|
||||
| where | the names |
|
||||
|---|---|
|
||||
| `MG_Pipe/MGPipeTypes.h` | `kCapRunAheadApply`, `MGPipeRunAheadCapBitsFor(BackendType, Bool)`, `kMGPipeMaxBufferBindingPoints`, `kMGPipeShaderBufferClass{Uniform,ShaderStorage,AtomicCounter,Count}`, `MGPProgramBindings`, `MGPProgramSamplerUnit`, `MGPProgramStorageOverride`, `kMGPipeMaxProgram{BlockBindings,SamplerUnits,StorageOverrides}`, `MGPProgramDesc::LinkStatus`, `kDrawClientArrays` |
|
||||
| `MG_Pipe/MGPipeValueTypes.h` | `MGPipeImageAccess`, `kMGPipeImageAccess{ReadOnly,WriteOnly,ReadWrite}`, `MGPipeImageAccessIsValid`, `MGPipeDecodeImageAccess`, `MGPipeImageAccessReads/Writes` |
|
||||
| `MG_Pipe/MGPipe.h` | `MGPipeWaitClass` (`kWaitNone/kWaitApplied/kWaitReply/kWaitPresent/kWaitClassCount`), `kMGPipeSubsystemBufferBindings`, `kMGPipeSubsystemsMigratedAtP5e` |
|
||||
| `MG_Pipe/generated/PipeWire.inc` | `MGPWireOp::SetProgramBindings` (80), `kMGPipeWaitClasses[]`, `MGPipeWaitClassFor(op)` |
|
||||
| `MG_Pipe/PipeApply.h` | `MGPipeBarriered(op, payload, st)` (implemented), `MGPipeApplierCurrentRecordIsBarriered()` / `MGPipeApplierSetCurrentRecordBarriered()` (implemented, default `true`), `MGPipeApplySetShaderBuffers` and `MGPipeApplySetProgramBindings` (declared, aborting), `MGPipeApplierState::{BoundShaderBuffers, ShaderBufferStart, ShaderBufferCount, ShaderBufferWritableMask, ShaderBuffersSerial, IsTransformFeedbackActive}`, `MGPipeShaderCsoRecord::{BlockBindings, SamplerUnits, StorageOverrides, Signature, BindingsSerial}`, `MGPipeProgramStorageOverride` |
|
||||
| `MG_Impl/Pipe/SetHashSuppressor.h` | `MGPipeSuppressorSlot::{SetShaderBuffersUniform, SetShaderBuffersShaderStorage, SetShaderBuffersAtomicCounter, SetProgramBindings}` |
|
||||
| `MG_Impl/Pipe/Tracker.h` | `kMGPipeDirtyEmittedAtP5e`; bits 15/16/17 mapped onto bit 13 |
|
||||
| `MG_Impl/Pipe/ImageEmit.h` | `MGPipeEncodeImageAccess` promoted to a free function over the shared table |
|
||||
| `MG_Backend/DirectGLES/Managers.h` | `VertexArrayImpl::ResolveVaoTwin(handle)`, `TextureImpl::SyncTextureToBackendByHandle`, `TextureImpl::ResolveTextureTwin`, `BackendTextureObject::SyncMipmapsToBackendByHandle`, `BackendFramebufferObject::SyncToBackendByHandle`, `BackendRenderbufferObject::SyncToBackendByHandle`, `BackendProgramObjectImpl::SyncToBackendByHandle`, `PrgramImpl::ResolveProgramTwin` — all declared, all aborting by name, one block at the end of `Managers.cpp` |
|
||||
| `Config.h` / `ConfigLoader.cpp` | `IpcTable::RunAhead`, `IpcTable::PresentCredit`; the push default is `kMGPipeSubsystemsMigratedAtP5e` |
|
||||
| `MG_Backend/Init.cpp` | `kMGPipeP5eRunAheadReady` (`false`) and the caps arm through `MGPipeRunAheadCapBitsFor` |
|
||||
|
||||
**The three red-onces c0e ran, and what they name.** (1) flipping one row's `WaitClass` without
|
||||
regenerating → `gen_pipe: OUT OF DATE: MobileGL/MG_Pipe/generated/PipeWire.inc`, rc 1; with the
|
||||
regeneration, `PipeCatalogue.EveryRowCarriesTheWaitClassTheContractGivesIt` fails naming
|
||||
`GenerateMipmap`. (2) `sizeof(MGPProgramBindings)` 32 → 40 →
|
||||
`PipeCatalogue.LateArrivalsAreAppendedWithoutRenumbering` fails "Which is: 40 / 32". (3) making
|
||||
the caps arm ignore the backend type →
|
||||
`MagmaPipeIdentityTest.AMagmaServerNeverPublishesTheRunAheadCapBit` fails "Which is: 1024 / 0".
|
||||
@@ -242,7 +242,8 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
X(CopyFramebufferToTexture, MGPCopyFromFramebuffer) \
|
||||
X(ApplierReset, MGPApplierReset) \
|
||||
X(ObjectDeath, MGPHandleOnly) \
|
||||
X(SetContextValues, MGPContextValues)
|
||||
X(SetContextValues, MGPContextValues) \
|
||||
X(SetProgramBindings, MGPProgramBindings)
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -602,6 +603,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// believed, and the bound is the header's own 32-bit Size field.
|
||||
Uint64 tail0 = 0;
|
||||
Uint64 tail1 = 0;
|
||||
Uint64 tail2 = 0; // P5e: set_program_bindings is the one three-tail row
|
||||
Uint32 tails = 0;
|
||||
switch (op) {
|
||||
case MGPWireOp::SetVertexBuffers: {
|
||||
@@ -667,6 +669,47 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
tails = 2;
|
||||
break;
|
||||
}
|
||||
case MGPWireOp::SetProgramBindings: {
|
||||
// THREE TAILS, THREE COUNTS, THREE INDEX SPACES (P5e,
|
||||
// MG_Remote/CONTRACT-P5E.md §1): Int32[BlockBindingCount] in GL uniform-block index
|
||||
// order, MGPProgramSamplerUnit[SamplerUnitCount] ascending by Location, and
|
||||
// MGPProgramStorageOverride[StorageOverrideCount] whose Name is a host span staged
|
||||
// whole - the override map is name-keyed by design.
|
||||
//
|
||||
// EACH COUNT IS REFUSED AGAINST ITS DECLARED BOUND, never truncated: a sampler
|
||||
// array past 256 locations is a program this record cannot describe, and silently
|
||||
// describing 256 of them would put uniforms on the wrong units with no marker
|
||||
// anywhere. The counts come off the wire, so every one is bounded before it is
|
||||
// multiplied.
|
||||
const auto& p = *static_cast<const MGPProgramBindings*>(payload);
|
||||
if (p.BlockBindingCount > kMGPipeMaxProgramBlockBindings) {
|
||||
WireProtocolFatalAt("SetProgramBindings.BlockBindingCount", p.BlockBindingCount,
|
||||
kMGPipeMaxProgramBlockBindings);
|
||||
}
|
||||
if (p.SamplerUnitCount > kMGPipeMaxProgramSamplerUnits) {
|
||||
WireProtocolFatalAt("SetProgramBindings.SamplerUnitCount", p.SamplerUnitCount,
|
||||
kMGPipeMaxProgramSamplerUnits);
|
||||
}
|
||||
if (p.StorageOverrideCount > kMGPipeMaxProgramStorageOverrides) {
|
||||
WireProtocolFatalAt("SetProgramBindings.StorageOverrideCount",
|
||||
p.StorageOverrideCount, kMGPipeMaxProgramStorageOverrides);
|
||||
}
|
||||
tail0 = TailBytesFor(p.BlockBindingCount, sizeof(Int32));
|
||||
tail1 = TailBytesFor(p.SamplerUnitCount, sizeof(MGPProgramSamplerUnit));
|
||||
tail2 = TailBytesFor(p.StorageOverrideCount, sizeof(MGPProgramStorageOverride));
|
||||
// ALWAYS THREE, even when a count is 0. The tails are positional and the third one's
|
||||
// offset is derived from the first two, so a record that declared "one tail" when
|
||||
// only the block bindings were present would put the same bytes at a different
|
||||
// offset than a record that declared three - and the two halves of the wire would
|
||||
// disagree about where the overrides start. An empty tail is zero bytes at an
|
||||
// 8-aligned offset, which costs nothing.
|
||||
tails = 3;
|
||||
// NOT SecondTailIsHostSpans: the spans are MEMBERS of the third tail's elements,
|
||||
// not a bare MGHostSpan array, so the encoder's blanket honesty pass would read 40
|
||||
// bytes of {span, binding, pad} as one span and a quarter of garbage. The arm in
|
||||
// ApplyChecked walks the elements and checks each Name itself.
|
||||
break;
|
||||
}
|
||||
case MGPWireOp::SetVertexAttribDefaults: {
|
||||
// TWO DECLARANTS THAT MUST AGREE (contract table 1 row 15): Count and
|
||||
// popcount(Mask). Nothing checks this today; a disagreement is a wire fault that
|
||||
@@ -749,6 +792,15 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
out.TailBytes[1] = tail1;
|
||||
out.TotalBytes = out.TailOffset[1] + tail1;
|
||||
}
|
||||
// P5e: the third tail, realigned to 8 for the second one's reason - set_program_bindings'
|
||||
// first tail is Int32[] and its elements are four bytes, so the array behind it would
|
||||
// otherwise start at a 4-aligned offset and every MGHostSpan in the third would be
|
||||
// misaligned on a host that cares.
|
||||
if (tails >= 3) {
|
||||
out.TailOffset[2] = Align8(out.TailOffset[1] + tail1);
|
||||
out.TailBytes[2] = tail2;
|
||||
out.TotalBytes = out.TailOffset[2] + tail2;
|
||||
}
|
||||
out.TailCount = tails;
|
||||
out.TotalBytes = Align8(out.TotalBytes);
|
||||
if (out.TotalBytes > kMaxRecordBytesOnTheWire) {
|
||||
@@ -1435,12 +1487,14 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// record declaring Count = 4000 while carrying 8 bytes to its own arithmetic.
|
||||
if (size != layout.TotalBytes) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} the record declares Size=%llu and "
|
||||
"its own count fields describe %llu bytes (fixed payload %llu + tails %llu/%llu)",
|
||||
"its own count fields describe %llu bytes (fixed payload %llu + tails "
|
||||
"%llu/%llu/%llu)",
|
||||
WireOpName(op), static_cast<unsigned long long>(size),
|
||||
static_cast<unsigned long long>(layout.TotalBytes),
|
||||
static_cast<unsigned long long>(layout.PayloadBytes),
|
||||
static_cast<unsigned long long>(layout.TailBytes[0]),
|
||||
static_cast<unsigned long long>(layout.TailBytes[1]));
|
||||
static_cast<unsigned long long>(layout.TailBytes[1]),
|
||||
static_cast<unsigned long long>(layout.TailBytes[2]));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
@@ -1806,6 +1860,33 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// Same as above: no applier, off the reduced path, both tails validated.
|
||||
return false;
|
||||
|
||||
case MGPWireOp::SetProgramBindings: {
|
||||
// P5e, opcode 80 (MG_Remote/CONTRACT-P5E.md §1). NO APPLIER ENTRY POINT EXISTS YET
|
||||
// and c0e does not invent one: the record's consumer is the program twin's
|
||||
// rebuild, which package pg writes. What the record needs NOW and cannot get later
|
||||
// is its shape - the three declared counts against their bounds and the three-tail
|
||||
// arithmetic, both of which MGPipeWireRecordLayout above has already run - plus the
|
||||
// honesty of every name span it carries.
|
||||
//
|
||||
// THE SPANS ARE WALKED HERE RATHER THAN BY THE ENCODER'S BLANKET PASS because they
|
||||
// are MEMBERS of the third tail's 40-byte elements, not a bare MGHostSpan array:
|
||||
// SecondTailIsHostSpans reads a tail AS spans, and doing that to {span, binding,
|
||||
// pad} would read one span and a quarter of garbage. All four arms of
|
||||
// CheckHostSpanIsHonest run, the segment-range one included, so an override name
|
||||
// pointing outside SEG_STAGE is caught on the way in rather than at the rebuild.
|
||||
if (layout.TailCount == 3 && layout.TailBytes[2] != 0 && m_segments != nullptr) {
|
||||
const auto* overrides = tailAt(2);
|
||||
const Uint64 count = layout.TailBytes[2] / sizeof(MGPProgramStorageOverride);
|
||||
for (Uint64 i = 0; i < count; ++i) {
|
||||
MGPProgramStorageOverride entry{};
|
||||
std::memcpy(&entry, overrides + i * sizeof(MGPProgramStorageOverride),
|
||||
sizeof(MGPProgramStorageOverride));
|
||||
CheckHostSpanIsHonest(entry.Name, *m_segments);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case MGPWireOp::SetGlobalConstants: {
|
||||
const auto& rec = *static_cast<const MGPGlobalConstants*>(payload);
|
||||
// The length cross-check is the applier's (PipeApply.cpp:2784, against the
|
||||
|
||||
@@ -171,10 +171,19 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// holds a pointer and two Uint64s. P5 emits no host span at all, so this rule costs
|
||||
// nothing now and is stated now because the phase that arms kDrawHasUserIndices would
|
||||
// otherwise have to discover it as a misaligned load on a device.
|
||||
// THREE TAILS AND NOT TWO SINCE P5e. set_program_bindings (opcode 80,
|
||||
// MG_Remote/CONTRACT-P5E.md §1) carries the uniform-block bindings, the sampler units and
|
||||
// the storage-override map as three arrays in three different index spaces, so the array
|
||||
// that used to be exactly "the two-tail rows plus draw_vbo's conditional second" grew by
|
||||
// one. Every loop over TailCount below already reads the count rather than the literal 2;
|
||||
// what changed is the storage and the assembly.
|
||||
inline constexpr Uint32 kMaxWireRecordTails = 3;
|
||||
|
||||
struct WireRecordLayout {
|
||||
Uint64 PayloadBytes = 0; // sizeof the op's payload struct
|
||||
Uint64 TailOffset[2] = {0, 0}; // from the START of the record, header included
|
||||
Uint64 TailBytes[2] = {0, 0};
|
||||
Uint64 TailOffset[kMaxWireRecordTails] = {0, 0, 0}; // from the START of the record,
|
||||
// header included
|
||||
Uint64 TailBytes[kMaxWireRecordTails] = {0, 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]
|
||||
@@ -194,9 +203,9 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// The catalogue's own spelling of an opcode, for a Fatal line. Out of range is "<opcode>".
|
||||
const char* WireOpName(MG_Pipe::MGPWireOp op);
|
||||
|
||||
// One tail array. Two of the 71 rows carry two (SetShaderBuffers, SetStreamOutputTargets)
|
||||
// and DrawVbo carries a conditional second one, which is why EncodeRecord's one-tail form
|
||||
// could not stay the only one.
|
||||
// One tail array. Two of the rows carry two (SetShaderBuffers, SetStreamOutputTargets),
|
||||
// DrawVbo carries a conditional second one and P5e's SetProgramBindings carries three,
|
||||
// which is why EncodeRecord's one-tail form could not stay the only one.
|
||||
struct WireTail {
|
||||
const void* Bytes = nullptr;
|
||||
Uint64 Size = 0;
|
||||
@@ -234,8 +243,10 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
|
||||
const void* varTail = nullptr, Uint64 varTailBytes = 0);
|
||||
|
||||
// The same call for the three rows that carry TWO tails. The one-tail form above is
|
||||
// this one with tailCount <= 1; nothing is duplicated between them.
|
||||
// The same call for the rows that carry more than one tail - two for
|
||||
// set_shader_buffers, set_stream_output_targets and draw_vbo's conditional second,
|
||||
// three for P5e's set_program_bindings. The one-tail form above is this one with
|
||||
// tailCount <= 1; nothing is duplicated between them.
|
||||
//
|
||||
// The tails a caller hands over are CROSS-CHECKED against the layout the payload
|
||||
// itself declares (MGPipeWireRecordLayout): a caller whose Count says 4000 while its
|
||||
|
||||
@@ -734,8 +734,13 @@ TEST_F(FieldOwnershipTest, StrictErrorsTurnsABarrierPulledReadIntoANamedAbort) {
|
||||
EXPECT_NE(r.Log.find("BARRIER-PULLED"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("MOBILEGL_IPC_STRICT_ERRORS=1"), std::string::npos) << r.Log;
|
||||
// The strict line names the phase that owes the answer; a strict abort that did not would
|
||||
// leave the reader exactly where the gate found them.
|
||||
EXPECT_NE(r.Log.find("retires in P8]"), std::string::npos) << r.Log;
|
||||
// leave the reader exactly where the gate found them. P5e re-annotated this row (and the
|
||||
// five object-class rows beside it) from "P8" to the phase that actually retires the pull,
|
||||
// which is why the expected text moved with FieldOwnership.def rather than the case being
|
||||
// re-pointed at another field: the string IS the debt entry, read out of the generated
|
||||
// table, and a case that stopped checking it would let the annotation rot.
|
||||
EXPECT_NE(r.Log.find("retires in P5e (Espryt unbarriered), P7 (Magma)]"), std::string::npos)
|
||||
<< r.Log;
|
||||
}
|
||||
|
||||
TEST_F(FieldOwnershipTest, TheSameReadWithoutStrictErrorsSurvivesAndIsCounted) {
|
||||
|
||||
@@ -56,7 +56,8 @@ namespace {
|
||||
X(MagmaPipeIdentityTest, AnIdleSlotIsRetiredAndReusedWithANewGeneration) \
|
||||
X(MagmaPipeIdentityTest, AReusedSlotDoesNotServeItsPredecessorsMemo) \
|
||||
X(MagmaPipeIdentityTest, TheAbaControlKnobServesTheStaleMemoAcrossAReusedSlot) \
|
||||
X(MagmaPipeIdentityTest, ALiveObjectKeepsItsSlotItsGenerationAndItsMemo)
|
||||
X(MagmaPipeIdentityTest, ALiveObjectKeepsItsSlotItsGenerationAndItsMemo) \
|
||||
X(MagmaPipeIdentityTest, AMagmaServerNeverPublishesTheRunAheadCapBit)
|
||||
|
||||
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
|
||||
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
|
||||
@@ -213,5 +214,48 @@ namespace {
|
||||
<< "a live object's memo was cleared without its slot changing owner";
|
||||
EXPECT_EQ(mint.Count(), 1u);
|
||||
}
|
||||
|
||||
// ---- P5e (MG_Remote/CONTRACT-P5E.md §1, §6, ruling 12) -------------------------------
|
||||
//
|
||||
// MAGMA DOES NOT RUN AHEAD, AND THIS IS WHERE THAT IS A TEST RATHER THAN A COMMENT.
|
||||
// kCapRunAheadApply is the client's whole permission to publish a record and move on: the
|
||||
// moment a server sets it, the apply thread promises it reads nothing of the client's. That
|
||||
// promise is FALSE for Magma for the whole of P5e - the four apply-thread allocator sites
|
||||
// inside MagmaP7AllocatorDebtScope are real debt P7 retires, and
|
||||
// MGPipeApplierCurrentRecordIsBarriered() answering true for every record on a server
|
||||
// without the bit is exactly what keeps them inside P5C's semantics and keeps rsp honest.
|
||||
//
|
||||
// The arm is a pure function precisely so this case can reach it: InitSplitRoles needs a
|
||||
// live session, a backend and a handshake, and none of those belong in a unit lane. RED
|
||||
// ONCE by making MGPipeRunAheadCapBitsFor answer for DirectVulkan too (the exact
|
||||
// perturbation the phase's red-once list names) - both EXPECTs below fail, by name.
|
||||
TEST_F(MagmaPipeIdentityTest, AMagmaServerNeverPublishesTheRunAheadCapBit) {
|
||||
// Whatever the integration constant says. `true` is what the P5e integration commit
|
||||
// will pass, so the Magma answer is pinned on BOTH sides of that flip and the case
|
||||
// does not quietly stop asserting anything the day the constant moves.
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectVulkan, /*ready=*/false) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
0u);
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectVulkan, /*ready=*/true) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
0u);
|
||||
|
||||
// And the Espryt half, so the case says what the arm IS and not only what it is not:
|
||||
// the bit is published for DirectGLES and ONLY once the integration commit flips
|
||||
// kMGPipeP5eRunAheadReady. Until then every P5e package lands with the wait rule inert.
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectGLES, /*ready=*/false) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
0u);
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectGLES, /*ready=*/true) &
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply),
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply));
|
||||
|
||||
// Bit 10 and nothing else: the arm contributes ONE bit, so a future editor cannot fold
|
||||
// an unrelated capability into it and have the two pins above still pass.
|
||||
EXPECT_EQ(MG_Pipe::MGPipeRunAheadCapBitsFor(BackendType::DirectGLES, /*ready=*/true),
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply));
|
||||
static_assert(static_cast<Uint64>(MG_Pipe::kCapRunAheadApply) == (1ull << 10),
|
||||
"kCapRunAheadApply moved bit; CONTRACT-P5E table 0 names bit 10");
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
@@ -65,8 +65,8 @@ TEST(PipeCatalogue, FrontendNeverTakesAnApplierAddress) {
|
||||
namespace {
|
||||
// Counting expansions of the catalogue. The Class parameter is a real enumerator, so a
|
||||
// per-class count is a constant expression too.
|
||||
#define MGP_COUNT_ONE(Name, Payload, Class, Flags) +1
|
||||
#define MGP_COUNT_CLASS(Name, Payload, Class, Flags) +((Class) == countedClass ? 1 : 0)
|
||||
#define MGP_COUNT_ONE(Name, Payload, Class, Flags, Wait) +1
|
||||
#define MGP_COUNT_CLASS(Name, Payload, Class, Flags, Wait) +((Class) == countedClass ? 1 : 0)
|
||||
|
||||
constexpr SizeT kExpandedCallCount = 0 MGP_CALL_LIST(MGP_COUNT_ONE);
|
||||
|
||||
@@ -77,13 +77,32 @@ namespace {
|
||||
|
||||
// Every payload named in the catalogue must be a memcpy-able POD, and so must every
|
||||
// payload the verify comparator knows about.
|
||||
#define MGP_ASSERT_CALL_PAYLOAD_POD(Name, Payload, Class, Flags) \
|
||||
#define MGP_ASSERT_CALL_PAYLOAD_POD(Name, Payload, Class, Flags, Wait) \
|
||||
static_assert(std::is_trivially_copyable_v<Payload>, #Name "'s payload " #Payload " is not trivially copyable");
|
||||
MGP_CALL_LIST(MGP_ASSERT_CALL_PAYLOAD_POD)
|
||||
|
||||
#define MGP_ASSERT_VERIFY_PAYLOAD_POD(Payload) \
|
||||
static_assert(std::is_trivially_copyable_v<Payload>, #Payload " is not trivially copyable");
|
||||
MGP_VERIFY_PAYLOAD_LIST(MGP_ASSERT_VERIFY_PAYLOAD_POD)
|
||||
|
||||
// The catalogue's own spelling of an opcode, for a failure message. The wire codec has one
|
||||
// of these and MG_Pipe may not reach it (MG_Remote is above this layer), so the table is
|
||||
// expanded from the catalogue here rather than a name being duplicated per EXPECT.
|
||||
#define MGP_CATALOGUE_NAME_ROW(Name, Payload, Class, Flags, Wait) #Name,
|
||||
constexpr const char* kCatalogueOpNames[] = {
|
||||
"<kInvalid>",
|
||||
MGP_CALL_LIST(MGP_CATALOGUE_NAME_ROW)
|
||||
};
|
||||
#undef MGP_CATALOGUE_NAME_ROW
|
||||
static_assert(sizeof(kCatalogueOpNames) / sizeof(kCatalogueOpNames[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
"the diagnostic name table and the opcode space disagree");
|
||||
|
||||
const char* WireOpNameForDiag(MGPWireOp op) {
|
||||
const SizeT index = static_cast<SizeT>(op);
|
||||
return index < static_cast<SizeT>(MGPWireOp::kOpCount) ? kCatalogueOpNames[index]
|
||||
: "<opcode out of range>";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// The handle is the whole object model. Eight bytes, a register pair, no padding.
|
||||
@@ -127,11 +146,12 @@ TEST(PipeCatalogue, GeneratedTablesHoldTheWholeCatalogue) {
|
||||
// The per-class counts PipeCalls.def documents in its header.
|
||||
// kScreen is 11 + P5c's applier_reset (MG_Remote/CONTRACT-P5C.md §5.1); kCtxObject is
|
||||
// 9 + P5c's object_death (§5.2), the framebuffer family's first wire delete opcode.
|
||||
// kCtxState is 17 + P5c rv's set_context_values (§5.3), the residual-value record.
|
||||
// kCtxState is 17 + P5c rv's set_context_values (§5.3), the residual-value record, + P5e's
|
||||
// set_program_bindings (MG_Remote/CONTRACT-P5E.md §1), the post-link binding record.
|
||||
EXPECT_EQ(ClassCount<kScreen>(), 12u);
|
||||
EXPECT_EQ(ClassCount<kCtxQuery>(), 8u);
|
||||
EXPECT_EQ(ClassCount<kCtxCso>(), 13u);
|
||||
EXPECT_EQ(ClassCount<kCtxState>(), 18u);
|
||||
EXPECT_EQ(ClassCount<kCtxState>(), 19u);
|
||||
EXPECT_EQ(ClassCount<kCtxObject>(), 10u);
|
||||
// 13 + the five P5b-appended verbs (MG_Remote/CONTRACT-P5B.md): bind_shader_image,
|
||||
// patch_parameter, bind_stream_output, set_storage_block_binding,
|
||||
@@ -142,9 +162,10 @@ TEST(PipeCatalogue, GeneratedTablesHoldTheWholeCatalogue) {
|
||||
// A row nobody has migrated is null - which is exactly what "this subsystem has not been
|
||||
// migrated, keep pulling" means (plan B section 4.1).
|
||||
//
|
||||
// UNTIL P5 R-17 THAT WAS EVERY ROW, and this case said so. It is now EXACTLY THE 41 ROWS WITH
|
||||
// NO MGPipeApply* ENTRY POINT (79 - the 38 that have one; the number was 34 at P5, 39 after
|
||||
// P5b's five sink-only verbs, and rv's set_context_values grew BOTH sides of the difference):
|
||||
// UNTIL P5 R-17 THAT WAS EVERY ROW, and this case said so. It is now EXACTLY THE 42 ROWS WITH
|
||||
// NO MGPipeApply* ENTRY POINT (80 - the 38 that have one; the number was 34 at P5, 39 after
|
||||
// P5b's five sink-only verbs, rv's set_context_values grew BOTH sides of the difference, and
|
||||
// P5e's set_program_bindings grew the null side alone):
|
||||
// the other 38 have an applier, R-17 installs adapters over them,
|
||||
// and a null there would no longer mean "keep pulling" - `MG_Impl/Pipe`'s call sites go through
|
||||
// the thunks, so a null would mean "call through a null pointer". The number is asserted rather
|
||||
@@ -161,7 +182,7 @@ TEST(PipeCatalogue, UninstalledTablesAreAllNull) {
|
||||
const void* const* context = reinterpret_cast<const void* const*>(&gMGPipeContext);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
MGPipeInstallMonolithTables();
|
||||
// The 41 rows with no MGPipeApply* entry point are still null, and null still means "this
|
||||
// The 42 rows with no MGPipeApply* entry point are still null, and null still means "this
|
||||
// subsystem has not been migrated, keep pulling". Named rather than counted, because the
|
||||
// count is the other case's job and two cases asserting the same number would both go red
|
||||
// for one change. P5c's two control records are among them by design (CONTRACT-P5C.md §5:
|
||||
@@ -177,6 +198,10 @@ TEST(PipeCatalogue, UninstalledTablesAreAllNull) {
|
||||
EXPECT_EQ(gMGPipeScreen.FenceCreate, nullptr);
|
||||
EXPECT_EQ(gMGPipeScreen.ApplierReset, nullptr);
|
||||
EXPECT_EQ(gMGPipeContext.ObjectDeath, nullptr);
|
||||
// P5e's opcode 80 joins them: the row is catalogued, its codec layout is written and its
|
||||
// tails are validated, but there is no MGPipeApplySetProgramBindings and no route - package
|
||||
// pg installs both. A null here is the contract, not an omission.
|
||||
EXPECT_EQ(gMGPipeContext.SetProgramBindings, nullptr);
|
||||
#else
|
||||
// A pull build compiles no applier and no routing, so the pre-migration statement is the
|
||||
// whole truth there and this case is the one that says so.
|
||||
@@ -251,6 +276,9 @@ TEST(PipeCatalogue, ExactlyTheRoutedRowsAreInstalledAndTheRestAreStillNull) {
|
||||
// notice's mailbox are the producers, byte for byte as before (G1/G2).
|
||||
EXPECT_EQ(gMGPipeScreen.ApplierReset, nullptr);
|
||||
EXPECT_EQ(gMGPipeContext.ObjectDeath, nullptr);
|
||||
// P5e's set_program_bindings (CONTRACT-P5E.md §1): catalogued with a null route until pg
|
||||
// installs the applier entry point and the adapter beside it.
|
||||
EXPECT_EQ(gMGPipeContext.SetProgramBindings, nullptr);
|
||||
#else
|
||||
// A pull build compiles no applier and no routing, so the pre-migration statement is still
|
||||
// the whole truth there.
|
||||
@@ -584,7 +612,14 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) {
|
||||
// - fixed POD, no blob, no tail, no reply - with an MGPipeApply* entry point, which the two
|
||||
// control records deliberately do not have.
|
||||
EXPECT_EQ(static_cast<Uint16>(MGPWireOp::SetContextValues), 79);
|
||||
EXPECT_EQ(static_cast<Uint16>(MGPWireOp::kOpCount), 80);
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md §1) appended ONE more, by the same rule: opcode 80, and
|
||||
// nothing before it moved. set_program_bindings is a kCtxState row with THREE tails and a
|
||||
// host span in the third; it lands with a null route and a sink that refuses it by name,
|
||||
// exactly as set_shader_buffers has sat catalogued-and-dead since P4a.
|
||||
EXPECT_EQ(static_cast<Uint16>(MGPWireOp::SetProgramBindings), 80);
|
||||
EXPECT_EQ(static_cast<Uint16>(MGPWireOp::kOpCount), 81);
|
||||
EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetProgramBindings),
|
||||
static_cast<Uint32>(kVarTail | kHostSpan));
|
||||
EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ApplierReset), static_cast<Uint32>(kNone));
|
||||
EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ObjectDeath), static_cast<Uint32>(kNone));
|
||||
EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetContextValues), static_cast<Uint32>(kNone));
|
||||
@@ -618,9 +653,44 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) {
|
||||
EXPECT_EQ(sizeof(MGPContextValues::TouchedBufferBindingPointCount), 60u);
|
||||
EXPECT_EQ(sizeof(MGPAttribValue), 56u);
|
||||
EXPECT_EQ(sizeof(MGPVertexAttribDefaults), 8u);
|
||||
// P5e's payloads (CONTRACT-P5E.md §1), pinned at runtime like every other MGP_ASSERT_POD.
|
||||
//
|
||||
// MGPProgramDesc GREW 192 -> 200, and that is the phase's one wire-format widening: the
|
||||
// descriptor's four trailing Uint8s end exactly at offset 24 and MGPBlobRef is 8-aligned,
|
||||
// so there was no spare byte for LinkStatus and the draft's "in the descriptor's existing
|
||||
// pad" was wrong. Pinned here, by value, because a record whose size moved silently is a
|
||||
// protocol break no other test would see - and because THIS number is the red-once the
|
||||
// phase names: change MGPProgramBindings' or the descriptor's size and this case fails.
|
||||
EXPECT_EQ(sizeof(MGPProgramDesc), 200u);
|
||||
EXPECT_EQ(sizeof(MGPProgramBindings), 32u);
|
||||
EXPECT_EQ(sizeof(MGPProgramSamplerUnit), 8u);
|
||||
EXPECT_EQ(sizeof(MGPProgramStorageOverride), 40u);
|
||||
// The three declared tail bounds. A program past one of them is a COUNTED REFUSAL and never
|
||||
// a truncation, so the numbers are the contract rather than an implementation detail.
|
||||
EXPECT_EQ(kMGPipeMaxProgramBlockBindings, 64u);
|
||||
EXPECT_EQ(kMGPipeMaxProgramSamplerUnits, 256u);
|
||||
EXPECT_EQ(kMGPipeMaxProgramStorageOverrides, 64u);
|
||||
// LinkStatus is a real byte and a zeroed descriptor means "not linked", which is what makes
|
||||
// the server's decline the safe default.
|
||||
MGPProgramDesc program{};
|
||||
EXPECT_EQ(program.LinkStatus, 0u);
|
||||
// set_shader_buffers' capacity, pinned on the wire side; MG_Impl/Pipe/PipeFill.cpp is the
|
||||
// one translation unit that also sees BufferState.h's constant and pins them together.
|
||||
EXPECT_EQ(kMGPipeMaxBufferBindingPoints, 84u);
|
||||
// The two draw-flag bits P5b's d1 arms are exclusive by contract and distinct by value.
|
||||
EXPECT_EQ(static_cast<Uint32>(kDrawIsIndirect), 1u << 5);
|
||||
EXPECT_EQ(static_cast<Uint32>(kDrawHasUserIndices) & static_cast<Uint32>(kDrawIsIndirect), 0u);
|
||||
// P5e's draw flag: the next free bit, and disjoint from all five before it.
|
||||
EXPECT_EQ(static_cast<Uint32>(kDrawClientArrays), 1u << 6);
|
||||
EXPECT_EQ(static_cast<Uint32>(kDrawClientArrays) &
|
||||
static_cast<Uint32>(kDrawHasUserIndices | kDrawPrimitiveRestart |
|
||||
kDrawIndicesAreClient | kDrawHasIndexRange |
|
||||
kDrawHasXfbCount | kDrawIsIndirect),
|
||||
0u);
|
||||
// ... and it still fits the one byte MGPDrawInfo::Flags is, which is why the record did not
|
||||
// grow: MGPDrawInfo stays 56 bytes.
|
||||
EXPECT_EQ(sizeof(MGPDrawInfo), 56u);
|
||||
EXPECT_LE(static_cast<Uint32>(kDrawClientArrays), 0xFFu);
|
||||
// The clear discriminants have ONE spelling now, and Whole is 0 so a zeroed record is a
|
||||
// whole-framebuffer clear.
|
||||
EXPECT_EQ(kMGPipeClearKindWhole, 0u);
|
||||
@@ -628,6 +698,107 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) {
|
||||
EXPECT_EQ(kMGPipeClearValueClassUint + 1, kMGPipeClearValueClassCount);
|
||||
}
|
||||
|
||||
// ---- P5e: the wait-class column (MG_Remote/CONTRACT-P5E.md §2.2) --------------------------
|
||||
//
|
||||
// THE POINT OF THIS CASE IS THAT IT DOES NOT READ PipeCalls.def. Every other statement about
|
||||
// the column - the generated table, the two gates in gen_pipe.py, the static_asserts in
|
||||
// PipeWire.inc - is derived from the def, so all of them move together when a row is edited.
|
||||
// This case re-states CONTRACT-P5E §2.2's table BY NAME, in the contract's own grouping, so a
|
||||
// row silently moved from kWaitApplied to kWaitNone has to be argued in two places or it goes
|
||||
// red here. That is the difference between a table and a contract.
|
||||
//
|
||||
// It matters because the column is not decoration: on a run-ahead server kWaitNone is a promise
|
||||
// that the record's apply reads NOTHING of the client's (rule F), and a row demoted to it by
|
||||
// accident is a torn read that renders wrong rather than aborting.
|
||||
TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) {
|
||||
// kWaitPresent: present alone, paced by the credit taken BEFORE the encode (§2.4).
|
||||
EXPECT_EQ(MGPipeWaitClassFor(MGPWireOp::Present), kWaitPresent);
|
||||
|
||||
// kWaitReply: every row whose answer is not derivable (R-5). Named, all fourteen, because
|
||||
// the flag-to-class agreement is what keeps a waiter from hanging on a slot nobody posts.
|
||||
const MGPWireOp replyRows[] = {
|
||||
MGPWireOp::GetCaps, MGPWireOp::ResourceCreate, MGPWireOp::ResourceRespecify,
|
||||
MGPWireOp::MapPersistent, MGPWireOp::FenceStatus, MGPWireOp::FenceWait,
|
||||
MGPWireOp::QueryAvailable, MGPWireOp::QueryResult, MGPWireOp::QueryTimestamp,
|
||||
MGPWireOp::SetTextureParams, MGPWireOp::ResourceSubData, MGPWireOp::ResourceReadback,
|
||||
MGPWireOp::GetTextureImage, MGPWireOp::ReadPixels,
|
||||
};
|
||||
for (const MGPWireOp op : replyRows) {
|
||||
EXPECT_EQ(MGPipeWaitClassFor(op), kWaitReply) << WireOpNameForDiag(op);
|
||||
EXPECT_NE(MGPipeCallFlagsFor(op) & static_cast<Uint32>(kReplySlot), 0u)
|
||||
<< WireOpNameForDiag(op);
|
||||
}
|
||||
|
||||
// kWaitApplied: the rows whose apply STILL reads a BARRIER_PULLED row or probes the client
|
||||
// allocator, so the client parks in its own wait and CONTRACT-P5C's semantics hold for them
|
||||
// unchanged - their pulls count into rsp and their probes are legal inside a scope. Two of
|
||||
// them are trailing items and are expected to leave this list in a later package:
|
||||
// generate_mipmap the moment tx2 resolves the texture from VerbMipRes instead of the active
|
||||
// unit, and set_storage_block_binding the moment pg resolves it through
|
||||
// MGPStorageBlockBinding::ShaderCso. When they do, THIS list is what has to be edited.
|
||||
const MGPWireOp appliedRows[] = {
|
||||
MGPWireOp::ApplierReset, MGPWireOp::GenerateMipmap,
|
||||
MGPWireOp::SetStorageBlockBinding, MGPWireOp::BeginStreamOutput,
|
||||
MGPWireOp::EndStreamOutput, MGPWireOp::PauseStreamOutput,
|
||||
MGPWireOp::ResumeStreamOutput, MGPWireOp::BindStreamOutput,
|
||||
MGPWireOp::CopyFramebufferToTexture,
|
||||
};
|
||||
for (const MGPWireOp op : appliedRows) {
|
||||
EXPECT_EQ(MGPipeWaitClassFor(op), kWaitApplied) << WireOpNameForDiag(op);
|
||||
}
|
||||
|
||||
// kWaitNone: the steady draw path and every state/CSO/object row without a reply. Named for
|
||||
// the rows the phase exists for, and then counted for the rest, so a new row cannot join
|
||||
// the set unnoticed.
|
||||
const MGPWireOp noneRows[] = {
|
||||
MGPWireOp::DrawVbo, MGPWireOp::LaunchGrid, MGPWireOp::Clear,
|
||||
MGPWireOp::Blit, MGPWireOp::MemoryBarrier, MGPWireOp::Flush,
|
||||
MGPWireOp::SetSwapInterval, MGPWireOp::BindShaderImage, MGPWireOp::PatchParameter,
|
||||
MGPWireOp::ObjectDeath, MGPWireOp::ResourceDestroy, MGPWireOp::UnmapPersistent,
|
||||
MGPWireOp::FenceCreate, MGPWireOp::FenceDestroy, MGPWireOp::FenceWaitServer,
|
||||
MGPWireOp::QueryCreate, MGPWireOp::QueryBegin, MGPWireOp::QueryEnd,
|
||||
MGPWireOp::QueryDestroy, MGPWireOp::QueryCounter, MGPWireOp::SetContextValues,
|
||||
MGPWireOp::SetProgramBindings, MGPWireOp::SetShaderBuffers,
|
||||
MGPWireOp::SetVertexBuffers, MGPWireOp::SetFramebufferState,
|
||||
MGPWireOp::CreateShaderState, MGPWireOp::SetDrawProgram,
|
||||
};
|
||||
for (const MGPWireOp op : noneRows) {
|
||||
EXPECT_EQ(MGPipeWaitClassFor(op), kWaitNone) << WireOpNameForDiag(op);
|
||||
}
|
||||
|
||||
// The partition, by count. 14 + 1 + 9 = 24 rows wait; every other row of the catalogue does
|
||||
// not. A row that changed class moves two of these numbers at once.
|
||||
SizeT reply = 0, applied = 0, present = 0, none = 0, other = 0;
|
||||
for (SizeT i = 1; i < static_cast<SizeT>(MGPWireOp::kOpCount); ++i) {
|
||||
switch (MGPipeWaitClassFor(static_cast<MGPWireOp>(i))) {
|
||||
case kWaitReply: ++reply; break;
|
||||
case kWaitApplied: ++applied; break;
|
||||
case kWaitPresent: ++present; break;
|
||||
case kWaitNone: ++none; break;
|
||||
default: ++other; break;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(reply, 14u);
|
||||
EXPECT_EQ(applied, 9u);
|
||||
EXPECT_EQ(present, 1u);
|
||||
EXPECT_EQ(none, static_cast<SizeT>(kMGPipeCallCount) - 24u);
|
||||
EXPECT_EQ(other, 0u) << "a row carries the kWaitClassCount terminator as its class";
|
||||
|
||||
// And the reply half of the partition BOTH WAYS, over the whole catalogue: exactly the rows
|
||||
// that own a slot wait for one. gen_pipe.py refuses a catalogue where they disagree; this
|
||||
// is the same statement where a reader of the table will look for it.
|
||||
for (SizeT i = 1; i < static_cast<SizeT>(MGPWireOp::kOpCount); ++i) {
|
||||
const auto op = static_cast<MGPWireOp>(i);
|
||||
const Bool ownsSlot = (MGPipeCallFlagsFor(op) & static_cast<Uint32>(kReplySlot)) != 0;
|
||||
EXPECT_EQ(ownsSlot, MGPipeWaitClassFor(op) == kWaitReply) << WireOpNameForDiag(op);
|
||||
}
|
||||
|
||||
// Opcode 0 is not a call and has no class, for the flags table's reason: a decoder holding
|
||||
// a byte off a corrupt stream must reach its own Fatal rather than a wait decision.
|
||||
EXPECT_EQ(MGPipeWaitClassFor(MGPWireOp::kInvalid), kWaitClassCount);
|
||||
EXPECT_EQ(MGPipeWaitClassFor(MGPWireOp::kOpCount), kWaitClassCount);
|
||||
}
|
||||
|
||||
// A well-formed record passes the applier's bounds gate. P0 has no applier, so "accepted"
|
||||
// is reported as "not applied" rather than "fatal".
|
||||
TEST(PipeCatalogue, ApplierAcceptsAWellFormedRecord) {
|
||||
@@ -817,8 +988,12 @@ TEST(PipeCatalogue, SixValueStructsHaveFieldLists) {
|
||||
// each with its own field list, so the comparator sees every one of them: 77. P5c appended
|
||||
// applier_reset's MGPApplierReset (CONTRACT-P5C.md §5.1) - object_death reuses
|
||||
// MGPHandleOnly, which has had a list since P0 - and rv added set_context_values'
|
||||
// MGPContextValues (§5.3): 79.
|
||||
EXPECT_EQ(kMGPipeVerifiedPayloadCount, 79u);
|
||||
// MGPContextValues (§5.3): 79. P5e appended set_program_bindings' MGPProgramBindings AND
|
||||
// the two TAIL ELEMENT types beside it, MGPProgramSamplerUnit and MGPProgramStorageOverride
|
||||
// (MG_Remote/CONTRACT-P5E.md §1) - the tails are listed for the same reason MGPBufferRange
|
||||
// and MGPVertexAttribWire are: the comparator has to see INTO an element whose members
|
||||
// include an MGHostSpan and a pad word, or it would memcmp the padding: 82.
|
||||
EXPECT_EQ(kMGPipeVerifiedPayloadCount, 82u);
|
||||
static_assert(MGPipeHasFieldVerifier<RenderStateParameters>::value);
|
||||
static_assert(MGPipeHasFieldVerifier<PixelStoreParameters>::value);
|
||||
static_assert(MGPipeHasFieldVerifier<PerBufferBlendState>::value);
|
||||
@@ -925,7 +1100,7 @@ TEST(PipeCatalogue, BufferRangeCarriesNoInlineHostSpan) {
|
||||
|
||||
// The call still declares the span it may carry, so the transport lays the tail out.
|
||||
Uint32 flags = 0;
|
||||
#define MGP_FLAGS_OF_SET_SHADER_BUFFERS(Name, Payload, Class, Flags) \
|
||||
#define MGP_FLAGS_OF_SET_SHADER_BUFFERS(Name, Payload, Class, Flags, Wait) \
|
||||
if (std::strcmp(#Name, "SetShaderBuffers") == 0) flags = static_cast<Uint32>(Flags);
|
||||
MGP_CALL_LIST(MGP_FLAGS_OF_SET_SHADER_BUFFERS)
|
||||
#undef MGP_FLAGS_OF_SET_SHADER_BUFFERS
|
||||
@@ -1048,7 +1223,7 @@ TEST(PipeCatalogue, VertexWireViewsAreFlatAndCarryIsLongSeparately) {
|
||||
// inherits this pin rather than the guess.
|
||||
TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) {
|
||||
Uint32 flags = 0;
|
||||
#define MGP_FLAGS_OF_RESOURCE_RESPECIFY(Name, Payload, Class, Flags) \
|
||||
#define MGP_FLAGS_OF_RESOURCE_RESPECIFY(Name, Payload, Class, Flags, Wait) \
|
||||
if (std::strcmp(#Name, "ResourceRespecify") == 0) flags = static_cast<Uint32>(Flags);
|
||||
MGP_CALL_LIST(MGP_FLAGS_OF_RESOURCE_RESPECIFY)
|
||||
#undef MGP_FLAGS_OF_RESOURCE_RESPECIFY
|
||||
@@ -1056,7 +1231,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) {
|
||||
// And it is the ONLY call that carries it: a second one would be a second decision, and
|
||||
// this predicate answers for exactly one call.
|
||||
Uint32 ackingCalls = 0;
|
||||
#define MGP_COUNT_ACKING_CALLS(Name, Payload, Class, Flags) \
|
||||
#define MGP_COUNT_ACKING_CALLS(Name, Payload, Class, Flags, Wait) \
|
||||
if ((static_cast<Uint32>(Flags) & static_cast<Uint32>(kNeedsAck)) != 0) ++ackingCalls;
|
||||
MGP_CALL_LIST(MGP_COUNT_ACKING_CALLS)
|
||||
#undef MGP_COUNT_ACKING_CALLS
|
||||
@@ -1071,7 +1246,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) {
|
||||
// ROWS, whose applier entry points return a Bool the client acts on destructively and which
|
||||
// carried no flag because in monolith that answer is a direct call's return value.
|
||||
Uint32 replySlotCalls = 0;
|
||||
#define MGP_COUNT_REPLY_SLOT_CALLS(Name, Payload, Class, Flags) \
|
||||
#define MGP_COUNT_REPLY_SLOT_CALLS(Name, Payload, Class, Flags, Wait) \
|
||||
if ((static_cast<Uint32>(Flags) & static_cast<Uint32>(kReplySlot)) != 0) ++replySlotCalls;
|
||||
MGP_CALL_LIST(MGP_COUNT_REPLY_SLOT_CALLS)
|
||||
#undef MGP_COUNT_REPLY_SLOT_CALLS
|
||||
@@ -1082,7 +1257,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) {
|
||||
// (PipeApply.h:820, :868, :897, :1023); map_persistent's void* is the fifth answer and was
|
||||
// already declared.
|
||||
Uint32 acceptanceWithSlot = 0;
|
||||
#define MGP_COUNT_ACCEPTANCE_ROWS(Name, Payload, Class, Flags) \
|
||||
#define MGP_COUNT_ACCEPTANCE_ROWS(Name, Payload, Class, Flags, Wait) \
|
||||
if ((std::strcmp(#Name, "ResourceCreate") == 0 || std::strcmp(#Name, "ResourceRespecify") == 0 || \
|
||||
std::strcmp(#Name, "ResourceSubData") == 0 || std::strcmp(#Name, "SetTextureParams") == 0) && \
|
||||
(static_cast<Uint32>(Flags) & static_cast<Uint32>(kReplySlot)) != 0) { \
|
||||
|
||||
@@ -362,29 +362,44 @@ namespace {
|
||||
// keeps the name it was born with and follows the phase constant instead of a literal
|
||||
// five: what it has always asserted is "a bit names a subsystem if and only if this build
|
||||
// emits a call for it", which is the property the emission gate and the residual-fill
|
||||
// skip both rest on. P3a took the vertex-input family over and P4a takes seven more bits
|
||||
// across four subsystems, so the set it compares against is now kMGPipeDirtyEmittedAtP4a -
|
||||
// and a bit that gained an arm without gaining an emitter, or the reverse, still fails
|
||||
// here.
|
||||
// skip both rest on. P3a took the vertex-input family over, P4a took seven more bits across
|
||||
// four subsystems, and P5e takes THE LAST THREE onto kMGPipeSubsystemBufferBindings, so the
|
||||
// set it compares against is now kMGPipeDirtyEmittedAtP5e - and a bit that gained an arm
|
||||
// without gaining an emitter, or the reverse, still fails here.
|
||||
TEST_F(TrackerWalk, OnlyTheFiveEmittedBitsNameASubsystem) {
|
||||
for (SizeT i = 0; i < kMGPipeDirtyCount; ++i) {
|
||||
const auto bit = static_cast<MGPipeDirty>(i);
|
||||
const Bool emitted = (kMGPipeDirtyEmittedAtP4a & MGPipeDirtyBit(bit)) != 0;
|
||||
const Bool emitted = (kMGPipeDirtyEmittedAtP5e & MGPipeDirtyBit(bit)) != 0;
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(bit) != 0, emitted) << kMGPipeDirtyNames[i];
|
||||
}
|
||||
// Each phase's constant SURVIVES as the next phase's A/B control, so the three are
|
||||
// pinned as a chain rather than one being edited into the next: 0x1ff is P4a's "T2"
|
||||
// arm and 0x7f is P3a's, and an operator's recorded mask has to keep meaning what it
|
||||
// meant.
|
||||
EXPECT_EQ(kMGPipeDirtyEmittedAtP5e & kMGPipeDirtyEmittedAtP4a, kMGPipeDirtyEmittedAtP4a);
|
||||
EXPECT_EQ(kMGPipeDirtyEmittedAtP4a & kMGPipeDirtyEmittedAtP3a, kMGPipeDirtyEmittedAtP3a);
|
||||
EXPECT_EQ(kMGPipeDirtyEmittedAtP3a & kMGPipeDirtyEmittedAtP2, kMGPipeDirtyEmittedAtP2);
|
||||
// The three bits P4a still does not emit for - the const-buffer, shader-buffer and
|
||||
// stream-output sets - name no subsystem, so their fields keep going through the
|
||||
// residual fill. Stated positively as well as through the loop above, because "only
|
||||
// these three are left" is the phase's own scope statement.
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewConstBuffers), 0u);
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers), 0u);
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewSoTargets), 0u);
|
||||
// THE LAST THREE BITS, which P4a still did not emit for and P5e takes: the const-buffer,
|
||||
// shader-buffer and stream-output sets are ONE family - the indexed buffer binding
|
||||
// points - on ONE subsystem, so an operator clearing bit 13 gets the whole family's
|
||||
// frontend walk back rather than two thirds of it. Stated positively as well as through
|
||||
// the loop above, because "these three, together" is the phase's own scope statement.
|
||||
//
|
||||
// set_stream_output_targets stays UNEMITTED for the whole of P5e (XFB is lockstep by
|
||||
// escalation, MG_Remote/CONTRACT-P5E.md §5.7) and still names the subsystem, for the
|
||||
// same reason: the bit answers "which A/B switch owns this family's legacy arm", not
|
||||
// "is there a record on the wire for me today".
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewConstBuffers),
|
||||
kMGPipeSubsystemBufferBindings);
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers),
|
||||
kMGPipeSubsystemBufferBindings);
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewSoTargets),
|
||||
kMGPipeSubsystemBufferBindings);
|
||||
// AND THE PHASE CONSTANT IS NOW THE WHOLE BIT SET: after P5e every dirty bit names a
|
||||
// subsystem, so the loop above has no "names none" case left to exercise, and this is
|
||||
// what says so rather than leaving the loop looking stronger than it is.
|
||||
EXPECT_EQ(kMGPipeDirtyEmittedAtP5e,
|
||||
static_cast<Uint32>((Uint64{1} << kMGPipeDirtyCount) - 1u));
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewRenderState), kMGPipeSubsystemRenderState);
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewPixelPack), kMGPipeSubsystemPixelPack);
|
||||
EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewPatchState), kMGPipeSubsystemPatchState);
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
| **P5b** inproc 下的 verb 迁移(Minecraft 优先) | **已收官(2026-09-16)** | `37fc4fdb..82683d4a`;`MEASUREMENTS.md` §7 |
|
||||
| **P5c** `inproc` 共享内存读点归零 | **已收官(2026-09-17)** | `11ac3de6..b88e8487` + triage 修复;契约 `MobileGL/MG_Remote/CONTRACT-P5C.md`;审计 `~/w7/notes/p5c/p5c-audit-v1.md` |
|
||||
| **P5d** `inproc` 性能专项 | **已收官(2026-09-18,三轮)** | `cb06538c`、`56a77348`、`1f8de61b`;报告 [`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md);`MEASUREMENTS.md` §9 |
|
||||
| P6 spawn transport | **下一个** | 届时只是传输替换 |
|
||||
| **P5e** 退役 Espryt draw path 的 lockstep | **进行中** | 契约 `MobileGL/MG_Remote/CONTRACT-P5E.md`(c0e 落地);计划 `~/w7/notes/p5e/BRIEF-P5E.md`、裁定 `~/w7/notes/p5e/INTEGRATOR-DECISIONS-P5E.md`(ID-80..98);包序 c0e → id → {vi, sb, pg, tx2, fb} ∥ ra,集成 commit 翻 `kMGPipeP5eRunAheadReady` |
|
||||
| P6 spawn transport | P5e 之后 | 届时只是传输替换 |
|
||||
|
||||
## 2. 当前头实测
|
||||
|
||||
@@ -85,10 +86,11 @@
|
||||
## 6. 下一步
|
||||
|
||||
0. P5d 的遗留(`P5D-INPROC-PERFORMANCE.md` "什么没完成"):R-1 序列化留给 P3b/P4b → P11(`gPipeInputs` 版本化已写进 P11 行);线程放置记录;小项随 P3b/P4b 顺手。
|
||||
1. **P6 spawn transport**:`SocketTransport` + `ServerMain` + 握手 / 退出语义 + EGL forwarder 的控制面帧;P5c 之后这只是传输替换。注意 P5c 留下的:`s_synced` / `g_syncedRenderStateParameters` 按 context 世代重置;两个豁免 scope 里的探测在 spawn 下根本不存在对应内存,P6 第一天的红就是它们的清单。
|
||||
2. 剩余首阻塞一轮(Magma compute/image、rd12、RGB mip、`texture-remint-pull` 仿真槽)。
|
||||
3. P6 出口门:P5b 的完整渲染路径在 `spawn` 下绿;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99。
|
||||
4. Redmi 四臂复测(P5c 的记录项,需设备窗口);79 trace 普查重跑(需全集语料)。
|
||||
1. **P5e 退役 draw path 的 lockstep**(进行中):c0e 已落地契约与线上行——`PipeCalls.def` 的第五列 `WaitClass` + 生成的 `MGPipeWaitClassFor(op)`、`set_program_bindings`(opcode 80,空路由)、`kCapRunAheadApply`(位 10,只在 DirectGLES 臂且只在 `kMGPipeP5eRunAheadReady` 为真时发布)、`kDrawClientArrays`、子系统位 13 与 push 默认 `0x3fff`、`MGPipeImageAccess` 一张表、`MOBILEGL_IPC_RUN_AHEAD` / `MOBILEGL_IPC_PRESENT_CREDIT`、以及跨包 seam(`MGPipeBarriered`、两个 applier 入口、六个 by-handle 后端签名)。全部 inert:caps 位未发布前 client 跑今天的 lockstep 路径。下一个是 **id**(registry 重键 + by-handle resolver + 分配器守卫),然后 {vi, sb, pg, tx2, fb} 并行、ra 从第一天起并行。
|
||||
2. **P6 spawn transport**:`SocketTransport` + `ServerMain` + 握手 / 退出语义 + EGL forwarder 的控制面帧;P5c 之后这只是传输替换。注意 P5c 留下的:`s_synced` / `g_syncedRenderStateParameters` 按 context 世代重置;两个豁免 scope 里的探测在 spawn 下根本不存在对应内存,P6 第一天的红就是它们的清单。
|
||||
3. 剩余首阻塞一轮(Magma compute/image、rd12、RGB mip、`texture-remint-pull` 仿真槽)。
|
||||
4. P5e 出口门(`~/w7/notes/p5e/BRIEF-P5E.md` §3 / §4):`integration-split-strict` 转硬绿车道、三条阴性对照、Redmi 四臂(monolith / lockstep / credit 1 / credit 2)。P6 出口门:P5b 的完整渲染路径在 `spawn` 下绿;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99。
|
||||
5. Redmi 四臂复测(P5c 的记录项,需设备窗口);79 trace 普查重跑(需全集语料)。
|
||||
|
||||
## 7. 记录位置
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MGPipe 路线图
|
||||
|
||||
> 状态:**P0、P0.5、P1、P2、P3a、P4a、P5、P5b、P5c、P5d 已收官**(P5c:2026-09-17,代码头 `b88e8487`;P5d:三轮,2026-09-18,代码头 `1f8de61b`)。当前头、逐门数字、开放项与下一步见 **[`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md)**;设计见 `ARCHITECTURE.md`;逐阶段实测见 `MEASUREMENTS.md`;P5d 报告见 [`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md)。**下一个是 P6 spawn transport**(此时只是传输替换)。
|
||||
> 状态:**P0、P0.5、P1、P2、P3a、P4a、P5、P5b、P5c、P5d 已收官**(P5c:2026-09-17,代码头 `b88e8487`;P5d:三轮,2026-09-18,代码头 `1f8de61b`)。当前头、逐门数字、开放项与下一步见 **[`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md)**;设计见 `ARCHITECTURE.md`;逐阶段实测见 `MEASUREMENTS.md`;P5d 报告见 [`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md)。**当前在 P5e**(退役 Espryt draw path 的 lockstep,契约 `MobileGL/MG_Remote/CONTRACT-P5E.md`);之后才是 P6 spawn transport(此时只是传输替换)。
|
||||
|
||||
## 通用纪律(每个 commit)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- **拆分不借机顺手修 `dev` 的 bug**:句柄化过程中发现的 `dev` 侧缺陷记成独立条目、独立 PR(开放问题 15、17)。
|
||||
- 过程(用户 2026-09-16,ID-66):不过度验证;轮次间不做对抗性审查,每个 P 阶段收官由 Codex 审一次,发现进入下一阶段首轮;只读 / 非核心任务派给 Codex;实现方与审查方错开模型族。
|
||||
|
||||
两条跑道分开:**monolith 跑道** P0 → P0.5 → P1 → P2 → P3a → P4a → P3b/P4b → P7 → P8 → P13,每段可独立交付、可随时中止;**IPC 跑道** P5 → P5b → **P5c** → P6 → P9 → P10 → P11 → P12。目标顺序:先完整的 separate-thread rendering(P5b,已达成),再让 `inproc` 变成只经 wire 交换的诚实两角色(P5c),最后才是 separate-process transport(P6,此时只是传输替换)。
|
||||
两条跑道分开:**monolith 跑道** P0 → P0.5 → P1 → P2 → P3a → P4a → P3b/P4b → P7 → P8 → P13,每段可独立交付、可随时中止;**IPC 跑道** P5 → P5b → P5c → P5d → **P5e** → P6 → P9 → P10 → P11 → P12。目标顺序:先完整的 separate-thread rendering(P5b,已达成),再让 `inproc` 变成只经 wire 交换的诚实两角色(P5c),再把它的 CPU 代价压到可接受(P5d),然后退役 draw path 上的 lockstep(P5e:client 发了就走,server 只从记录带来的句柄解析对象),最后才是 separate-process transport(P6,此时只是传输替换)。
|
||||
|
||||
## 阶段
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
| **P5b** `inproc` verb migration | ✅ `37fc4fdb..82683d4a` | class-C 普查决定顺序;d1 19 / i1 7 / t2 6 / f1 11 槽迁移;sync 五槽;具名 blit;GLES mip descriptor;r1 / r2;收官审查三项修复。发射表 A=2 / B=54 / C=15 | 主机全门 `348d22a4` complete;`integration-split` 107/107;合并普查零回退、79 trace 72/6/1;**Redmi 正确性 8/8 + 四臂 A/B、barrier tax 首测**。§7 |
|
||||
| **P5c** `inproc` 共享内存读点归零 | ✅ `11ac3de6..b88e8487` | server 端纹理 staged shadow(`StagedTextureStore`,句柄为键、整段覆盖、defined-ness 追踪);`SEG_EVENT` 三个 producer + `kEventGlError`(反向通道零裸指针);sink / twin 按记录句柄解析(blit 具名臂经 verb 句柄工作区,Magma 走 G6 消费臂);`applier_reset` / `object_death` 控制记录(opcode 77/78,framebuffer 首个 wire delete);`set_context_values`(79)退役全部值类 BARRIER-PULLED 行,三 shutter 自答;双层 `Fatal{RoleViolation}` 守卫 + `InBarrierWait` 接线;两个具名豁免 scope(通告家族 P4b/P7、G6 registry 家族 P3b/P4b)承接未到期的债 | 角色守卫开启下 unit 2187/2187(strict 下同绿)、`integration-split` 111/111、每层守卫 red-once 按名变红;纹理 `0xDD` audit 在 bsl / complementary 两条 in-world trace 零 Fatal;rsp 按帧实测(bsl 948.5/帧,残留全为钉住的对象类 15 行);G1 三个认定 resize(SwapchainObject::Create / CopyTexSubImage2D / ScopedRestartIndexSubstitution,均已具名)+ `.text` −16 B、pull 构建零 MG_Remote 符号;G5 保护区字节一致;双生成器 --check/--self-test 绿 | P5b |
|
||||
| **P5d** `inproc` 性能专项 | ✅ `cb06538c`、`56a77348`、`1f8de61b` | 一二轮:persistent-map 推送三级臂(mprotect 脏页追踪 + 哈希抑制 + 绑定过滤 `PushDrawConsumers`)、barrier 批处理 `MOBILEGL_IPC_BATCH_WAITS` + 延期销毁队列、自旋时钟 64 合 1、零页注册 + shadow 页对齐分配。三轮(按符号化 profile 逐项切,四包 + 一份 lockstep 可行性研究):apply 空转轮询零锁(控制邮箱原子影子位)、`Doorbell::Wait` 校准自旋稳态不读时钟、wait/park 计数进 stats;`OnApplyThread()` 内联为线程指针比较、三个 scope 深度计数器退掉 `thread_local`;shadow 分配尺寸补页、追踪范围向外对齐、边块哈希消失、脏页排空修复;image unit 高水位、残余填充 memo、server stamp 常量表;`generate_mipmap` 不再免等(apply 读残余输入)、death 测试围栏 | 三轮设备(Redmi,CPU 定频):inproc **64.3 → 103-106 fps**(p50),client 线程 CPU 15.0 → 9.2 ms/帧、apply 12.4 → 7.0-7.4;monolith 同场景 115(120 Hz vsync 封顶的运行)/ 206(一次未封顶,渲染线程 5.0 ms/帧 大核);配对比较以逐线程 CPU/帧为主,见报告"数字"节;unit 2203/2203、`integration-split` 111/111(两条曾 flaky 的用例复跑 6/6);双生成器、include 闭包、dirty-surface、doc 引用绿。未完成:R-1 序列化(研究结论:留给 P3b/P4b 后的 P11 `gPipeInputs` 版本化)、线程放置(内核忽略亲和)、`SetHashSuppressor` 哈希等小项。报告:[`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md) | P5b |
|
||||
| **P6** spawn transport | P5c 之后 | `SocketTransport`(socketpair + fork/execve,envp 剔除 + 强制 monolith 双保险);`ServerMain`;`MOBILEGL_IPC_SERVER_PATH` + `dladdr` 兜底;有界重试握手;EOF 即时退出;device-lost latch;EGL forwarder 的控制面帧;P5c 留下的 `s_synced` / `g_syncedRenderStateParameters` 按 context 世代重置 | P5b 的完整渲染路径在 `spawn` 下绿;进程树只多一个子进程;`HeadlessGL` fork 预检无孤儿;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99 |
|
||||
| **P5e** 退役 Espryt draw path 的 lockstep | 🚧 进行中 | 契约 `MG_Remote/CONTRACT-P5E.md`(规则 F:未设障记录的 apply 不得读任何 client 内存;19 条裁定见 §9)。八包:**c0e** 契约 + 线上行(`PipeCalls.def` 第五列 `WaitClass` + `MGPipeWaitClassFor`、`set_program_bindings` opcode 80 空路由、`kCapRunAheadApply` 位 10、`kDrawClientArrays`、子系统位 13、`MGPipeImageAccess` 一张表、两个旋钮、跨包 seam 声明)→ **id** 身份(registry 按 `{slot, gen}` 重键、by-handle resolver、分配器守卫)→ **vi / sb / pg / tx2 / fb** 五个 per-draw 家族并行(VAO / 缓冲绑定点 / program 归档 + 绑定 / 纹理 + sampler 窗口 / FBO + image)∥ **ra** 传输机制(等待规则 `EmitAndWaitTails`、present credit、`gPipeInputs` 变 server 角色内存、事件环流控)。集成 commit 翻 `kMGPipeP5eRunAheadReady` | 集成门(§3):unit 两臂绿;`integration-split` 111+ 绿 + inproc 普查零回退 + 79 trace 无新首阻塞;`integration-split-strict` 转**硬绿车道**(只允许 §7 白名单的 `[BARRIER-PULLED]` 标记,未设障记录 `rsp` = 0);三条阴性对照(`MOBILEGL_IPC_RUN_AHEAD=0` SSIM 一致、`VERB_BARRIER=0` 在设障行变红、Magma 上 `RUN_AHEAD=1` 记一条日志后跑 lockstep);每包 red-once 在集成树复跑;G1 0/0/0/0、G2、G14、G5;monolith push 四条 A/B trace SSIM 不变。设备出口(§4,Redmi 30 s 在世界窗口,四臂):fps ≥ lockstep、client 线程 CPU ms/帧 < lockstep、SSIM 不变,且逐 op 等待清单里**没有 `kWaitApplied` 行、没有逐 draw 的 reply 行** | P5d |
|
||||
| **P6** spawn transport | P5e 之后 | `SocketTransport`(socketpair + fork/execve,envp 剔除 + 强制 monolith 双保险);`ServerMain`;`MOBILEGL_IPC_SERVER_PATH` + `dladdr` 兜底;有界重试握手;EOF 即时退出;device-lost latch;EGL forwarder 的控制面帧;P5c 留下的 `s_synced` / `g_syncedRenderStateParameters` 按 context 世代重置 | P5b 的完整渲染路径在 `spawn` 下绿;进程树只多一个子进程;`HeadlessGL` fork 预检无孤儿;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99 |
|
||||
| **P3b / P4b** 深化(Espryt) | 待排 | 按存储属主键控的发射游标与 view 索引重映射;`g_fboTextureSyncList`;`ResolvedTextureBindingMemo` / `SamplerPassMemo` / image sweep / program registry 重键;XFB scatter 搬到 client;删 fragColor 重推导 workaround 与 `g_broadcastMemo*`;raw-depth-fetch sampler 原生化;回读 / pack state;P4a 记下的 R-3 / R-4 / R-5 / R-7 / R-10 / R-11;`ProgramArtifacts.h` 的 NDK 尺寸钉 | 纹理 / program 场景;CTS `texture_*` / `shader_image_*` / `packed_pixels` 在 0.5 pp 内;每一个 Iris trace;`TextureUploadShapeScenario` 升级成门 |
|
||||
| **P7** DirectVulkan(Magma)全量迁移 | 待排 | 其余 10 个子系统:`SetupDrawSnapshot` 探测字段塌成 dirty mask;占位纹理原生化;具名 UBO host payload(D-B8);内部 shader 烘焙;`VertexInputStateFactory` 内容寻址 CSO;D18 容器纪律保留 | 集成 + trace 在 Magma 的 push 与 split 下全绿;verify 零分歧;`nm -D libMobileGLServer.so \| grep glslang` 为空;CTS 0.5 pp 内。**再基线检查点:中点完成子系统 < 40% 立即重定基线** |
|
||||
| **P8** emulation 下放 + 索引宿主镜像 + 协议广度 | 待排 | `MG_Impl/Pipe/HostResolve.cpp`(client 数组范围、最大索引扫描、`*IndirectCount` 解析,逐站点 reconcile);`Server/IndexHostMirror`;CopyImage 镜像搬到 client;viewport-array 回放验证;`generate_mipmap` 计划 + CPU 回退纹素;大 blob carrier(开放问题 11);无 present fence tick;`kCapDriverOrderedXfbCapture` | `'^DirectGLES\.Split\.'` 与 `'^DirectGLES\.'` 逐名相同;trace split 双后端 SSIM ≥ 0.99 含两个 `coherent_as_flush` fixture;`ClientArrayAfterComputeWriteScenario`;`create-indirect` 上 `roundtrips-per-frame` 读零;`index-mirror-bytes` 逐用例发布 |
|
||||
|
||||
+123
-4
@@ -136,12 +136,15 @@ def read(path):
|
||||
|
||||
|
||||
class Call(object):
|
||||
def __init__(self, index, name, payload, cls, flags):
|
||||
def __init__(self, index, name, payload, cls, flags, wait="kWaitNone"):
|
||||
self.Index = index # 1-based; this is the wire opcode
|
||||
self.Name = name
|
||||
self.Payload = payload
|
||||
self.Class = cls
|
||||
self.Flags = flags
|
||||
# P5e: PipeCalls.def's fifth column. Defaulted so the self-test's canned Calls -
|
||||
# which exercise the FLAG gates and say nothing about waiting - stay one line.
|
||||
self.Wait = wait
|
||||
|
||||
@property
|
||||
def IsScreen(self):
|
||||
@@ -176,7 +179,8 @@ class Call(object):
|
||||
return ", ".join(params), ", ".join(args)
|
||||
|
||||
|
||||
CALL_RE = re.compile(r"^\s*X\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*([\w|]+?)\s*\)\s*\\?\s*$")
|
||||
CALL_RE = re.compile(r"^\s*X\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*([\w|]+?)\s*,"
|
||||
r"\s*(\w+)\s*\)\s*\\?\s*$")
|
||||
|
||||
|
||||
def parse_calls():
|
||||
@@ -195,7 +199,7 @@ def parse_calls():
|
||||
match = CALL_RE.match(line)
|
||||
if match:
|
||||
calls.append(Call(len(calls) + 1, match.group(1), match.group(2), match.group(3),
|
||||
match.group(4).split("|")))
|
||||
match.group(4).split("|"), match.group(5)))
|
||||
# The macro ends at the first line without a continuation backslash.
|
||||
if not line.rstrip().endswith("\\"):
|
||||
inside = False
|
||||
@@ -564,6 +568,60 @@ def check_call_flags_are_known(calls):
|
||||
% (call.Name, "|".join(f for f in call.Flags if f != "kNone")))
|
||||
|
||||
|
||||
WAIT_ENUM_RE = re.compile(r"enum\s+MGPipeWaitClass\s*:\s*Uint8\s*\{(.*?)\}\s*;", re.S)
|
||||
WAIT_MEMBER_RE = re.compile(r"^\s*(kWait\w+)\s*(?:=\s*\d+\s*)?,", re.M)
|
||||
|
||||
|
||||
def parse_wait_classes():
|
||||
"""The MGPipeWaitClass enumerators, READ OUT OF MGPipe.h, for parse_call_flags' reason:
|
||||
this list is what the generated kMGPipeWaitClasses[] table spells into C++, and a copy
|
||||
here would go stale in exactly the case that matters. kWaitClassCount is the terminator and
|
||||
is NOT a class a row may carry - a row that named it would generate a table entry the
|
||||
lookup uses to mean 'this opcode never came from the catalogue'."""
|
||||
text = read(os.path.join(PIPE_DIR, "MGPipe.h"))
|
||||
body = WAIT_ENUM_RE.search(text)
|
||||
if not body:
|
||||
sys.exit("MGPipe.h: enum MGPipeWaitClass : Uint8 is missing or has changed shape")
|
||||
names = WAIT_MEMBER_RE.findall(body.group(1))
|
||||
if "kWaitClassCount" not in names:
|
||||
sys.exit("MGPipe.h: enum MGPipeWaitClass has no kWaitClassCount terminator")
|
||||
classes = [n for n in names if n != "kWaitClassCount"]
|
||||
if not classes:
|
||||
sys.exit("MGPipe.h: enum MGPipeWaitClass declares no wait classes")
|
||||
return classes
|
||||
|
||||
|
||||
KNOWN_WAIT_CLASSES = tuple(parse_wait_classes())
|
||||
|
||||
|
||||
def check_wait_classes_are_known(calls):
|
||||
"""P5e (CONTRACT-P5E.md §2.2). Two gates over PipeCalls.def's fifth column, both in --check
|
||||
as well, because the column is what the client's wait rule and the sink's barriered
|
||||
predicate are BOTH computed from - a typo here is a wrong wait on a live wire rather than a
|
||||
compile error in one consumer.
|
||||
|
||||
1. The token must be an MGPipeWaitClass enumerator, and never the kWaitClassCount
|
||||
terminator, which the generated lookup uses for "not from this catalogue".
|
||||
2. kReplySlot and kWaitReply must agree, BOTH WAYS. A reply-slot row that did not wait
|
||||
would be a waiter that hangs - the client would run on and read a slot nobody had
|
||||
posted into yet; a waiting row with no slot would block on an answer nobody posts.
|
||||
This is the same class of defect R-13.4 found between the flag column and the payloads,
|
||||
and it is stated here for the same reason: nothing else has both facts in one place."""
|
||||
for call in calls:
|
||||
if call.Wait not in KNOWN_WAIT_CLASSES:
|
||||
sys.exit("PipeCalls.def: %s carries wait class %s, which is not an MGPipeWaitClass "
|
||||
"enumerator (%s)" % (call.Name, call.Wait, ", ".join(KNOWN_WAIT_CLASSES)))
|
||||
owns_slot = "kReplySlot" in call.Flags
|
||||
waits_reply = call.Wait == "kWaitReply"
|
||||
if owns_slot and not waits_reply:
|
||||
sys.exit("PipeCalls.def: %s owns a reply slot but its wait class is %s; a "
|
||||
"kReplySlot row must be kWaitReply or the client reads a slot nobody "
|
||||
"posted into" % (call.Name, call.Wait))
|
||||
if waits_reply and not owns_slot:
|
||||
sys.exit("PipeCalls.def: %s is kWaitReply but carries no kReplySlot; it would "
|
||||
"block on an answer nobody posts" % call.Name)
|
||||
|
||||
|
||||
def parse_coverage():
|
||||
text = read(os.path.join(PIPE_DIR, "Coverage.def"))
|
||||
accessors = []
|
||||
@@ -800,6 +858,48 @@ inline constexpr MGPipeCallClass MGPipeCallClassFor(MGPWireOp op) {
|
||||
: kCallClassCount;
|
||||
}
|
||||
|
||||
// P5e (MG_Remote/CONTRACT-P5E.md 2.2): THE WAIT CLASS OF EVERY OPCODE, indexed the same way.
|
||||
// Index 0 is kWaitClassCount - never a real class - for the flags table's reason: the one
|
||||
// caller that can pass an opcode this catalogue never produced is a decoder holding bytes off
|
||||
// a stream, and it must reach its own Fatal{ProtocolCorruption} rather than a wait decision.
|
||||
//
|
||||
// This is the STATIC HALF of MGPipeBarriered(op, payload, applierState), the predicate the emit
|
||||
// table and the sink compute identically. The other half is payload-derived (an open
|
||||
// transform-feedback span, a draw carrying client vertex arrays) and lives in that function,
|
||||
// because it depends on state a table indexed by opcode cannot see.
|
||||
inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount)] = {
|
||||
/* 0 */ static_cast<Uint8>(kWaitClassCount),
|
||||
""")
|
||||
for call in calls:
|
||||
out.append(" /* %2d %-24s*/ static_cast<Uint8>(%s)," % (call.Index, call.Name, call.Wait))
|
||||
out.append("};")
|
||||
out.append("static_assert(sizeof(kMGPipeWaitClasses) / sizeof(kMGPipeWaitClasses[0]) ==")
|
||||
out.append(" static_cast<SizeT>(MGPWireOp::kOpCount),")
|
||||
out.append(" \"the wait-class table and the opcode space disagree\");")
|
||||
out.append("""
|
||||
inline constexpr MGPipeWaitClass MGPipeWaitClassFor(MGPWireOp op) {
|
||||
const SizeT index = static_cast<SizeT>(op);
|
||||
return index < static_cast<SizeT>(MGPWireOp::kOpCount)
|
||||
? static_cast<MGPipeWaitClass>(kMGPipeWaitClasses[index])
|
||||
: kWaitClassCount;
|
||||
}
|
||||
|
||||
// THE COLUMN'S OWN INVARIANT, stated in C++ as well as in the generator: a reply-slot row
|
||||
// waits for its reply and a waiting row owns a slot. gen_pipe.py refuses a catalogue where the
|
||||
// two disagree, and this is the same statement where a reader of the generated table will look
|
||||
// for it - a kReplySlot row that did not wait would run on and read a slot nobody had posted
|
||||
// into, and a kWaitReply row with no slot would block on an answer nobody posts.
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::kInvalid) == kWaitClassCount,
|
||||
"opcode 0 is not a call and has no wait class");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::Present) == kWaitPresent,
|
||||
"present is paced by the credit, not by a wait after the publish");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::DrawVbo) == kWaitNone,
|
||||
"the steady draw path is what P5e exists to stop waiting on");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::ApplierReset) == kWaitApplied,
|
||||
"a make-current is a fresh server and every verb after it must see the reset");
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::ReadPixels) == kWaitReply,
|
||||
"a readback's answer is not derivable (R-5)");
|
||||
|
||||
// Spot checks the generator states about its own output, so that a catalogue edit that
|
||||
// silently drops a flag is a build break here and not a wrong decode six packages away.
|
||||
static_assert(MGPipeCallFlagsFor(MGPWireOp::kInvalid) == static_cast<Uint32>(kNone),
|
||||
@@ -1515,6 +1615,23 @@ def self_test(accessors):
|
||||
"combines kNone with",
|
||||
lambda: check_call_flags_are_known([flag_kNone])))
|
||||
|
||||
# P5e's two gates over the fifth column (CONTRACT-P5E.md §2.2). The column decides whether
|
||||
# a live client waits after publishing, so a typo or a disagreement with the flag column is
|
||||
# a wrong wait on the wire rather than a compile error in one consumer - which is exactly
|
||||
# the shape R-13.4 found between the flag column and the payloads.
|
||||
wait_typo = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kNone"], "kWaitApplyed")
|
||||
wait_slot_no_wait = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kReplySlot"], "kWaitNone")
|
||||
wait_no_slot = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kNone"], "kWaitReply")
|
||||
controls.append(("wait class that is not an MGPipeWaitClass enumerator",
|
||||
"which is not an MGPipeWaitClass enumerator",
|
||||
lambda: check_wait_classes_are_known([wait_typo])))
|
||||
controls.append(("a kReplySlot row that does not wait for its reply",
|
||||
"owns a reply slot but its wait class is",
|
||||
lambda: check_wait_classes_are_known([wait_slot_no_wait])))
|
||||
controls.append(("a kWaitReply row with no reply slot",
|
||||
"but carries no kReplySlot",
|
||||
lambda: check_wait_classes_are_known([wait_no_slot])))
|
||||
|
||||
# R-16 meta-control (codex review finding 9; verified by execution in
|
||||
# p5-results/wave1-codex-verify.md §9). The exact perturbation there replaced the
|
||||
# flag-typo control's callback with `sys.exit("unrelated parser failure")` and,
|
||||
@@ -1540,8 +1657,9 @@ def self_test(accessors):
|
||||
# The positive control: the canned struct's exact list passes, and the parser sees the
|
||||
# padding member as padding and the function as not a member.
|
||||
check_field_lists_cover_struct_members({"Canned": ["A", "B", "C"]}, ["Canned"], [canned_struct])
|
||||
# ... and the real catalogue's real flags pass the same gate.
|
||||
# ... and the real catalogue's real flags and wait classes pass the same gates.
|
||||
check_call_flags_are_known(calls_for_control)
|
||||
check_wait_classes_are_known(calls_for_control)
|
||||
if trips == 0:
|
||||
sys.exit("gen_pipe: self-test: no negative control tripped - the gates are not checking anything")
|
||||
if trips != len(controls):
|
||||
@@ -1561,6 +1679,7 @@ def main():
|
||||
calls = parse_calls()
|
||||
payloads = parse_verify_payloads()
|
||||
check_call_flags_are_known(calls)
|
||||
check_wait_classes_are_known(calls)
|
||||
check_call_payloads_have_field_lists(calls, payloads)
|
||||
check_field_lists_cover_struct_members(parse_field_lists(), payloads)
|
||||
accessors, deltas, sticky, emitted = parse_coverage()
|
||||
|
||||
@@ -702,9 +702,15 @@ def self_test():
|
||||
"the field set (%d of %d)" % (total, len(accessors)))
|
||||
if len(sticky) != 7:
|
||||
sys.exit("gen_pipe_field_ownership: self-test: Coverage.def no longer has seven sticky fields")
|
||||
if len(refused) != 7:
|
||||
# EIGHT SINCE P5e (MG_Remote/CONTRACT-P5E.md 5.6): GetBufferBindingPoint joined the seven
|
||||
# when set_shader_buffers got its Coverage.def emitted row. The number is pinned rather
|
||||
# than merely derived because it is the SIZE OF THE SHAPE-ONLY SET - the rows where an
|
||||
# emitted call exists and still does not supply the field - and a row that quietly left
|
||||
# that set would flip to RECORD_SUPPLIED and let the residual fill skip a pull nothing
|
||||
# replaces. Every addition to it is a phase's deliberate act, so it is spelled here.
|
||||
if len(refused) != 8:
|
||||
sys.exit("gen_pipe_field_ownership: self-test: EmittedCallSuppliesTheWholeField refuses %d "
|
||||
"fields, not the seven the contract's derivation is written against" % len(refused))
|
||||
"fields, not the eight the contract's derivation is written against" % len(refused))
|
||||
if trips == 0:
|
||||
sys.exit("gen_pipe_field_ownership: self-test: no negative control tripped - the gates are "
|
||||
"not checking anything")
|
||||
@@ -713,7 +719,7 @@ def self_test():
|
||||
% (len(controls) - trips, len(controls)))
|
||||
print("gen_pipe_field_ownership: self-test: %d negative-control trip(s), each asserted against "
|
||||
"its OWN message; harness control OK; positive control OK "
|
||||
"(%d fields partitioned, 7 sticky forwards, 7 refusals)" % (trips, total))
|
||||
"(%d fields partitioned, 7 sticky forwards, 8 refusals)" % (trips, total))
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user