mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Pipe, MG_Impl, MG_Test] (Disaggregated): P5e c0e - the types, the subsystem bit and the image-access table
- kCapRunAheadApply (bit 10) and MGPipeRunAheadCapBitsFor(backend, ready): the arm is a pure function so "Magma never publishes it" is a unit case rather than a line only a live split session reaches. Espryt's half is gated on the integration constant. - MGPProgramBindings (opcode 80's head) plus its two tail element types, the three declared bounds, and MGPProgramDesc::LinkStatus. The descriptor grows 192 -> 200: its four trailing Uint8s end exactly at offset 24 and MGPBlobRef is 8-aligned, so the draft's "in the descriptor's existing pad" was wrong and there is no spare byte. The payload is instantiated only under MOBILEGL_PIPE_PUSH, so G1 does not see it. - kDrawClientArrays (bit 6) and kMGPipeMaxBufferBindingPoints (84). The draw flag is what makes a client-memory vertex array visible on the wire; it is refused under run-ahead by vi, staged by P8, and the lockstep arm is unchanged. - MGPipeImageAccess + MGPipeDecodeImageAccess in MGPipeValueTypes.h, and the client encode in ImageEmit.h moved onto those enumerators instead of literal 0/1/2. One table, both roles: the server's open-coded reverse mapping and its "the encoding does not exist" comment were the drift this closes. The encode is now a free function so the unit case can reach both halves. - MGPipeWaitClass, the enum PipeCalls.def's fifth column spells; kMGPipeSubsystemBufferBindings (bit 13) with kMGPipeSubsystemsMigratedAtP5e = 0x3fff; the three suppressor slots for set_shader_buffers, one per Class, so an emission of one class cannot cancel another's. - Tracker.h maps dirty bits 15/16/17 onto bit 13 - inert, because the emission gate also tests kMGPipeWiredSubsystems and the family's wired constant is still 0 - and TrackerTest follows the phase constant, which is now the whole bit set.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -927,6 +984,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 +1524,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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user