Two things land together: the plan and contract draft P6 needs before it starts, and the
working notes that until now existed on exactly one machine.
1. P6's plan and contract draft (neither started; not one line of code)
docs/Disaggregated/P6-SPAWN-PLAN.md the package plan
docs/Disaggregated/P6-CONTRACT-DRAFT.md the contract draft (moves to
MG_Remote/CONTRACT-P6.md when c6 lands)
The three findings that mattered, all written into the documents:
a. P6 is SMALLER than its roadmap cell suggests. The transport primitives are already in
the tree and already tested: SocketDoorbell including hangup detection and the two
lost-wakeup fences (Doorbell.cpp:211-360, four cases in FdPassingTest), SCM_RIGHTS fd
passing, ShmSegment::Adopt, and the SurfaceOp / WindowKind schema already defined in
protocol.fbs. inproc uses a real second shm mapping rather than an alias, and
SessionRings.h:22-28 says that was done so today is not the first time it runs. P6 is
assembly plus a control plane plus a process, not "write a socket transport".
b. The evidence for "P6 is only a transport swap" has EXPIRED. It was bought by P5c, whose
audit found 59 direct memory accesses in a tree that already looked clean. P5e changed
the tree again. So the first package, a6, is a read-only audit that writes no code, and
every contract row that depends on it is marked PENDING a6 rather than decided in
advance.
c. P6 does not get a game running on the phone. ANativeWindow* is meaningless in the child,
and protocol.fbs:186-196's own comment says Android transfers the window out of band -
that band is P12. P6 lands pbuffer / surfaceless / offscreen, which is what HeadlessGL,
the split-equivalent lane and trace replay need, and which is exactly what P6's own exit
gate measures. A real window reaching the server is a named refusal. This gets its own
section because "P6 is done" would otherwise read as "the game runs now".
The contract adds rule G: a role may not name a process-local handle. It extends P5c's
rule E (a role may not name the other role's memory) from memory to identity - EGLDisplay /
EGLSurface / EGLContext / ANativeWindow* / fd number / pid / mapped address either cross as
a token this contract defines or they do not cross, and a descriptor crosses only through
SCM_RIGHTS, where the kernel renumbers it.
What P5e left for P6 gets its own section. The latch semantics are already designed
(ARCHITECTURE.md:504) and are copied unchanged. What is new is that after run-ahead the
client no longer waits while the peer process can DIE: the client holds records that were
published and will never be applied, and Android's freezer suspends the child without
closing anything. So slow and dead must be distinguishable WITHOUT a timeout threshold.
That is P6's only new design; everything else is carriage. It shares a predicate with E1's
outstanding re-specification (ID-122), and the two should be designed together.
Two gates carried over from P5e's lessons: G2 requires integration-spawn's test-name set to
match integration-split's name for name, because a differing set means the lane is quietly
running less; and the arm-proof gate requires every entry to record the child pid and
transport=spawn in its OWN private log - ID-124 established that this kind of false green
really happens, and a spawn lane that actually ran monolith would pass every other gate.
S2's shape must be settled before it is written, and the document says why: E1 is the
cautionary case.
2. The working notes move into the repository (docs/Disaggregated/notes/, 415 .md files)
ROADMAP / CURRENT_STAGE_PROGRESS / MEASUREMENTS cite ruling numbers, audit line numbers and
the provenance of gate figures from ~/w7/notes/, a directory that exists on one machine and
is under no version control. A record cited hundreds of times that lives only locally is not
a record, so it moves in whole, with its directory structure intact.
Only .md moves. Scripts, run logs, census and gate result JSON, patches, APKs and nm dumps
stay where they are: they are evidence DATA rather than documents - large, regenerated per
run, and mostly carrying absolute paths and device serials that mean something only on the
machine that produced them. The mapping and this split are written in
docs/Disaggregated/notes/README.md.
Two exceptions are recorded there too: P6's BRIEF has been promoted to a first-class
document and keeps no copy under notes/, so the two cannot drift apart; and recovered/ holds
material salvaged from failed workflow runs, which may duplicate the like-named phase
directories and is kept because a few of those were the only complete version at the time.
CI's citation lint takes docs/Disaggregated/*.md only (non-recursive), and notes/ is
deliberately outside it: those notes' file:line citations were written against their own
code heads, and resolving them against today's HEAD would fail wholesale - which is not an
error, it is their correct state as a historical record. To check one, use the revision it
declares (--rev).
3. Inconsistencies fixed in passing
- README.md's status line still said P5e was in progress while the stage table already said
closed.
- CURRENT_STAGE_PROGRESS's next-step item 1 still listed the exit gate as pending, though
(a) and (b) were run in the previous commit; rewritten to closed plus the one item that
actually remains (E1).
- ROADMAP's P6 row still had the pre-phase plan in three of its cells; it now carries the
package order, the scope boundary and the two new gates.
Checked with check_doc_citations.py: 125 citations across docs/Disaggregated/*.md, 0 problems.
69 KiB
Scout — the frontend (MG_State/GLState + MG_Impl/GLImpl) as P4a's client emission must read it
Tree: C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg @ 37da3c3a
(feat/disaggregated, P3a closed). Every path below is relative to MobileGL/ unless it starts
with docs/. Every line number was opened in this tree; where a file is large I name the exact
range I read.
Read alongside: ~/w7/notes/p3a/INTEGRATOR-DECISIONS.md (ID-1..24). The rulings that bind P4a
directly are ID-11/ID-13 (G5 byte-identical functions — none of them are in P4a's files),
ID-12/ID-8b (the applier keeps object records across make-current; only
MGPipeApplierReleaseObjectRecords drops them; the client therefore needs no re-publication on
FreshlyPrimed, but must say so as a rule), ID-18/ID-21 (leak-at-exit storage: every MGPipe
process singleton is heap-constructed and never destroyed; a new P4a singleton MUST follow),
and ID-19 (P3a already costs +699/+422 ns/draw; P4a's per-verb emission budget is tight).
0. The one-paragraph shape
P4a's kinds are Framebuffer, Texture, Renderbuffer, SamplerCso/SamplerViewCso, ShaderCso
(MG_Pipe/MGPipeHandles.h:24-40). Unlike P3a's buffers, five of the six already raise a death
notice and all six already have a lifetime id; what none of them has is a client-minted
handle, a client emitter, or an applier record. The frontend versions the emitters need already
exist and are already read by the tracker's shutters (MG_Impl/Pipe/Tracker.h:303-312) — bits
11..14 (NewFramebuffer, NewSamplerViews, NewSamplers, NewShaderImages) plus bits 6..8
(NewShader, NewShaderBindings, NewGlobalConstants) are computed, latched and counted
today and emitted by nobody. P4a is therefore mostly "wire the emitters onto shutters that
already fire", plus four genuinely new pieces of frontend work: the resolved ReadSurface, the
framebuffer Complete bit, the texture region list on the wire, and the program archive blob.
1. The object classes: ids, versions, constructors, destructors
1.1 Summary table (the hook points)
| kind | class + header | lifetime id | ctor | dtor / death | already raises |
|---|---|---|---|---|---|
| Framebuffer | FramebufferObject, MG_State/GLState/FramebufferState/FramebufferObject.h:105-191 |
m_lifetimeId :176, GetLifetimeId() :165, allocator FramebufferObject.cpp:20-24 |
FramebufferObject.cpp:126-135 |
FramebufferObject.cpp:27-37 (#if MOBILEGL_PIPE_PUSH only) |
NotifyStateObjectDestroyed(MGPipeKind::Framebuffer, …) :36 |
| Texture | TextureObjectBase : ITextureObject, TextureState/TextureObject.h:117-234 |
m_lifetimeId :205, GetLifetimeId() decl :84 / def TextureObject.cpp:334-336, allocator TextureObject.cpp:19,26-28 |
TextureObject.cpp:56-... |
TextureObject.cpp:31-41 — virtual, declared on the BASE so 2D/3D/cube/buffer/view all announce once (TextureObject.h:120-128) |
MGPipeKind::Texture TextureObject.cpp:40 |
| Renderbuffer | RenderbufferObject, RenderbufferState/RenderbufferObject.h:23-71 |
m_lifetimeId :64, GetLifetimeId() :58, allocator RenderbufferObject.cpp:21,24-26 |
RenderbufferObject.cpp:28 |
RenderbufferObject.cpp:31-41 |
MGPipeKind::Renderbuffer :40 |
| Sampler | SamplerObject, SamplerState/SamplerObject.h:16-79 |
m_lifetimeId :76, GetLifetimeId() :63, allocator SamplerObject.cpp:20-24 |
SamplerObject.cpp:26-27 |
SamplerObject.cpp:30-40 |
MGPipeKind::SamplerCso :39 |
| Program | ProgramObject, ProgramState/ProgramObject.h:25-... |
m_lifetimeId :1353, GetLifetimeId() :1262, allocator ProgramObject.cpp:26-30 |
inline ProgramObject.h:55 |
ProgramObject.cpp:32-45 (CancelLink() then the notice) |
MGPipeKind::ShaderCso ProgramObject.cpp:43 |
| Shader | ShaderObject, ProgramState/ShaderObject.h:24, dtor :52 |
NONE | — | no notice | — |
| ProgramPipeline | ProgramPipelineObject, ProgramState/ProgramPipelineObject.h:19-170 |
NONE | — | no notice | — (composites are ordinary ProgramObjects, §9.4) |
| Buffer (P3a, the model) | BufferState/BufferObject.cpp |
g_nextBufferLifetimeId :20, AllocateLifetimeId :23-25 |
:35-47 — mints the handle unconditionally, emits create only if the subsystem is on |
:49-65 — MGPipeEmitResourceDestroyAndFree returns the latch, and the legacy OnDestroy runs only when it says false |
emits resource_destroy itself (no notice) |
| VAO (P3a, the CSO model) | VertexArrayState/VertexArrayObject.cpp:41-62 |
MGPipeEmitVertexElementsDestroyAndFree(m_lifetimeId) — backend-neutral, client-side, emit → notice → free slot, in that fixed order |
1.2 The two death shapes, and which one each P4a kind takes
MG_State/GLState/StateObjectDeathNotice.h (whole file, 62 lines) is the P2 mechanism: one
StateObjectDeathOps{ void(*OnDestroyed)(MGPipeKind, Uint64 lifetimeId) } (:39-43), a plain
non-atomic global g_stateObjectDeathOps (:45, written at backend bring-up, read from
destructors — both on the context thread), SetStateObjectDeathOps :47, and
NotifyStateObjectDestroyed :55-59. Declares nothing at all in a pull build (:12, :62).
The P3a ruling P4a inherits (ID-15 C-1, MG_Pipe/PipeMutation.h:127-141): a death path that
lives only in a backend's death-ops table is no path at all under a backend that installs
none — Magma installs none — so every VAO leaked its slot and its ~1.3 KB applier record and
Fatal'd past 65536 slots. The fix was MGPipeEmitVertexElementsDestroyAndFree, a client-side
helper taking the lifetime id (the object is already gone) that emits delete_*, raises the
notice, and frees the slot in that fixed order (PipeMutation.h:136-141,
MG_Impl/Pipe/PipeFill.cpp:801-...).
Therefore: for all five P4a kinds the notice at the destructor is NOT sufficient. Each
destructor must call a client helper of the same shape. The five call sites are the five #if MOBILEGL_PIPE_PUSH destructor bodies listed in 1.1 — they already exist, so P4a edits five
existing lines rather than adding five destructors (which would resize the pull build's symbol
set and break G1).
1.3 The per-object version counters P4a's shutters and records need
Framebuffer (FramebufferObject.h):
m_attachmentVersions:178, oneUint16per attachment point (FramebufferAttachmentTypeCount= 41 entries,:24-71), read whole viaGetAllFramebufferAttachmentVersions():152-154.m_objectVersion:190,GetObjectVersion():156— bumped byBumpAttachmentVersion(FramebufferObject.cpp:231-235), bySetReadBuffer(:205-210) and by the fiveSet Default*setters (macro:216-229).SetDrawBufferbumps only throughBumpAttachmentVersion(buffer)(:195-199) — i.e. it versions the new attachment slot, not the draw-buffer array, which is a real quirk:glDrawBufferswritingNONEoverCOLOR0bumpsm_attachmentVersions[None], not[Color0].m_drawBuffers:180(Array<FramebufferAttachmentType, MAX_DRAW_BUFFERS=kMGMaxDrawBuffers>,:107,113),m_readBuffer:181, defaults seeded in the ctor:126-135(BackLeftfor name 0,Color0otherwise).FramebufferAttachmentObject:74-103is the attachment struct:SharedPtr<ITextureObject>orSharedPtr<RenderbufferObject>(:96-97),m_textureUploadTarget:98,m_textureLevel:99,m_textureLayer:100,m_layered:101,m_isValid:102.IsComplete()FramebufferObject.cpp:89-99,GetSize():101-119(resolvesUnknownupload target toGetUploadTargets()[0]).FramebufferStateFramebufferState/FramebufferState.h:15-45:m_framebufferObjects(UnorderedMap<Uint, SharedPtr<…>>):41,m_bindingSlots[Draw|Read]:43-44,NoteAttachmentChanged/GetAnyAttachmentGeneration:33-34under#if MOBILEGL_PIPE_PUSH:28-40.MarkFramebufferObjectForDeletionFramebufferState.cpp:59-72rebinds any slot holding the victim to FBO 0 and erases the map entry.
Texture (TextureState/TextureObject.h): ITextureObject :23-115 is the interface (39
pure virtuals); TextureObjectBase :117-234 the storage-free base; TextureObjectMipmap
:236-303 adds the level API; TextureObjectWithOneMipmap :344-378 holds
MipmapUploadTargetArray<1> m_textureStorage :377. Concrete leaves: TextureObject1D/2D/3D.h,
TextureObject2DCube.h, TextureObjectBuffer.h, TextureObjectView.h.
Three independent counters, all on the base:
m_textureParamsVersion(Uint16):213,GetTextureParamsVersion():158— the glTexParameter family.m_shapeVersion(Uint64, starts at 1):217,GetShapeVersion():160, bumped only byBumpShapeVersion()(:202, defTextureObject.cpp:44-54), which also callspGLContext->BumpSamplingResolutionGeneration():53. Invalidates the completeness memom_completeMemoShapeVersion[2]/m_completeMemoValue[2]:220-221, read byIsMipmapCompleteForFilterCachedTextureObject.cpp:298-307.m_contentVersion(Uint64, starts at 1):224,GetContentVersion():159, bumped byMarkStorageDirty(dirty=true)(TextureObject.cpp:379-386),MarkStorageDirtyRegion(:392-398) andBumpContentVersion()(:309-312).- Other state the descriptor needs:
m_internalFormat:207,m_sampler:208(every texture owns aSamplerObject(0),TextureObject.cpp:58),m_swizzleParams:209,m_levelRange:211({0,1000}),m_immutableLevels:212,m_depthStencilTextureMode:225,m_viewMinLevel/NumLevels/MinLayer/NumLayers:228-231,m_samples:232,m_fixedSampleLocations:233. - Views:
TextureObjectView.h:44-135,m_storageOwner:134,GetViewStorageOwner()override:49; the base returns a static nullTextureObject.cpp:338-343.IsTextureView()TextureObject.h:102. A view is composed onto the ROOT at creation, so one hop always reaches storage (:90-101) — that is exactlyMGPResourceDesc::ViewOf. - Texture buffers:
TextureObjectBuffer.h:16-48—m_bufferBindingSlot:46,m_bufferRangeOffset:47,m_bufferRangeSize:48withkWholeBuffer = ~0:28,GetBufferRangeSizeInBytes():35-41resolves live against the buffer's current size — which is exactly thekMGPipeWholeBuffer/ "resolved live" contract ofMGPipeTypes.h:173-177. TextureStateTextureState/TextureState.h:39-173:MAX_TEXTURE_IMAGE_UNITS = 192:42,MAX_PER_STAGE_TEXTURE_IMAGE_UNITS = 32:47;m_textureUnits:166,m_imageTextureBindings:167,m_textureObjects:169,m_defaultTextureObjects:172(name 0, one per target, immortal).ImageTextureBinding:18-37={Texture, Level, Layered, Layer, Access, Format, Version}withBind():27-36bumpingVersion.TextureUnitTextureState/TextureUnit.h:15-26:m_slots[TextureTargetCount]:24+m_sampler:25;SetSamplerObjectTextureUnit.cpp:28-40bumpspGLContext->BumpTextureBindGeneration():39.
Renderbuffer — the outlier: no version at all. RenderbufferObject.h:63-70 has
m_externalIndex, m_lifetimeId, m_internalFormat, m_width, m_height, m_samples,
m_allocated, m_componentSizes and nothing else. SetInternalFormat
(RenderbufferObject.cpp:96-99), AllocateStorage (:101-105) and SetSamples (:107-109)
write the members and bump nothing and notify nothing. See §5.2 — this is a real P4a hole.
Sampler — m_version (Uint16) SamplerObject.h:77, GetVersion() :58;
BumpVersion() :73 (def SamplerObject.cpp:43-54) is the only writer, called by all 14
setters (:56-...), each of which early-outs on an unchanged value. It does three things:
++m_version, pGLContext->BumpSamplingResolutionGeneration() (:50), and
MGP_NOTE_AGGREGATE(TextureParams) (:53). m_samplerParameters :78 is the whole
SamplerParameters POD that MGPSamplerDesc::Parameters ships byte-for-byte
(MGPipeTypes.h:283-287; docs/Disaggregated/ARCHITECTURE.md:4.1 insists borderColorForm
travels with it). SamplerState SamplerState.h:17-31 is just a map + IndexGenerator.
Program — five counters, all read by the tracker (Tracker.h:251-260):
m_linkVersion(mutable Uint32)ProgramObject.h:1418,GetLinkVersion():793— bumped only byBumpLinkObservableVersions()(decl:1340), called at Link()'s prologue and at both publish sites (ProgramObject.cpp:72,:119).m_backendStateVersion(mutable):1399,GetBackendStateVersion():790.m_blockBindingVersion:1401,GetBlockBindingVersion():1013, bumped bySetUniformBlockBinding:1020-1027(and the storage-block setter).m_imageUnitVersion:1394,GetImageUnitVersion():855, bumped insideSetUniformSamplerOrImageUnitIndexonly for image uniforms:847-849.m_uniformWriteSetVersion:1393,GetUniformWriteSetVersion():601.m_uboContentVersion(mutable):1417,GetUBOContentVersion():740,MarkUBOContentDirty():741-743—~0uis reserved as the backends' "never uploaded" sentinel and the wrap skips it.m_artifacts:1426andm_spirv:1429are private and reachable only throughArtifacts()/Spirv(), which joinm_pendingLink:1434/m_pendingSpirv:1438(invariant I5,:1264-1275).
2. The mutation entry points, per family
Frontend GL entry points live in MG_Impl/GLImpl/<Family>/GL_<Family>.cpp in a two-layer
shape: a Foo_State(...) function that mutates MG_State and validates, and a public Foo(...)
that calls it then dispatches through MG_Backend::gBackendFunctionsTable.GL.Foo behind an
MGP_FILL(Verb). P4a's emission sites are the _State bodies (mutation) and, for the verbs,
the MGP_FILL point.
2.1 Framebuffer — MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp (182 KB), decls
GL_Framebuffer.h:16-96
- Attach:
FramebufferTexture{,1D,2D,3D,Layer}GL_Framebuffer.h:44-49,NamedFramebufferTexture{,1D,2D,3D,Layer}:50-55,FramebufferRenderbuffer:34,NamedFramebufferRenderbuffer:35-36. All land inFramebufferObject::AttachTexture(FramebufferObject.cpp:137-142) /AttachRenderbuffer(:144-148) /Detach(:150-153), each ending inBumpAttachmentVersion(:231-235) →++m_attachmentVersions[type],++m_objectVersion,MGP_NOTE_AGGREGATE(FramebufferAttachment). - Draw/read buffers:
DrawBuffer/DrawBuffers/ReadBuffer:78-80and theirNamed*forms:56-58. Implementations:DrawBuffersForFramebuffer_StateGL_Framebuffer.cpp:1719-1830,DrawBuffers_State:1831-1836,DrawBuffer_State:1838-1848,ReadBufferForFramebuffer_State:1850-1899(ends atfbo->SetReadBuffer(attType):1898),ReadBuffer_State:1901-1906,NamedFramebufferDrawBuffers_State:1908-1916. - Default geometry (
ARB_framebuffer_no_attachments):FramebufferParameteri:71,NamedFramebufferParameteri:73→ the five settersFramebufferObject.h:146-150, defined by the macro atFramebufferObject.cpp:216-229. - Renderbuffer storage:
RenderbufferStorage/RenderbufferStorageMultisample:23-25,NamedRenderbufferStorage{,Multisample}:30-32; bodiesRenderbufferStorageMultisample_StateGL_Framebuffer.cpp:817-842,AllocateRenderbufferStorage_State:844-858,RenderbufferStorage_State:860-866. None of them notify anything (§5.2). BindFramebuffer:85,BindRenderbuffer:38,DeleteFramebuffers:81,DeleteRenderbuffers:37,Blit{,Named}Framebuffer:75-77,83-84,Invalidate*:61-66,CheckFramebufferStatus:82/CheckNamedFramebufferStatus:69,ReadPixels/ReadnPixels:16-18,ClearBuffer*:19-22/ClearNamedFramebuffer*:59-60,67-68.DefaultFramebufferInfoGL_Framebuffer.h:87-96holds the default FBO plus its three synthetic attachments;pDefaultFramebufferInfo->defaultFBOis the identity every "is this the default" test compares against (GL_Framebuffer.cpp:1834,:1844,:1904). This is exactly the four comparisonsMGPipeHandles.h:67-73retires intokMGPipeDefaultFramebuffer{0,1}.
2.2 Texture — MG_Impl/GLImpl/Texture/GL_Texture.cpp (426 KB), decls GL_Texture.h
Storage / respecify (each → MGPResourceDesc create-or-respecify):
TexImage3D_State:2352,TexImage2D_State:2524,TexImage1D_State:2678— eachAllocateStorage(...)(:2473,:2621,:2734), optionalUpdateMipmapSubData(:2515,:2668,:2754), thenMarkStorageDirty(..., true)(:2518,:2674,:2758).TexImage2DMultisample_State:2308,TexImage3DMultisample_State:2264.CompressedTexImage{1,2,3}D_State:4984,:4904,:4830(AllocateStorage:4895/:4975,MarkStorageDirty:4901/:4981).TexStorage{1,2,3}D:6002,:6027,:6052— allocate every level withMarkStorageDirty(..., false)(:5554,:5628,:5698) thenTruncateMipmapLevels(:5564,:5644,:5714).TexStorage{2,3}DMultisample:6110,:6121, shared validatorTexStorageMultisample_State:6098.- DSA
TextureStorage*/TextureSubImage*/CompressedTextureSubImage*—GL_Texture.h:41-60. TextureView:5838(declGL_Texture.h:83) →TextureState::CreateTextureViewObject(TextureState.h:55-58).TextureBuffer/TextureBufferRangeGL_Texture.h:73-74.- The generated-mip storage grow:
GL_Texture.cpp:528-539(AllocateStorage,MarkStorageDirty(false),TruncateMipmapLevels, thenBumpContentVersion():539— the level SET grew without any CPU byte being dirty).
Sub-image (each → MGPSubData + regions):
TexSubImage3D_State:1725→MarkStorageDirtyRegion(target, level, {x,y,z}, {w,h,d}):1820-1821.TexSubImage2D_State:1825→MarkStorageDirtyRegion(..., {x,y,0}, {w,h,1}):1940.TexSubImage1D_State:1945→:2007.CompressedTexSubImage{1,2,3}D_State:4818,:4658,:4489.ClearTexImage:1102/ClearTexSubImage:1118→MarkStorageDirty(..., true):1006,:1097.- Copy family:
CopyTexImage2D_State:4389/_Backend:4467,CopyTexSubImage2D_Backend:4028,CopyTexSubImage3D_State:3986,CopyTexSubImage1D_State:4372,CopyTexImage1D_State:4474, DSACopyTextureSubImage{1,2,3}DGL_Texture.h:133-136. CopyImageSubDataGL_Texture.h:143, validatorValidateCopyImageSubData_StateGL_Texture.cpp:4263, backend hopCopyImageSubData_Backend:4034(verbCopyImageSubData, classkBlitOrCopy,MG_Pipe/FillPoints.def:113) — the payload isMGPCopyRegion(MGPipeTypes.h:708-716).
Parameters (each → MGPTextureParams, or the sampler-owned half):
TexParameter{f,i,fv,iv,Iiv,Iuiv}_StateGL_Texture.cpp:2012,:2110,:2126,:2163,:2194,:2224; DSATextureParameter*GL_Texture.h:62-67.- They land on
TextureObjectBase::Set*, each of which bumpsm_textureParamsVersionand firesMGP_NOTE_AGGREGATE(TextureParams):SetInternalFormatTextureObject.cpp:98-119(alsoBumpShapeVersion:116and aBumpTextureBindGenerationfor the default-texture Unknown↔defined transition:108-113),SetBorderColor:136-145,SetBorderColorI:151-160,SetBorderColorUI:166-175,SetSwizzleParam:202-225,SetSwizzleParamRGBA:227-233,SetBaseLevel:239-252(+BumpShapeVersion:251),SetMaxLevel:254-264(+:263),SetImmutableLevels:274-284,SetSamples:318-322,SetFixedSampleLocations:328-332,SetDepthStencilTextureModeinline in the headerTextureObject.h:188-193. - TEXTURE_BORDER_COLOR / MIN_FILTER / WRAP_ are NOT texture-object state*: they live on the
texture's own
SamplerObject(TextureObject.cpp:125-179), so aglTexParameteri(GL_TEXTURE_ MIN_FILTER)runsSamplerObject::BumpVersionand firesMGP_NOTE_AGGREGATE(TextureParams)through that path.
Mipmap generation: GenerateMipmap GL_Texture.cpp:6690-6709 (EnsureGeneratedMipmapStorage Allocated :6707 then GenerateMipmap_Backend :1623), GenerateTextureMipmap :6711-6720
(binds temporarily, WithTemporarilyBoundNamedTexture), guard ValidateGenerateMipmapTexture
:6675-6688, auto-gen hook MaybeAutoGenerateMipmap :1628. Verb GenerateMipmap, class
kTextureOp (FillPoints.def:114), payload MGPMipPlan (MGPipeTypes.h:741-745).
Binds: BindTexture_State :4995, BindTextureUnit :6296, BindTextures :6395,
BindImageTexture :6612-6670, BindImageTextures :6418. BindImageTexture is the one
that is already a verb (FillPoints.def:122, class kTextureOp): it writes
GetImageTextureBinding(unit).Bind(...) :6665-6666, calls NoteTextureUnitTouched(unit)
:6667, then MGP_FILL(BindImageTexture) :6668 and dispatches :6669.
glBindTexture/glBindSampler are not verbs — they route through
TextureState::NoteUnitTouched (TextureState.h:78-100) only.
Readback: GetTexImage_State :5251 / _Backend :5079, GetCompressedTexImage_State :3801,
GetTextureSubImage GL_Texture.h:75-76, CopyTextureImageToClientOrPBO_State
GL_Texture.cpp:5375.
2.3 Sampler — MG_Impl/GLImpl/Sampler/GL_Sampler.cpp (27 KB), decls GL_Sampler.h:14-29
SamplerParameter{f,i,fv,iv,Iiv,Iuiv} :15-20, BindSampler :29, BindSamplers :28,
GenSamplers :25, CreateSamplers :27, DeleteSamplers :26, the six Get* queries.
All setters funnel to SamplerObject::Set* → BumpVersion() (§1.3). glDeleteSamplers goes
through GLContext::MarkSamplerObjectForDeletion (Core.cpp:1228-1240), which unbinds the
sampler from all 192 units first (:1232-1237) so TextureUnit::SetSamplerObject
(TextureUnit.cpp:28-40) bumps the bind generation.
2.4 Program / pipeline — MG_Impl/GLImpl/Program/GL_Program.cpp (188 KB) +
GL_ProgramPipeline.cpp (12.7 KB) + ProgramInterface.cpp (49 KB)
UseProgram_StateGL_Program.cpp:1451-1481(publicUseProgram:2530) → refuses while XFB is active-and-unpaused:1457-1464, refuses an unlinked program:1473-1479, thenpGLContext->UseProgram(program):1480→ProgramState::UseProgram(Core.cpp:393-395).LinkProgram_State:1395(public:2522),DeleteProgram_State:606(public:2434).- The one uniform funnel:
Uniform_State<ItemCount,T>GL_Program.cpp:1483-1561. EveryglUniform*andglProgramUniform*reaches it once per LOCATION (:1495-1496). Non-opaque arm:MarkUniformWrittenAtLocation:1499→BufferUniformWriteif SPIR-V is pending:1516-1519→MapUBO()/GetUBOOffset:1520-1532→ bytes-equal dedupe:1542→Memcpy:1543→MarkUBOContentDirty():1544. Opaque arm:1545-1560:SetUniformSamplerOrImageUnitIndex(location, unit):1559, which bumpsm_imageUnitVersiononly for image uniforms (ProgramObject.h:847-849). Wrappers:Uniformv_State:1563-1587(readsGetProgramForUniform():1567),ProgramUniformv_State:1590. Public entry points from:2534(Uniform1f),:2553(Uniform1i),:2999(ProgramUniform1i), etc. glUniformBlockBinding→ProgramObject::SetUniformBlockBindingProgramObject.h:1020-1027(bumpsm_backendStateVersionandm_blockBindingVersion).glShaderStorageBlockBindingis a verb (FillPoints.def:124, classkProgramOp— the class exists solely because it syncs Espryt's render state and textures,:150-151,:323-334).- Pipelines:
GL_ProgramPipeline.cpp; frontend stateCore.h:200-215(GenProgramPipelineNames:204,CreateProgramPipelineObject:205,BindProgramPipelineObject:208→Core.cpp:1391,MaterializeProgramPipelineObject:211,MarkProgramPipelineForDeletion:212,GetBoundProgramPipeline:215→Core.cpp:1429,m_boundProgramPipelineCore.h:660).
2.5 Pixel store — glPixelStore{i,f}
GLContext::SetPixelStoreParam Core.cpp:1134-1136 → RenderState::SetPixelStoreParam
RenderState/RenderState.cpp:828-856 (a 16-arm macro switch, Pack ×8 + Unpack ×8).
GetPixelStoreParameters(Bool isUnpack) RenderState.cpp:885-887. Storage:
m_pixelStorePackParameters / m_pixelStoreUnpackParameters RenderState.h:180-181 — deliberately
outside RenderStateParameters m_parameters :177, so they are NOT part of the CSO byte range,
and the setter bumps NO version (SetPixelStoreParam never calls BumpVersions() :159-162).
3. Which mutation sites already carry a MGP_NOTE_MUTATION / MGP_NOTE_AGGREGATE
Macros: MG_Pipe/PipeMutation.h:155-158 (push) / :160-161 (pull no-ops).
MGPipeAggregate enum :56-74. Dispatch: MG_Impl/Pipe/PipeFill.cpp:529-552
(MGPipeNoteAggregate) and :509 (MGPipeNoteFrontendMutation).
Complete census of the P4a-relevant sites (grep over MG_State/ + MG_Impl/):
MGP_NOTE_AGGREGATE(FramebufferAttachment) — 3 sites, all in
FramebufferState/FramebufferObject.cpp: :209 (SetReadBuffer), :221 (the five default-geometry
setters via macro), :234 (BumpAttachmentVersion, i.e. every attach/detach/draw-buffer write).
MGP_NOTE_AGGREGATE(TextureParams) — 13 sites: TextureObject.cpp:118, 144, 159, 174, 224, 232, 250, 262, 283, 321, 331; TextureObject.h:192 (SetDepthStencilTextureMode);
SamplerState/SamplerObject.cpp:53 (BumpVersion, the choke point for all sampler params).
MGP_NOTE_AGGREGATE(TextureContent) — 5 sites: TextureObject.cpp:311 (BumpContentVersion),
:383 (MarkStorageDirty), :395 (MarkStorageDirtyRegion);
TextureState/TextureObject2DCube.cpp:53, 65 (the cube overrides).
MGP_NOTE_MUTATION(...) — only four sites in the whole tree, all in
TextureState/TextureState.h: :85 GetMaxTouchedTextureUnit, :98 and :111
GetTextureBindGeneration, :133 GetSamplingResolutionGeneration. (P3a widened
gen_pipe_dirty_surface.py's scan root to MG_State/GLState precisely to bring these inside the
gate — MG_Pipe/DirtySurface.def:150-153.)
Nothing at all fires for: any RenderbufferObject mutation, any ProgramObject version bump,
glUseProgram, glBindProgramPipeline, glBindImageTexture's ImageTextureBinding::Bind
(only NoteUnitTouched runs), and SetPixelStoreParam.
4. The dirty bits P4a inherits — already computed, emitted by nobody
MG_Impl/Pipe/Tracker.h. Enum MGPipeDirty :57-84; names :106-125;
MGPipeSubsystemForDirty :130-154 (returns 0 for every bit past 10 :149-153);
MGPipeMixShutter :170-173; MGPipeWidenedCounter :186-204.
Shutters computed in MGPipeTracker::Update :215-361:
| bit | name | shutter, verbatim | line |
|---|---|---|---|
| 6 | NewShader |
Mix(program->GetLifetimeId(), program->GetLinkVersion()) |
:252, stored :261 |
| 7 | NewShaderBindings |
Mix(Mix(Mix(ImageUnitVersion, BackendStateVersion), BlockBindingVersion), UniformWriteSetVersion) |
:253-257, :262 |
| 8 | NewGlobalConstants |
Mix(LifetimeId, UBOContentVersion) |
:258, :263 |
| 11 | NewFramebuffer |
Mix(ctx.GetAnyFramebufferAttachmentGeneration(), m_framebufferBind.Observe(ctx.GetFramebufferBindingSlot(Draw).GetVersion())) |
:303-306 |
| 12 | NewSamplerViews |
Mix(textureContent, ctx.GetTextureBindGeneration()) |
:307-308 |
| 13 | NewSamplers |
Mix(textureParams, ctx.GetSamplingResolutionGeneration()) |
:309-310 |
| 14 | NewShaderImages |
Mix(Mix(textureContent, textureParams), program->GetImageUnitVersion()) |
:311-312 |
textureContent / textureParams are read at :266-267. The program is read from
ctx.GetCurrentProgram() :246 and deliberately not GetProgramForDraw() — "the tracker must
not force a compile just to answer did the shader move" :242-245. P4a must respect that: the
emitter, which needs the artifacts, is the thing that joins.
Also: m_primed / FreshlyPrimed() :349-350, :417-420; Reset() :378-392 (called from
Update whenever m_context moves :219-222); the fire tallies :353-359.
kMGPipeDirtyEmittedAtP2 :96-99 and kMGPipeDirtyEmittedAtP3a :102-104 — P4a adds a
kMGPipeDirtyEmittedAtP4a constant rather than editing either (the rule is stated at :93-95).
Subsystem bits: MG_Pipe/MGPipe.h:72-95; kMGPipeSubsystemResources = 1<<7 :84,
kMGPipeSubsystemVertexInput = 1<<8 :85, kMGPipeSubsystemsMigratedAtP3a = 0x1ff :95.
Bit 63 is the CSO content-addressing negative control :90. Bits 9+ are free for P4a.
5. The dirty-surface rows P4a's kinds already carry, and the two holes
MG_Pipe/DirtySurface.def. Legend :101-143 (kImmediate, kReverseChannel, kNoBackendRead,
kExplicitDestroy, kUnpublishedDestroy, kPulledEveryVerb, kPulledPartialShutter|BIT).
Gate: scripts/gen_pipe_dirty_surface.py --check / --self-test, scan root now both
MG_Impl/GLImpl and MG_State/GLState, and a MGP_NOTE_MUTATION site counts as a publish
mechanism whose "mutator" is the enclosing function (:16-28, :145-155).
Rows P4a touches:
X(BumpTextureBindGeneration, NEW_SAMPLER_VIEWS):248X(BumpSamplingResolutionGeneration, NEW_SAMPLERS):256(added at P3a by the widened root)X(NoteUnitTouched, kPulledPartialShutter|NEW_SAMPLER_VIEWS):271— the bind generation moves only on thebindingChangedarm;GetMaxTouchedTextureUnithas no shutter at all:265-266X(SetPixelStoreParam, kPulledPartialShutter|NEW_PIXEL_PACK):237with the reasoning at:224-236: the setter writes BOTH halves, only the pack half has a bit, soglPixelStorei(GL_UNPACK_ALIGNMENT, 8)moves nothing at allX(SetActiveTextureUnit, kImmediate):164- Deaths
:299-307:MarkFramebufferObjectForDeletion,MarkRenderbufferObjectForDeletion,MarkSamplerObjectForDeletion,MarkTextureObjectForDeletionare allkExplicitDestroy;MarkProgramForDeletion,MarkProgramPipelineForDeletion,MarkShaderForDeletionarekUnpublishedDestroy:301-302, :305— a recorded hole ("programs, program pipelines and shaders have no per-object handle on the wire at all in P2",:113-124). P4a closes the program row and must decide explicitly what to do with pipeline and shader (a pipeline has no wire object; a composite does — see §9.4). MGP_DIRTY_SURFACE_UNDECIDED_LIST:332is empty; every bit answer above is derived and checked in both directions.
5.1 Hole A — glUseProgram has no row and no publisher
GLContext::UseProgram (Core.cpp:393-395) writes ProgramState::m_currentProgram
(ProgramState.h:100). The scanner sees pGLContext->UseProgram(...) from
GL_Program.cpp:1467/:1480, so a row is owed. Bit 6's shutter mixes lifetimeId ⊕ linkVersion
(Tracker.h:252), so a switch between two programs DOES move it — the row's honest answer is
NEW_SHADER, but it is not there today, meaning the --check gate will fail the moment the
scan is re-run against a UseProgram call site it now recognises. Verify before writing the brief.
5.2 Hole B — a renderbuffer respecify publishes nothing
RenderbufferObject::{SetInternalFormat, AllocateStorage, SetSamples}
(RenderbufferObject.cpp:96-109) bump no version and raise no notice, and
AllocateRenderbufferStorage_State (GL_Framebuffer.cpp:844-858) adds nothing. Bit 11's shutter
(Tracker.h:303-306) is anyAttachmentGeneration ⊕ drawFboBindVersion — neither moves when an
already-attached renderbuffer is re-storaged. So
glBindRenderbuffer; glRenderbufferStorage(newsize) on a renderbuffer that is already an FBO
attachment is invisible to the framebuffer bit. P4a needs either (a) a version + aggregate on
RenderbufferObject (matching TextureObjectBase::BumpShapeVersion, which does exactly this and
routes through BumpSamplingResolutionGeneration), or (b) resource_respecify emitted straight
from the storage entry points the way BufferObject::NotifyRespecify does
(BufferObject.cpp:67-79) — option (b) matches ARCHITECTURE's rule that renderbuffers share
MGPResourceDesc (docs/Disaggregated/ARCHITECTURE.md §4.1 table).
6. The pixel-store shadows — two of them, on opposite sides
6.1 Frontend: unpack is fully resolved client-side, pack is not
Every texture upload calls
MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack(pixels, pGLContext->GetPixelStoreParameters(true), …) before touching the level shadow — call sites
GL_Texture.cpp:905, 1780-1782, 1902, 1991-1992, 2502-2503, 2655-2656, 2749-2750, 6195-6196
(decl MG_Util/Texture/PixelStoreProcessor.h:16, def PixelStoreProcessor.cpp:858). The result
is a tightly packed buffer copied row by row into the level shadow
(GL_Texture.cpp:1803-1817). This is why docs/Disaggregated/ARCHITECTURE.md: §3.2's "explicitly
not migrated" list contains set_pixel_unpack_state ("前端已在 glTexImage 时解析压缩格式、强制默
认 unpack") and why MGPPixelPackState is PACK-only (MGPipeTypes.h:531-542).
Consequence for P4a: MGPSubData's bytes are already tight; MGPSubRegion::SrcRowStride / SrcSliceStride (MGPipeTypes.h:611-612) describe the LEVEL SHADOW's pitch, not the
application's.
Pack, by contrast, is read live at readback: GL_Texture.cpp:5356, :5411,
DirectGLES.cpp:6593, :8043, :9530, :9909.
6.2 Espryt: two process-wide driver shadows, asymmetrically invalidated — the pollution risk
- UNPACK:
ScopedDefaultUnpackState,MG_Backend/DirectGLES/Managers.cpp:4851-4918. Shadow staticss_synced, s_alignment, s_rowLength, s_skipRows, s_skipPixels, s_imageHeight, s_skipImagesat:4911-4917—static inlineclass members, i.e. one set per PROCESS, not per context.EnsureShadowSynced():4874-4891pins the driver to GL defaults exactly once and setss_synced = true:4878.s_syncedis written at:4878and read at:4875and nowhere else in the file — there is no invalidation path at all. The class comment:4842-4850states the invariant it depends on: "The backend unpack state is set ONLY by MobileGL's own save/restore helpers … so the shadow stays accurate". Any glPixelStorei on anUNPACK_*from outside those helpers, or a driver context whose unpack state is not the pinned default, desynchronises it permanently. The known outside writer is the ring path'sglPixelStorei(GL_UNPACK_ROW_LENGTH, texelSize.x())atManagers.cpp:6400(guarded by!ringStaged, comment:6297). - PACK:
PixelStoreImpl, declaredMG_Backend/DirectGLES/Managers.h:1783-1806(struct PackState{Alignment,RowLength,SkipRows,SkipPixels}:1793-1802), definedManagers.cpp:8004-8054— file-scopeg_packState/g_packStateKnown:8006-8007,PinPackState:8009-8016,ApplyPackState:8019-8040,CurrentPackState:8042-8049,InvalidatePackStateCache:8051-8053. This one IS invalidated, at MakeCurrent (DirectGLES.cpp:10578) and at context destroy (:11112).ScopedPackStateRAIIDirectGLES.cpp:6577-6588,PackStateFromContext:6592-6593; users at:7092, 8477, 8588, 8683, 9103, 9432. - Header note
Managers.h:1789-1791:PACK_IMAGE_HEIGHT / SKIP_IMAGES / SWAP_BYTES / LSB_FIRSThave no ES equivalent and are honoured on the CPU from frontend context state — i.e. the boundary must keep carrying the fullPixelStoreParameters(28 B,MGPipeTypes.h:538-542), not the four ES-expressible fields.
P4a's stake: once texture upload is emitted by handle, Espryt's upload path stops being the
thing that reads the frontend's unpack state (ARCHITECTURE.md:252 says it "改为从描述符取步长,
UNPACK_ROW_LENGTH 从 SrcRowStride/bpp 设"), so the P4a emitter has to carry the strides that
ScopedDefaultUnpackState's pin currently makes unnecessary — and the asymmetric invalidation
above becomes a correctness dependency rather than a performance one.
7. ReadSurface resolution and framebuffer completeness
MGPFramebufferState (MG_Pipe/MGPipeTypes.h:355-372, 304 B) demands three things the client
must resolve before the record goes out:
MGPSurface ReadSurface:361— "The RESOLVED read surface, not an index. This is what structurally closes the read-buffer-shared-FBO defect class."Uint8 Complete:364.Uint64 ContentHash:370— both the server's render-pass memo key and the client's emission suppressor.MGPSurfaceitself:343-353(24 B):{Res, InternalFormat, Kind(Texture|Renderbuffer|None), Layered, Level, Layer, UploadTarget}—InternalFormatis inline so four cross-object masks fall out with zero lookups (:341-342).
Where the client gets each piece:
- Bound FBO:
ctx.GetFramebufferBindingSlot(FramebufferTarget::Draw|Read)(Core.h:547,FramebufferState.cpp:49-57); default-FBO identity isFramebufferImpl::pDefaultFramebufferInfo->defaultFBO(GL_Framebuffer.h:89, 95). - Attachments:
fbo->GetAllAttachmentObjects()(FramebufferObject.h:131,FramebufferObject.cpp:159-161) or per pointGetAttachment(type):155-157; thenIsTexture()/IsRenderbuffer()/GetTexture()/GetRenderbuffer()/GetTextureLevel()/GetTextureLayer()/IsLayered()/GetTextureUploadTarget()(FramebufferObject.cpp:53-87), format fromITextureObject::GetFormat()orRenderbufferObject::GetInternalFormat(). - The read surface:
fbo->GetReadBuffer()(FramebufferObject.h:137) thenfbo->GetAttachment(readBuffer)— this is the exact two-step every consumer already does: frontendGL_Framebuffer.cpp:2373, 2921, 2992,Framebuffer/Validators.cpp:185,Getter/GL_Getter.cpp:343, 2114,Texture/GL_Texture.cpp:4445; EsprytManagers.cpp:7286, 7391, 7398, 7415; MagmaVkRenderPassManager.cpp:618,VulkanRenderer.cpp:1806, 1823, 8755, 9243, 10261-10264. The defect class it closes is visible in Espryt'sBackendFramebufferObject::SyncReadBufferToBackend(Managers.cpp:7410-7430), whose comment at:7424-7426says it mustBind(FramebufferTarget::Read)first "When this is reached fromSyncCurrentFBO's 'same FBO as draw' skip path" — the shared-FBO case where the read-buffer sync was skipped. With the surface pre-resolved on the client, that whole hazard is unrepresentable. Complete:FramebufferObject::CheckCompleteness()FramebufferObject.cpp:163-193— walks the 41 attachment slots, skips!IsValid(), requires equalGetSize()and per-attachmentIsComplete(), and requires at least one valid attachment. The GL-visible status is more than that:CheckFramebufferStatus_StateGL_Framebuffer.cpp:2265-2296addsHasDefinedAttachment(to pickINCOMPLETE_ATTACHMENTvsMISSING_ATTACHMENT:2284-2286),HasNonRenderableColorAttachment→UNSUPPORTED:2288-2290, andActiveBackendRejectsDistinctDepthStencil() && HasUnsupportedDistinctDepthStencilAttachments:2291-2294(this one reads the backend, so it cannot be part of a purely-client answer — see the risk note in §14).CheckNamedFramebufferStatus_State:2298-...is the same body. The TODO block at:2279-2282records that DRAW_BUFFER / READ_BUFFER / MULTISAMPLE / LAYER_TARGETS incompleteness is not implemented.Width/Height/Layers/Samples/FixedSampleLocationsfor a no-attachment FBO come fromGetDefaultWidth/Height/Layers/Samples/FixedSampleLocations(FramebufferObject.h:141-145).DrawBuffers[8]fromGetDrawBuffers():135(an array ofFramebufferAttachmentType, so the client converts to theInt8attachment index /-1the payload wants,MGPipeTypes.h:362).
8. Texture sub-data accumulation — the union box and the region list already exist
MG_State/GLState/TextureState/MipmapStorage.h is where a P4a MGPSubData + MGPSubRegion[]
is built from; the frontend structure and the payload were designed against each other.
struct MipmapDirtyRegion:26-39—{IntVec3 lo, hi}half-open,Empty():29,CoversWholeLevel(levelSize):30-33,TexelCount():34-38. Cleared (all zero) while clean:20-25.- Union box per level:
m_dirtyRegions:119;MarkDirtyRegion(level, offset, size):58unions and sets the flag;GetDirtyRegion(level):60reads it, meaningful only whileIsDirty(level):54. - Behind it, the disjoint rect list:
kMaxDirtyRects = 96:75,m_dirtyRects:127(per-levelVector<MipmapDirtyRegion>),GetDirtyRects(level, out, maxRects):82,InsertDirtyRect:114. The design notes:62-81are load-bearing for the wire format: the union box "stays the source of truth (every write funnels throughMarkDirty/MarkDirtyRegioninto BOTH representations), backends OPT IN to the list purely as an upload-size refinement";GetDirtyRectsreturning 0 means "upload the union box instead" and covers every reason at once — tracking unavailable, a single rect, more rects thanmaxRects, or a summed area close enough to the box's that one big upload wins:76-81. 96 slots because a 16-slot list forced far-apart merges to >90% of the box's area on Minecraft's ~100 sprites/frame:68-74. An EMPTY list is the resting state and always means "the union box is the whole story":120-126. - Other level state the descriptor and the blob need:
m_texelSizes:116,m_data:117(the level shadow),m_isDirty:118,m_compressedData/m_compressedFormats:128-129(kept beside, not instead of, the uncompressed shadow — rationale:84-92),m_requestedCompressedFormats:130(:99-109). - The
ITextureObjectface of all of this:MarkStorageDirtyTextureObject.h:252,MarkStorageDirtyRegion:257-262(base fallback degrades to whole-level),GetStorageDirtyRegion:264-267(base fallback returns the whole level),GetStorageDirtyRects:274-281(base fallback returns 0),IsStorageDirty:253,MapMipmapData:251,GetMipmapTexelSize:243,GetMipmapByteSize:244,GetMipmapLevelCount:242. Concrete implsTextureObject.cpp:379-410(TextureObjectWithOneMipmap), cube overridesTextureObject2DCube.cpp:53, 65. - The payload side:
MGPSubRegionMGPipeTypes.h:607-614(X,Y,Z,W,H,D,SrcOffset,SrcRowStride, SrcSliceStride; strides CARRIED, not inferred from a pointer comparison:603-606),MGPSubData:636-648(Res, Target, Level, SourceIsVerbatimLevelShadow, UnionBox, RegionCount, Blob). "Carries the union box AND the region list so the SERVER picks the upload shape — the decision belongs on the side that pays the GPU cost. Mali prices texture upload by JOB COUNT: ~100 sprite rects against one union box measured +6 ms/frame":616-618. The blob rule:624-635: a non-zeroBlob.Sizethat is not exactly the record's own byte size isFatal{ProtocolCorruption}; zero means "not declared", which is what a monolith emission is.MGPSubDataComplete:686-691is the forward terminator for a server-initiated pull. - Espryt's existing consumer, whose shape
MGPSubRegioncopies:UnpackStagingBlockManagers.cpp:4934-4942(src, rowBytes, rows, slices, srcRowStride, srcSliceStride, offset), with the ring-staging contract at:4920-4933and:6297.
9. Program artifacts, reflection, per-stage SPIR-V, the link version
MG_State/GLState/ProgramState/ProgramArtifacts.h (35 KB, 576 lines) is the P0.5 extraction that
makes MGPProgramDesc's reflection blob possible — it includes only <Includes.h> (:10), never
ShaderObject.h or SpvcSession.h.
kInvalidUniformOffset = ~0u:26.struct TypeFacts:31-59— 20 flattened predicates (13Bool+ 7 4-byte scalars), POD of exactly 44 bytes, asserted:551-552.struct ResourceReflection:63-86— one flattenedglslang::TObjectReflection(name, glDefineType, offset, size, index, counterIndex, arrayStride, topLevelArraySize, topLevelArrayStride, binding, location, stages, arraySize, TypeFacts type); aliased asUniformReflection/BlockReflection/PipeInputReflection/PipeOutputReflection:88-91.struct XfbVarying:95-120—{name, type, size, bufferIndex, offsetBytes, byteSize, packedOffsetBytes, blockInstanceName, blockName, blockMemberIndex, blockMemberElement}.struct LinkArtifacts:160-343— the whole link output in one movable block;program(SharedPtr<glslang::TProgram>) is live only between LinkProgram() and the end of DoReflection and is null for an L1-memo-served link:161-165; then the four owned reflection vectors:168-171,lastStageIsFragment:177,computeLocalSize:178, …, ending atxfbVaryingNameMaxLength/xfbNeedsScatteredCapture/xfbPackedStride:340-342. Indexed by TProgram index, deliberately:155-158.struct SpirvArtifacts:359-399—generatedSpirv(Vector<Vector<unsigned>>, one module per entry of the linked-shader snapshot, in that order):360,enableSpirvValidation:361,uniformOffsets:364,globalUboScratch:365,reservedNumSamplesOffset:370,spirvStatus:377,nativeFloat64:386,pointSizeDemoted:398.- The archive:
VisitFields(Self&, Visitor)free templates:401-547, one per type, the same table serving serializer (const) and deserializer (non-const):403-406. This is the ready-made writer forMGPProgramDesc::Reflection. - Trip wires
:549-575:sizeof(TypeFacts)==44; per-STL pins under__GLIBCXX__MGL_RESOURCEREFLECTION_SIZE 128,MGL_XFBVARYING_SIZE 128,MGL_LINKARTIFACTS_SIZE 1056,MGL_SPIRVARTIFACTS_SIZE 88:558-562; the libc++ (NDK) branch is inert until the integrator pins it:563-565— that pin is a P4a chore if the archive crosses on device.
MGPProgramDesc (MG_Pipe/MGPipeTypes.h:323-335, 192 B): {Cso, StageMask (== the linked stage set), GlobalUboSize, ReservedNumSamplesOffset, SpirvStatus, NativeFloat64, PointSizeDemoted, EnableSpirvValidation, MGPBlobRef Spirv[6], MGPBlobRef Reflection}.
9.1 The "as last linked" rule the emitter must obey
ProgramObject::GetLinkedShaderSnapshot() ProgramObject.h:106 returns
Vector<LinkedShaderRef> (:99-103, {shader, source, node}), rebuilt in Link()'s prologue.
HasLinkedShaderStage(stage) :115-120. The header states it twice (:92-98, :121-133):
glAttachShader/glCompileShader take effect only at the next link and neither moves
m_linkVersion, so anything keyed on the link generation must consume this snapshot, never the
live attach list (GetAttachedShaders() :89-90), and the SPIR-V module list is sized and
indexed by the snapshot, not by the attach list. StageMask therefore comes from the snapshot.
9.2 The join gate
Artifacts()/Spirv() join through EnsureLinkJoined() (inline ProgramObject.h:1275, blocking
half ProgramObject.cpp:50-83) and EnsureSpirvJoined() (ProgramObject.cpp:89-122).
JoinPendingLink moves the node out FIRST to avoid re-entry :55-60, moves the artifacts
:66, and calls BumpLinkObservableVersions() :72 (the second bump; the first was at
enqueue :67-71). GLContext::GetProgramForDraw Core.cpp:609-628 is join site J1 and calls
currentProgram->JoinLinkAndSpirv() :627 — both phases, and the comment :622-626 explains
why phase A alone reopens the hazard. GetProgramForDispatch Core.cpp:763-...,
GetProgramForUniform :786-....
P4a's emitter must be the joiner. ARCHITECTURE.md:198 says create_shader_state is emitted
"从编译池的终止 continuation" — i.e. from the compile pool's terminal continuation, not from the
draw — which is the monolith-unreachable async win. That is a design decision for the brief, not
a fact about this tree: today nothing emits it at all.
9.3 What the server still specialises (do NOT try to push it)
docs/Disaggregated/ARCHITECTURE.md:263 lists the eight extra inputs a backend program depends on
(draw-FBO snorm/unorm clamp mask, fragColor broadcast count, storage-block binding signature,
atomic-counter set, live image formats, patch params; Magma adds default-FB height for FragCoord
Y-flip and the XFB layout): create_shader_state publishes the artifacts, the server
specialises at verb time.
9.4 Program pipelines and the composite band
GLContext::GetProgramForDraw Core.cpp:630-...: when no program is current it takes the bound
pipeline :631, joins every graphics stage program :642-645, computes
pipeline->ComputeDrawProgramSignature() :647 (ProgramPipelineObject.h:94-98, an
Array<Uint64, kGraphicsStageCount*2> of per-stage lifetimeId+linkVersion), checks
GetCachedDrawProgram(signature) :648-651 (ProgramPipelineObject.h:149) and otherwise
flattens the pipeline into a fresh MakeShared<ProgramObject>(0u) :661 — deliberately
unnamed (:658-660), graphics stages only (:663-668: a composite carrying a compute module
SIGSEGVs inside vkCreateGraphicsPipelines on Adreno 830).
RefreshCompositeUniforms :649 / SetMirroredUniformVersions :606
(ProgramPipelineObject.h:123-148).
A composite is an ordinary ProgramObject with its own lifetime id, so it fits the ShaderCso
slot space — and MGPipeHandles.h:83-97 already reserves the top 1/16 of that space for exactly
these (kMGPipeShaderCsoSlotLimit = 1<<20 :88, kMGPipeShaderCsoCompositeSlotBase :89-90,
MGPipeIsCompositeShaderSlot :92-94), and SlotAllocator.cpp:19-23 refuses the band from the
ordinary allocator. ProgramPipelineObject itself has no lifetime id and no wire object
(:19-170; the only identity is m_everBound :170 / GetEverBound :51).
ShaderObject (ProgramState/ShaderObject.h:24, dtor :52) has no lifetime id — consistent
with kUnpublishedDestroy and with the design (shaders never cross).
10. PipeInputs — what already exists for these kinds, and what fills it
MG_Backend/MGPipe/PipeInputs.h (736 lines). Storage list
MGP_INPUT_STORAGE_LIST :73-129; the block struct PipeInputs :158-687; three storage
classes V/O/F documented :144-153; sizeof(PipeInputs) < 20 KB asserted :714; the
field-count identity :709-712.
P4a-relevant accessors and their storage:
| accessor | decl | storage member | class |
|---|---|---|---|
GetFramebufferBindingSlot(FramebufferTarget) |
:508-517 |
m_framebufferBindingSlot[kFramebufferTargetCount] :681 |
O (raw ptr into the live context) |
GetImageTextureBinding(Int unit) ×2 |
:518-533 |
m_imageTextureBindingBase :682 |
O |
GetTextureUnitObject(Int unit) |
:549-... |
m_textureUnitBase :686 |
O |
GetProgramForDraw() |
:539-543 |
m_programForDraw :684 |
O (SharedPtr) |
GetProgramForDispatch() |
:534-538 |
m_programForDispatch :683 |
O |
GetTransformFeedbackProgram() |
:544-548 |
m_transformFeedbackProgram :685 |
O |
GetActiveTextureUnit() |
:199-203 |
m_activeTextureUnit :629 |
V |
GetMaxTouchedTextureUnit() |
:313-317 |
m_maxTouchedTextureUnit :647 |
V |
GetTextureBindGeneration() |
:398-401 |
m_textureBindGeneration :662 |
V |
GetSamplingResolutionGeneration() |
— | m_samplingResolutionGeneration :661 |
V |
GetTextureContextId() |
:403-... |
m_textureContextId :663 |
V |
GetPixelStoreParameters |
— | m_pixelStore ([0]=pack, [1]=unpack) :103 |
V |
GetClampReadColor() |
:244-248 |
m_clampReadColor :635 |
V |
| forwarded (F, sticky) | :558-577 |
none | GetProgramObject(Uint) :570, GetTextureObject(Uint) :571, ValidateProgramName :574, GetBufferBindingPointCount :569, HasOpenTransformFeedbackSpan :572, InvalidateCompileEnv :573, RecordError :577 |
kMGPipeForwardedFieldCount = 7 :135, asserted equal to the sticky count :136-137. The F
accessors carry no MGP_INPUT_CHECK / MGP_INPUT_VERIFY_READ and the reason is stated
:563-568.
The two friend doors: MGPipeFillAccess :611 (client filler) and MGPipeApplyAccess :618
(the applier scattering render-state chunks straight into m_renderState).
gPipeInputs :706 — inline PipeInputs& gPipeInputs = *new PipeInputs();, leak-at-exit,
with the full ID-18 rationale at :692-705 (its O-class members are SharedPtrs to FRONTEND
objects; destroying the block at exit runs ~VertexArrayObject → ~BufferObject →
MGPipeApplyResourceDestroy → the backend's twin tables). Any P4a SharedPtr<ProgramObject> /
SharedPtr<ITextureObject> added here inherits that rule verbatim.
10.1 FillPoints.def rows that fill them
MG_Pipe/FillPoints.def. Verb list :79-148 (69 verbs, must be exactly the member set of
MG_Backend::GLFunctionsTable in declaration order :17-22, enforced by gen_pipe.py);
class list :152-153 (nine classes); the may-read table MGP_FILL_FIELD_LIST :158-338.
P4a's fields, by class:
GetFramebufferBindingSlot:kDraw:200,kDispatch:219,kClear:232,kBlitOrCopy:247,kTextureOp:291,kReadback:296,kProgramOp:328.GetTextureUnitObject:kDraw:165,kDispatch:213,kClear:240,kBlitOrCopy:256,kTextureOp:278,kReadback:298,kProgramOp:329.GetImageTextureBinding::170, 218, 245, 261, 279, 308, 334.GetTextureContextId/GetTextureBindGeneration/GetSamplingResolutionGeneration/GetMaxTouchedTextureUnit: the same seven classes (:166-169, 214-217, 241-244, 257-260, 280-283, 304-307, 330-333).GetActiveTextureUnit:kBlitOrCopy:255,kTextureOp:277,kReadback:297.GetProgramForDraw:kDraw:161.GetProgramForDispatch:kDispatch:209.GetPixelStoreParameters:kReadbackONLY:294.GetClampReadColor:kReadback:299.- The eight statically over-approximated rows are all KEPT with reasons
:30-70; three of them are P4a-adjacent (kTextureOp/kDispatch + IsCapabilityEnabledbecause Magma'sGenerateMipmapandPrepareStorageImageTexturesmaterialise a queued clear that readsGL_FRAMEBUFFER_SRGB:43-49;kTextureOp + GetBufferBindingPoint/GetFramebufferBindingSlotfor the depth-mipmap shader path:284-292).P3a STATUS:65-70says the omission sweep rides the full gl44to46 caselist run and every row stays until it lands — so P4a must not retire one on desktop evidence either.
10.2 Coverage.def rows P4a's calls will claim
MG_Pipe/Coverage.def. Accessor→call map :29-132; sticky list :144-151; delta list :156-158;
emitted list :198-232 (what a call now SUPPLIES, so the residual fill loop may skip it).
Existing accessor rows for P4a's kinds: GetFramebufferBindingSlot → SetFramebufferState :84,
GetImageTextureBinding → SetShaderImages :85, GetActiveTextureUnit → SetSamplerViews :30,
GetMaxTouchedTextureUnit → SetSamplerViews :88, GetSamplingResolutionGeneration → SetSamplerViews :109, GetTextureBindGeneration → SetSamplerViews :112,
GetTextureContextId → SetSamplerViews :113, GetTextureObject → SetSamplerViews :114,
GetTextureUnitObject → SetSamplerViews :115, GetProgramForDraw → SetDrawProgram :100,
GetProgramForDispatch → SetDispatchProgram :99, GetProgramObject → CreateShaderState :101,
GetPixelStoreParameters → SetPixelPackState :94.
None of them is in the EMITTED list yet. The two standing warnings for P4a:
GetPixelStoreParametersis deliberately absent from EMITTED:173-181— the field ism_pixelStore[2]andset_pixel_pack_statecarries the pack half only, so a row there would be a half-truth that neither the poison nor the verify comparator could see. P4a must not add it.- P3a's own row (
GetBoundVertexArray → BindVertexElements:202) is "shape-only" and the note:191-197says the decision of whether an emitted call supplies the WHOLE field lives inPipeFill.cpp'sEmittedCallSuppliesTheWholeField(:1116), deliberately rather than silently by the row's presence. Same discipline applies to every P4a row.
11. The payloads P4a fills (all already declared, MG_Pipe/MGPipeTypes.h)
MGPResourceDesc :150-176 (88 B) — one create/respecify shape for buffers, every texture
target and renderbuffers: Target :152, StorageKind (== TextureStorageType) :153,
BindMask :157 (the ELEMENT_ARRAY bit is the index-mirror switch :154-156),
InternalFormat "already resolved to an uncompressed fallback by the client" :158,
Width/Height/Depth :159, ArrayLayers/Levels/Samples :160,
FixedSampleLocations/Immutable :161, Usage/StorageFlags :162-163,
HasDefinedContent :164, ImageBindableHint :165, GlNameForDiag (diagnostics only —
a GL name is never an identity :167-170), ViewOf :172, BufferForTexBuffer :173,
BufOffset/BufSize with kMGPipeWholeBuffer = ~0 :174, 177.
MGPipeResourceRespecifyNeedsAck(desc) :680-682 — desc.Immutable != 0 (rationale :668-679).
Other P4a payloads: MGPSamplerDesc :283-287 (32 B); MGPSamplerView :293-304 (36 B, view
restrictions only — rationale :289-292: an attachment-only / image-only / CopyImageSubData-only
texture has no sampler view to hang params on); MGPTextureParams :307-318 (32 B, per texture
OBJECT, with ForceResync :314 mirroring Espryt's m_forceTextureParamsResync);
MGPSurface :343-353; MGPFramebufferState :355-372; MGPSamplerViews :440-444,
MGPSamplerStates :447-451, MGPImageView :453-462, MGPShaderImages :464-468 (all
{Start, Count, ContentHash} var-tail headers); MGPProgramDesc :323-335;
MGPGlobalConstants :507-513; MGPSubData/MGPSubRegion :607-648; MGPCopyRegion
:708-716; MGPMipPlan :741-745; MGPReadbackInfo :748-756; MGPClear :729-739;
MGPBlit :718-725.
Catalogue rows (MG_Pipe/PipeCalls.def, MGP_CALL_LIST_DOCUMENTED_COUNT 71 :75):
CreateSamplerState/DeleteSamplerState :104-105, CreateSamplerView/DeleteSamplerView
:106-107, CreateShaderState/BindShaderState/DeleteShaderState :108-110,
SetFramebufferState :113, SetSamplerViews :117, BindSamplerStates :118,
SetShaderImages :119, SetGlobalConstants :122, SetDrawProgram/SetDispatchProgram
:126-127, SetTextureParams :133, ResourceSubData :134, ResourceSubDataComplete :136,
ResourceCopyRegion :139, GenerateMipmap :140, GetTextureImage :141, Blit :143,
Clear :144, ReadPixels :145. Record numbering never churns — the wire opcode is the
1-based position, so a new call is APPENDED to its group :26-29.
12. The client emission machinery P4a mirrors
- Slot allocator
MG_Impl/Pipe/SlotAllocator.{h,cpp}—Allocatecpp:38-81,AllocateFor:83-95,FindByLifetimeId:97-105,Acquire:107-111,Free:113-130(stale-gen free is a no-op:117-119),IsLive:132-137,HighWater:151-153,Reset:161-168. Gen moves only on reusecpp:66-75,h:24-28.MGPipeSlots()cpp:170-181is never destroyed with the ID-18 rationale spelled out at:171-179. - CSO cache
MG_Impl/Pipe/CsoCache.h— the exact template for a P4a sampler-CSO or program-CSO cache: version-first skip:14-25, hash + memcmp confirm:84-98(a bare 64-bit equality would alias two states —:20-24),Mint:138-172, LRUEvict:174-183(emitsdelete_*and frees the slot), capacity 64:53, counters:57-69,s_hashForTestseam:121-128,MGPipeCsoCacheInstance()never destroyed:197-203. - Set-hash suppressor
MG_Impl/Pipe/SetHashSuppressor.h—MGPipeSuppressorSlot:44-53already reservesSetSamplerViews(P3b),BindSamplerStates(P3b),SetShaderImages(P4b),SetShaderBuffers(P4b),SetStreamOutputTargets(P4b). There is noSetFramebufferStateslot — P4a adds one (or latchesMGPFramebufferState::ContentHashitself).ShouldEmit:61-67(hash 0 is reserved for "never emitted"; a computed 0 remaps to 1:31-33),Invalidate/InvalidateAll:71-75. The requirement a wired slot puts on its hash is stated:23-29: it must cover every input the record carries, not just the set. - Resource tracker
MG_Impl/Pipe/ResourceTracker.h—MGPipeBindMaskForBufferTarget:96-134+ the exhaustivenessstatic_assert:136-148,MGPipeBuildResourceDesc:171-...,MGPipeBuildSubDataRecord:225-...,MGPipeForEachSubDataRecordRange:257-...(the splitter for ranges past a single record's bound),class MGPipeResourceTracker:282-...(Acquire:292,Find:303,Resolve:313,Retire:325,NotePublished:342,BindMask:355,NoteBoundAs:369,RefreshBindMask:407, counters:448-463,ResetForTest:493),MGPipeResourceTrackerInstance()never destroyed:554-559, and the reverse-channel callbacksMGPipeClientOnBufferWriteback:570-...,MGPipeClientOnGpuWritten:596-...,MGPipeInstallClientResourceCallbacks:623-.... - Vertex-input emitter
MG_Impl/Pipe/VertexInputEmit.h— the CSO-with-blob template: wire builders:78-104,:106-121, content hash:123-129,class MGPipeVertexInputEmitter:136-...(EmitVertexElements:157,EmitVertexBuffers:200,EmitIndexBuffer:256,RecordIsPublished:307,NoteRecordDestroyed:319,Reset:340,EmitCreate:373, the blob staging array:413), instance never destroyed:433-441. - The validate point
MG_Impl/Pipe/PipeFill.cpp:1504-1620+: poison serial:1518-1524, verb + identity:1525-1527, the no-context early exit that still clears the pending base instance:1528-1536, the dirty walk:1544-1545, thewants()lambda that goes throughMGPipeSubsystemForDirtyand never names a subsystem constant:1548-1557, theFreshlyPrimed()block:1570-1585(CSO cache reset,MGPipeApplierReset, suppressor invalidate-all, vertex-input emitter reset — and the note at:1574-1582that the resource tracker is deliberately NOT reset), then the per-subsystem emissions:1587-1615. P4a's emitters go in this ladder. Also here:SubsystemForEmitter:957-...+ its pairingstatic_asserts:984-1032,kMGPipeWiredSubsystems:1071,EmittedCallSuppliesTheWholeField:1116,MGPipeNoteAggregate:529-552, and the seven F-class forwarder bodies:875-906. - Applier
MG_Pipe/PipeApply.h—MGPipeResourceOps:74-86(+Set/Get:90-91), slot boundskMGPipeMaxResourceSlots = 1<<20/kMGPipeMaxVertexElementsSlots = 1<<16:112-113with the "a slot at or above these isFatal{ProtocolCorruption}, never a resize" rule:97-111,MGPipeResourceRecord:117-132,MGPipeVertexElementsRecord:141-152,MGPipeApplierState:154-...— and the explicit split at:194-218: object records (Resources:217,VertexElementsCsos:218) survive a make-current and are cleared only by the object's own death signal orMGPipeApplierReleaseObjectRecords:300; working state (BoundVertexElements:236,VertexBuffers:240, …) is cleared byMGPipeApplierReset:291. Refusal counters:228-229exist because "a no-op nobody can see is a dropped call nobody can see":220-227.
13. Unit tests under MG_Test that cover these frontend objects
All registered with LABELS unit via gtest_discover_tests (e.g.
MG_Test/Framebuffer/CMakeLists.txt:20, MG_Test/Texture/CMakeLists.txt:20, 38).
| file | size | suites | what it pins for P4a |
|---|---|---|---|
MG_Test/Framebuffer/FramebufferTest.cpp |
1669 L, 61 TEST |
FramebufferTest, PackedReadbackEncodeTest |
attach/detach, draw/read buffer validation, completeness |
MG_Test/Texture/TextureTest.cpp |
6714 L, 192 TEST |
TextureTest, SharedExponentRGB9E5Test |
the whole texture surface; ID-8 records that P3a's wave1/wave2 both appended at the same point here and collided — resolve by union, keep every name |
MG_Test/Texture/TextureViewTest.cpp |
551 L | TextureViewTest (:147, 159, 169, 178, 198, 224, 236, 259, …) |
view state, TEXTURE_VIEW_*, composition onto the root, "a framebuffer attach is bounded by the view's own level count" :198 — directly MGPResourceDesc::ViewOf / MGPSamplerView |
MG_Test/Texture/VkClearManagerTest.cpp |
the queued-clear materialisation FillPoints.def:284-289 names |
||
MG_Test/State/ObjectLifetimeIdTest.cpp |
158 L, 6 TEST(ObjectLifetimeIdTest,…) |
the id invariant P4a's handles rest on: constructs/destroys on the heap 64× (:57-58), publishes through a volatile sink so the new/delete pair cannot be elided (:42-46), asserts id ≠ 0 (:72-73) and strictly increasing (:74-76), and skips rather than passes quietly if the allocator never repeats an address (:21-24). Covers BufferObject, RenderbufferObject, VertexArrayObject (:34-36) — not Texture/Framebuffer/Sampler/Program, all of which now have ids. Cheapest P4a test win. |
|
MG_Test/State/RenderStateTest.cpp |
render state incl. the pixel-store setters | ||
MG_Test/Program/ProgramTest.cpp |
4990 L | link, reflection, uniforms | |
MG_Test/Program/ProgramArtifactsTest.cpp |
133 L, 5 TEST(ProgramArtifacts,…) |
AliasesAreTheSameTypes :28, TypeFactsIsPodOf44Bytes :44, VisitFieldsCoversEveryMember :80, VisitFieldsPassesConstnessThrough :90, SizesArePinnedOnThisToolchain :115 |
the archive contract MGPProgramDesc::Reflection serialises through; :115 is where the libc++/NDK numbers get read from (ProgramArtifacts.h:553-557) |
MG_Test/Program/ProgramPipelineCompositeTest.cpp |
724 L | ProgramPipelineCompositeTest |
the composite path of §9.4 |
MG_Test/Program/{AsyncCompile,AsyncLink,AsyncSpirvPhase,AsyncTeardown,OptimisticStatus,ParallelShaderCompile}Test.cpp |
the join gates a P4a emitter will trip | ||
MG_Test/Program/ProgramInterfaceTest.cpp, XfbBlockVaryingTest.cpp, TessellationLinkTest.cpp |
reflection surface | ||
MG_Test/Pipe/* |
PipeCatalogueTest (585 L — payload sizes and the 71-record arithmetic), PipeInputsTest (644 L), TrackerTest (917 L), CsoCacheTest (200 L), SlotAllocatorTest (292 L), ResourceEmitTest (1874 L), VertexInputEmitTest (635 L), RenderStateSpansTest (1368 L), MagmaPipeIdentityTest (217 L) |
ResourceEmitTest and VertexInputEmitTest are the two files a P4a *EmitTest is modelled on; PipeCatalogueTest is where every new payload size assertion lands |
|
MG_Test/Util/PipeStatsTest.cpp |
CounterNamesAreStable (:260), SummaryLineCarriesEveryClassAndGate (:137) |
any new PipeStats::CallClass must be pinned in both |
|
MG_Test/ScopedPipeVerb.h |
the fixture helper for driving a verb in a unit test |
Fixture trap to carry forward — ID-14: MG_Test/Buffer/BufferTest.cpp's ScopedBackendOps
scoped only BufferBackendOps, so under a push build 26 of 86 dispatch cases routed into the
pipe; 83302ca2 made it save/null/restore the pipe table too, the way ResourceEmitTest's
ApplierGuard scopes the applier. Any P4a test that installs a backend ops table needs the same
two-scope shape, and the follow-up (a pipe-shaped mock so the same assertions run over the
handle path) is still open.
14. Named risks and open questions for the brief
Completecannot be a purely client-side answer today.CheckFramebufferStatus_State(GL_Framebuffer.cpp:2291-2294) consultsActiveBackendRejectsDistinctDepthStencil(), andHasNonRenderableColorAttachment:2288reads the backend's probed format-capability cache (the comment at:101says so).FramebufferObject::CheckCompleteness()(FramebufferObject.cpp:163-193) is backend-free but is a weaker answer. Decide explicitly which oneMGPFramebufferState::Completecarries and say so in the payload comment; a client that emits the GL-visible status is reading the backend from the client side, which P4a is supposed to be removing.- Renderbuffer respecify publishes nothing (§5.2) — a real correctness hole, not a narrowing item.
glUseProgramhas noDirtySurface.defrow (§5.1); the gate is likely to go red the moment P4a touches that path.- Espryt's unpack shadow is never invalidated (§6.2,
Managers.cpp:4911vsDirectGLES.cpp:10578/:11112) — once P4a's descriptor carries the strides, the shadow stops being a benign optimisation. FramebufferObject::SetDrawBufferversions the wrong attachment slot (FramebufferObject.cpp:195-199): it bumpsm_attachmentVersions[buffer], i.e. the value being written, not the index being written to.m_objectVersionand the aggregate still move, so bit 11 is safe; a narrower P4a shutter built onm_attachmentVersionswould not be.- The composite band's bookkeeping is unwritten.
MGPipeHandles.h:83-94andSlotAllocator.cpp:19-23reserve and defend the band, but nothing mints from it yet, and a composite is created on a cache miss insideGetProgramForDraw(Core.cpp:661) — aMakeSharedon the draw path, whose death is an ordinary~ProgramObject. ProgramArtifacts.h's libc++ size pins are inert (:563-565). If the archive is to cross on device in P4a, the integrator owes those four numbers from an NDK build.MGPProgramDescis 192 B with six SPIR-V blobs; the ring caps one record at half its capacity (ARCHITECTURE.md§4:RingProducer::MaxRecordBytes()), socreate_shader_stateis named there as one of the two emissions that must be split by the emitter.MGPipeForEach SubDataRecordRange(ResourceTracker.h:257) is the existing splitter shape.- ID-19's cost budget. P3a already added +699 (Espryt) / +422 (Magma) ns/draw and rd12 moved
+30%/+27% on device. Every P4a per-verb emission needs a suppressor slot or a version-first
skip before it hashes anything — the
MGPipeCsoCache::Acquireshape (CsoCache.h:14-25), not a hash-every-verb shape. kUnpublishedDestroyfor pipelines and shaders (DirtySurface.def:301-302, 305) should be resolved deliberately: a program pipeline has no wire object and a shader never crosses, so the honest P4a outcome may be "the program row becomeskExplicitDestroy, the other two staykUnpublishedDestroywith an updated reason", not "all three close".