diff --git a/MobileGL/MG_Impl/Pipe/ImageEmit.h b/MobileGL/MG_Impl/Pipe/ImageEmit.h index 59ece2b0..9b18ef53 100644 --- a/MobileGL/MG_Impl/Pipe/ImageEmit.h +++ b/MobileGL/MG_Impl/Pipe/ImageEmit.h @@ -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(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(access)); - return 0; - } - } - Array m_entries{}; MGPShaderImages m_lastImages{}; Uint32 m_window = 0; diff --git a/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h b/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h index 99c0cc18..82659443 100644 --- a/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h +++ b/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h @@ -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 diff --git a/MobileGL/MG_Impl/Pipe/Tracker.h b/MobileGL/MG_Impl/Pipe/Tracker.h index 9ec51c01..01cee868 100644 --- a/MobileGL/MG_Impl/Pipe/Tracker.h +++ b/MobileGL/MG_Impl/Pipe/Tracker.h @@ -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; } } diff --git a/MobileGL/MG_Pipe/MGPipe.h b/MobileGL/MG_Pipe/MGPipe.h index dc531293..e30d0a13 100644 --- a/MobileGL/MG_Pipe/MGPipe.h +++ b/MobileGL/MG_Pipe/MGPipe.h @@ -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. diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index 90f89fa0..179ea574 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -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(kCapRunAheadApply) + : static_cast(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."}. + 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 diff --git a/MobileGL/MG_Pipe/MGPipeValueTypes.h b/MobileGL/MG_Pipe/MGPipeValueTypes.h index e2da5312..5749fb4d 100644 --- a/MobileGL/MG_Pipe/MGPipeValueTypes.h +++ b/MobileGL/MG_Pipe/MGPipeValueTypes.h @@ -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(MGPipeImageAccess::ReadOnly); + inline constexpr Uint8 kMGPipeImageAccessWriteOnly = static_cast(MGPipeImageAccess::WriteOnly); + inline constexpr Uint8 kMGPipeImageAccessReadWrite = static_cast(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(MGPipeImageAccess::Count); + } + + inline constexpr MGPipeImageAccess MGPipeDecodeImageAccess(Uint8 encoded) { + return static_cast(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 by design and are diff --git a/MobileGL/MG_Test/Pipe/TrackerTest.cpp b/MobileGL/MG_Test/Pipe/TrackerTest.cpp index e248da03..9711e0cb 100644 --- a/MobileGL/MG_Test/Pipe/TrackerTest.cpp +++ b/MobileGL/MG_Test/Pipe/TrackerTest.cpp @@ -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(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((Uint64{1} << kMGPipeDirtyCount) - 1u)); EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewRenderState), kMGPipeSubsystemRenderState); EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewPixelPack), kMGPipeSubsystemPixelPack); EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewPatchState), kMGPipeSubsystemPatchState);