Files
MobileGL/docs/archive/notes/p5e/scout-S1.md
T

20 KiB
Raw Blame History

S1 — VAO / vertex input / index, under run-ahead

Head 2fde7034. Paths under MobileGL/. Arm assumed throughout: MOBILEGL_PIPE_PUSH with bits 7 and 8 set (ResourceSubsystemEnabled() / VertexInputSubsystemEnabled(), Managers.h:911-918), EsprytSlotTablesEnabled(), Transport != Monolith. The E note's a.1 is the starting map; the site set below is re-resolved and differs from it in three places, marked [new].

1. The live reads of this family, at 2fde7034

Legend as the E note's: live = frontend object/array dereference; id = GetLifetimeId() + a probe of MGPipeSlots(); mint = a write into the client allocator; rec = record arm. "hit" / "miss" is the memo state of the site's own memo.

# site what it dereferences kind when
1 DirectGLES.cpp:4772 ResolveVaoTwin(currentVAO) → :1617-1618 g_backendVertexArrayObjects.Find(vao.get()) → SlotTables.h:411-414 → HandleOf :436-449: stateObj->GetLifetimeId(), then the table's one-entry front memo (:444) or MGPipeSlots().FindByLifetimeId (:445-446); a miss takes GetOrCreate(vao) :215-266, which is MGPipeSlots().Acquire :247-248 live + id (mint on a first sight) every draw; the GetLifetimeId() read runs on the memo hit too
2 DirectGLES.cpp:4775 currentVAO->GetConfigVersion() live every draw, unconditional
3 SyncVaoAttributeBuffersByHandle memo hit, :733 IsBufferDrawCleanByHandle(entry.handle, entry.resource, entry.frontend); under a transport Managers.cpp:3148-3150 does NOT dereference frontend none (handle-only) per distinct VBO, only when memo->vboCleanEpoch != bufferEpoch (:730)
4 same, dirty repair :735-736 currentVAOObject->GetAttribute(entry.attribIndex).Buffer → EnsureBufferResource(obj) → HandleOfBuffer (Managers.cpp:3444, :2922-2941, scope :2938) live + id on a dirty probe
5 same, memo miss :744-772 GetAllAttributes() (32 VertexAttribute, each a SharedPtr<BufferObject> + 10 scalars), attrib.Buffer, EnsureBufferResource(bufferObject) :764, HandleOfBuffer(bufferKey) :769 live + id (twice per distinct buffer) memo key miss
6 IBO arm :896-925 currentVAOObject->GetIndexBufferBindingSlot().GetBoundObject() :897; HandleOfBuffer(possibleIBO.get()) :907; EnsureBufferResource(possibleIBO) :917 live + id every indexed draw; :897 and :907 run on the hit too
7 SyncCurrentVAO :1641-1652 → SyncToBackend (Managers.cpp:4881-4892) → SyncToBackendFromApplier (:5160-5350) nothing frontend — every field is st.BoundVertexElements / rec->Attributes[] / st.VertexBuffers[] / st.IndexBuffer.Res (:5220-5345) rec every draw
8 SyncCurrentVertexAttributeValues :1662-1719 MGB_CTX->GetBoundVertexArray() :1668; program->GetActiveAttributeLocationMask() :1672 (S4); vao->GetConfigVersion() :1683; miss: vao->GetAttribute(location).Enabled :1688; program->GetAttribType(location) :1701,:1715 (S4) live :1668,:1672,:1683 every draw with a program; :1688 on memo miss
9 :1700 MGB_CTX->GetCurrentVertexAttribute(location) rec (set_vertex_attrib_defaults, RECORD_SUPPLIED — PipeFill.cpp:2304, :2594-2660) pending-mask draws only
10 DrawArrays :6724-6735 (scope :6729), MultiDrawArrays :6767-6782 (scope :6775) g_backendVertexArrayObjects.Find(currentVAO.get()), then SyncClientSideAttributesForDrawArrays(currentVAO, first, count) (Managers.cpp:5364-5468): GetAllAttributes(), and attrib.Offset dereferenced as a raw CLIENT POINTER :5382,:5430 live + id + client memory every glDrawArrays / glMultiDrawArrays with a VAO bound
11 [new] BoundElementArrayBuffer() :6447-6452 / BoundElementArrayBufferId() :6456-6461, reached from ScopedRestartIndexSubstitution :6591, :6687 MGB_CTX->GetBoundVertexArray(), GetIndexBufferBindingSlot().GetBoundObject(), EnsureBufferResource(ibo) → HandleOfBuffer live + id every DrawElements-family call whose ResolveRestartSubstitution (:6464-6480) is not None; also MultiDraw.cpp:45,:905
12 [new] :1646 g_backendVertexArrayObjects.CollectGarbageIfNeeded() no-op on the slot arm (Managers.h:558-563) — —

