From 82b1f743a7924bec298a55f397eaeb0ceb3b2110 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 17 Sep 2026 09:27:36 -0400 Subject: [PATCH] [MG_Pipe, MG_Impl, MG_Remote, MG_Backend] (Disaggregated): P5c rv - set_context_values retires the value-class BARRIER_PULLED rows set_context_values (opcode 79, MGPContextValues 96B, kCtxState) carries the active texture unit, the max touched unit, the fifteen touched-binding-point counts and the five value-class XFB rows, emitted at validate under the subsystem bit and hash-suppressed as a whole; the emit gate and the residual-skip gate read the same answer so they can never disagree. MGPipeApplySetContextValues writes gPipeInputs (the PackState shape). MGPAttribValue grows 24->56B to carry the frontend's converted three views, and MGPipeApplySetVertexAttribDefaults finally writes them (the 'ignores MGPAttribValue::ValueClass' warning dies), so GetCurrentVertexAttribute leaves the refusal set. The three texture shutters answer the applier's own serials under a server-stamped verb (APPLIER_DERIVED), storage answer kept verbatim elsewhere. FieldOwnership.def: nine value-class rows become derived RECORD_SUPPLIED, the three shutters APPLIER_DERIVED, the nine object-class rows keep their phases; FieldOwnershipTest pins that the remaining pulls are exactly the object-class list. gen_pipe.py gains SCAN_EXEMPT_ACCESSORS with per-name reasons and retiring phases (it repairs the --check gate the hd merge's two G6 probes had left red). Evidence: unit 2175/2175; integration-split 111/111; both generators' --check and --self-test green; rsp on the Xfb capture scenario 36 -> 22; red-once - gating the emission off turns seven named scenarios red, restoring returns 111/111. CONTRACT-P5C section 5.3. --- MobileGL/MG_Backend/MGPipe/PipeInputs.h | 29 +++ MobileGL/MG_Impl/Pipe/PipeFill.cpp | 174 ++++++++++++---- MobileGL/MG_Impl/Pipe/PipeFill.h | 6 +- MobileGL/MG_Impl/Pipe/SetHashSuppressor.h | 10 +- MobileGL/MG_Impl/Pipe/Tracker.h | 37 ++-- MobileGL/MG_Pipe/Coverage.def | 50 +++-- MobileGL/MG_Pipe/FieldOwnership.def | 74 +++---- MobileGL/MG_Pipe/MGPipeTypes.h | 58 +++++- MobileGL/MG_Pipe/PipeApply.cpp | 76 ++++++- MobileGL/MG_Pipe/PipeApply.h | 38 ++++ MobileGL/MG_Pipe/PipeCalls.def | 24 ++- MobileGL/MG_Pipe/PipeFields.def | 12 +- MobileGL/MG_Pipe/PipeRoute.cpp | 12 +- MobileGL/MG_Pipe/PipeRoute.h | 13 +- .../MG_Pipe/generated/PipeFieldOwnership.inc | 54 ++--- MobileGL/MG_Pipe/generated/PipeFilled.inc | 20 +- MobileGL/MG_Pipe/generated/PipeTables.inc | 7 +- MobileGL/MG_Pipe/generated/PipeThunks.inc | 4 + MobileGL/MG_Pipe/generated/PipeVerify.inc | 10 +- MobileGL/MG_Pipe/generated/PipeWire.inc | 15 +- MobileGL/MG_Remote/Client/WireTables.cpp | 23 ++- MobileGL/MG_Remote/Client/WireTables.h | 12 +- MobileGL/MG_Remote/Server/PipeApplier.cpp | 5 + MobileGL/MG_Remote/Wire/PipeWireCodec.cpp | 14 +- MobileGL/MG_State/GLState/Core.h | 5 +- MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp | 189 ++++++++++++++---- MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp | 42 ++-- .../MG_Test/Pipe/RenderStateSpansTest.cpp | 17 +- MobileGL/MG_Test/Pipe/TrackerTest.cpp | 35 ++-- MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp | 50 +++++ MobileGL/MG_Test/Wire/RemoteClientTest.cpp | 12 +- scripts/gen_pipe.py | 21 +- scripts/gen_pipe_field_ownership.py | 29 +-- 33 files changed, 899 insertions(+), 278 deletions(-) diff --git a/MobileGL/MG_Backend/MGPipe/PipeInputs.h b/MobileGL/MG_Backend/MGPipe/PipeInputs.h index 9b4ed544..26d3daf9 100644 --- a/MobileGL/MG_Backend/MGPipe/PipeInputs.h +++ b/MobileGL/MG_Backend/MGPipe/PipeInputs.h @@ -13,6 +13,15 @@ // the live context happens on the client side, in MG_Impl/Pipe/PipeFill.cpp. #include +#if MOBILEGL_BUILD_DISAGGREGATED +// P5c (rv, CONTRACT-P5C.md §5.3): the three texture shutters' server-side answer lives in the +// applier - MGPipeApplierTextureShutterSerial() / MGPipeApplierContextSerial(), declared here +// so the accessors below can answer with them under a server-stamped verb. MG_Pipe is below +// MG_Backend, so this direction is the layering's, and PipeApply.h forward-declares +// PipeInputs rather than including this header, so there is no cycle. +#include +#endif + // MOBILEGL_PIPE_POISON: the per-verb generation stamps and the read-side // Fatal{UnmigratedPipeInput} check. Derived here, once. The repository's debug gate is // MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG (Defines.h); the verify CI build is @@ -471,9 +480,19 @@ namespace MobileGL::MG_Pipe { MGP_INPUT_VERIFY_READ(MGPipeInputField::GetRenderStateParameters, 0, 0); return m_renderState; } + // THE THREE TEXTURE SHUTTERS (P5c rv, CONTRACT-P5C.md §5.3). Their FieldOwnership rows + // have said "a shutter, not a value: the server answers from its own Serial" since P5; + // rv is the edit that makes the accessor DO it. Under a SERVER-STAMPED verb the answer + // is the applier's own serial (APPLIER_DERIVED): server-owned, monotone, moved by every + // applied record that can move what the frontend generation guarded. Everywhere else - + // monolith, a split build on monolith transport, any read outside a stamped verb - the + // storage answer is kept, byte for byte (G1). Uint64 GetSamplingResolutionGeneration() const { MGP_INPUT_CHECK(MGPipeInputField::GetSamplingResolutionGeneration); MGP_INPUT_VERIFY_READ(MGPipeInputField::GetSamplingResolutionGeneration, 0, 0); +#if MOBILEGL_BUILD_DISAGGREGATED + if (m_serverStampedVerb) return MGPipeApplierTextureShutterSerial(); +#endif return m_samplingResolutionGeneration; } const IntVec4& GetScissorBox() const { @@ -489,11 +508,21 @@ namespace MobileGL::MG_Pipe { Uint64 GetTextureBindGeneration() const { MGP_INPUT_CHECK(MGPipeInputField::GetTextureBindGeneration); MGP_INPUT_VERIFY_READ(MGPipeInputField::GetTextureBindGeneration, 0, 0); +#if MOBILEGL_BUILD_DISAGGREGATED + // See GetSamplingResolutionGeneration: the server answers from its own Serial. + if (m_serverStampedVerb) return MGPipeApplierTextureShutterSerial(); +#endif return m_textureBindGeneration; } Uint64 GetTextureContextId() const { MGP_INPUT_CHECK(MGPipeInputField::GetTextureContextId); MGP_INPUT_VERIFY_READ(MGPipeInputField::GetTextureContextId, 0, 0); +#if MOBILEGL_BUILD_DISAGGREGATED + // A context IDENTITY rather than a generation: stable within the served context, + // moved by every MGPipeApplierReset - which is all the backends' per-context memo + // keys ask of it. + if (m_serverStampedVerb) return MGPipeApplierContextSerial(); +#endif return m_textureContextId; } Uint64 GetTransformFeedbackCapturedVertices() const { diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 5a375049..6d6d1b04 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -1886,6 +1886,11 @@ namespace MobileGL::MG_Pipe { case MGPipeFieldEmitter::SetDrawProgram: case MGPipeFieldEmitter::SetDispatchProgram: return kMGPipeSubsystemPrograms; + // P5c rv (CONTRACT-P5C.md §5.3): the residual-value record rides the residual + // subsystem - the one family with no dirty bit of its own, which is why its + // emission gate is the subsystem bit plus the whole-record hash and nothing else. + case MGPipeFieldEmitter::SetContextValues: + return kMGPipeSubsystemResidualValues; case MGPipeFieldEmitter::kNone: break; } @@ -2062,10 +2067,15 @@ namespace MobileGL::MG_Pipe { // commit, not at the merge, and no file is touched twice. // // The sampler bit covers SamplerEmit.h AND ImageEmit.h: one family, one A/B. + // P5c rv: the residual subsystem joins the wired mask for set_context_values - the + // record's emission is NOT gated on a dirty bit (there is none for the family, + // NoDirtyBitOwnsTheResidualSubsystem says so), so this bit is what the residual-fill + // skip consults for the eight fields the record supplies. constexpr Uint64 kMGPipeWiredSubsystems = kMGPipeSubsystemRenderState | kMGPipeSubsystemPixelPack | kMGPipeSubsystemPatchState | kMGPipeSubsystemVertexAttribDefaults | + kMGPipeSubsystemResidualValues | kMGPipeSubsystemResources | kMGPipeSubsystemVertexInput | kMGPipeWiredFramebufferSubsystem | @@ -2097,17 +2107,6 @@ namespace MobileGL::MG_Pipe { // (ARCHITECTURE.md 4.6 D5, MGPipeTypes.h). The unpack half has no carrier at all, // so the field keeps being pulled and the verify comparator keeps proving it. // - // GetCurrentVertexAttribute's three views are NOT bit-identical: GLContext - // CONVERTS between them (SetCurrentVertexAttributeFloat writes (Int32)value into - // intValue), while MGPipeApplySetVertexAttribDefaults (package A's) memcpys one - // Data[4] into all three views and ignores MGPAttribValue::ValueClass. The CLIENT - // half of that is fixed - the call now carries the class the frontend actually - // wrote and that class's own bytes - but the APPLIER still cannot reproduce the - // conversion, so this row stays SHAPE-ONLY: the field keeps being pulled, and - // retiring that pull is blocked on A teaching the applier to switch on - // ValueClass. EmitVertexAttribDefaults checks rather than trusts, and repairs the - // mirror when the applier's write does not reproduce the value. - // // GetBoundVertexArray is P3a's row, and Coverage.def asks for the decision to be // taken HERE, deliberately, rather than inherited from the row's presence. THE // ANSWER IS NO, and it is not a matter of degree: the field's storage is a @@ -2137,25 +2136,21 @@ namespace MobileGL::MG_Pipe { // null on every draw of every push build. What retires them is not a better // applier, it is the phase where the backend stops reading a frontend object. // - // GetMaxTouchedTextureUnit is the sixth and its argument is different, which is - // why it is written out: it is a plain Int, and set_sampler_views' Count IS that - // value plus one. But the set is SUPPRESSED on an unchanged content hash and is - // emitted only when NEW_SAMPLER_VIEWS fires, and that bit's shutter - - // Mix(textureContent, GetTextureBindGeneration()) - does NOT move on a redundant - // re-bind of the object a unit already holds, while the high-water mark DOES. So - // the applier's Count can lag the frontend's mark by exactly the case the - // suppressor exists to swallow, and the field keeps being pulled. + // GetMaxTouchedTextureUnit was the sixth and its argument was different - a plain + // Int whose carrier (set_sampler_views' Count) is hash-suppressed while the + // high-water mark still moves on a redundant re-bind. P5c rv RETIRED it from this + // list (CONTRACT-P5C.md §5.3): set_context_values carries the mark as a VALUE of + // its own, whole-record suppressed, so the lag the suppressor could introduce is + // gone and the derivation's RECORD_SUPPLIED answer is honest. constexpr Bool EmittedCallSuppliesTheWholeField(MGPipeInputField field) { switch (field) { case MGPipeInputField::GetPixelStoreParameters: - case MGPipeInputField::GetCurrentVertexAttribute: case MGPipeInputField::GetBoundVertexArray: case MGPipeInputField::GetFramebufferBindingSlot: case MGPipeInputField::GetImageTextureBinding: case MGPipeInputField::GetTextureUnitObject: case MGPipeInputField::GetProgramForDraw: case MGPipeInputField::GetProgramForDispatch: - case MGPipeInputField::GetMaxTouchedTextureUnit: return false; default: return true; @@ -2175,6 +2170,16 @@ namespace MobileGL::MG_Pipe { case MGPipeInputField::GetPatchDefaultOuterLevel: case MGPipeInputField::GetPatchDefaultInnerLevel: case MGPipeInputField::GetCurrentVertexAttribute: + // P5c rv's eight: MGPipeApplySetContextValues writes them out of the record, + // field for field, through MGPipeApplyAccess::SetContextValues. + case MGPipeInputField::GetActiveTextureUnit: + case MGPipeInputField::GetMaxTouchedTextureUnit: + case MGPipeInputField::GetTouchedBufferBindingPointCount: + case MGPipeInputField::IsTransformFeedbackActive: + case MGPipeInputField::IsTransformFeedbackPaused: + case MGPipeInputField::GetTransformFeedbackGeneration: + case MGPipeInputField::GetBoundTransformFeedbackLifetimeId: + case MGPipeInputField::GetTransformFeedbackCapturedVertices: return true; default: return false; @@ -2256,15 +2261,18 @@ namespace MobileGL::MG_Pipe { // when the hash has not moved. That is coalescing rule 4, and this is its one wired // consumer in P2. // - // THE PAYLOAD AND ITS ONE MISSING HALF. A CurrentVertexAttributeValue is one value in - // three views, and GLContext CONVERTS between them numerically, so "the bytes of one - // view" is not the value: glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in intValue and - // 0x3FC00000 in floatValue, and every glVertexAttribI4i/ui is a different pair again. - // MGPAttribValue carries ValueClass for exactly this reason, so the client sends the - // class the frontend actually wrote (GLContext::GetCurrentVertexAttributeClass) and - // THAT class's own four words. What is still missing is the other half: - // MGPipeApplySetVertexAttribDefaults (package A's file) memcpys the four words into - // all three views regardless of ValueClass, which cannot reproduce the conversion. + // THE PAYLOAD, SINCE P5c rv (CONTRACT-P5C.md §5.3). A CurrentVertexAttributeValue is + // one value in three views, and GLContext CONVERTS between them numerically, so "the + // bytes of one view" is not the value: glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in + // intValue and 0x3FC00000 in floatValue, and every glVertexAttribI4i/ui is a different + // pair again. MGPAttribValue now carries all three views VERBATIM + // (FloatView/IntView/UintView) plus the class the frontend actually wrote + // (GLContext::GetCurrentVertexAttributeClass), and MGPipeApplySetVertexAttribDefaults + // writes each view from its own array - the cross-view conversion's authoritative + // answer is the client's, and the applier no longer reconverts anything. The pre-rv + // shape (one Data[4] memcpied into all three views, ValueClass ignored) is exactly + // what kept this row EMITTED-AND-STILL-PULLED in EmittedCallSuppliesTheWholeField; + // with the applier fixed, the field is RECORD_SUPPLIED outright. // // The suppressing memcmp below is over the three VIEWS only, and that is not an // oversight: the class decides how the views are REBUILT, so two writes that leave @@ -2277,8 +2285,9 @@ namespace MobileGL::MG_Pipe { // and says so once. That is what keeps the block correct in the window this call used // to corrupt - a glVertexAttrib4f followed by a non-kDraw verb, where the residual // fill does not run for this field and nothing else would have put the value back. - // The day the applier honours ValueClass the compare stops failing and the repair - // stops happening, with no edit here. + // Since rv the applier writes all three views verbatim, so the compare below is + // expected to pass on every emission; it stays armed because it is the one observable + // of that write being verbatim in a window no other gate looks at. Uint64 g_attribDefaultRepairs = 0; // The header of the last set_vertex_attrib_defaults that actually went out. Count == 0 @@ -2349,14 +2358,73 @@ namespace MobileGL::MG_Pipe { } if (!reproduced) { ++g_attribDefaultRepairs; - MGLOG_W_ONCE("MGPipe: MGPipeApplySetVertexAttribDefaults does not reproduce the " - "carried value on this build (it ignores MGPAttribValue::ValueClass) " - "- the client is keeping m_currentVertexAttribute authoritative"); + MGLOG_W_ONCE("MGPipe: MGPipeApplySetVertexAttribDefaults did not reproduce the " + "carried three views on this build - the client is keeping " + "m_currentVertexAttribute authoritative"); MGPipeFillAccess::CopyField(gPipeInputs, ctx, MGPipeInputField::GetCurrentVertexAttribute); } return sizeof(MGPVertexAttribDefaults) + header.Count * sizeof(MGPAttribValue); } +#if MOBILEGL_BUILD_DISAGGREGATED + // set_context_values (P5c rv, CONTRACT-P5C.md §5.3): the residual-value record. One + // POD carrying every value-class field no other set_* supplies - the two texture-unit + // counters, the 15 per-target touched-buffer-binding counts and the five XFB values - + // emitted at validate WHEN ANY COVERED VALUE MOVED, which the whole-record hash says: + // there is deliberately no dirty bit for the family (the tracker's value-class dirty + // accounting is untouched - "零新增记账", ARCHITECTURE.md 5.2) and no dirty mask in the + // payload, so a suppressed record means "nothing moved", never "field invalid" (§1). + // + // THE PRODUCER IS TRANSPORT-GATED, and the residual-fill skip for the same eight fields + // is gated on the same answer (MGPipeValidateForVerb's contextValuesWireLive): under + // monolith - or with a transport configured but no live session (the bring-up window, a + // server-role-only fixture) - nothing is emitted and the fields keep being pulled, byte + // for byte as before (G1). + Uint64 EmitContextValues(GLContext& ctx) { + MGPContextValues values{}; + values.ActiveTextureUnit = static_cast(ctx.GetActiveTextureUnit()); + values.MaxTouchedTextureUnit = static_cast(ctx.GetMaxTouchedTextureUnit()); + // The array is indexed by BufferTarget value, all 15 of them (MGPipeTypes.h); + // targets with no binding points answer 0 (BufferState::GetTouchedBindPointCount). + static_assert( + std::extent_v == + static_cast(BufferTarget::BufferTargetCount), + "MGPContextValues' per-target array and BufferTargetCount have drifted"); + for (Uint32 t = 0; t < static_cast(BufferTarget::BufferTargetCount); ++t) { + values.TouchedBufferBindingPointCount[t] = + static_cast(ctx.GetTouchedBufferBindingPointCount(static_cast(t))); + } + values.IsTransformFeedbackActive = ctx.IsTransformFeedbackActive() ? 1 : 0; + values.IsTransformFeedbackPaused = ctx.IsTransformFeedbackPaused() ? 1 : 0; + values.TransformFeedbackGeneration = ctx.GetTransformFeedbackGeneration(); + values.BoundTransformFeedbackLifetimeId = ctx.GetBoundTransformFeedbackLifetimeId(); + values.TransformFeedbackCapturedVertices = ctx.GetTransformFeedbackCapturedVertices(); + // The whole record is the hash input, padding included - `values{}` zeroes it, so + // the pad bytes are defined and the hash is stable. + const Uint64 contentHash = XXH64(&values, sizeof(values), 0); + if (!MGPipeSetHashSuppressorInstance().ShouldEmit(MGPipeSuppressorSlot::SetContextValues, + contentHash)) { + return 0; + } + MGPipeRouteSetContextValues(values); + return sizeof(MGPContextValues); + } + + // The fields set_context_values supplies. A validate whose verb class reads NONE of + // them skips the record build entirely - the record exists so a verb's reads are + // answered, and a verb that never reads them needs no publication. + constexpr Bool VerbMaskReadsContextValues(const MGPipeFieldMask& mask) { + return MGPipeFieldMaskHas(mask, MGPipeInputField::GetActiveTextureUnit) || + MGPipeFieldMaskHas(mask, MGPipeInputField::GetMaxTouchedTextureUnit) || + MGPipeFieldMaskHas(mask, MGPipeInputField::GetTouchedBufferBindingPointCount) || + MGPipeFieldMaskHas(mask, MGPipeInputField::IsTransformFeedbackActive) || + MGPipeFieldMaskHas(mask, MGPipeInputField::IsTransformFeedbackPaused) || + MGPipeFieldMaskHas(mask, MGPipeInputField::GetTransformFeedbackGeneration) || + MGPipeFieldMaskHas(mask, MGPipeInputField::GetBoundTransformFeedbackLifetimeId) || + MGPipeFieldMaskHas(mask, MGPipeInputField::GetTransformFeedbackCapturedVertices); + } +#endif + // set_residual_value_state (P2 brief D9, ARCHITECTURE.md 9.4). // @@ -2669,6 +2737,19 @@ namespace MobileGL::MG_Pipe { // emission the server refuses is an emission whose acceptance already cleared a frontend // dirty flag the legacy arm still owed. Same table, same four families, one place. const Uint64 pushMask = MG_Config::Features.PipePush; +#if MOBILEGL_BUILD_DISAGGREGATED + // P5c rv (CONTRACT-P5C.md §5.3): set_context_values is the carrier for the eight + // value-class fields ONLY with a live wire. ONE answer gates both halves - the emission + // below and the residual-fill skip in step 4 - so they can never disagree about who + // supplies a field: with no live session (a monolith transport, the bring-up window, a + // server-role-only fixture) nothing is emitted and the fields keep being pulled, byte + // for byte as before (G1). + const Bool contextValuesWireLive = + MG_Config::Transport != MG_Config::TransportMode::Monolith && + MG_Remote::Client::ContextValuesWireLive(); +#else + constexpr Bool contextValuesWireLive = false; +#endif const auto wants = [&](MGPipeDirty bit) { const Uint64 subsystem = MGPipeSubsystemForDirty(bit); return subsystem != 0 && (pushMask & subsystem) != 0 && @@ -2808,6 +2889,19 @@ namespace MobileGL::MG_Pipe { payloadBytes += EmitVertexAttribDefaults(*ctx, tracker.FreshlyPrimed()); } +#if MOBILEGL_BUILD_DISAGGREGATED + // P5c rv (CONTRACT-P5C.md §5.3): the residual-value record, emitted when any covered + // value moved. THE GATE IS THE SUBSYSTEM BIT PLUS THE WIRE BEING LIVE - the family has + // no dirty bit (NoDirtyBitOwnsTheResidualSubsystem) and no P4a consumer predicate (it + // is not one of the four families), and the "did anything move" question is the + // whole-record hash inside EmitContextValues. The class-mask test skips the build for + // verbs that read none of the eight fields. + if (contextValuesWireLive && (pushMask & kMGPipeSubsystemResidualValues) != 0 && + VerbMaskReadsContextValues(mask)) { + payloadBytes += EmitContextValues(*ctx); + } +#endif + // P3a's vertex segment, in the order the design fixes: vertex elements, then the // vertex buffers that fill them, then the index binding. All three are LIVE now (m1): // bits 5 / 9 / 10 map onto kMGPipeSubsystemVertexInput in Tracker.h:145-148 and all @@ -2857,12 +2951,20 @@ namespace MobileGL::MG_Pipe { // FIELD, and on a backend with no consumer - or at a mask that leaves one of the // family's D-K2 dependency bits clear - no P4a call went out at all, so withholding // the pull here would leave the field unfilled at the very verb that reads it. + // + // AND THE LAST CONJUNCT IS P5c rv's (CONTRACT-P5C.md §5.3): set_context_values has + // NO PRODUCER without a live wire (its emission is transport-gated, above), so its + // eight fields keep being pulled under monolith - G1's byte-for-byte rule - and are + // skipped only when the record really crosses. The two sides read the ONE answer + // computed at the top of this function, so they cannot disagree. const Bool supplied = subsystem != 0 && (subsystem & kMGPipeWiredSubsystems) != 0 && (pushMask & subsystem) != 0 && P4aFamilyHasItsConsumer(subsystem) && P4aFamilyDependenciesAreSet(subsystem, pushMask) && EmittedCallSuppliesTheWholeField(field) && - (applierDerives || AppliedWithoutDerivation(field)); + (applierDerives || AppliedWithoutDerivation(field)) && + (emitter != MGPipeFieldEmitter::SetContextValues || + contextValuesWireLive); if (!supplied) MGPipeFillAccess::CopyField(inputs, *ctx, field); #if MOBILEGL_PIPE_POISON // The value is copied either way; only the stamp is withheld for the omitted pair. diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.h b/MobileGL/MG_Impl/Pipe/PipeFill.h index 6f17fb01..04b34fbf 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.h +++ b/MobileGL/MG_Impl/Pipe/PipeFill.h @@ -113,8 +113,10 @@ namespace MobileGL::MG_Pipe { // (EmitVertexAttribDefaults). It is the ONE observable of that repair: the window it // covers is a verb whose class does not read m_currentVertexAttribute, where reading the // storage to check it would be the poison violation the fill table exists to forbid. So - // TrackerShippedEmitter asserts on this counter instead, and the day package A's applier - // switches on MGPAttribValue::ValueClass the counter stops moving. + // TrackerShippedEmitter asserts on this counter instead. Since P5c rv the record carries + // all three views verbatim (CONTRACT-P5C.md §5.3) and the applier writes each from its own + // array, so the counter is expected to stay at 0 - the check that increments it is the + // trip wire that remains. // // Not hot-path instrumentation: it is incremented only inside the repair branch, which // runs only when the call actually went out, which is only when an attribute default diff --git a/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h b/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h index 19fd070b..99c0cc18 100644 --- a/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h +++ b/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h @@ -45,7 +45,9 @@ namespace MobileGL::MG_Pipe { // One slot per kVarTail set_* (ARCHITECTURE.md 5.1's call list), PLUS // SetFramebufferState, which is not kVarTail at all: MGPFramebufferState carries a // ContentHash for TWO jobs - the server's render-pass memo key and the client's emission - // suppressor - and the second one needs a slot here like any other. The enum is + // suppressor - and the second one needs a slot here like any other, PLUS + // SetContextValues (P5c rv), also not kVarTail: one fixed-width POD whose whole-record + // hash is the "did any covered value move" answer. The enum is // CLIENT-ONLY and is not a wire opcode, so appending before Count is safe. enum class MGPipeSuppressorSlot : Uint32 { SetVertexBuffers = 0, // P3a - wired, and its hash includes BaseInstance @@ -64,6 +66,12 @@ namespace MobileGL::MG_Pipe { SetStreamOutputTargets, // P4b 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 + // SetFramebufferState's note: MGPContextValues is one fixed-width POD, and the + // whole-record hash IS its "did any covered value move" answer (there is deliberately + // no dirty mask in the payload - a suppressed record means "nothing moved", never + // "field invalid"). + SetContextValues, // P5c rv - wired, split+transport only (PipeFill.cpp gates) Count, }; diff --git a/MobileGL/MG_Impl/Pipe/Tracker.h b/MobileGL/MG_Impl/Pipe/Tracker.h index 1fc2a375..9ec51c01 100644 --- a/MobileGL/MG_Impl/Pipe/Tracker.h +++ b/MobileGL/MG_Impl/Pipe/Tracker.h @@ -821,31 +821,30 @@ namespace MobileGL::MG_Pipe { Uint64 m_walks[kMGPipeVerbClassCount]{}; }; - // ONE attribute default, flattened onto the wire (P2 brief D10). A named function rather - // than four lines inside the emitter because this flattening is the whole correctness - // question of set_vertex_attrib_defaults: a CurrentVertexAttributeValue is one value in - // three views and GLContext converts NUMERICALLY between them, so four words alone are - // not the value - glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in intValue and 0x3FC00000 in - // floatValue. MGPAttribValue::ValueClass is what makes the four words readable again, and - // TrackerAttribPayload pins that here instead of leaving it to the emitter's shape. + // ONE attribute default, flattened onto the wire (P2 brief D10, AMENDED at P5c rv). A + // named function rather than four lines inside the emitter because this flattening is the + // whole correctness question of set_vertex_attrib_defaults: a CurrentVertexAttributeValue + // is one value in three views and GLContext converts NUMERICALLY between them, so four + // words alone are not the value - glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in intValue + // and 0x3FC00000 in floatValue. Since rv the record carries ALL THREE VIEWS VERBATIM + // (MGPAttribValue::FloatView/IntView/UintView, CONTRACT-P5C.md §5.3) and the applier writes + // each view from its own array; ValueClass is the record of which view the application + // wrote directly, kept for the comparator and for readers - the applier no longer needs + // it to rebuild anything. inline void MGPipeFillAttribValue(Uint32 location, const MG_State::GLState::CurrentVertexAttributeValue& value, Uint32 writtenClass, MGPAttribValue& out) { out = MGPAttribValue{}; out.Location = location; out.ValueClass = static_cast(writtenClass); - static_assert(sizeof(out.Data) == sizeof(value.floatValue), "MGPAttribValue::Data is four words"); - switch (writtenClass) { - case MG_State::GLState::kVertexAttribValueClassInt: - std::memcpy(out.Data, value.intValue.data(), sizeof(out.Data)); - break; - case MG_State::GLState::kVertexAttribValueClassUint: - std::memcpy(out.Data, value.uintValue.data(), sizeof(out.Data)); - break; - default: - std::memcpy(out.Data, value.floatValue.data(), sizeof(out.Data)); - break; - } + static_assert(sizeof(out.FloatView) == sizeof(value.floatValue), + "MGPAttribValue's views are four words each"); + static_assert(sizeof(out.IntView) == sizeof(value.intValue) && + sizeof(out.UintView) == sizeof(value.uintValue), + "MGPAttribValue's views are four words each"); + std::memcpy(out.FloatView, value.floatValue.data(), sizeof(out.FloatView)); + std::memcpy(out.IntView, value.intValue.data(), sizeof(out.IntView)); + std::memcpy(out.UintView, value.uintValue.data(), sizeof(out.UintView)); } // The monolith's one tracker. Under split there is one per client context; the context diff --git a/MobileGL/MG_Pipe/Coverage.def b/MobileGL/MG_Pipe/Coverage.def index 33a63caf..d67d4e53 100644 --- a/MobileGL/MG_Pipe/Coverage.def +++ b/MobileGL/MG_Pipe/Coverage.def @@ -212,33 +212,36 @@ // mirrors null on every draw of every push build. What retires those pulls is not a better // applier, it is the phase where the backend stops reading a frontend object at all. // -// THE SIXTH IS A DIFFERENT ARGUMENT AND IT IS WORTH WRITING DOWN, because it looks like the -// easy one. GetMaxTouchedTextureUnit is a plain Int, and set_sampler_views' Count IS that -// value plus one (the second merge rule: a high-water mark is directly the count argument). -// But the set is SUPPRESSED on an unchanged content hash and is emitted only when bit 12 fires, -// and bit 12's shutter is Mix(textureContent, GetTextureBindGeneration()) - which does NOT -// move on a redundant re-bind of the object a unit already holds, while the high-water mark -// DOES (see NoteUnitTouched in DirtySurface.def). So the applier's Count can lag the frontend's -// high-water mark by exactly the case the suppressor exists to swallow, and the field keeps -// being pulled. Narrowing that is P3b/P4b's, with the backend debounce it takes over. +// THE SIXTH left that arm at P5c rv: GetMaxTouchedTextureUnit is a plain Int whose problem was +// never shape, it was the CARRIER - set_sampler_views' Count is the set's window, and the set +// is suppressed on an unchanged content hash while the high-water mark still moves on a +// redundant re-bind. set_context_values (CONTRACT-P5C.md §5.3) carries the mark as a VALUE of +// its own, whole-record suppressed, so the mark rides an unsuppressed field and the row is +// RECORD_SUPPLIED outright. // -// FOUR ACCESSORS THAT MAP TO A P4a CALL ARE DELIBERATELY NOT HERE, for GetPixelStoreParameters' -// reason - a row here says "this field is supplied", and for these it would be a half-truth: -// GetActiveTextureUnit - glActiveTexture's selector. set_sampler_views carries the RESOLVED -// per-unit set and no active-unit selector at all; nothing on the wire carries it. -// GetTextureContextId - a context identity the backend keys its own tables on. No call -// carries it and none should: it is the server's question about the client, not state. -// GetTextureBindGeneration / GetSamplingResolutionGeneration - frontend SHUTTERS. What -// replaces them server-side is the applier's own Serial, which is a different value with a -// different owner; claiming the sets supply the generations would make the fill loop skip -// two counters no record carries. +// P5c rv ADDS EIGHT ROWS, and they are the whole of the value-class debt that was left +// (CONTRACT-P5C.md §5.3): seven residual values no set_* call carried - the two texture-unit +// counters, the 15-target touched-count array and the five transform-feedback values - plus +// GetMaxTouchedTextureUnit re-homed from SetSamplerViews. All eight name SetContextValues, one +// POD emitted at validate when any covered value moved, hash-suppressed whole. The OBJECT rows +// (GetTextureUnitObject and its four P4a siblings above) stay BARRIER_PULLED with their +// retiring phases; the three texture shutters (below) take no row here either - they carry no +// wire value at all, the accessors answer the applier's own Serial. +// +// GetTextureContextId / GetTextureBindGeneration / GetSamplingResolutionGeneration stay out +// DELIBERATELY, and the reason is ownership rather than coverage: they are frontend SHUTTERS, +// and what replaces them server-side is the applier's own Serial - a different value with a +// different owner. A row here would claim the sets supply the generations, and the fill loop +// would skip two counters no record carries. // 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. #define MGP_COVERAGE_EMITTED_LIST(X) \ + X(GetActiveTextureUnit, SetContextValues) \ X(GetBlendColor, SetDynamicState) \ X(GetBlendEquationIndexed, CreateRenderState) \ X(GetBlendFuncIndexed, CreateRenderState) \ + X(GetBoundTransformFeedbackLifetimeId, SetContextValues) \ X(GetBoundVertexArray, BindVertexElements) \ X(GetClampReadColor, SetDynamicState) \ X(GetClearColor, SetDynamicState) \ @@ -254,7 +257,7 @@ X(GetImageTextureBinding, SetShaderImages) \ X(GetLineWidth, SetDynamicState) \ X(GetLogicOp, CreateRenderState) \ - X(GetMaxTouchedTextureUnit, SetSamplerViews) \ + X(GetMaxTouchedTextureUnit, SetContextValues) \ X(GetMinSampleShadingValue, CreateRenderState) \ X(GetPatchDefaultInnerLevel, SetPatchState) \ X(GetPatchDefaultOuterLevel, SetPatchState) \ @@ -272,9 +275,14 @@ X(GetScissorBox, SetDynamicState) \ X(GetStencilState, CreateRenderState) \ X(GetTextureUnitObject, SetSamplerViews) \ + X(GetTouchedBufferBindingPointCount, SetContextValues) \ + X(GetTransformFeedbackCapturedVertices, SetContextValues) \ + X(GetTransformFeedbackGeneration, SetContextValues) \ X(GetViewport, SetDynamicState) \ X(GetViewportIndexed, SetDynamicState) \ X(IsCapabilityEnabled, CreateRenderState) \ - X(IsCapabilityEnabledIndexed, CreateRenderState) + X(IsCapabilityEnabledIndexed, CreateRenderState) \ + X(IsTransformFeedbackActive, SetContextValues) \ + X(IsTransformFeedbackPaused, SetContextValues) // clang-format on diff --git a/MobileGL/MG_Pipe/FieldOwnership.def b/MobileGL/MG_Pipe/FieldOwnership.def index b53306be..f60190cd 100644 --- a/MobileGL/MG_Pipe/FieldOwnership.def +++ b/MobileGL/MG_Pipe/FieldOwnership.def @@ -18,7 +18,8 @@ // the client for it. THIS CLASS IS DERIVED, NOT LISTED: it is // kMGPipeFieldEmittedBy != kNone (Coverage.def's MGP_COVERAGE_EMITTED_LIST) // minus the fields EmittedCallSuppliesTheWholeField refuses -// (MG_Impl/Pipe/PipeFill.cpp). 32 fields today. A row below that names a +// (MG_Impl/Pipe/PipeFill.cpp). 41 fields since P5c rv (32 before it). A row +// below that names a // field the derivation already placed here is a CONTRADICTION and stops // the generator - which is the only way this file can stay true as the // emitted list grows. @@ -50,14 +51,20 @@ // whose row stops the pull. It is a string, not an enum, because two of them name two // phases for the two backends and flattening that would lose the half that matters. #define MGP_FIELD_OWNERSHIP_LIST(X) \ - /* ---- BARRIER_PULLED: the 20 non-sticky rows the reduced path actually reads ---- */ \ + /* ---- BARRIER_PULLED: the 9 non-sticky rows the reduced path actually reads ---- */ \ /* scout-unmigrated-census section 3 intersects each verb class's may-read mask with "no */ \ /* record supplies it" and unions kClear (7 of 18), kDraw (19 of 47) and kReadback (12 of */ \ - /* 17). That union is 21 fields; GetPixelStoreParameters is the 21st and it is */ \ - /* APPLIER_DERIVED below, for the reason written there. OpenRA adds no field to this set - */ \ - /* it widens the SITE set, not the field set. */ \ - X(GetActiveTextureUnit, BARRIER_PULLED, "P3b/P4b", \ - "the server answers from its own state; Coverage.def:215-219 says no call carries it") \ + /* 17). That union was 21 fields at P5; P5c rv (CONTRACT-P5C.md §5.3) retired NINE of them */ \ + /* to RECORD_SUPPLIED through set_context_values (the two texture-unit counters, the */ \ + /* touched-count array, the five XFB values) plus GetCurrentVertexAttribute (the amended */ \ + /* set_vertex_attrib_defaults payload), and moved the three texture SHUTTERS to */ \ + /* APPLIER_DERIVED (their own rows said it since P5: "a shutter, not a value: the server */ \ + /* answers from its own Serial" - rv made the accessor DO it). GetPixelStoreParameters is */ \ + /* the 21st of the census and it is APPLIER_DERIVED below, for the reason written there. */ \ + /* WHAT IS LEFT IS EXACTLY THE OBJECT CLASS: nine fields whose storage is a frontend heap */ \ + /* reference no record can carry, each with the phase that retires it, and */ \ + /* FieldOwnershipTest pins this list as ALL the remaining non-sticky rows. OpenRA adds no */ \ + /* field to this set - it widens the SITE set, not the field set. */ \ /* DirectGLES.cpp:4486, PrepareForDraw, UNCONDITIONAL ON EVERY DRAW. No #if, no arm guard, */ \ /* 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 and */ \ @@ -72,14 +79,6 @@ "7 of 15 BufferTargets have no call; the field is one array over all 15") \ X(GetBufferBindingPoint, BARRIER_PULLED, "P3b/P4b, P7", \ "frontend BindingSlotRange1D pointer") \ - X(GetTouchedBufferBindingPointCount, BARRIER_PULLED, "P3b/P4b", \ - "no call carries the touched-count high-water mark") \ - /* The applier CANNOT reproduce GLContext's cross-view conversion: the frontend writes */ \ - /* (Int32)value into intValue while MGPipeApplySetVertexAttribDefaults memcpys one Data[4] */ \ - /* into all three views (PipeFill.cpp:1884-1896). So the record exists, the applier writes */ \ - /* the field, and the value is still wrong - which is BARRIER_PULLED, not APPLIER_DERIVED. */ \ - X(GetCurrentVertexAttribute, BARRIER_PULLED, "P3b/P4b", \ - "the applier writes it but cannot reproduce the cross-view conversion") \ /* 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. */ \ @@ -87,19 +86,6 @@ "frontend BindingSlot pointer") \ X(GetImageTextureBinding, BARRIER_PULLED, "P3b/P4b, P7", \ "frontend ImageTextureBinding base pointer") \ - /* set_sampler_views' Count IS this value plus one, but the set is suppressed on an */ \ - /* unchanged content hash while the high-water mark still moves (PipeFill.cpp:1917-1923). */ \ - X(GetMaxTouchedTextureUnit, BARRIER_PULLED, "P3b/P4b", \ - "hash-suppressed set, high-water mark still moves") \ - /* The three texture SHUTTERS. Coverage.def:220-224 is explicit that no call carries them */ \ - /* and none should: what replaces them server-side is the applier's own Serial, a different */ \ - /* value with a different owner. So these are not values to migrate. */ \ - X(GetSamplingResolutionGeneration, BARRIER_PULLED, "P3b/P4b", \ - "a shutter, not a value: the server answers from its own Serial") \ - X(GetTextureBindGeneration, BARRIER_PULLED, "P3b/P4b", \ - "a shutter, not a value: the server answers from its own Serial") \ - X(GetTextureContextId, BARRIER_PULLED, "P3b/P4b", \ - "a shutter, not a value: the server answers from its own Serial") \ X(GetTextureUnitObject, BARRIER_PULLED, "P3b/P4b, P7", \ "frontend TextureUnit base pointer; 13 Espryt + 8 Magma sites") \ /* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \ @@ -113,24 +99,15 @@ /* phases. Overturned by: nothing in P5b; P7/P8 retire both rows together. */ \ X(GetProgramForDispatch, BARRIER_PULLED, "P7 (Magma), P8 (Espryt)", \ "frontend SharedPtr; the record carries a handle") \ - /* The XFB six. XFB itself is off the reduced path, but kDraw's may-read mask carries all */ \ - /* six and the draw walk reads them regardless - which is exactly the case a field census */ \ - /* taken from "what the scenario does" rather than from the mask would miss. */ \ - X(IsTransformFeedbackActive, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ - "read on every kDraw walk although XFB is off the reduced path") \ - X(IsTransformFeedbackPaused, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ - "read on every kDraw walk although XFB is off the reduced path") \ + /* 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 */ \ + /* case a field census taken from "what the scenario does" rather than from the mask would */ \ + /* miss. The family's five VALUE rows rode set_context_values out of this list at P5c rv. */ \ X(GetTransformFeedbackProgram, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ "frontend SharedPtr") \ - X(GetTransformFeedbackGeneration, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ - "no call carries it") \ - X(GetBoundTransformFeedbackLifetimeId, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ - "the D21 counter-slot rekey's key; no call carries it") \ - X(GetTransformFeedbackCapturedVertices, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \ - "no call carries it") \ \ /* ---- APPLIER_DERIVED ---- */ \ - /* THE ONE ROW, and it is the one the contract asked P5 to split into two field ids. It is */ \ + /* THE FIRST ROW, and it is the one the contract asked P5 to split into two field ids. It is */ \ /* split HERE INSTEAD, by ARGUMENT, in the argument-exception list below - see that list's */ \ /* header for the evidence and for what would overturn the decision. The field itself is */ \ /* APPLIER_DERIVED because set_pixel_pack_state arrives and MGPipeApplyAccess::PackState */ \ @@ -139,6 +116,19 @@ /* half of it). */ \ X(GetPixelStoreParameters, APPLIER_DERIVED, "-", \ "set_pixel_pack_state; the applier writes m_pixelStore[0] (PipeApply.cpp:1373)") \ + /* THE THREE TEXTURE SHUTTERS (P5c rv, CONTRACT-P5C.md §5.3). No call carries them and none */ \ + /* should (Coverage.def's emitted-list header): what replaces them server-side is the */ \ + /* applier's own Serial - a different value with a different owner. rv is the edit that */ \ + /* makes the accessor DO it: under a server-stamped verb the PipeInputs accessor answers */ \ + /* MGPipeApplierTextureShutterSerial() / MGPipeApplierContextSerial() (PipeApply.cpp) */ \ + /* instead of the client's residual-fill copy, and the class flips from BARRIER_PULLED to */ \ + /* here. Under monolith the storage answer is kept, byte for byte (G1). */ \ + X(GetSamplingResolutionGeneration, APPLIER_DERIVED, "-", \ + "a shutter, not a value: the server answers from its own Serial") \ + X(GetTextureBindGeneration, APPLIER_DERIVED, "-", \ + "a shutter, not a value: the server answers from its own Serial") \ + X(GetTextureContextId, APPLIER_DERIVED, "-", \ + "a shutter, not a value: the server answers from its own Serial") \ \ /* ---- FATAL: two non-sticky fields, each off the reduced path for a checkable reason ----- */ \ /* Three until P5b: GetProgramForDispatch moved up to BARRIER_PULLED when package i1 put */ \ diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index b384ffe2..90f89fa0 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -936,14 +936,29 @@ namespace MobileGL::MG_Pipe { }; MGP_ASSERT_POD(MGPGlobalConstants, 40); - // The float/int/uint view is resolved on the CLIENT by ClassifyVertexAttribType. + // One attribute default, resolved on the CLIENT. P5c (rv, CONTRACT-P5C.md §5.3) AMENDED + // the shape: a CurrentVertexAttributeValue is one value in THREE views and GLContext + // converts between them NUMERICALLY (SetCurrentVertexAttributeFloat writes (Int32)value + // into intValue), so the four words of the written class alone were never the value - + // glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in intValue and 0x3FC00000 in floatValue. The + // record now carries all three views VERBATIM, as the frontend computed them, and the + // applier writes each view from its own array; ValueClass stays as the record of which + // view the application wrote directly (the verify comparator covers it, and the + // emission-side suppressor compares the three views, not it). This is the catalogue's + // second payload-size amendment after P5b's MGPCopyRegion 64 -> 72 (CONTRACT-P5C.md §7.6): + // 24 -> 56 bytes, and PipeCatalogueTest pins the new size. struct MGPAttribValue { Uint32 Location; Uint8 ValueClass; // Float | Int | Uint | Double Uint8 Pad0[3]; - Uint32 Data[4]; + // The frontend's three views, VERBATIM - floatValue / intValue / uintValue of the + // CurrentVertexAttributeValue, converted between each other by the CLIENT. The + // applier writes each view from its own array and never reconverts. + Uint32 FloatView[4]; + Uint32 IntView[4]; + Uint32 UintView[4]; }; - MGP_ASSERT_POD(MGPAttribValue, 24); + MGP_ASSERT_POD(MGPAttribValue, 56); // Var-tail header: MGPAttribValue[popcount(Mask)] follows. struct MGPVertexAttribDefaults { @@ -1634,13 +1649,16 @@ namespace MobileGL::MG_Pipe { MGP_ASSERT_POD(MGPCopyFromFramebuffer, 48); // --------------------------------------------------------------------------------- - // P5c: the two appended rows (MG_Remote/CONTRACT-P5C.md §5). APPENDED, never inserted: - // applier_reset is opcode 77 and object_death opcode 78, and no earlier opcode moved. + // P5c: the three appended rows (MG_Remote/CONTRACT-P5C.md §5). APPENDED, never inserted: + // applier_reset is opcode 77, object_death opcode 78, and set_context_values (rv, §5.3) + // opcode 79 - and no earlier opcode moved. // - // Neither has an MGPipeApply* entry point and neither gains one. Both reach + // THE TWO CONTROL RECORDS have no MGPipeApply* entry point and never gain one. Both reach // MG_Remote::Wire::WireVerbSink like the five P5b verbs, and under monolith neither has a // producer - the reset is the GL thread's direct MGPipeApplierReset() call and the death - // notice's mailbox hop, byte for byte as today (G1/G2). + // notice's mailbox hop, byte for byte as today (G1/G2). set_context_values is the ordinary + // shape instead: a routed set_* row with an MGPipeApply* entry point, emitted at validate + // with an active transport and never produced under monolith (G1). // --------------------------------------------------------------------------------- // applier_reset = tracker.FreshlyPrimed()'s server half (P5c §5.1). The one field is the @@ -1660,6 +1678,32 @@ namespace MobileGL::MG_Pipe { // object (§5.2), so a null handle arriving is Fatal{ProtocolCorruption, // "ObjectDeath.Handle"} at the sink. + // set_context_values = opcode 79, P5c rv (CONTRACT-P5C.md §5.3): the rv field table as + // ONE fixed-width POD. It carries every value-class field that no set_* call supplies - + // the two texture-unit counters, the per-target touched-buffer-binding high-water marks + // (indexed by BufferTarget, all 15, the four bind-point targets today and the rest + // zero), and the five transform-feedback values - and it retires their BARRIER_PULLED + // rows to RECORD_SUPPLIED. There is NO dirty mask in the payload: the record is + // whole-record hash-suppressed like every other set_* call, and a suppressed record + // means "nothing moved", never "field invalid" (§1). The three texture SHUTTERS + // (GetSamplingResolutionGeneration / GetTextureBindGeneration / GetTextureContextId) + // deliberately carry NO field here: they are APPLIER_DERIVED, answered by the accessor + // from the applier's own Serial (MG_Backend/MGPipe/PipeInputs.h). + struct MGPContextValues { + Uint32 ActiveTextureUnit; // feeds GetActiveTextureUnit + Uint32 MaxTouchedTextureUnit; // feeds GetMaxTouchedTextureUnit + // 15 = BufferTarget::BufferTargetCount (BufferObject.h:15-33). MG_Pipe may not include + // MG_State, so the pairing is asserted where both sides are visible (PipeFill.cpp). + Uint32 TouchedBufferBindingPointCount[15]; + Uint8 IsTransformFeedbackActive; + Uint8 IsTransformFeedbackPaused; + Uint8 Pad0[2]; + Uint64 TransformFeedbackGeneration; + Uint64 BoundTransformFeedbackLifetimeId; + Uint64 TransformFeedbackCapturedVertices; + }; + MGP_ASSERT_POD(MGPContextValues, 96); + // --------------------------------------------------------------------------------- // Reverse channel payloads (section 7.1) // --------------------------------------------------------------------------------- diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index 7b7453b1..6f023a24 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -213,6 +213,23 @@ namespace MobileGL::MG_Pipe { inputs.m_patchDefaultOuterLevel = outer; inputs.m_patchDefaultInnerLevel = inner; } + // P5c (rv, CONTRACT-P5C.md §5.3): set_context_values' write, one field per payload + // member, on the server side of the wire. Like PackState it is a plain store - the + // stamp is the filler/verb boundary's statement, not the applier's (see the struct's + // header comment). + static void SetContextValues(PipeInputs& inputs, const MGPContextValues& values) { + inputs.m_activeTextureUnit = static_cast(values.ActiveTextureUnit); + inputs.m_maxTouchedTextureUnit = static_cast(values.MaxTouchedTextureUnit); + for (SizeT i = 0; i < PipeInputs::kBufferTargetCount; ++i) { + inputs.m_touchedBindingPointCount[i] = + static_cast(values.TouchedBufferBindingPointCount[i]); + } + inputs.m_transformFeedbackActive = values.IsTransformFeedbackActive != 0; + inputs.m_transformFeedbackPaused = values.IsTransformFeedbackPaused != 0; + inputs.m_transformFeedbackGeneration = values.TransformFeedbackGeneration; + inputs.m_boundTransformFeedbackLifetimeId = values.BoundTransformFeedbackLifetimeId; + inputs.m_transformFeedbackCapturedVertices = values.TransformFeedbackCapturedVertices; + } // ---------------------------------------------------------------------------- // D5: the 29 PipeInputs fields that are PURE FUNCTIONS of RenderStateParameters. @@ -1189,6 +1206,12 @@ namespace MobileGL::MG_Pipe { MGPipeApplierState& MGPipeApplier() { return g_applier; } + // P5c (rv, CONTRACT-P5C.md §5.3): the three texture shutters' server-side answers. See + // MGPipeApplierState::TextureShutterSerial / ContextSerial for the bump rule. + Uint64 MGPipeApplierTextureShutterSerial() { return g_applier.TextureShutterSerial; } + Uint64 MGPipeApplierContextSerial() { return g_applier.ContextSerial; } + void MGPipeApplierNoteTextureStateMoved() { ++g_applier.TextureShutterSerial; } + void MGPipeSetResourceOps(const MGPipeResourceOps* ops) { g_resourceOps = ops; } const MGPipeResourceOps* MGPipeGetResourceOps() { return g_resourceOps; } @@ -1306,6 +1329,13 @@ namespace MobileGL::MG_Pipe { // the first compare after the switch is a mismatch, which is the safe direction. ++g_applier.VertexBuffersSerial; ++g_applier.IndexBufferSerial; + // P5c (rv): a make-current is a fresh server, and the two shutter serials are what the + // PipeInputs texture-shutter accessors answer with - so both ADVANCE here, for the + // serials' own reason: a counter that restarts walks back through values already + // stamped into a memo that outlived the switch. ContextSerial's move is also + // GetTextureContextId's whole job (the backends key their per-context memos on it). + ++g_applier.TextureShutterSerial; + ++g_applier.ContextSerial; // ---- P4a's working state, cleared for the same reason and with the same serial rule // (D-J4). The OBJECT records - texture and renderbuffer resources, sampler CSOs, @@ -1351,6 +1381,10 @@ namespace MobileGL::MG_Pipe { g_applier.BoundVertexElements = kMGPipeNullHandle; ++g_applier.VertexBuffersSerial; ++g_applier.IndexBufferSerial; + // P5c (rv): same rule for the shutter serials - the served context is going away, and + // a memo that outlives it must not match a value these have already answered with. + ++g_applier.TextureShutterSerial; + ++g_applier.ContextSerial; // P4a's five object tables go with them, and the working handles they could name go // too - a bound shader CSO whose record has just been dropped must not survive as a // handle the next call resolves against. @@ -1493,6 +1527,18 @@ namespace MobileGL::MG_Pipe { MGPipeApplyAccess::PackState(gPipeInputs) = pack.Pack; } + // set_context_values (P5c rv, CONTRACT-P5C.md §5.3): the residual-value record's server + // half. A plain store of the whole POD - there is no dirty mask and no field-level gate, + // because the record crosses only when one of its source values moved (the client's + // whole-record hash suppression) and a suppressed record means "nothing moved", never + // "field invalid" (§1). + void MGPipeApplySetContextValues(const MGPContextValues& values) { + static_assert(std::extent_v == + PipeInputs::kBufferTargetCount, + "the record's per-target array and PipeInputs' have drifted"); + MGPipeApplyAccess::SetContextValues(gPipeInputs, values); + } + void MGPipeApplySetPatchState(const MGPPatchState& patch) { PipeInputs& inputs = gPipeInputs; RenderStateParameters& working = MGPipeApplyAccess::RenderState(inputs); @@ -1581,11 +1627,16 @@ namespace MobileGL::MG_Pipe { continue; } PipeInputs::CurrentVertexAttributeValue& slot = slots[location]; - // The three views are always populated; which one a shader input consumes is - // ClassifyVertexAttribType's answer, not the carrier's, so all three cross. - std::memcpy(slot.floatValue.data(), value.Data, sizeof(slot.floatValue)); - std::memcpy(slot.intValue.data(), value.Data, sizeof(slot.intValue)); - std::memcpy(slot.uintValue.data(), value.Data, sizeof(slot.uintValue)); + // P5c (rv, CONTRACT-P5C.md §5.3): the record carries ALL THREE VIEWS VERBATIM, as + // the frontend computed them, and the applier writes each view from its own array. + // The pre-rv shape - one Data[4] memcpied into all three views - could not + // reproduce the frontend's NUMERIC cross-view conversion (glVertexAttrib4f(loc, + // 1.5f, ...) leaves 1 in intValue and 0x3FC00000 in floatValue), which is exactly + // why the field stayed EMITTED-AND-STILL-PULLED until rv. ValueClass rides along + // for the comparator; the applier no longer needs it to rebuild anything. + std::memcpy(slot.floatValue.data(), value.FloatView, sizeof(slot.floatValue)); + std::memcpy(slot.intValue.data(), value.IntView, sizeof(slot.intValue)); + std::memcpy(slot.uintValue.data(), value.UintView, sizeof(slot.uintValue)); } if (fault == nullptr && consumed != hdr.Count) { fault = "Count does not match the attributes Mask names"; @@ -1767,6 +1818,9 @@ namespace MobileGL::MG_Pipe { // the twin re-derives its storage flags from the new mask at its next sync and decides // for itself whether the backend needs a recreate. ++record->Serial; + // P5c (rv): a respecify can move a texture's shape, which is exactly what the + // sampling-resolution shutter guarded - the server-side answer moves with it. + MGPipeApplierNoteTextureStateMoved(); // A RESPECIFY REDEFINES A STORE, SO THE PENDING UPLOADS AGAINST THE STORE IT REPLACES // GO WITH IT - AND ONLY THOSE. They are boxes and rects in a level's coordinate system @@ -2025,6 +2079,10 @@ namespace MobileGL::MG_Pipe { const Uint32 gen = record->Gen; *record = MGPipeResourceRecord{}; record->Gen = gen; + // P5c (rv): a destroyed object may still be BOUND somewhere the texture shutters guard; + // the server's answer for them moves with the death rather than waiting for the next + // unit-set record to say so. + MGPipeApplierNoteTextureStateMoved(); // The applier's own state is consistent before the backend hears the news, so a hook // that looked back at this applier could not see a resource that is already gone. The @@ -2700,6 +2758,8 @@ namespace MobileGL::MG_Pipe { // bytes are CARRIED, never cleared here: the server ORs them into its own flags and // clears its own copy, and the client never clears a server flag. ++record->ParamsSerial; + // P5c (rv): a parameter change is what the sampling-resolution shutter guarded. + MGPipeApplierNoteTextureStateMoved(); return true; } @@ -2729,6 +2789,8 @@ namespace MobileGL::MG_Pipe { return; } ++g_applier.SamplerViewsSerial; + // P5c (rv): a texture bind is what the bind-generation shutter guarded. + MGPipeApplierNoteTextureStateMoved(); } void MGPipeApplyBindSamplerStates(const MGPSamplerStates& hdr, const MGPipeHandle* tail) { @@ -2739,6 +2801,8 @@ namespace MobileGL::MG_Pipe { return; } ++g_applier.SamplerStatesSerial; + // P5c (rv): a sampler bind or parameter change is what the shutters guarded. + MGPipeApplierNoteTextureStateMoved(); } void MGPipeApplySetShaderImages(const MGPShaderImages& hdr, const MGPImageView* tail) { @@ -2754,6 +2818,8 @@ namespace MobileGL::MG_Pipe { return; } ++g_applier.ShaderImagesSerial; + // P5c (rv): an image bind moves the texture bind generation's guarded set too. + MGPipeApplierNoteTextureStateMoved(); } // ================================================================================ diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index e15e32fc..7abb38a1 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -693,6 +693,27 @@ namespace MobileGL::MG_Pipe { MGPipeHandle BoundShaderCso = kMGPipeNullHandle; Uint64 ProgramBindingSerial = 0; + // ---- 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 + // a value: the server answers from its own Serial", which their rows have said since + // P5 - and THESE are the serials, read by the PipeInputs accessors under a + // server-stamped verb instead of the client's residual-fill copy. + // + // TextureShutterSerial answers the two GENERATIONS. It is an MGGen like its seven + // siblings above: server-owned, monotone, ++ on every applied record that can move + // what the frontend's bind / sampling-resolution generations guard - the three unit + // sets, set_texture_params, a respecify or destroy of any resource, a bind_shader_image + // verb (the sink bumps it, PipeApplier.cpp) - and ADVANCED, never zeroed, by + // MGPipeApplierReset / MGPipeApplierReleaseObjectRecords. Over-firing is the safe + // direction for a memo key: a moved serial costs a re-sync, a stuck one renders stale. + // + // ContextSerial answers GetTextureContextId: stable within the served context, moved + // by every MGPipeApplierReset (a make-current is a fresh server, §5.1), which is all + // the backends' per-context memo keys need. + Uint64 TextureShutterSerial = 0; + Uint64 ContextSerial = 0; + #if MOBILEGL_BUILD_DISAGGREGATED // ---- P5c (hd, CONTRACT-P5C §3.2): THE CURRENT VERB'S OWN HANDLES. ---------------- // @@ -761,6 +782,17 @@ namespace MobileGL::MG_Pipe { // The monolith's single applier. Under split there is one per served context. MGPipeApplierState& MGPipeApplier(); + // 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 + // bump rule - advance, never zero - is stated once, beside the state. + Uint64 MGPipeApplierTextureShutterSerial(); + Uint64 MGPipeApplierContextSerial(); + // The one writer-side helper: every applier entry point that can move what the frontend's + // texture bind / sampling-resolution generations guard bumps the shutter serial through + // this, so the bump rule lives in exactly one place. + void MGPipeApplierNoteTextureStateMoved(); + // A MAKE-CURRENT, NOT A TEARDOWN - and the distinction is the whole of this function's // contract. It runs on every change of the current GLContext (MGPipeTracker::Update resets // the tracker whenever the context pointer moves, and the emitter calls this from the @@ -829,6 +861,12 @@ namespace MobileGL::MG_Pipe { // set_patch_state. The trio also travels in pipeline chunk P0, and the applier asserts // under verify that the two carriers agree - the redundancy is a trip wire, not waste. void MGPipeApplySetPatchState(const MGPPatchState& patch); + // set_context_values (P5c rv, CONTRACT-P5C.md §5.3): the residual-value record. Writes + // every field it carries into gPipeInputs through MGPipeApplyAccess - the server-owned + // write that retires the eight value-class BARRIER_PULLED rows. Emitted only with an + // active transport; under monolith there is no producer and the fields keep coming + // through the residual fill (G1). + void MGPipeApplySetContextValues(const MGPContextValues& values); // set_vertex_attrib_defaults: `tail` is hdr.Count MGPAttribValues for the attributes // named by hdr.Mask, in ascending location order. void MGPipeApplySetVertexAttribDefaults(const MGPVertexAttribDefaults& hdr, const MGPAttribValue* tail); diff --git a/MobileGL/MG_Pipe/PipeCalls.def b/MobileGL/MG_Pipe/PipeCalls.def index 3c4103fd..4c2ec2c3 100644 --- a/MobileGL/MG_Pipe/PipeCalls.def +++ b/MobileGL/MG_Pipe/PipeCalls.def @@ -45,14 +45,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 17 16 of the 17 set_* calls + the temporary set_residual_value_state +// kCtxState 18 16 of the 18 set_* calls + the temporary set_residual_value_state +// + P5c rv's set_context_values // 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 78 +// total 79 // // 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: @@ -94,10 +95,15 @@ // GL-thread direct call into server memory, and object_death, the framebuffer family's // FIRST wire delete opcode and the handle-keyed replacement for the death-notice mailbox. // Neither has an MGPipeApply* entry point and neither gains one; both reach WireVerbSink. -// 78 unique records. +// - P5c rv (§5.3) appended ONE more (opcode 79): set_context_values, the residual-value +// record that retires the value-class BARRIER_PULLED rows. Unlike the two control records +// it IS an ordinary set_* row: MGPipeApplySetContextValues writes gPipeInputs, the row is +// 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. // --------------------------------------------------------------------------------------- -#define MGP_CALL_LIST_DOCUMENTED_COUNT 78 +#define MGP_CALL_LIST_DOCUMENTED_COUNT 79 // clang-format off #define MGP_CALL_LIST(X) \ @@ -256,7 +262,15 @@ /* 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) \ + /* ---- 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 ---- */ \ + /* ---- five transform-feedback values. Emitted at validate when any covered value moved ---- */ \ + /* ---- (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) // clang-format on // Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"): diff --git a/MobileGL/MG_Pipe/PipeFields.def b/MobileGL/MG_Pipe/PipeFields.def index 1b693073..ebe2df50 100644 --- a/MobileGL/MG_Pipe/PipeFields.def +++ b/MobileGL/MG_Pipe/PipeFields.def @@ -149,7 +149,7 @@ F(ShaderCso) F(Version) F(Blob) #define MGP_FIELDS_MGPAttribValue(F) \ - F(Location) F(ValueClass) F(Data) + F(Location) F(ValueClass) F(FloatView) F(IntView) F(UintView) #define MGP_FIELDS_MGPVertexAttribDefaults(F) \ F(Mask) F(Count) @@ -268,6 +268,14 @@ #define MGP_FIELDS_MGPApplierReset(F) \ F(ContextSerial) +// P5c rv (CONTRACT-P5C.md §5.3): set_context_values' POD, one row per member. The 15-entry +// array is one field, exactly as MGPFramebufferState's Color[8] is one. +#define MGP_FIELDS_MGPContextValues(F) \ + F(ActiveTextureUnit) F(MaxTouchedTextureUnit) F(TouchedBufferBindingPointCount) \ + F(IsTransformFeedbackActive) F(IsTransformFeedbackPaused) \ + F(TransformFeedbackGeneration) F(BoundTransformFeedbackLifetimeId) \ + F(TransformFeedbackCapturedVertices) + // ---- the value structs and the host span (P1 brief D8). Not call payloads themselves, but // members of ones (ResidualValueBlock, MGPPixelPackState, MGPCaps) and of PipeInputs, so the // comparator has to see INTO them: with these lists the memcmp fallback of MGPipeFieldEqual is @@ -385,7 +393,7 @@ P(MGPSurfaceInfo) \ P(MGPImageBind) P(MGPPatchParameter) P(MGPStreamOutputBind) P(MGPStorageBlockBinding) \ P(MGPCopyFromFramebuffer) \ - P(MGPApplierReset) \ + P(MGPApplierReset) P(MGPContextValues) \ P(RenderStateParameters) P(PixelStoreParameters) P(SamplerParameters) P(PerBufferBlendState) \ P(StencilFaceState) \ P(DynamicBackendParameters) P(MGHostSpan) \ diff --git a/MobileGL/MG_Pipe/PipeRoute.cpp b/MobileGL/MG_Pipe/PipeRoute.cpp index ff67bbb0..a9b4e359 100644 --- a/MobileGL/MG_Pipe/PipeRoute.cpp +++ b/MobileGL/MG_Pipe/PipeRoute.cpp @@ -6,7 +6,7 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header -// The MONOLITH arm of R-17's routing: thirty-seven adapters that unpack a generated table +// The MONOLITH arm of R-17's routing: thirty-eight adapters that unpack a generated table // row's parameters and call the MGPipeApply* entry point the call site used to call directly. // Owner: package c1. See PipeRoute.h for why the arm exists and what R-17 actually cost. // @@ -164,11 +164,11 @@ namespace MobileGL::MG_Pipe { Bool MGPipeTablesAreInstalled() { return g_arm != MGPipeRouteArm::kNone; } // --------------------------------------------------------------------------------- - // The thirty-seven monolith adapters + // The thirty-eight monolith adapters // --------------------------------------------------------------------------------- // // Three shapes, so the eye can check them against PipeTables.inc in one pass rather than - // reading thirty-seven bodies. A row that does not fit one of the three is written out by + // reading thirty-eight bodies. A row that does not fit one of the three is written out by // hand BELOW the macros, never by widening a macro - a macro that grew a special case is // how one of these silently stops being a parameter shuffle. @@ -207,6 +207,11 @@ namespace MobileGL::MG_Pipe { MGP_MONO_PLAIN(SetIndexBuffer, MGPIndexBuffer) MGP_MONO_PLAIN(SetPixelPackState, MGPPixelPackState) MGP_MONO_PLAIN(SetPatchState, MGPPatchState) + // P5c (rv): dormant under monolith - PipeFill.cpp's producer is transport-gated, so + // nothing routes here without a live wire. It exists because the row has an + // MGPipeApply* entry point (unlike the two control records) and because the wire + // emitter's server-role arm forwards to this table (WireTables.cpp). + MGP_MONO_PLAIN(SetContextValues, MGPContextValues) // -- context, blob companion -------------------------------------------------- MGP_MONO_BLOB(CreateRenderState, MGPRenderStateDesc) @@ -341,6 +346,7 @@ namespace MobileGL::MG_Pipe { gMGPipeContext.SetVertexAttribDefaults = &Mono_SetVertexAttribDefaults; gMGPipeContext.SetPixelPackState = &Mono_SetPixelPackState; gMGPipeContext.SetPatchState = &Mono_SetPatchState; + gMGPipeContext.SetContextValues = &Mono_SetContextValues; gMGPipeContext.SetResidualValueState = &Mono_SetResidualValueState; gMGPipeContext.SetTextureParams = &Mono_SetTextureParams; gMGPipeContext.ResourceSubData = &Mono_ResourceSubData; diff --git a/MobileGL/MG_Pipe/PipeRoute.h b/MobileGL/MG_Pipe/PipeRoute.h index 5dee9a84..7e794bf9 100644 --- a/MobileGL/MG_Pipe/PipeRoute.h +++ b/MobileGL/MG_Pipe/PipeRoute.h @@ -6,7 +6,8 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header -// THE CLIENT -> WIRE ROUTING OF THE 37 MGPipeApply* ENTRY POINTS (P5, integrator ruling R-17). +// THE CLIENT -> WIRE ROUTING OF THE 38 MGPipeApply* ENTRY POINTS (P5, integrator ruling R-17; +// 38 since P5c rv added set_context_values, CONTRACT-P5C.md section 5.3). // Owner: package c1. // // WHAT WAS MISSING. `gMGPipeWireRecordApply` is the DECODE hook and it has existed since w1: @@ -218,7 +219,7 @@ namespace MobileGL::MG_Pipe { void MGPipeNoteInstalledArm(MGPipeRouteArm arm); // --------------------------------------------------------------------------------- - // The call-site names: MGPipeRoute for each of the thirty-seven + // The call-site names: MGPipeRoute for each of the thirty-eight // --------------------------------------------------------------------------------- // // EVERY ONE TAKES THE APPLIER'S OWN SIGNATURE, so converting a call site is a rename and @@ -307,6 +308,14 @@ namespace MobileGL::MG_Pipe { MGP_SetPixelPackState(&pack); } inline void MGPipeRouteSetPatchState(const MGPPatchState& patch) { MGP_SetPatchState(&patch); } + // P5c (rv, CONTRACT-P5C.md §5.3): emitted ONLY with an active transport (PipeFill.cpp + // gates on it); under monolith the row is never produced and the fields keep coming + // through the residual fill (G1). The row IS an ordinary routed set_* otherwise - the + // monolith adapter exists so the wire emitter's server-role arm has something defined to + // forward to, and the catalogue's null-row accounting (PipeCatalogueTest) counts it. + inline void MGPipeRouteSetContextValues(const MGPContextValues& values) { + MGP_SetContextValues(&values); + } inline void MGPipeRouteSetVertexAttribDefaults(const MGPVertexAttribDefaults& hdr, const MGPAttribValue* tail) { MGP_SetVertexAttribDefaults(&hdr, tail, hdr.Count); diff --git a/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc b/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc index cbccb9ad..46835c98 100644 --- a/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc +++ b/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc @@ -34,7 +34,7 @@ inline constexpr const char* kMGPipeFieldOwnershipNames[] = { }; inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCount] = { - MGPipeFieldOwnership::kBarrierPulled, // GetActiveTextureUnit + MGPipeFieldOwnership::kRecordSupplied, // GetActiveTextureUnit MGPipeFieldOwnership::kRecordSupplied, // GetBlendColor MGPipeFieldOwnership::kRecordSupplied, // GetBlendEquationIndexed MGPipeFieldOwnership::kRecordSupplied, // GetBlendFuncIndexed @@ -43,14 +43,14 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingSlot MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingPoint MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingPointCount - MGPipeFieldOwnership::kBarrierPulled, // GetTouchedBufferBindingPointCount + MGPipeFieldOwnership::kRecordSupplied, // GetTouchedBufferBindingPointCount MGPipeFieldOwnership::kRecordSupplied, // GetClampReadColor MGPipeFieldOwnership::kRecordSupplied, // GetClearColor MGPipeFieldOwnership::kRecordSupplied, // GetClearDepth MGPipeFieldOwnership::kRecordSupplied, // GetClearStencil MGPipeFieldOwnership::kRecordSupplied, // GetColorMaskIndexed MGPipeFieldOwnership::kRecordSupplied, // GetCullFaceMode - MGPipeFieldOwnership::kBarrierPulled, // GetCurrentVertexAttribute + MGPipeFieldOwnership::kRecordSupplied, // GetCurrentVertexAttribute MGPipeFieldOwnership::kRecordSupplied, // GetDepthFunc MGPipeFieldOwnership::kRecordSupplied, // GetDepthMask MGPipeFieldOwnership::kRecordSupplied, // GetDepthRangeIndexed @@ -58,7 +58,7 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou MGPipeFieldOwnership::kBarrierPulled, // GetImageTextureBinding MGPipeFieldOwnership::kRecordSupplied, // GetLineWidth MGPipeFieldOwnership::kRecordSupplied, // GetLogicOp - MGPipeFieldOwnership::kBarrierPulled, // GetMaxTouchedTextureUnit + MGPipeFieldOwnership::kRecordSupplied, // GetMaxTouchedTextureUnit MGPipeFieldOwnership::kRecordSupplied, // GetMinSampleShadingValue MGPipeFieldOwnership::kRecordSupplied, // GetPatchDefaultInnerLevel MGPipeFieldOwnership::kRecordSupplied, // GetPatchDefaultOuterLevel @@ -75,33 +75,33 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou MGPipeFieldOwnership::kRecordSupplied, // GetProvokingVertexMode MGPipeFieldOwnership::kRecordSupplied, // GetRenderStateParameters MGPipeFieldOwnership::kRecordSupplied, // GetRenderStateParametersVersion - MGPipeFieldOwnership::kBarrierPulled, // GetSamplingResolutionGeneration + MGPipeFieldOwnership::kApplierDerived, // GetSamplingResolutionGeneration MGPipeFieldOwnership::kRecordSupplied, // GetScissorBox MGPipeFieldOwnership::kRecordSupplied, // GetStencilState - MGPipeFieldOwnership::kBarrierPulled, // GetTextureBindGeneration - MGPipeFieldOwnership::kBarrierPulled, // GetTextureContextId + MGPipeFieldOwnership::kApplierDerived, // GetTextureBindGeneration + MGPipeFieldOwnership::kApplierDerived, // GetTextureContextId MGPipeFieldOwnership::kBarrierPulled, // GetTextureObject MGPipeFieldOwnership::kBarrierPulled, // GetTextureUnitObject - MGPipeFieldOwnership::kBarrierPulled, // GetTransformFeedbackCapturedVertices - MGPipeFieldOwnership::kBarrierPulled, // GetTransformFeedbackGeneration + MGPipeFieldOwnership::kRecordSupplied, // GetTransformFeedbackCapturedVertices + MGPipeFieldOwnership::kRecordSupplied, // GetTransformFeedbackGeneration MGPipeFieldOwnership::kFatal, // GetTransformFeedbackPausedPrimitiveCounter MGPipeFieldOwnership::kBarrierPulled, // GetTransformFeedbackProgram MGPipeFieldOwnership::kRecordSupplied, // GetViewport MGPipeFieldOwnership::kRecordSupplied, // GetViewportIndexed MGPipeFieldOwnership::kRecordSupplied, // IsCapabilityEnabled MGPipeFieldOwnership::kRecordSupplied, // IsCapabilityEnabledIndexed - MGPipeFieldOwnership::kBarrierPulled, // IsTransformFeedbackActive - MGPipeFieldOwnership::kBarrierPulled, // IsTransformFeedbackPaused + MGPipeFieldOwnership::kRecordSupplied, // IsTransformFeedbackActive + MGPipeFieldOwnership::kRecordSupplied, // IsTransformFeedbackPaused MGPipeFieldOwnership::kFatal, // InvalidateCompileEnv MGPipeFieldOwnership::kBarrierPulled, // ValidateProgramName MGPipeFieldOwnership::kBarrierPulled, // RecordError - MGPipeFieldOwnership::kBarrierPulled, // GetBoundTransformFeedbackLifetimeId + MGPipeFieldOwnership::kRecordSupplied, // GetBoundTransformFeedbackLifetimeId MGPipeFieldOwnership::kBarrierPulled, // HasOpenTransformFeedbackSpan }; // The ROADMAP phase whose row retires the pull. "-" for every class but BARRIER-PULLED. inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = { - "P3b/P4b", // GetActiveTextureUnit + "-", // GetActiveTextureUnit "-", // GetBlendColor "-", // GetBlendEquationIndexed "-", // GetBlendFuncIndexed @@ -110,14 +110,14 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = "P8 (indirect), P9 (readback), P13 (transfer)", // GetBufferBindingSlot "P3b/P4b, P7", // GetBufferBindingPoint "P7/P13", // GetBufferBindingPointCount - "P3b/P4b", // GetTouchedBufferBindingPointCount + "-", // GetTouchedBufferBindingPointCount "-", // GetClampReadColor "-", // GetClearColor "-", // GetClearDepth "-", // GetClearStencil "-", // GetColorMaskIndexed "-", // GetCullFaceMode - "P3b/P4b", // GetCurrentVertexAttribute + "-", // GetCurrentVertexAttribute "-", // GetDepthFunc "-", // GetDepthMask "-", // GetDepthRangeIndexed @@ -125,7 +125,7 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = "P3b/P4b, P7", // GetImageTextureBinding "-", // GetLineWidth "-", // GetLogicOp - "P3b/P4b", // GetMaxTouchedTextureUnit + "-", // GetMaxTouchedTextureUnit "-", // GetMinSampleShadingValue "-", // GetPatchDefaultInnerLevel "-", // GetPatchDefaultOuterLevel @@ -142,27 +142,27 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] = "-", // GetProvokingVertexMode "-", // GetRenderStateParameters "-", // GetRenderStateParametersVersion - "P3b/P4b", // GetSamplingResolutionGeneration + "-", // GetSamplingResolutionGeneration "-", // GetScissorBox "-", // GetStencilState - "P3b/P4b", // GetTextureBindGeneration - "P3b/P4b", // GetTextureContextId + "-", // GetTextureBindGeneration + "-", // GetTextureContextId "P7", // GetTextureObject "P3b/P4b, P7", // GetTextureUnitObject - "P3b/P4b (Espryt), P7 (Magma)", // GetTransformFeedbackCapturedVertices - "P3b/P4b (Espryt), P7 (Magma)", // GetTransformFeedbackGeneration + "-", // GetTransformFeedbackCapturedVertices + "-", // GetTransformFeedbackGeneration "-", // GetTransformFeedbackPausedPrimitiveCounter "P3b/P4b (Espryt), P7 (Magma)", // GetTransformFeedbackProgram "-", // GetViewport "-", // GetViewportIndexed "-", // IsCapabilityEnabled "-", // IsCapabilityEnabledIndexed - "P3b/P4b (Espryt), P7 (Magma)", // IsTransformFeedbackActive - "P3b/P4b (Espryt), P7 (Magma)", // IsTransformFeedbackPaused + "-", // IsTransformFeedbackActive + "-", // IsTransformFeedbackPaused "-", // InvalidateCompileEnv "P9", // ValidateProgramName "P9", // RecordError - "P3b/P4b (Espryt), P7 (Magma)", // GetBoundTransformFeedbackLifetimeId + "-", // GetBoundTransformFeedbackLifetimeId "P7/P9", // HasOpenTransformFeedbackSpan }; @@ -301,8 +301,8 @@ inline constexpr SizeT kMGPipeVerbBoundaryOpCount = 23; inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3; // The class sizes, as constants a test can pin without recounting the table. -inline constexpr SizeT kMGPipeRecordSuppliedFieldCount = 32; -inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 1; -inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 27; +inline constexpr SizeT kMGPipeRecordSuppliedFieldCount = 41; +inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 4; +inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 15; inline constexpr SizeT kMGPipeFatalFieldCount = 3; static_assert(kMGPipeRecordSuppliedFieldCount + kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount == kMGPipeInputFieldCount, "the four class sizes do not partition the field set"); diff --git a/MobileGL/MG_Pipe/generated/PipeFilled.inc b/MobileGL/MG_Pipe/generated/PipeFilled.inc index ca9014fb..e109b551 100644 --- a/MobileGL/MG_Pipe/generated/PipeFilled.inc +++ b/MobileGL/MG_Pipe/generated/PipeFilled.inc @@ -308,6 +308,7 @@ enum class MGPipeFieldEmitter : Uint8 { BindRenderState, BindVertexElements, CreateRenderState, + SetContextValues, SetDispatchProgram, SetDrawProgram, SetDynamicState, @@ -323,6 +324,7 @@ inline constexpr const char* kMGPipeFieldEmitterNames[] = { "BindRenderState", "BindVertexElements", "CreateRenderState", + "SetContextValues", "SetDispatchProgram", "SetDrawProgram", "SetDynamicState", @@ -334,7 +336,7 @@ inline constexpr const char* kMGPipeFieldEmitterNames[] = { }; inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount] = { - MGPipeFieldEmitter::kNone, // GetActiveTextureUnit + MGPipeFieldEmitter::SetContextValues, // GetActiveTextureUnit MGPipeFieldEmitter::SetDynamicState, // GetBlendColor MGPipeFieldEmitter::CreateRenderState, // GetBlendEquationIndexed MGPipeFieldEmitter::CreateRenderState, // GetBlendFuncIndexed @@ -343,7 +345,7 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount MGPipeFieldEmitter::kNone, // GetBufferBindingSlot MGPipeFieldEmitter::kNone, // GetBufferBindingPoint MGPipeFieldEmitter::kNone, // GetBufferBindingPointCount - MGPipeFieldEmitter::kNone, // GetTouchedBufferBindingPointCount + MGPipeFieldEmitter::SetContextValues, // GetTouchedBufferBindingPointCount MGPipeFieldEmitter::SetDynamicState, // GetClampReadColor MGPipeFieldEmitter::SetDynamicState, // GetClearColor MGPipeFieldEmitter::SetDynamicState, // GetClearDepth @@ -358,7 +360,7 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount MGPipeFieldEmitter::SetShaderImages, // GetImageTextureBinding MGPipeFieldEmitter::SetDynamicState, // GetLineWidth MGPipeFieldEmitter::CreateRenderState, // GetLogicOp - MGPipeFieldEmitter::SetSamplerViews, // GetMaxTouchedTextureUnit + MGPipeFieldEmitter::SetContextValues, // GetMaxTouchedTextureUnit MGPipeFieldEmitter::CreateRenderState, // GetMinSampleShadingValue MGPipeFieldEmitter::SetPatchState, // GetPatchDefaultInnerLevel MGPipeFieldEmitter::SetPatchState, // GetPatchDefaultOuterLevel @@ -382,23 +384,23 @@ inline constexpr MGPipeFieldEmitter kMGPipeFieldEmittedBy[kMGPipeInputFieldCount MGPipeFieldEmitter::kNone, // GetTextureContextId MGPipeFieldEmitter::kNone, // GetTextureObject MGPipeFieldEmitter::SetSamplerViews, // GetTextureUnitObject - MGPipeFieldEmitter::kNone, // GetTransformFeedbackCapturedVertices - MGPipeFieldEmitter::kNone, // GetTransformFeedbackGeneration + MGPipeFieldEmitter::SetContextValues, // GetTransformFeedbackCapturedVertices + MGPipeFieldEmitter::SetContextValues, // GetTransformFeedbackGeneration MGPipeFieldEmitter::kNone, // GetTransformFeedbackPausedPrimitiveCounter MGPipeFieldEmitter::kNone, // GetTransformFeedbackProgram MGPipeFieldEmitter::SetDynamicState, // GetViewport MGPipeFieldEmitter::SetDynamicState, // GetViewportIndexed MGPipeFieldEmitter::CreateRenderState, // IsCapabilityEnabled MGPipeFieldEmitter::CreateRenderState, // IsCapabilityEnabledIndexed - MGPipeFieldEmitter::kNone, // IsTransformFeedbackActive - MGPipeFieldEmitter::kNone, // IsTransformFeedbackPaused + MGPipeFieldEmitter::SetContextValues, // IsTransformFeedbackActive + MGPipeFieldEmitter::SetContextValues, // IsTransformFeedbackPaused MGPipeFieldEmitter::kNone, // InvalidateCompileEnv MGPipeFieldEmitter::kNone, // ValidateProgramName MGPipeFieldEmitter::kNone, // RecordError - MGPipeFieldEmitter::kNone, // GetBoundTransformFeedbackLifetimeId + MGPipeFieldEmitter::SetContextValues, // GetBoundTransformFeedbackLifetimeId MGPipeFieldEmitter::kNone, // HasOpenTransformFeedbackSpan }; -inline constexpr SizeT kMGPipeEmittedFieldCount = 40; +inline constexpr SizeT kMGPipeEmittedFieldCount = 47; struct MGPipeFilledState { Uint64 CurrentVerbSerial; diff --git a/MobileGL/MG_Pipe/generated/PipeTables.inc b/MobileGL/MG_Pipe/generated/PipeTables.inc index 3ca5fde5..efa9fa4e 100644 --- a/MobileGL/MG_Pipe/generated/PipeTables.inc +++ b/MobileGL/MG_Pipe/generated/PipeTables.inc @@ -29,7 +29,7 @@ struct MGPipeScreen { void (*ApplierReset)(const MGPApplierReset* payload); }; -// context: 66 calls. A null entry means the backend does not implement this +// context: 67 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); @@ -98,11 +98,12 @@ struct MGPipeContext { void (*SetStorageBlockBinding)(const MGPStorageBlockBinding* payload, const void* blobBytes, Uint64 blobByteCount); void (*CopyFramebufferToTexture)(const MGPCopyFromFramebuffer* payload); void (*ObjectDeath)(const MGPHandleOnly* payload); + void (*SetContextValues)(const MGPContextValues* payload); }; inline constexpr SizeT kMGPipeScreenCallCount = 12; -inline constexpr SizeT kMGPipeContextCallCount = 66; -inline constexpr SizeT kMGPipeCallCount = 78; +inline constexpr SizeT kMGPipeContextCallCount = 67; +inline constexpr SizeT kMGPipeCallCount = 79; // A table that is not exactly its call count of function pointers has grown a // member that no generator knows about. diff --git a/MobileGL/MG_Pipe/generated/PipeThunks.inc b/MobileGL/MG_Pipe/generated/PipeThunks.inc index 81740bed..0139b3a7 100644 --- a/MobileGL/MG_Pipe/generated/PipeThunks.inc +++ b/MobileGL/MG_Pipe/generated/PipeThunks.inc @@ -328,3 +328,7 @@ inline void MGP_ApplierReset(const MGPApplierReset* payload) { inline void MGP_ObjectDeath(const MGPHandleOnly* payload) { gMGPipeContext.ObjectDeath(payload); } + +inline void MGP_SetContextValues(const MGPContextValues* payload) { + gMGPipeContext.SetContextValues(payload); +} diff --git a/MobileGL/MG_Pipe/generated/PipeVerify.inc b/MobileGL/MG_Pipe/generated/PipeVerify.inc index 2ebe220e..dd9ce0fb 100644 --- a/MobileGL/MG_Pipe/generated/PipeVerify.inc +++ b/MobileGL/MG_Pipe/generated/PipeVerify.inc @@ -113,6 +113,7 @@ inline Bool MGPipeVerify(const MGPStreamOutputBind& a, const MGPStreamOutputBind inline Bool MGPipeVerify(const MGPStorageBlockBinding& a, const MGPStorageBlockBinding& b, const char** outField); 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 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); @@ -262,6 +263,8 @@ struct MGPipeHasFieldVerifier : std::true_type {}; template <> struct MGPipeHasFieldVerifier : std::true_type {}; template <> +struct MGPipeHasFieldVerifier : std::true_type {}; +template <> struct MGPipeHasFieldVerifier : std::true_type {}; template <> struct MGPipeHasFieldVerifier : std::true_type {}; @@ -670,6 +673,11 @@ inline Bool MGPipeVerify(const MGPApplierReset& a, const MGPApplierReset& b, con return true; } +inline Bool MGPipeVerify(const MGPContextValues& a, const MGPContextValues& b, const char** outField) { + MGP_FIELDS_MGPContextValues(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; @@ -717,4 +725,4 @@ inline Bool MGPipeVerify(const MGPVertexBindingPointWire& a, const MGPVertexBind #undef MGP_VERIFY_FIELD -inline constexpr SizeT kMGPipeVerifiedPayloadCount = 78; +inline constexpr SizeT kMGPipeVerifiedPayloadCount = 79; diff --git a/MobileGL/MG_Pipe/generated/PipeWire.inc b/MobileGL/MG_Pipe/generated/PipeWire.inc index f5faaa2c..fd9ea6a0 100644 --- a/MobileGL/MG_Pipe/generated/PipeWire.inc +++ b/MobileGL/MG_Pipe/generated/PipeWire.inc @@ -148,7 +148,8 @@ enum class MGPWireOp : Uint16 { CopyFramebufferToTexture = 76, ApplierReset = 77, ObjectDeath = 78, - kOpCount = 79, + SetContextValues = 79, + kOpCount = 80, }; // THE FLAGS, EXPORTED ONCE, INDEXED BY OPCODE (P5 R-13.4). MGPWireRecHeader::Flags is @@ -248,6 +249,7 @@ inline constexpr Uint32 kMGPipeCallFlags[static_cast(MGPWireOp::kOpCount) /* 76 CopyFramebufferToTexture*/ static_cast(kNone), /* 77 ApplierReset */ static_cast(kNone), /* 78 ObjectDeath */ static_cast(kNone), + /* 79 SetContextValues */ static_cast(kNone), }; static_assert(sizeof(kMGPipeCallFlags) / sizeof(kMGPipeCallFlags[0]) == static_cast(MGPWireOp::kOpCount), @@ -925,6 +927,14 @@ static_assert(sizeof(MGPWireRec_ObjectDeath) == ((sizeof(MGPWireRecHeader) + sizeof(MGPHandleOnly) + 7u) & ~SizeT(7u)), "MGPWireRec_ObjectDeath gained padding; the wire format moved"); +struct alignas(8) MGPWireRec_SetContextValues { + MGPWireRecHeader Header; + MGPContextValues Payload; +}; +static_assert(sizeof(MGPWireRec_SetContextValues) == + ((sizeof(MGPWireRecHeader) + sizeof(MGPContextValues) + 7u) & ~SizeT(7u)), + "MGPWireRec_SetContextValues 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(size), static_cast(remaining)); @@ -1208,6 +1218,9 @@ inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size, case MGPWireOp::ObjectDeath: MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ObjectDeath, "ObjectDeath"); break; + case MGPWireOp::SetContextValues: + MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetContextValues, "SetContextValues"); + break; case MGPWireOp::kInvalid: case MGPWireOp::kOpCount: default: diff --git a/MobileGL/MG_Remote/Client/WireTables.cpp b/MobileGL/MG_Remote/Client/WireTables.cpp index fbd9c557..ca9fe284 100644 --- a/MobileGL/MG_Remote/Client/WireTables.cpp +++ b/MobileGL/MG_Remote/Client/WireTables.cpp @@ -6,12 +6,12 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header -// The ENCODE TWIN of gMGPipeWireRecordApply: thirty-seven emitters that turn a table call into +// The ENCODE TWIN of gMGPipeWireRecordApply: thirty-eight emitters that turn a table call into // a wire record. Owner: package c1 (P5 ruling R-17). See WireTables.h for the install order // and MG_Pipe/PipeRoute.h for what R-17 actually cost. // // EVERY EMITTER IS THE SAME FOUR STEPS, and the macros below exist so that a reader can check -// thirty-seven rows against PipeTables.inc in one pass instead of reading thirty-seven bodies: +// thirty-eight rows against PipeTables.inc in one pass instead of reading thirty-eight bodies: // // 1. require a session - a slot that fell through to a driver this role does not have is // the failure R-4 exists to prevent, and there is no fall-through here either; @@ -22,7 +22,7 @@ // 4. post the answer, for the rows that have one, into MG_Pipe's reply mailbox. // // WHAT IS DELIBERATELY NOT HERE. b1's `PushPersistentMapsBeforeVerb` / `MarkGpuWritesFor*` are -// NOT called from these thirty-seven. They are pre-VERB hooks and these are not verbs: they +// NOT called from these thirty-eight. They are pre-VERB hooks and these are not verbs: they // are the resource, CSO and state records that a verb is later drawn against. The five class-B // verbs in EmitTables.cpp call them, once each, immediately before their record, which is the // ordering b1's B-1 fix depends on. Calling them here as well would push a persistent map @@ -196,6 +196,11 @@ namespace MobileGL::MG_Remote::Client { MGP_WIRE_PLAIN(SetIndexBuffer, MGPIndexBuffer, Context) MGP_WIRE_PLAIN(SetPixelPackState, MGPPixelPackState, Context) MGP_WIRE_PLAIN(SetPatchState, MGPPatchState, Context) + // P5c (rv), CONTRACT-P5C.md §5.3: the residual-value record is an ordinary routed + // set_* row - a fixed POD, no blob, no tail, no reply. The PRODUCER is transport-gated + // (PipeFill.cpp's EmitContextValues), so under monolith this wrapper is never reached; + // the server-role arm forwards to the monolith adapter exactly like its siblings. + MGP_WIRE_PLAIN(SetContextValues, MGPContextValues, Context) // -- context, mandatory blob -------------------------------------------------- MGP_WIRE_BLOB(CreateRenderState, MGPRenderStateDesc, Blob) @@ -541,6 +546,17 @@ namespace MobileGL::MG_Remote::Client { return true; } + // P5c (rv), CONTRACT-P5C.md §5.3. Whether set_context_values can cross RIGHT NOW: a live, + // started session whose tables are not being torn down. PipeFill.cpp gates BOTH halves of + // the row on this - the emission and the residual-fill skip - so a configured-but-wireless + // transport (the bring-up window, a server-role-only fixture) keeps the pull, and the two + // can never disagree about who supplies the eight fields. + Bool ContextValuesWireLive() { + ClientSession* session = ClientSession::Active(); + return session != nullptr && session->Started() && + !g_clientTablesUninstalled.load(std::memory_order_acquire); + } + ObjectDeathEmit EmitObjectDeathRecord(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId) { using MG_Pipe::MGPipeHandle; @@ -628,6 +644,7 @@ namespace MobileGL::MG_Remote::Client { gMGPipeContext.SetVertexAttribDefaults = &Wire_SetVertexAttribDefaults; gMGPipeContext.SetPixelPackState = &Wire_SetPixelPackState; gMGPipeContext.SetPatchState = &Wire_SetPatchState; + gMGPipeContext.SetContextValues = &Wire_SetContextValues; gMGPipeContext.SetResidualValueState = &Wire_SetResidualValueState; gMGPipeContext.SetTextureParams = &Wire_SetTextureParams; gMGPipeContext.ResourceSubData = &Wire_ResourceSubData; diff --git a/MobileGL/MG_Remote/Client/WireTables.h b/MobileGL/MG_Remote/Client/WireTables.h index ffffe0de..69cf4d2a 100644 --- a/MobileGL/MG_Remote/Client/WireTables.h +++ b/MobileGL/MG_Remote/Client/WireTables.h @@ -8,7 +8,7 @@ // THE CLIENT ARM OF R-17's ROUTING: the encode twin of `gMGPipeWireRecordApply`. Owner: c1. // -// Thirty-seven thin emitters over `ClientSession::EmitAndWait`, installed over the two +// Thirty-eight thin emitters over `ClientSession::EmitAndWait`, installed over the two // generated tables and the escape table that `MG_Pipe/PipeRoute.h` declares, so that under // split every resource, CSO, texture and program record leaves the GL thread as a WIRE RECORD // instead of executing synchronously against a context the apply thread now owns. @@ -41,7 +41,7 @@ namespace MobileGL::MG_Remote::Client { - // Installs the thirty-seven wire emitters over gMGPipeScreen / gMGPipeContext / + // Installs the thirty-eight wire emitters over gMGPipeScreen / gMGPipeContext / // gMGPipeRouteEscapes and records the arm. Idempotent. void InstallClientWireTables(); @@ -59,7 +59,7 @@ namespace MobileGL::MG_Remote::Client { // deletes that reach a process with no session run the applier as they do under monolith. void ReinstallMonolithAfterTeardown(); - // How many records the thirty-seven emitters have published. It counts the ROUTED rows + // How many records the thirty-eight emitters have published. It counts the ROUTED rows // only - a resource_create, a set_vertex_buffers, a create_shader_state - and never the // five class-B verbs, so it is the one number that says "the resource/CSO/state path really // ran" as opposed to "a Clear crossed". @@ -106,6 +106,12 @@ namespace MobileGL::MG_Remote::Client { // caller then makes the direct call the record replaced. Bool EmitApplierResetRecord(); + // P5c (rv), CONTRACT-P5C.md §5.3: whether set_context_values can cross right now (a live, + // started session, tables not being torn down). PipeFill.cpp gates the record's emission + // AND the residual-fill skip for its eight fields on this one answer, so the two halves + // can never disagree about who supplies them. + Bool ContextValuesWireLive(); + // The three answers a death notice can produce (§5.2): the record crossed; the client's // own allocator cannot resolve the dying object (the server never saw it, so there is no // twin to kill and NOTHING crossed); or no live session could carry the record. The diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 4e91e0b5..3eb09c5d 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -783,6 +783,11 @@ namespace MobileGL::MG_Remote::Server { static_cast(bind.Access), static_cast(bind.Format)); ++m_imageBinds; + // P5c (rv): an image bind moves the frontend's texture bind generation, and this verb + // carries no set_shader_images alongside it - so the server-side shutter serial the + // accessors now answer with (CONTRACT-P5C.md §5.3) moves here, at the one place the + // event reaches the applier's side. + MG_Pipe::MGPipeApplierNoteTextureStateMoved(); return true; } diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp index 82f76a52..fabec6c9 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp @@ -241,7 +241,8 @@ namespace MobileGL::MG_Remote::Wire { X(SetStorageBlockBinding, MGPStorageBlockBinding) \ X(CopyFramebufferToTexture, MGPCopyFromFramebuffer) \ X(ApplierReset, MGPApplierReset) \ - X(ObjectDeath, MGPHandleOnly) + X(ObjectDeath, MGPHandleOnly) \ + X(SetContextValues, MGPContextValues) namespace { @@ -2101,6 +2102,17 @@ namespace MobileGL::MG_Remote::Wire { return m_verbs != nullptr && m_verbs->OnObjectDeath(*static_cast(payload)); + // ---- P5c rv (CONTRACT-P5C.md §5.3): the residual-value record, opcode 79 ------------- + // + // An ordinary set_* row, unlike the two control records beside it: a fixed-width POD + // with no blob, no tail and no reply, so the bounds gate is the whole validation and + // the arm is the applier entry point - the same shape as set_pixel_pack_state beside + // it. There is no sink half and no acceptance answer: the write into gPipeInputs is + // the whole effect. + case MGPWireOp::SetContextValues: + MGPipeApplySetContextValues(*static_cast(payload)); + return true; + case MGPWireOp::SetSwapInterval: // Class C, wave 3 (census-classC.md "static cross"); not a verb (FillPoints.def:21). return false; diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 079fb662..db2d9519 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -256,8 +256,9 @@ namespace MobileGL { // four words on a wire are not the value unless the class travels with them: // glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in intValue and 0x3FC00000 in // floatValue. set_vertex_attrib_defaults carries this as MGPAttribValue's - // ValueClass so the applier can redo the conversion instead of memcpying one - // view into all three. + // ValueClass AND, since P5c rv (CONTRACT-P5C.md §5.3), all three views + // verbatim - the applier writes each view from its own array rather than + // redoing the conversion. // // It is kept BESIDE the array rather than inside CurrentVertexAttributeValue // because that struct is mirrored into PipeInputs and compared there by a diff --git a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp index 90168c7c..c1ebcb9f 100644 --- a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp +++ b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp @@ -197,11 +197,15 @@ TEST_F(FieldOwnershipTest, TheClassSizesPartitionTheFieldSet) { EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kBarrierPulled)], kMGPipeBarrierPulledFieldCount); EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kFatal)], kMGPipeFatalFieldCount); - // The census's own arithmetic (scout-unmigrated-census section 2.1): 31 of the 63 fields - // are served by NO pushed record - 24 non-sticky plus the seven sticky - so 32 are. - EXPECT_EQ(kMGPipeRecordSuppliedFieldCount, SizeT{32}); + // The census's own arithmetic (scout-unmigrated-census section 2.1), updated by P5c rv + // (CONTRACT-P5C.md §5.3): rv moved the NINE value-class rows to RECORD-SUPPLIED through + // set_context_values / the amended set_vertex_attrib_defaults and the three texture + // shutters to APPLIER-DERIVED, so 22 of the 63 fields are served by NO pushed record - + // 15 BARRIER-PULLED (the object class: nine non-sticky rows plus six of the seven sticky + // forwards), four APPLIER-DERIVED and three FATAL - and 41 are. + EXPECT_EQ(kMGPipeRecordSuppliedFieldCount, SizeT{41}); EXPECT_EQ(kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount, - SizeT{31}); + SizeT{22}); } TEST_F(FieldOwnershipTest, EveryBarrierPulledRowNamesTheRetiringPhase) { @@ -217,36 +221,56 @@ TEST_F(FieldOwnershipTest, EveryBarrierPulledRowNamesTheRetiringPhase) { } // The 21 the reduced path actually reads (scout-unmigrated-census section 3: the union of -// kClear's 7, kDraw's 19 and kReadback's 12). Twenty of them are BARRIER-PULLED; the -// twenty-first is GetPixelStoreParameters, whose PACK half the applier writes and whose UNPACK -// half has no carrier and no backend reader at all. +// kClear's 7, kDraw's 19 and kReadback's 12) - AS P5c rv LEFT THEM (CONTRACT-P5C.md §5.3): +// NINE moved to RECORD-SUPPLIED through set_context_values (the two texture-unit counters, +// the touched-count array, the five XFB values) plus GetCurrentVertexAttribute through the +// amended set_vertex_attrib_defaults payload, and the three texture shutters moved to +// APPLIER-DERIVED ("a shutter, not a value: the server answers from its own Serial"). What +// remains BARRIER-PULLED is EXACTLY the object class - nine non-sticky fields whose storage +// is a frontend heap reference no record can carry - plus GetPixelStoreParameters, whose +// PACK half the applier writes and whose UNPACK half has no carrier and no backend reader at +// all. TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) { const MGPipeInputField pulled[] = { - MGPipeInputField::GetActiveTextureUnit, MGPipeInputField::GetBoundVertexArray, MGPipeInputField::GetBufferBindingSlot, MGPipeInputField::GetBufferBindingPoint, - MGPipeInputField::GetTouchedBufferBindingPointCount, - MGPipeInputField::GetCurrentVertexAttribute, MGPipeInputField::GetFramebufferBindingSlot, MGPipeInputField::GetImageTextureBinding, - MGPipeInputField::GetMaxTouchedTextureUnit, - MGPipeInputField::GetProgramForDraw, - MGPipeInputField::GetSamplingResolutionGeneration, - MGPipeInputField::GetTextureBindGeneration, - MGPipeInputField::GetTextureContextId, MGPipeInputField::GetTextureUnitObject, - MGPipeInputField::GetTransformFeedbackCapturedVertices, - MGPipeInputField::GetTransformFeedbackGeneration, + MGPipeInputField::GetProgramForDraw, + MGPipeInputField::GetProgramForDispatch, MGPipeInputField::GetTransformFeedbackProgram, - MGPipeInputField::GetBoundTransformFeedbackLifetimeId, - MGPipeInputField::IsTransformFeedbackActive, - MGPipeInputField::IsTransformFeedbackPaused, }; for (const auto field : pulled) { EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kBarrierPulled) << kMGPipeInputFieldNames[Index(field)] << " left the reduced path's debt"; } + // rv's exit line, pinned as a SET and not only as nine rows: the non-sticky + // BARRIER-PULLED list above is ALL the non-sticky debt - value-class membership is zero + // (CONTRACT-P5C.md §7 table 2) - and the only other BARRIER-PULLED rows are six of the + // seven sticky forwards (the seventh, InvalidateCompileEnv, is FATAL since P5c ev). + const MGPipeInputField pulledSticky[] = { + MGPipeInputField::GetBufferBindingPointCount, + MGPipeInputField::GetProgramObject, + MGPipeInputField::GetTextureObject, + MGPipeInputField::HasOpenTransformFeedbackSpan, + MGPipeInputField::ValidateProgramName, + MGPipeInputField::RecordError, + }; + SizeT pulledCount = 0; + for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { + const auto field = static_cast(i); + if (kMGPipeFieldOwnership[i] != MGPipeFieldOwnership::kBarrierPulled) continue; + ++pulledCount; + Bool named = false; + for (const auto f : pulled) named = named || field == f; + for (const auto f : pulledSticky) named = named || field == f; + EXPECT_TRUE(named) << kMGPipeInputFieldNames[i] + << " is BARRIER-PULLED and not in the pinned object-class list"; + } + EXPECT_EQ(pulledCount, 15u) << "9 non-sticky object rows + 6 sticky forwards"; + EXPECT_EQ(pulledCount, kMGPipeBarrierPulledFieldCount); EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters), MGPipeFieldOwnership::kApplierDerived); EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 0u), @@ -271,6 +295,36 @@ TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) { EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDispatch), MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDraw)) << "GetProgramForDispatch is GetProgramForDraw's twin and must share its class"; + + // AND THE NINE rv RETIRED, asserted in their NEW classes rather than deleted: a row that + // quietly fell back to BARRIER-PULLED is exactly what this list is for. + const MGPipeInputField suppliedByContextValues[] = { + MGPipeInputField::GetActiveTextureUnit, + MGPipeInputField::GetMaxTouchedTextureUnit, + MGPipeInputField::GetTouchedBufferBindingPointCount, + MGPipeInputField::IsTransformFeedbackActive, + MGPipeInputField::IsTransformFeedbackPaused, + MGPipeInputField::GetTransformFeedbackGeneration, + MGPipeInputField::GetBoundTransformFeedbackLifetimeId, + MGPipeInputField::GetTransformFeedbackCapturedVertices, + }; + for (const auto field : suppliedByContextValues) { + EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kRecordSupplied) + << kMGPipeInputFieldNames[Index(field)] << " no longer rides set_context_values"; + } + EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetCurrentVertexAttribute), + MGPipeFieldOwnership::kRecordSupplied) + << "the amended set_vertex_attrib_defaults payload carries all three views"; + const MGPipeInputField shutters[] = { + MGPipeInputField::GetSamplingResolutionGeneration, + MGPipeInputField::GetTextureBindGeneration, + MGPipeInputField::GetTextureContextId, + }; + for (const auto field : shutters) { + EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kApplierDerived) + << kMGPipeInputFieldNames[Index(field)] + << " is a shutter: the server answers from its own Serial"; + } } TEST_F(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) { @@ -395,19 +449,76 @@ TEST_F(FieldOwnershipTest, ARecordSuppliedFieldIsReadableAfterAServerStamp) { (void)gPipeInputs.GetRenderStateParameters(); EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0}); // The pixel store is in kReadback's class, not kClear's, so its readable half is exercised - // under the verb that actually reads it. + // under the verb that actually reads it. And P5c rv's record-supplied rows join it there: + // GetActiveTextureUnit is exactly the read that used to count into `rsp`. MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); (void)gPipeInputs.GetPixelStoreParameters(false); // the half that has a carrier + (void)gPipeInputs.GetActiveTextureUnit(); // P5c rv: set_context_values carries it + (void)gPipeInputs.GetMaxTouchedTextureUnit(); + EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0}); + // And a shutter answers the applier's own Serial under a server stamp (APPLIER-DERIVED), + // not the client's residual-fill copy: still zero pulls, and the answer MOVES when the + // applier's texture state does. + (void)gPipeInputs.GetTextureBindGeneration(); + EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0}); +} + +// P5c rv (CONTRACT-P5C.md §5.3): the residual-value record's applier write, read back through +// the accessors a backend uses, under the stamps that publish them - and the three texture +// shutters answering the applier's own serials rather than the client's fill. kReadback's +// class carries the two texture-unit counters, kDraw's the rest. +TEST_F(FieldOwnershipTest, SetContextValuesLandsInPipeInputsAndTheShuttersAnswerTheApplier) { + MGPContextValues values{}; + values.ActiveTextureUnit = 5; + values.MaxTouchedTextureUnit = 23; + values.TouchedBufferBindingPointCount[static_cast(BufferTarget::Uniform)] = 7; + values.IsTransformFeedbackActive = 1; + values.IsTransformFeedbackPaused = 0; + values.TransformFeedbackGeneration = 0x1112131415161718ull; + values.BoundTransformFeedbackLifetimeId = 0x2122232425262728ull; + values.TransformFeedbackCapturedVertices = 0x3132333435363738ull; + MGPipeApplySetContextValues(values); + + MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); + EXPECT_EQ(gPipeInputs.GetActiveTextureUnit(), 5); + EXPECT_EQ(gPipeInputs.GetMaxTouchedTextureUnit(), 23); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + EXPECT_EQ(gPipeInputs.GetTouchedBufferBindingPointCount(BufferTarget::Uniform), SizeT{7}); + EXPECT_EQ(gPipeInputs.GetTouchedBufferBindingPointCount(BufferTarget::Vertex), SizeT{0}); + EXPECT_TRUE(gPipeInputs.IsTransformFeedbackActive()); + EXPECT_FALSE(gPipeInputs.IsTransformFeedbackPaused()); + EXPECT_EQ(gPipeInputs.GetTransformFeedbackGeneration(), 0x1112131415161718ull); + EXPECT_EQ(gPipeInputs.GetBoundTransformFeedbackLifetimeId(), 0x2122232425262728ull); + EXPECT_EQ(gPipeInputs.GetTransformFeedbackCapturedVertices(), 0x3132333435363738ull); + // Eight record-supplied reads and not one residual pull. + EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0}); + + // The three shutters answer the applier's own serials under a stamped verb. They are + // SHUTTERS - the value matters only in that it MOVES when the server's texture state does + // and never walks backwards - so the pin is the identity with the applier's counters, not + // any particular number. + EXPECT_EQ(gPipeInputs.GetTextureBindGeneration(), MGPipeApplierTextureShutterSerial()); + EXPECT_EQ(gPipeInputs.GetSamplingResolutionGeneration(), MGPipeApplierTextureShutterSerial()); + EXPECT_EQ(gPipeInputs.GetTextureContextId(), MGPipeApplierContextSerial()); + const Uint64 before = MGPipeApplierTextureShutterSerial(); + MGPipeApplierNoteTextureStateMoved(); + EXPECT_EQ(gPipeInputs.GetTextureBindGeneration(), before + 1) + << "a texture-state apply moved the serial but the shutter did not answer with it"; EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0}); } TEST_F(FieldOwnershipTest, ABarrierPulledReadAfterAServerStampIsCountedNotFatal) { - MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); + // P5c rv: the exemplars are OBJECT-class now - the value rows this case used to read + // (GetActiveTextureUnit / GetTextureContextId / GetMaxTouchedTextureUnit) are + // RECORD-SUPPLIED / APPLIER-DERIVED since rv, and reading them here would count nothing. + // The three below are all of kDraw's class, all SharedPtr reads, and all safe on + // never-filled storage. + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); ASSERT_EQ(MGPipeResidualPullCount(), Uint64{0}); - (void)gPipeInputs.GetActiveTextureUnit(); + (void)gPipeInputs.GetBoundVertexArray(); EXPECT_EQ(MGPipeResidualPullCount(), Uint64{1}); - (void)gPipeInputs.GetTextureContextId(); - (void)gPipeInputs.GetMaxTouchedTextureUnit(); + (void)gPipeInputs.GetProgramForDraw(); + (void)gPipeInputs.GetTransformFeedbackProgram(); EXPECT_EQ(MGPipeResidualPullCount(), Uint64{3}); } @@ -452,16 +563,18 @@ TEST_F(FieldOwnershipTest, TheAppliersOwnClearDisarmsTheStampWithoutTheClientsFi } // The verb's own may-read table still holds on the server: kClear does not read -// GetActiveTextureUnit, so reading it there is a stale answer rather than a residual pull, +// GetProgramForDraw, so reading it there is a stale answer rather than a residual pull, // and it stays Fatal. Counting it would trade a loud staleness for a quiet one. +// (P5c rv: the exemplar moved - GetActiveTextureUnit is RECORD-SUPPLIED since rv and would +// say nothing about the pulled set here.) TEST_F(FieldOwnershipTest, ABarrierPulledFieldOutsideTheVerbsClassIsStillFatal) { #if MGTEST_HAVE_FORK const ChildResult r = RunInChild([] { MGPipeServerStampVerbBoundary(MGPipeVerb::Clear); - (void)gPipeInputs.GetActiveTextureUnit(); // BARRIER-PULLED, but not in kClear's class + (void)gPipeInputs.GetProgramForDraw(); // BARRIER-PULLED, but not in kClear's class }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetActiveTextureUnit@Clear\"}"), std::string::npos) + EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDraw@Clear\"}"), std::string::npos) << r.Log; EXPECT_EQ(r.Log.find("BARRIER-PULLED"), std::string::npos) << r.Log; #else @@ -473,8 +586,8 @@ TEST_F(FieldOwnershipTest, ResidualPullsReachThePublishedPerFrameCounter) { namespace PS = MG_Util::PipeStats; PS::SetEnabledForTesting(true); PS::ResetForTesting(); - MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); - (void)gPipeInputs.GetActiveTextureUnit(); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + (void)gPipeInputs.GetBoundVertexArray(); EXPECT_EQ(PS::FrameCalls(PS::CallClass::ResidualPulls), Uint64{1}); EXPECT_NE(PS::FormatWindowLine().find("rsp="), std::string::npos) << PS::FormatWindowLine(); PS::ResetForTesting(); @@ -484,28 +597,30 @@ TEST_F(FieldOwnershipTest, ResidualPullsReachThePublishedPerFrameCounter) { #if MGTEST_HAVE_FORK // R-7.3's proof that the instrumentation can go red. An instrumentation that cannot is -// decoration, and the set it counts is not empty. +// decoration, and the set it counts is not empty. (P5c rv: the exemplar is object-class now - +// the value rows this case was written against ride set_context_values and answer +// RECORD-SUPPLIED.) TEST_F(FieldOwnershipTest, StrictErrorsTurnsABarrierPulledReadIntoANamedAbort) { const ChildResult r = RunInChild([] { MG_Config::Ipc.StrictErrors = true; - MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); - (void)gPipeInputs.GetActiveTextureUnit(); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + (void)gPipeInputs.GetBoundVertexArray(); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetActiveTextureUnit@ReadPixels\"}"), + EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log; 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 P3b/P4b"), std::string::npos) << r.Log; + EXPECT_NE(r.Log.find("retires in P8]"), std::string::npos) << r.Log; } TEST_F(FieldOwnershipTest, TheSameReadWithoutStrictErrorsSurvivesAndIsCounted) { const ChildResult r = RunInChild([] { - MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); - (void)gPipeInputs.GetActiveTextureUnit(); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + (void)gPipeInputs.GetBoundVertexArray(); if (MGPipeResidualPullCount() != 1) ::_exit(7); }); ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log; diff --git a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp index 0d034e2c..3d1e9d26 100644 --- a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp @@ -127,10 +127,11 @@ 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. EXPECT_EQ(ClassCount(), 12u); EXPECT_EQ(ClassCount(), 8u); EXPECT_EQ(ClassCount(), 13u); - EXPECT_EQ(ClassCount(), 17u); + EXPECT_EQ(ClassCount(), 18u); EXPECT_EQ(ClassCount(), 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,11 +143,12 @@ TEST(PipeCatalogue, GeneratedTablesHoldTheWholeCatalogue) { // 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 (78 - the 37 that have one; the number was 34 at P5, 39 after -// P5b's five sink-only verbs): the other 37 have an applier, R-17 installs adapters over them, +// 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): +// 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 -// than the emptiness, because "37 installed" and "34 still null" are the two halves of a +// than the emptiness, because "38 installed" and "41 still null" are the two halves of a // partition and a case that checked only one of them would pass an installer that had // overwritten rows it does not own. // THE NAME IS KEPT, AND SO IS THE STATEMENT IT MAKES - only the ROWS it makes it about have @@ -209,8 +211,9 @@ TEST(PipeCatalogue, ExactlyTheRoutedRowsAreInstalledAndTheRestAreStillNull) { EXPECT_EQ(installed + nulls, static_cast(kMGPipeCallCount)); #if MOBILEGL_PIPE_PUSH - // 33 + 4 = 37, and the split is the honest shape of R-17 rather than an implementation - // detail: 37 is the number of MGPipeApply* entry points PipeApply.h declares, 33 of them + // 34 + 4 = 38, and the split is the honest shape of R-17 rather than an implementation + // detail: 38 is the number of MGPipeApply* entry points PipeApply.h declares (37 at P5, and + // P5c rv's set_context_values - CONTRACT-P5C.md §5.3 - is the 38th), 34 of them // fit a GENERATED row and go in the two tables, and FOUR cannot be expressed by any // generated signature and go in the hand-written escape table beside them // (ResourceRespecify's uncarried initialBytes, ResourceFlushRange's likewise, @@ -220,8 +223,8 @@ TEST(PipeCatalogue, ExactlyTheRoutedRowsAreInstalledAndTheRestAreStillNull) { // BOTH NUMBERS ARE ASSERTED. If the escape table were left out of this case, moving a row // out of the generated tables and forgetting to install its escape would read as a smaller // "installed" count and nothing else - and the call site would take a null. - EXPECT_EQ(installed, 33u) << "the routed rows and the applier's entry points disagree"; - EXPECT_EQ(nulls, static_cast(kMGPipeCallCount) - 33u); + EXPECT_EQ(installed, 34u) << "the routed rows and the applier's entry points disagree"; + EXPECT_EQ(nulls, static_cast(kMGPipeCallCount) - 34u); const void* const* escapes = reinterpret_cast(&gMGPipeRouteEscapes); SizeT escapesInstalled = 0; for (SizeT i = 0; i < sizeof(MGPipeRouteEscapes) / sizeof(void*); ++i) { @@ -229,7 +232,7 @@ TEST(PipeCatalogue, ExactlyTheRoutedRowsAreInstalledAndTheRestAreStillNull) { } EXPECT_EQ(escapesInstalled, 4u) << "an escape row is null; its call site would take a null " "pointer rather than fall back to anything"; - EXPECT_EQ(installed + escapesInstalled, 37u) + EXPECT_EQ(installed + escapesInstalled, 38u) << "the two tables plus the escapes must be exactly PipeApply.h's entry points"; // And the rows that MUST still be null, named rather than counted: these are calls with no @@ -576,9 +579,15 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) { // and object_death a kCtxObject one; both carry no blob, no reply and no tail. EXPECT_EQ(static_cast(MGPWireOp::ApplierReset), 77); EXPECT_EQ(static_cast(MGPWireOp::ObjectDeath), 78); - EXPECT_EQ(static_cast(MGPWireOp::kOpCount), 79); + // P5c rv (§5.3) appended the residual-value record AFTER the two control records, by the + // same rule: opcode 79, and nothing before it moved. It is an ordinary kCtxState set_* row + // - 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(MGPWireOp::SetContextValues), 79); + EXPECT_EQ(static_cast(MGPWireOp::kOpCount), 80); EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ApplierReset), static_cast(kNone)); EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ObjectDeath), static_cast(kNone)); + EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetContextValues), static_cast(kNone)); // And the P5b rows carry what their contract says: one blob (the block name) and nothing // else, and the extended draw row keeps its two flags. EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetStorageBlockBinding), static_cast(kHasBlob)); @@ -601,6 +610,14 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) { // struct to pin - the 16 bytes are pinned above with the handle family. EXPECT_EQ(sizeof(MGPApplierReset), 8u); EXPECT_EQ(sizeof(MGPHandleOnly), 16u); + // rv's two (§5.3/§7.6): the residual-value POD - 2 + 15 Uint32s, 2 Uint8s and 2 pad bytes, + // then the three Uint64s - and the AMENDED attribute carrier, which grew 24 -> 56 to carry + // all three views verbatim (the frontend's cross-view conversion is the authoritative + // answer; the applier no longer reconverts). + EXPECT_EQ(sizeof(MGPContextValues), 96u); + EXPECT_EQ(sizeof(MGPContextValues::TouchedBufferBindingPointCount), 60u); + EXPECT_EQ(sizeof(MGPAttribValue), 56u); + EXPECT_EQ(sizeof(MGPVertexAttribDefaults), 8u); // The two draw-flag bits P5b's d1 arms are exclusive by contract and distinct by value. EXPECT_EQ(static_cast(kDrawIsIndirect), 1u << 5); EXPECT_EQ(static_cast(kDrawHasUserIndices) & static_cast(kDrawIsIndirect), 0u); @@ -799,8 +816,9 @@ TEST(PipeCatalogue, SixValueStructsHaveFieldLists) { // MGPPatchParameter, MGPStreamOutputBind, MGPStorageBlockBinding, MGPCopyFromFramebuffer), // 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 - so: 78. - EXPECT_EQ(kMGPipeVerifiedPayloadCount, 78u); + // MGPHandleOnly, which has had a list since P0 - and rv added set_context_values' + // MGPContextValues (§5.3): 79. + EXPECT_EQ(kMGPipeVerifiedPayloadCount, 79u); static_assert(MGPipeHasFieldVerifier::value); static_assert(MGPipeHasFieldVerifier::value); static_assert(MGPipeHasFieldVerifier::value); diff --git a/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp b/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp index c49c94c0..4b810286 100644 --- a/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp +++ b/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp @@ -1304,6 +1304,9 @@ namespace { // set_vertex_attrib_defaults: a var-tail call, and the one consumer of the set-hash // suppressor. The tail is in ascending location order and Count matches Mask, which // is the contract the applier now enforces in every build rather than in a debug one. + // P5c rv: each entry carries all THREE views verbatim (CONTRACT-P5C.md §5.3) and the + // applier writes each view from its own array - a float-written attribute keeps the + // frontend's converted int/uint words, which is what the pre-rv shape could not do. { MG_Test::ScopedPipeVerb draw(MGPipeVerb::DrawArrays); MGPVertexAttribDefaults hdr{}; @@ -1311,16 +1314,26 @@ namespace { hdr.Count = 2; MGPAttribValue tail[2]{}; tail[0].Location = 2; + tail[0].ValueClass = MG_State::GLState::kVertexAttribValueClassFloat; const float first[4] = {1.5f, 2.5f, 3.5f, 4.5f}; - std::memcpy(tail[0].Data, first, sizeof(first)); + std::memcpy(tail[0].FloatView, first, sizeof(first)); + const Int32 firstInt[4] = {1, 2, 3, 4}; // the frontend's conversion of 1.5f & co + std::memcpy(tail[0].IntView, firstInt, sizeof(firstInt)); tail[1].Location = 9; + tail[1].ValueClass = MG_State::GLState::kVertexAttribValueClassFloat; const float second[4] = {-1.f, 0.f, 0.5f, 1.f}; - std::memcpy(tail[1].Data, second, sizeof(second)); + std::memcpy(tail[1].FloatView, second, sizeof(second)); + const Int32 secondInt[4] = {-1, 0, 0, 1}; + std::memcpy(tail[1].IntView, secondInt, sizeof(secondInt)); MGPipeApplySetVertexAttribDefaults(hdr, tail); EXPECT_EQ(gPipeInputs.GetCurrentVertexAttribute(2).floatValue[0], 1.5f); EXPECT_EQ(gPipeInputs.GetCurrentVertexAttribute(2).floatValue[3], 4.5f); EXPECT_EQ(gPipeInputs.GetCurrentVertexAttribute(9).floatValue[2], 0.5f); + // ... and the CONVERTED views are the record's, not a memcpy of the float bits: + // 1.5f's bits are 0x3FC00000, and the frontend's int view of it is 1. + EXPECT_EQ(gPipeInputs.GetCurrentVertexAttribute(2).intValue[0], 1); + EXPECT_EQ(gPipeInputs.GetCurrentVertexAttribute(9).intValue[0], -1); } // delete_render_state: the record stops being live and a bound handle stops being diff --git a/MobileGL/MG_Test/Pipe/TrackerTest.cpp b/MobileGL/MG_Test/Pipe/TrackerTest.cpp index 66952e9e..e248da03 100644 --- a/MobileGL/MG_Test/Pipe/TrackerTest.cpp +++ b/MobileGL/MG_Test/Pipe/TrackerTest.cpp @@ -892,30 +892,35 @@ namespace { const MGPAttribValue value = PayloadFor(3); EXPECT_EQ(value.Location, 3u); EXPECT_EQ(value.ValueClass, MG_State::GLState::kVertexAttribValueClassFloat); - EXPECT_EQ(value.Data[0], Word(1.5f)); - EXPECT_EQ(value.Data[1], Word(-2.5f)); - // The defect this exists to stop: 1.5f's int VIEW is 1, and a carrier that sent the - // float bits while calling them class 0 for every attribute would be sending - // 0x3FC00000 where the frontend holds 1. - EXPECT_NE(value.Data[0], static_cast(Ctx().GetCurrentVertexAttribute(3).intValue[0])); + EXPECT_EQ(value.FloatView[0], Word(1.5f)); + EXPECT_EQ(value.FloatView[1], Word(-2.5f)); + // P5c rv: the record carries ALL THREE VIEWS VERBATIM (CONTRACT-P5C.md §5.3), so the + // frontend's converted int view travels too - 1.5f's int VIEW is 1, and the carrier + // that used to send the float bits into all three views would be sending 0x3FC00000 + // where the frontend holds 1. + EXPECT_EQ(value.IntView[0], + static_cast(Ctx().GetCurrentVertexAttribute(3).intValue[0])); + EXPECT_NE(value.FloatView[0], value.IntView[0]); } TEST_F(TrackerAttribPayload, AnIntWriteCarriesTheIntWordsAndNamesItsClass) { Ctx().SetCurrentVertexAttributeInt(5, Array{7, -9, 11, 13}); const MGPAttribValue value = PayloadFor(5); EXPECT_EQ(value.ValueClass, MG_State::GLState::kVertexAttribValueClassInt); - EXPECT_EQ(static_cast(value.Data[0]), 7); - EXPECT_EQ(static_cast(value.Data[1]), -9); - // and NOT the float view the frontend converted it into - EXPECT_NE(value.Data[0], Word(7.0f)); + EXPECT_EQ(static_cast(value.IntView[0]), 7); + EXPECT_EQ(static_cast(value.IntView[1]), -9); + // and the float view is the frontend's CONVERSION of it, not the int bits + EXPECT_EQ(value.FloatView[0], Word(7.0f)); + EXPECT_NE(value.IntView[0], Word(7.0f)); } TEST_F(TrackerAttribPayload, AUintWriteCarriesTheUintWordsAndNamesItsClass) { Ctx().SetCurrentVertexAttributeUint(6, Array{4000000000u, 2u, 3u, 4u}); const MGPAttribValue value = PayloadFor(6); EXPECT_EQ(value.ValueClass, MG_State::GLState::kVertexAttribValueClassUint); - EXPECT_EQ(value.Data[0], 4000000000u); - EXPECT_NE(value.Data[0], Word(4000000000.0f)); + EXPECT_EQ(value.UintView[0], 4000000000u); + EXPECT_EQ(value.FloatView[0], Word(4000000000.0f)); + EXPECT_NE(value.UintView[0], Word(4000000000.0f)); } // The class is PER ATTRIBUTE and it is the last writer's, not the context's - a payload @@ -925,9 +930,9 @@ namespace { Ctx().SetCurrentVertexAttributeInt(2, Array{1, 2, 3, 4}); EXPECT_EQ(PayloadFor(1).ValueClass, MG_State::GLState::kVertexAttribValueClassFloat); EXPECT_EQ(PayloadFor(2).ValueClass, MG_State::GLState::kVertexAttribValueClassInt); - // Same numbers, different classes, so the same four words mean different things: - // 1.0f is 0x3F800000 and the integer 1 is 0x00000001. - EXPECT_NE(PayloadFor(1).Data[0], PayloadFor(2).Data[0]); + // Same numbers, different classes, so the WRITTEN views differ: 1.0f is 0x3F800000 + // and the integer 1 is 0x00000001. + EXPECT_NE(PayloadFor(1).FloatView[0], PayloadFor(2).IntView[0]); // An attribute nobody wrote answers Float, which is what the GL default (0,0,0,1) is. EXPECT_EQ(PayloadFor(7).ValueClass, MG_State::GLState::kVertexAttribValueClassFloat); // and a later write of the other class moves the class of THAT attribute only diff --git a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp index 1c36d721..47ca3189 100644 --- a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp +++ b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp @@ -38,6 +38,10 @@ #include #include #include +// P5c rv (CONTRACT-P5C.md §5.3): set_context_values' round trip reads the applied record back +// out of gPipeInputs, which needs the stamp machinery (a RECORD-SUPPLIED read outside a +// server-stamped verb is the monolith answer's business, and this fixture has no client fill). +#include // P5c ct: object_death's round trip releases REAL Espryt twin-table entries, so the suite // drives the same registries the sink dispatches to (Managers.h). A suite that substituted a // mock here would pin the dispatch and nothing about the release (R-16). @@ -1326,6 +1330,52 @@ TEST_F(PipeWireCodecTest, ObjectDeathReachesTheSinkWithItsHandleAndKind) { EXPECT_EQ(wire.Decoder().AppliedSeq(), static_cast(kKindCount)); } +// ===================================================================================== +// P5c rv (MG_Remote/CONTRACT-P5C.md §5.3): the residual-value record round-trips +// ===================================================================================== +// +// Not a sink row: the decoder's arm runs the REAL applier (MGPipeApplySetContextValues), so +// the round trip is read back out of gPipeInputs - under a server stamp, because a +// RECORD-SUPPLIED read outside one is the pre-stamp behaviour this build still tests +// elsewhere, and the fields' own poison would otherwise (correctly) refuse the read. + +TEST_F(PipeWireCodecTest, SetContextValuesRoundTripsIntoPipeInputs) { + Wire2 wire; + MGPContextValues values{}; + values.ActiveTextureUnit = 9; + values.MaxTouchedTextureUnit = 41; + for (Uint32 t = 0; t < 15; ++t) values.TouchedBufferBindingPointCount[t] = 100 + t; + values.IsTransformFeedbackActive = 1; + values.IsTransformFeedbackPaused = 1; + values.TransformFeedbackGeneration = 0xA1A2A3A4A5A6A7A8ull; + values.BoundTransformFeedbackLifetimeId = 0xB1B2B3B4B5B6B7B8ull; + values.TransformFeedbackCapturedVertices = 0xC1C2C3C4C5C6C7C8ull; + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetContextValues, &values, sizeof(values)), + kInvalidSeq); + bool applied = false; + ASSERT_TRUE(wire.PumpOne(&applied)); + EXPECT_TRUE(applied); + EXPECT_EQ(wire.Decoder().AppliedSeq(), 1u); + + // kReadback's class carries the two texture-unit counters, kDraw's the rest + // (FillPoints.def) - the stamp is what publishes a RECORD-SUPPLIED field for the read. + MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); + EXPECT_EQ(gPipeInputs.GetActiveTextureUnit(), 9); + EXPECT_EQ(gPipeInputs.GetMaxTouchedTextureUnit(), 41); + MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); + for (Uint32 t = 0; t < 15; ++t) { + EXPECT_EQ(gPipeInputs.GetTouchedBufferBindingPointCount(static_cast(t)), + SizeT{100 + t}) + << "target " << t; + } + EXPECT_TRUE(gPipeInputs.IsTransformFeedbackActive()); + EXPECT_TRUE(gPipeInputs.IsTransformFeedbackPaused()); + EXPECT_EQ(gPipeInputs.GetTransformFeedbackGeneration(), 0xA1A2A3A4A5A6A7A8ull); + EXPECT_EQ(gPipeInputs.GetBoundTransformFeedbackLifetimeId(), 0xB1B2B3B4B5B6B7B8ull); + EXPECT_EQ(gPipeInputs.GetTransformFeedbackCapturedVertices(), 0xC1C2C3C4C5C6C7C8ull); + MGPipeServerClearVerbBoundary(); +} + // ===================================================================================== // P5b t2 (MG_Remote/CONTRACT-P5B.md §2 t2). c0b's cases above round-trip each row once; these // pin the fields t2's EMITTERS actually fill and the one ordering property the span family has. diff --git a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp index 20abcbf1..cb03dec1 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp +++ b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp @@ -1064,7 +1064,7 @@ TEST(PipeRouting, AnErrorStatusIsNotFoldedIntoAcceptedOrRefused) { #endif // MGTEST_HAVE_FORK // ===================================================================================== -// B3 / codex 9: the CLIENT arm's 37 rows are observed, not just the monolith install +// B3 / codex 9: the CLIENT arm's 38 rows are observed, not just the monolith install // ===================================================================================== namespace { @@ -1127,6 +1127,11 @@ TEST(PipeRouting, TheInstalledClientArmIsWireAndNotMonolithAndEveryRoutedRowMove C1F_MOVED(Context, SetVertexAttribDefaults); C1F_MOVED(Context, SetPixelPackState); C1F_MOVED(Context, SetPatchState); + // P5c rv (CONTRACT-P5C.md §5.3): the residual-value record is the 34th routed row - an + // ordinary set_* row with an MGPipeApply* entry point, so it rides BOTH tables like its + // siblings (its producer is transport-gated in PipeFill.cpp, which is a different gate's + // business). + C1F_MOVED(Context, SetContextValues); C1F_MOVED(Context, SetResidualValueState); C1F_MOVED(Context, SetTextureParams); C1F_MOVED(Context, ResourceSubData); @@ -1140,8 +1145,9 @@ TEST(PipeRouting, TheInstalledClientArmIsWireAndNotMonolithAndEveryRoutedRowMove C1F_ESCAPE(CreateShaderState); #undef C1F_ESCAPE const SizeT movedContext = CountDifferingCells(gMGPipeContext, MGPipeMonolithContext()); - EXPECT_EQ(movedScreen + movedContext, 33u) - << "exactly the 33 generated routed rows must differ from the monolith adapters; " + EXPECT_EQ(movedScreen + movedContext, 34u) + << "exactly the 34 generated routed rows must differ from the monolith adapters " + "(33 at P5, + set_context_values at P5c rv); " << movedScreen + movedContext << " did, so a row was left on the monolith adapter (it would run the applier on the GL " "thread under split) or an unrouted row was overwritten"; diff --git a/scripts/gen_pipe.py b/scripts/gen_pipe.py index e0e4bab6..13557e02 100644 --- a/scripts/gen_pipe.py +++ b/scripts/gen_pipe.py @@ -462,6 +462,25 @@ def check_field_lists_cover_struct_members(field_lists, payloads, header_texts=N ACCESSOR_READ_RE = re.compile(r"\b(?:MGB_CTX|pGLContext)\s*->\s*(\w+)") +# The scanner's NAMED exemptions: accessors a backend reads through MGB_CTX-> / pGLContext-> +# that are NOT PipeInputs fields and never will be, each with the reason written down. A name +# here is a DEBT ENTRY, not a silence: it exists so the gate can tell "scoped, named, phased" +# from "forgot the row". +# +# P5c (the tx/ev/hd merge): the G6 frontend-keyed registry's framebuffer arm - two probes a +# backend makes inside MGPipeFrontendKeyedRegistryScope (CONTRACT-P5C.md section 3.1's second +# named exemption: the registry probes ride the scope, an unwrapped probe still aborts, and +# the scope retires with the twin tables at P3b/P4b). They are object-registry lookups, not +# PipeInputs state reads, so no Coverage.def row can ever describe them - a row there is a +# field in the fill table, and these have no storage to fill. +SCAN_EXEMPT_ACCESSORS = { + "GetFramebufferObject": "P5c G6 registry probe (the default framebuffer's object), inside " + "MGPipeFrontendKeyedRegistryScope; retires with the twin tables (P3b/P4b)", + "FindFramebufferObjectByLifetimeId": "P5c G6 registry probe, inside " + "MGPipeFrontendKeyedRegistryScope; retires with the twin tables (P3b/P4b)", +} + + def scan_live_accessors(accessors, backend_dir=None, verbose=True): """Every accessor a backend reads through MGB_CTX-> or pGLContext-> (comments and strings masked) must have a Coverage.def row - a read without a row is a PipeInputs @@ -479,7 +498,7 @@ def scan_live_accessors(accessors, backend_dir=None, verbose=True): masked = mask_comments_and_strings(read(path)) for match in ACCESSOR_READ_RE.finditer(masked): read_names.setdefault(match.group(1), set()).add(os.path.relpath(path, REPO_ROOT)) - unknown = sorted(n for n in read_names if n not in known) + unknown = sorted(n for n in read_names if n not in known and n not in SCAN_EXEMPT_ACCESSORS) if unknown: sys.exit("Coverage.def: accessor(s) read by a backend with no row: %s" % ", ".join("%s (%s)" % (n, ", ".join(sorted(read_names[n]))) for n in unknown)) diff --git a/scripts/gen_pipe_field_ownership.py b/scripts/gen_pipe_field_ownership.py index d2e42367..ab887570 100644 --- a/scripts/gen_pipe_field_ownership.py +++ b/scripts/gen_pipe_field_ownership.py @@ -558,10 +558,13 @@ def self_test(): # substring is not decoration: see expect_trip. # 1. THE HEADLINE CONTROL (exit gate E4): take one field out of the table. It is in no - # class, and that is a build failure rather than a silent default. - dropped = edit(r"X\(GetActiveTextureUnit," + GAP + r"BARRIER_PULLED," + GAP + r"\"[^\"]*\"," - + GAP + r"\"[^\"]*\"\)", "", "remove GetActiveTextureUnit's row") - controls = [("a field in NO class (GetActiveTextureUnit's row removed)", + # class, and that is a build failure rather than a silent default. GetBoundVertexArray is + # the control's field since P5c rv (it keeps a hand-written BARRIER_PULLED row; the value + # rows the first version used are RECORD_SUPPLIED-derived now, so they have no row to + # edit). + dropped = edit(r"X\(GetBoundVertexArray," + GAP + r"BARRIER_PULLED," + GAP + r"\"[^\"]*\"," + + GAP + r"\"[^\"]*\"\)", "", "remove GetBoundVertexArray's row") + controls = [("a field in NO class (GetBoundVertexArray's row removed)", "field(s) in NO class", lambda: run(own_text=dropped))] @@ -575,9 +578,9 @@ def self_test(): lambda: run(own_text=doubled))) # 3. A row naming something that is not a field at all. - typo = edit(r"X\(GetActiveTextureUnit,", "X(GetActiveTextureUnitt,", "misspell a field name") + typo = edit(r"X\(GetBoundVertexArray,", "X(GetBoundVertexArrayy,", "misspell a field name") controls.append(("a row naming a non-field", - "GetActiveTextureUnitt, which is not a PipeInputs field", + "GetBoundVertexArrayy, which is not a PipeInputs field", lambda: run(own_text=typo))) # 4. A BARRIER_PULLED row with no retiring phase: the debt is only sized if every row @@ -585,17 +588,17 @@ def self_test(): # THE REPLACEMENT IS NOT A RAW STRING. It was, and the backslashes survived into the # substitution, mangled the row past ROW_RE's reach and made this control a silent # duplicate of #1 for a whole round. - unphased = edit(r"(X\(GetActiveTextureUnit," + GAP + r"BARRIER_PULLED,)" + GAP + r"\"[^\"]*\",", + unphased = edit(r"(X\(GetBoundVertexArray," + GAP + r"BARRIER_PULLED,)" + GAP + r"\"[^\"]*\",", "\\1 \"-\",", "blank a BARRIER_PULLED row's retiring phase") controls.append(("a BARRIER_PULLED row with no retiring phase", - "GetActiveTextureUnit is BARRIER_PULLED and names no retiring phase", + "GetBoundVertexArray is BARRIER_PULLED and names no retiring phase", lambda: run(own_text=unphased))) # 5. A class that is not one of the four. - bogus = edit(r"(X\(GetActiveTextureUnit,)" + GAP + r"BARRIER_PULLED,", + bogus = edit(r"(X\(GetBoundVertexArray,)" + GAP + r"BARRIER_PULLED,", "\\1 SOMEHOW_FINE,", "introduce a fifth class") controls.append(("a fifth class", - "GetActiveTextureUnit is in class SOMEHOW_FINE", + "GetBoundVertexArray is in class SOMEHOW_FINE", lambda: run(own_text=bogus))) # 6. A sticky forward that lost its own row - the seven most dangerous fields are exactly @@ -699,9 +702,9 @@ 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) != 9: + if len(refused) != 7: sys.exit("gen_pipe_field_ownership: self-test: EmittedCallSuppliesTheWholeField refuses %d " - "fields, not the nine the contract's derivation is written against" % len(refused)) + "fields, not the seven 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") @@ -710,7 +713,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, 9 refusals)" % (trips, total)) + "(%d fields partitioned, 7 sticky forwards, 7 refusals)" % (trips, total)) return 0