EnsureBufferResourceForHandle (Managers.cpp:3193-3435) holds the SharedPtr but reads nothing from it under a transport: the shadow base is resource->hostBytes (:3208-3222), SyncPersistentMappedRange is skipped (:3334-3341), definedness comes from the descriptor plus staged coverage (:3376-3385). (nullptr, h) is already the live shape at DirectGLES.cpp:953.

Scope sites this family owns: DirectGLES.cpp:1609, :6729, :6775 — 3 of the 20; plus every HandleOfBuffer reached from rows 4, 5, 6, 11 (Managers.cpp:2938).

2. What the records and the applier already carry

  • create_vertex_elements / bind_vertex_elements / delete_vertex_elements — PipeCalls.def:151-153, all kCtxCso, therefore already published without a wait (ClientSession.cpp:915-922). Emitter VertexInputEmit.h:158-189 (EmitVertexElements) and :352-397 (EmitCreate, all 32 attributes and all 32 binding points, MGPVertexAttribWire MGPipeTypes.h:497-508). Applier record MGPipeVertexElementsRecord (PipeApply.h:389-400): Attributes[32], BindingPoints[32], server-owned ContentSerial bumped on every applied create, including a re-create on the same handle.
  • set_vertex_buffers — PipeCalls.def:168 (kCtxState, kVarTail), emitter VertexInputEmit.h:200-247, record MGPVertexBuffers / MGPVertexBuffer (MGPipeTypes.h:805-836). Applier: VertexBuffers[32], VertexBufferStart/Count, VertexFetchBaseInstance, VertexBuffersSerial (PipeApply.h:598-616), bumped at PipeApply.cpp:2376-2377.
  • set_index_buffer — PipeCalls.def:169, emitter VertexInputEmit.h:255-270, record MGPIndexBuffer (MGPipeTypes.h:839-845). Applier IndexBuffer + IndexBufferSerial (PipeApply.h:620-622), bumped at PipeApply.cpp:2391-2392.
  • set_vertex_attrib_defaults — PipeCalls.def:177, PipeFill.cpp:2594-2660; all three views verbatim since P5c rv, so GetCurrentVertexAttribute is RECORD_SUPPLIED.
  • Client-side dirty rules: bit 8 NewVertexElements = mix(lifetimeId, configVersion) (Tracker.h:294-298); bit 9 NewVertexBuffers = mix(mix(AnyVaoAttributeGeneration, vaoIdentity), pendingBaseInstance) (Tracker.h:542-543); bit 10 NewIndexBuffer = mix(vaoIdentity, mix(indexSlotVersion, boundLifetimeId)) (Tracker.h:558-566). All three emit at the validate point, order elements → buffers → index (PipeFill.cpp:3225-3233).
  • Twin lifetime, already handle-shaped: ReleaseTwinsForWireObjectDeath(handle, VertexElementsCso) → g_backendVertexArrayObjects.ReleaseByHandle(handle) (Managers.cpp:352-353), plus the client's own MGPipeEmitVertexElementsDestroyAndFree (PipeFill.cpp:1050) and NoteRecordDestroyed (VertexInputEmit.h:319-329).
  • SyncToBackendFromApplier (Managers.cpp:5160-5350) is the existence proof: the whole driver-VAO configuration is already produced from records with zero frontend reads, and its dirty gate is three server-owned serials plus the backend's own g_bufferBackendIdGeneration.

So the records cover the family in full except client-memory vertex arrays (§7).

3. The gap — what does not exist yet

G1. Nothing. For rows 1–9 and 11 there is no missing field: every value the server needs is already in MGPipeApplierState. This family's P5e work is a re-key and a re-source, not a record addition. That is the headline and it is what makes S1 cheap next to S2/S3/S5.

G2. Client-memory vertex arrays have no wire form. EmitVertexBuffers publishes Res = kMGPipeNullHandle for them by design (VertexInputEmit.h:196-199, :211-213) and the SERVER uploads the bytes by dereferencing the client pointer (Managers.cpp:5372-5466). The brief's "client-side arrays are already refused under split" is NOT confirmed — they are not refused. EmitTables.cpp refuses only client INDEX arrays for the multi-draw forms (:651 CLIENT_INDICES) and CLIENT_COMMANDS / UNBOUND_PARAMETER (:682,:685); nothing refuses a client vertex array. Options, in P5e order:

  • (a) refuse, Fatal{UnmigratedVerb, "DrawArrays+CLIENT_ARRAYS"} raised on the GL thread in EmitDrawRecord's array arm, gated on the run-ahead caps bit so the lockstep arm is unchanged;
  • (b) stage, a new tail on draw_vbo shaped exactly like kDrawHasUserIndices (MGPipeTypes.h:1430, codec PipeWireCodec.cpp:1985-1998): a per-attribute {BindingIndex, MGHostSpan} array staged whole from the draw's known [first, first+count) range. This is P8's HostResolve.cpp territory and is the honest answer. P5e should take (a) and name (b) as P8's, because (a) is two lines and makes visible a path that was never split-clean; (b) is a record and belongs with the index-array flattening it mirrors.

G3. One pin, not a field. The twin key below is only 1:1 while the vertex-elements CSO stays IDENTITY-addressed (VertexInputEmit.h:19-28, D-G1). If P7 ever content-addresses it, two frontend VAOs share a handle — and therefore one driver VAO name and its 64 scratch buffer ids (Managers.h:1204-1300). P5e must state that as a requirement of the key and pin it in a test, not leave it as a performance remark.

4. The retirement design

4.1 The twin key is st.BoundVertexElements. The handle ResolveVaoTwin resolves today is MGPipeSlots().Acquire(VertexElementsCso, vao->GetLifetimeId()) (SlotTables.h:247-248) — the SAME handle EmitVertexElements mints (VertexInputEmit.h:172) and the applier stores as BoundVertexElements (PipeApply.h:594). So:

BackendVertexArrayObject* ResolveVaoTwinByHandle() {          // split arm, no scope, no allocator
    const MGPipeHandle cso = MGPipeApplier().BoundVertexElements;
    if (MGPipeHandleIsNull(cso)) return nullptr;              // == "no VAO bound"
    auto& twin = g_backendVertexArrayObjects.GetOrCreate(cso); // SlotTables.h:294-340
    if (!twin) twin = MakeShared<BackendVertexArrayObject>();
    return twin.get();
}

PrepareForDraw:4770-4772 stops reading GetBoundVertexArray(); the null arm at :4816-4821 (BindBackendVAOId(0)) is reached from the null handle instead. No behaviour change: one driver VAO per frontend VAO, exactly as now. GetOrCreate(handle)'s backward-generation refusal (SlotTables.h:331-339) is the ABA answer, and ReleaseByHandle (SlotTables.h:394-408) the death answer — both already written and already called from Managers.cpp:352.

4.2 The buffer walk reads the applier, not the VAO. SyncVaoAttributeBuffersByHandle (:699-790) keeps its shape and loses its frontend argument:

  • miss walk :744-772 → iterate i in [st.VertexBufferStart, +st.VertexBufferCount), skip MGPipeHandleIsNull(st.VertexBuffers[i].Res), dedupe on Res (a {slot,gen} compare replaces the pointer compare at :754-760), EnsureBufferResourceForHandle(nullptr, Res);
  • entry.handle = Res directly — HandleOfBuffer at :769 deleted;
  • dirty repair :735-736 → EnsureBufferResourceForHandle(nullptr, entry.handle);
  • probe :733 → IsBufferDrawCleanByHandle(entry.handle, entry.resource, nullptr); the frontend parameter is already dead under a transport (Managers.cpp:3148-3150) and ResolvedDrawBuffers::Entry::frontend (Managers.h:1223) leaves the split arm, which also retires the dangling raw pointer the struct's own note warns about (:1224-1239);
  • attribIndex (Managers.h:1241) becomes the BINDING index, kept only for diagnostics. Memo key is unchanged and already server-owned: {elementsHandle, elementsSerial, buffersSerial} (Managers.h:1263-1265), i.e. {BoundVertexElements, rec->ContentSerial, VertexBuffersSerial}.

4.3 The IBO arm. :896-925 → st.IndexBuffer.Res and st.IndexBufferSerial. The identity compare memo->iboHandle == iboHandle stays (the handle is now read from the applier, not probed); the extra HandleOfBuffer at :907 goes. Add iboSerial to the memo so a rebind that moved the serial re-ensures without a frontend read — the client shutter (Tracker.h:558-566) already fires on the slot version AND the bound lifetime id, so a redundant rebind is correctly suppressed.

4.4 The two config-version reads. :4775 is DEAD on this arm already (SyncNeccessaryBuffers uses vaoConfigVersion only in the legacy branch, :831-833): delete it under the transport check. :1683 is the key of PendingAttribValueMask (Managers.h:1286-1290); re-key it on {elementsHandle, elementsSerial, activeMask} and read rec->Attributes[location].Enabled (PipeApply.h:394) at :1688. Every configuration change re-creates the record and bumps ContentSerial (PipeApply.h:396-399), so the key is strictly stronger than the wrapping Uint16.

4.5 Clean / needs-re-sync, answered from server-owned serials only. After 4.2–4.4 the family's four questions are: BoundVertexElements + ContentSerial (configuration), VertexBuffersSerial (the buffer set), IndexBufferSerial (the element slot), CurrentBufferMutationEpoch() + syncedChangeSerial vs record->Serial (buffer content, Managers.cpp:3147-3153). The epoch is a backend atomic (Managers.cpp:794) bumped only from backend bodies, i.e. apply-thread memory under a transport — that must be pinned, not assumed (§7).

4.6 Scopes. DirectGLES.cpp:1609, :6729, :6775 are deleted with the sites they wrap; the HandleOfBuffer scope entries this family reaches (Managers.cpp:2938) lose their last vertex-input caller. After that, GetBoundVertexArray (FieldOwnership.def:72) has no Espryt draw-path reader left except the two client-array arms and the restart substitution (§4.7), so its row's retiring phase moves from "P8" to "P5e (Espryt)".

4.7 The restart substitution. BoundElementArrayBuffer() (:6447-6452) → st.IndexBuffer.Res; BoundElementArrayBufferId() (:6456-6461) → FindBufferResourceForHandle(st.IndexBuffer.Res) (resolve only, exactly as SyncToBackendFromApplier:5330 does). :6591's read in the substitution body takes the same.

5. Monolith / G1

Every edit above is inside an existing #if MOBILEGL_PIPE_PUSH arm that is already selected by VertexInputSubsystemEnabled() / ResourceSubsystemEnabled() (Managers.h:911-918) — the functions touched (SyncVaoAttributeBuffersByHandle, SyncToBackendFromApplier, BindAttributeBufferByHandle) do not exist in the pull build at all. ResolveVaoTwin is the one exception: its three arms (:1611-1622 slot, :1623-1636 legacy memo) stay, and the handle arm is added as a fourth guarded by Transport != Monolith, so MOBILEGL_PIPE_PUSH under Transport=monolith keeps Find(vao.get()) byte for byte. ResolvedDrawBuffers's frontend and configVersion members stay declared for the legacy arm (they are already outside the MOBILEGL_PIPE_PUSH block at Managers.h:1223,:1251), so no mangled name and no struct layout the pull build sees is changed. G2/G14: no new name enters the pull build's symbol set; the new ResolveVaoTwinByHandle is MOBILEGL_BUILD_DISAGGREGATED-only.

6. Red-once

  1. Twin key. SanityTest.cpp:4083-4084 already pins that VertexElementsCso resolves through the handle arm. Add: with the twin keyed on BoundVertexElements, revert ResolveVaoTwin to Find(vao.get()) with the scope at :1609 removed → Fatal{RoleViolation, "MGPipeSlots"} naming HandleOf (SlotAllocator.cpp:22-38). The scope deletion is what makes the revert loud.
  2. The buffer walk. New MG_Test case beside VertexInputEmit: publish set_vertex_buffers for buffers A,B, then repoint the frontend VAO's attribute 0 at C WITHOUT re-emitting, then run the sync — the walk must ensure A,B. Revert 4.2 and it ensures C; the case names st.VertexBuffers.
  3. The index arm. Same shape on set_index_buffer / st.IndexBuffer.Res, with the frontend element slot rebound after publish. Revert 4.3 → red naming IndexBuffer.Res.
  4. The attrib-value memo. Revert 4.4's key to GetConfigVersion() under run-ahead and the strict lane aborts Fatal{UnmigratedPipeInput, "GetBoundVertexArray@draw_vbo"} (PipeInputs.h:137-170, MGPipeStickyForwardPull) — this is P5e-3's detector doing the work.
  5. Client arrays. With §3 G2 (a) in place: a glDrawArrays with an enabled client-memory attribute under run-ahead is a named refusal, and reverting the refusal is red by DoublePrecisionScenario / a new client-array scenario under integration-split-strict.
  6. Integration, unchanged suites that must stay green under run-ahead: VertexAttribBindingScenario, VertexArrayEnableDisableScenario, PrimitiveRestartScenario, ResidentIndexScenario, MultiDrawScenario, DrawParametersScenario, DoublePrecisionScenario, HandleRecycleScenario, ObjectSubsystemControlScenario (the 0x0ff / 0x17f A/B at Managers.h:897-905 must keep its verdicts).

7. Risks and unsettled

  • R1 (blocking, owned by S6/S8). Client-memory vertex arrays, §3 G2. Until refused or staged, the apply thread dereferences a raw client pointer per glDrawArrays and run-ahead makes the bytes movable. It also GENERATES and fills driver buffers on the apply thread (Managers.cpp:5392-5399,:5455-5457) — harmless, but it means SyncClientSideAttributesForDrawArrays cannot simply be deleted.
  • R2. CurrentBufferMutationEpoch() (Managers.cpp:794, Managers.h's site enumeration) is the only non-serial input left in 4.5. It is an atomic and every bump found is in a backend body, but P5e must pin "no GL-thread bump under a transport" — a client-side bump would make the memo's clean stamp a cross-thread read.
  • R3. SyncCurrentVertexAttributeValues cannot go run-ahead clean on S1's work alone: :1672, :1701, :1715 read the frontend program. S1's edits are a no-op for safety until S4 lands. Sequence S4's program twin before or with this function's edit.
  • R4. The set_vertex_buffers window: Count is "highest ENABLED attribute + 1" (VertexInputEmit.h:203-207) while create_vertex_elements carries all 32 (VertexInputEmit.h:352-366). An attribute enabled in the record but outside the window resolves VertexBufferForAttributeIndex == nullptr (Managers.cpp:4795-4809) and is skipped at :5262. Unreachable today because both walks read the same Enabled bits in the same validate step; under run-ahead they still do (both are client-side, same step) — but it is the same class as S2's caveat A8 and should be pinned by a test rather than argued.
  • R5. EnsureBufferResourceForHandle's hostBytes refresh (Managers.cpp:3370) is a self-assignment under a transport (:3217-3220 returns resource->hostBytes). Passing nullptr changes nothing — confirmed by reading, not measured. The fp64 narrowing (SyncFloat64AttributeAsFloat32ByHandle) reads resource->hostBytes, i.e. the server staged store, and is already server-owned.
  • R6. g_bufferBackendIdGeneration (Managers.cpp:5172-5174) and the reset rule MGPipeApplierReset must ADVANCE VertexBuffersSerial/IndexBufferSerial rather than zero them — the long note at Managers.cpp:5176-5191 says the twin's whole gate depends on it. Under run-ahead an applier_reset is a waited control record (P5e-2), so this holds; if any package makes applier_reset fire-and-forget, this gate breaks first.
  • R7, not settled. Whether ResolvedDrawBuffers should survive at all once the walk is over st.VertexBuffers[Start..+Count): that array is already a compact, deduped, handle-keyed list bounded by Count (1–3 entries on the Minecraft workload, not 32 cold slots). The memo may be pure cost after 4.2. Measure before keeping it.