59 KiB
Scout — Espryt's framebuffer / renderbuffer / texture surface that P4a converts
Tree: C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg, branch feat/disaggregated
@ 37da3c3a (P3a docs commit). Every line number below was opened in this tree. Paths are repo-relative
from MobileGL/ unless stated.
Files that carry essentially all of it:
| file | lines | what P4a touches |
|---|---|---|
MG_Backend/DirectGLES/DirectGLES.cpp |
11142 | SyncCurrentFBO, BindCurrentFBO, ForceBindCurrentFBO, SyncAndBindFramebufferObject, the two texture sync lists + the unit-bindings epoch, image units, readbacks, blits, CopyImage, GenerateMipmap, the scoped state guards |
MG_Backend/DirectGLES/Managers.cpp |
10698 | BackendFramebufferObject, BackendTextureObject, BackendRenderbufferObject, ScratchFBOImpl, PixelStoreImpl, the unpack ring staging, the death-notice consumer |
MG_Backend/DirectGLES/Managers.h |
2260 | the twin classes' members and the TwinRegistry alias / StateBackendObjectRegistry two-arm template |
MG_Backend/DirectGLES/SlotTables.h |
577 | BackendSlotTable — the {slot,gen} table, both GetOrCreate overloads, ReleaseByHandle, the holder list |
MG_Backend/DirectGLES/Utils.cpp/.h |
2405 / 543 | format caveat/widening predicates, ReadbackImpl client-store walks |
MG_Pipe/MGPipeTypes.h, MG_Pipe/PipeCalls.def |
— | the payloads P4a must fill (§7) |
0. The contract P4a is filling in (already written, all stubs today)
MG_Pipe/PipeCalls.def rows that P4a owns (none are new; only their emitters/appliers are):
:113X(SetFramebufferState, MGPFramebufferState, kCtxState, kNone):118X(SetSamplerViews, MGPSamplerViews, kCtxState, kVarTail):119X(BindSamplerStates, MGPSamplerStates, kCtxState, kVarTail):120X(SetShaderImages, MGPShaderImages, kCtxState, kVarTail):133X(SetTextureParams, MGPTextureParams, kCtxObject, kNone):140X(GenerateMipmap, MGPMipPlan, kCtxObject, kNone):141X(GetTextureImage, MGPReadbackInfo, kCtxObject, kReplySlot):139X(ResourceCopyRegion, MGPCopyRegion, kCtxObject, kNone):142/:143X(Blit, MGPBlit, kCtxVerb, kNone),X(Clear, MGPClear, kCtxVerb, kNone):144X(ReadPixels, MGPReadbackInfo, kCtxVerb, kReplySlot)- CSO create/delete:
:105-108CreateSamplerState/DeleteSamplerState/CreateSamplerView/DeleteSamplerView.
Payload shapes (MG_Pipe/MGPipeTypes.h), with the fields that decide P4a's work:
MGPResourceDesc:150-175, 88 B. One create/respecify shape for buffers, every texture target and renderbuffers —Target,StorageKind(==TextureStorageType),BindMask,InternalFormat,Width/Height/Depth,ArrayLayers/Levels/Samples,FixedSampleLocations,Immutable,HasDefinedContent,ImageBindableHint(:166, "client-side everImageBound; pre-emptive allocation"),ViewOf(:173, texture-view storage owner),BufferForTexBuffer+BufOffset/BufSize(:174-175) for a buffer texture. So texture creation reuses P3a'sresource_create/respecify.MGPSurface:342-352, 24 B:Res,InternalFormatinline (so the cross-object masks fall out with no lookup),Kind(Texture|Renderbuffer|None),Layered,Level,Layer,UploadTarget.MGPFramebufferState:354-371, 304 B:Fbo(kMGPipeDefaultFramebufferfor the default FBO),Color[8],Depth,Stencil,ReadSurface— "the RESOLVED read surface, not an index. This is what structurally closes the read-buffer-shared-FBO defect class" (:359-361),DrawBuffers[8]asInt8attachment indices with -1 = NONE,Width/Height/Layers/Samples,FixedSampleLocations/IsDefault/Complete, andContentHash(:369) doing two jobs: the server's render-pass memo key and the client-side emission suppressor. Note for the implementer:Color[8]andDrawBuffers[8]are 8, while the frontend today carriesFramebufferObject::MAX_DRAW_BUFFERSdraw buffers andColor0..Color31attachment points (Managers.h:1579-1581computesMAX_COLOR_ATTACHMENT_SLOTSasColor31-Color0+1= 32). The attachment-relocation table (§1.4) is 32 wide. This is a real width mismatch P4a has to rule on.MGPSamplerView:288-303, 36 B: "ONLY the view restrictions. Everything aglTexParameterwrites lives onset_texture_paramsinstead, because a texture that is only an FBO attachment, only an image binding or only aglCopyImageSubDataendpoint has no sampler view to hang it on" (:288-291). CarriesInternalFormat(aliasing format forglTextureView),Target,MinLevel/NumLevels/MinLayer/NumLayers,Samples,FixedSampleLocations.MGPTextureParams:306-318, 32 B:Res,BaseLevel/MaxLevel,Swizzle[4],DepthStencilMode,ForceResync— "Mirrorsm_forceTextureParamsResync" (:312-313),MinLod/MaxLod/LodBias.MGPBoundView:428-434/MGPSamplerViews:436-440;MGPImageView:443-452/MGPShaderImages:454-458;MGPSamplerStates:442-446.MGPCopyRegion:708-716(64 B),MGPBlit:718-727(56 B),MGPClear:729-739(48 B),MGPMipPlan:741-745(16 B),MGPReadbackInfo:748-756(64 B,Resnull forread_pixels: "the bound read surface answers").
MGPTextureParams has no border colour and no filter/wrap/compare/anisotropy; those live in
MGPSamplerDesc (:283-287, a blob of SamplerParameters) — but Espryt today pushes filters/wrap/LOD/
anisotropy onto the texture object through SyncBuiltinSamplerToBackend (§2.4). That mapping
(built-in sampler → sampler CSO, or → an extra params field) is P4a's second contract ruling.
1. How Espryt resolves the current draw/read framebuffer today
1.1 The FBO twin and its table
FramebufferImpl::BackendFramebufferObject — Managers.h:1537-1610.
Members (all of them are the server-side state P4a must relocate):
| member | line | meaning |
|---|---|---|
m_backendFBOId |
Managers.h:1561 |
driver FBO name, glGenFramebuffers in the ctor (Managers.cpp:7015) |
m_contextGeneration |
:1562 |
ES context generation the id belongs to; dtor only deletes when it matches (Managers.cpp:7035) |
m_frontendDrawBuffers[MAX_DRAW_BUFFERS] |
:1570-1571 |
the app's draw-buffer array verbatim (holes / reversals allowed) |
m_backendDrawBuffers[MAX_DRAW_BUFFERS] |
:1577 |
the ES-legal compacted array actually handed to glDrawBuffers |
m_backendColorSlots[32] |
:1593 |
the permutation: where frontend COLOR_ATTACHMENTn physically lives as a backend COLOR_ATTACHMENTm |
m_frontendReadBuffer / m_backendReadBuffer |
:1600-1601 |
read-buffer memo |
m_syncedFrontendAttachmentVersions |
:1604 |
per-attachment-point memo, keyed on FRONTEND attachment versions |
m_syncedBackendIdGeneration |
:1609 |
g_attachmentBackendIdGeneration at the last attachment walk |
Table: extern TwinRegistry<FramebufferObject, BackendFramebufferObject, MGPipeKind::Framebuffer> g_backendFramebufferObjects; — declared Managers.h:1612-1613, defined Managers.cpp:7754-7755.
TwinRegistry is the alias at Managers.h:547-553 that swallows the kind in the pull build;
StateBackendObjectRegistry (Managers.h:303-540) dispatches to m_slotTable
(BackendSlotTable, Managers.h:538) when EsprytSlotTablesEnabled() (SlotTables.h:132-135),
otherwise to the legacy UnorderedMap<StateObject*, Entry> (Managers.h:319).
The other two tables of this family:
TextureImpl::g_backendTextureObjects—Managers.h:1521-1522, definedManagers.cpp:7001.RenderbufferImpl::g_backendRenderbufferObjects—Managers.h:2257-2258, definedManagers.cpp:10695-10696.- (
SamplerImpl::g_backendSamplerObjects—Managers.h:2230-2231, definedManagers.cpp:10583.)
All four still mint their handle inside MG_Backend off a frontend GetLifetimeId()
(SlotTables.h:231-232) — that is the debt SlotTables.h:70-77 records against itself and that
P4a discharges for these kinds, exactly as P3a's seventh table (BufferImpl::BackendBufferResourceTable,
Managers.h:690-692) already does with GetOrCreate(MGPipeHandle) (SlotTables.h:278-326) and
ReleaseByHandle (SlotTables.h:350-361).
1.2 The frontend binding slot read
GetFramebufferBindingSlotChecked(FramebufferTarget) — DirectGLES.cpp:173-193. On the handle arm it is a
plain MGB_CTX->GetFramebufferBindingSlot(target) (:176); on the legacy arm it goes through the raw
pointer cache g_fbSlotCache / g_fbSlotCacheContext (:156-157, :179-188), which the comment at
:159-172 explains was a poison/verify bypass and is therefore kept only for the pull control.
Five call sites: :1944, :2242, :3080, :3210, :3300.
1.3 SyncCurrentFBO — DirectGLES.cpp:2229-2315
Sequence, verbatim:
:2233-2235— threeCollectGarbageIfNeeded()ticks (framebuffer, texture, renderbuffer). These are no-ops on the handle arm (Managers.h:477-491).:2237— walks{Draw, Read}in that order;lastUpdatedFBOcarries the draw object forward.- The four-part memo,
:2254-2266:slotVersion(slot.GetVersion()),objectVersion(currentFBO->GetObjectVersion()), the rawcurrentPtr(identity only), andg_attachmentBackendIdGeneration. All four stamped together byStampSyncedFBO(:2221-2227) intog_fboSyncedSlotVersions/g_fboSyncedObjectVersions/g_fboSyncedObjects/g_fboSyncedBackendIdGenerations(declaredManagers.h:1690-1710, definedManagers.cpp:7756-7762). The comment at:2245-2253records the bug that fixed: leaving the slot version toForceBindCurrentFBOalone made the early-out never fire and a Minecraft-shaped frame re-ran the whole attachment walk on all 5495 draws. :2268-2271— no FBO bound at all →MGLOG_E_ONCE, continue.- Default-framebuffer branch
:2273-2284: nothing to sync except resettingg_alphaWidenedDrawBufferMask/g_integerColorDrawBufferMaskto 0 for the DRAW target, then stamp. - Same-FBO-as-draw skip
:2286-2302: when the READ target names the object already synced as DRAW, the attachment/draw-buffer work is skipped butSyncReadBufferToBackendis still called (:2295-2298), through aFindon the twin table. This is theread-buffer shared FBOfix (draw_buffers_1);MGPFramebufferState::ReadSurfaceis the structural replacement. :2304-2310— resolve-or-create the twin, materialise it if empty,SyncToBackend(currentFBO, target).
1.4 BackendFramebufferObject::SyncToBackend — Managers.cpp:7521-7737
:7533Bind(asTarget)first.- Draw buffers
:7537-7585: memcmp againstm_frontendDrawBuffers; applied only whenasTarget == Draw(:7551) — the comment:7544-7550records the Minecraft 26.x OIT bug where a READ-only sync would landglDrawBufferson the wrong framebuffer and falsely stamp the memo.FramebufferAttachmentType::None→GL_NONE(:7558-7560) — that is the "draw-buffer None" handling; default-framebuffer FRONT/BACK tokens pass through unremapped (:7564-7569); everything else is forced toGL_COLOR_ATTACHMENT0 + i(:7571), andnEffectiveBuffersis the highest non-None index + 1 (:7573). RecomputeBackendColorSlots—Managers.cpp:7432-7519. Three passes: draw-buffer slotspins attachmentato backend points(:7453-7474); survivors keep identity (:7479-7486); displaced attachments are parked on the lowest free point (:7492-7504). Returns "moved", and each moved attachment has its version memo forced to~0(:7515-7516). When anything moved the read-buffer memo is dropped (:7580-7582).GetBackendAttachmentType(:7739-7752) is the single reader.- Draw-target-only mask recompute
:7587-7619:snormClampOutputMask,unormClampOutputMask,g_alphaWidenedDrawBufferMask,g_integerColorDrawBufferMask(globals declaredManagers.h:1650,:1659, definedManagers.cpp:7308-7309; the shader-side masks go intoPrgramImpl::g_snormFallbackClampOutputMask/g_unormFallbackClampOutputMask). - Read buffer
:7623-7625→SyncReadBufferToBackend(Managers.cpp:7410-7430), which binds as READ first (:7427) thenglReadBuffer. - Attachment walk
:7627-7724: re-arm every point whenm_syncedBackendIdGeneration != g_attachmentBackendIdGeneration(:7632-7636); per point comparem_syncedFrontendAttachmentVersions[i] != attachmentVersions[i](:7650); an empty colour point belowMaxColorAttachmentsis explicitly detached withglFramebufferRenderbuffer(..., 0)(:7659-7666) — the comment:7651-7658says removing that detach breaks the permutation invariant. - Re-entrancy
:7726-7736: the walk itself can re-mint a texture id, soSyncToBackendrecurses untilg_attachmentBackendIdGenerationis quiescent.
SyncAttachmentObject — Managers.cpp:7142-7216: the attachment id/layer/level resolution.
- Texture branch
:7145-7194: find-or-create the texture twin,SyncMipmapsToBackendonly (:7161— see §2.6, the parameter gap), then three shapes: layered →glFramebufferTexture(target, att, id, level)(:7162-7165); 3D / 2DArray / 1DArray / CubeMapArray / 2DMSArray →glFramebufferTextureLayer(..., level, layer)(:7166-7177); otherwise bind +glFramebufferTexture2Dwith the cube-face target (:7178-7194, the cube-face bind rule at:7184-7190). - Renderbuffer branch
:7195-7214: find-or-create,SyncToBackend,Bind(),glFramebufferRenderbuffer. - An empty attachment returns
trueand issues nothing (:7215).
Read-side helpers on the frontend READ binding: GetReadColorAttachment (Managers.cpp:7280-7291),
IsFixedPointFallbackReadAttachment (:7385-7408), IsAlphaWidenedFallbackReadAttachment (:7377-7383),
IsAlphaWidenedColorAttachment (:7293-7306), ComputeAlphaWidenedDrawBufferMask (:7357-7372),
IsIntegerColorAttachment (:7344-7355), IsSnormFallbackAttachment / IsUnormFallbackAttachment
(:7246-7276).
1.5 Binding: BindCurrentFBO / ForceBindCurrentFBO / SyncAndBindFramebufferObject
BindCurrentFBO(target)—DirectGLES.cpp:3206-3260. No fast path on the slot version — the comment:3211-3216recordsKHR-GL32.packed_pixelsreading out of the previous subtest's framebuffer. Handle arm: a plainFind(:3229-3232); legacy arm: the 64-slotg_fboTwinLookupMemo(DirectGLES.cpp:140-141, used:3237-3243). Default FBO →BindFramebufferId(..., 0)through the shadow (:3253-3259) — "a raw bind here would leave the shadow claiming the previous user FBO, false-skipping its next re-bind". This is the default-FBO-bind trap from the readback-state-guards work; its siblings areSyncAndBindFramebufferObject's early return (:3267-3280) andNoteFramebufferIdDeleted(Managers.cpp:7096-7105).SyncAndBindFramebufferObject(fbo, target, forceSync)—DirectGLES.cpp:3262-3294; used by the DSA clears andBlitNamedFramebuffer.forceSync→InvalidateSyncedState()(Managers.cpp:7119-7140).ForceBindCurrentFBO(target)—DirectGLES.cpp:3296-3308: sync+bind then stamp all four memo arrays by hand.
Driver-binding shadow: g_driverFBOBindings / g_driverFBOBindingKnown (Managers.cpp:7051-7056),
BindFramebufferId (:7058-7079), CurrentFramebufferBinding (:7081-7094, the one glGetIntegerv
cold pin), NoteFramebufferIdDeleted (:7096-7105), InvalidateFramebufferBindingCache
(:7107-7117 — also clears the three synced-FBO arrays; called from DirectGLES.cpp:5597, :6112,
:6211, :10133, :10577, :11110).
Call sites that drive the pair (SyncCurrentFBO then BindCurrentFBO): PrepareForDraw
(DirectGLES.cpp:3359 + :3363), Clear (:4511 + :4518), BlitFramebuffer (:6433, :6442-6443),
CopyTexImage2D (:7176 + :7187), CopyTexSubImage2D (:7270 + :7282), ClearBufferfi/fv/iv/uiv
(:7866/7912/7925/7941 + :7869/7915/7931/7944), the four ClearNamedFramebuffer*
(:7969, :7986, :8006, :8026 via ForceBindCurrentFBO), ReadPixels (:9510 + :9513),
GetTexImage (:9681), the multisample-resolve fallback (:6507-6508).
1.6 The fragColor MRT broadcast
PrgramImpl DirectGLES.cpp:3037-3057: the memo g_broadcastMemoFbo / g_broadcastMemoSlotVersion /
g_broadcastMemoObjectVersion / g_broadcastMemoValid / g_broadcastMemoCount, keyed exactly like
SyncCurrentFBO's trio and cleared by InvalidateBroadcastMemo() (:3055-3057, called from
DestroyEGLContext :11113). Derivation DirectGLES.cpp:3079-3102: walk drawFBO->GetDrawBuffers(),
enabledDrawBuffers = i+1 for every non-None slot, g_fragColorBroadcastCount = max(count,1).
It is read from the frontend, deliberately not from the backend FBO sync (:3076-3078), because a
program compiled against a stale count would not be relinked until the following draw. It then enters the
program-staleness condition at :3154 (twin->GetFragColorBroadcastCount() != g_fragColorBroadcastCount),
i.e. the draw-buffer array is a shader-compilation input. P4a must keep that ordering when the FBO
state becomes a pushed MGPFramebufferState.
1.7 The other emulations that live in this surface
- Alpha-widened three-channel attachments (
Managers.h:1642-1659): the stored alpha is pinned at 1.0 — clears write 1.0 (SubstituteWidenedClearAlpha,Managers.h:1670-1680, usedDirectGLES.cpp:7917-7920,:7933-7936,:7946-7949, and for DSA at:7961-7963viaIsWidenedNamedDrawBuffer:7894-7907), draws get their alpha write mask forced off (RenderStateImpl::SyncRenderState(forColorClear), contract atDirectGLES.h:209-226, the applied mask memog_syncedColorMaskAlphaWidenMaskatDirectGLES.cpp:2346), and readback overwrites alpha (IsAlphaWidenedFallbackReadAttachment→forceOpaqueAlpha,DirectGLES.cpp:9533-9543).g_integerColorDrawBufferMaskexists becauseglClearBufferfvon an integer buffer isINVALID_OPERATION(Managers.h:1652-1658). - SNORM / UNORM caveat formats:
IsSnormFallbackAttachment/IsUnormFallbackAttachment(Managers.cpp:7246-7276) → the two shader clamp masks; readback clampapplyFixedPointReadClamp(DirectGLES.cpp:9542, the clamp itself:9204-9231). - Layered attachment shapes:
attachmentObject.IsLayered()(5 reads),GetTextureLayer()(1),GetTextureLevel()(6),GetTextureUploadTarget()(4) — the three-way branch atManagers.cpp:7162-7194; the layered-blit substitute atDirectGLES.cpp:6265-6421. - Blit LOD / layered blit:
BlitFramebufferDirectGLES.cpp:6423-…; the non-zero destination array layer is served byglCopyImageSubData(:6288,:6354-6413) because a driver may ignore the layer (:6387). The multisample replicate path builds its own programs (:5889-5910,:6105-6191) and the aspect split is at:6218-6258.EmulateTextureLodBias(Utils.cpp:1749-1830,Utils.h:527-536) is the shader-side LOD-bias emulation, invokedManagers.cpp:9855, gated byShouldAvoidExplicitLodBiasOnAngleLlvmpipe()(Managers.cpp:94-97). - D24S8 dual-mode sampling:
GL_DEPTH_STENCIL_TEXTURE_MODEis forwarded per texture (Managers.cpp:6956-6977; cachem_cacheDepthStencilTextureMode,Managers.h:1501), and the second half — one D24S8 sampled through its own name asSTENCIL_INDEXand through aglTextureViewasDEPTH_COMPONENTin one pass (Better Clouds) — is whyGL_ARB_texture_viewis only advertised behind a host extension andMG_Config::Features.EsprytEnableTextureView(BackendObject_DirectGLES.cpp:1177-1203; the known break isSyncTextureViewToBackendnormalising the view's ES internalformat independently of the storage,:1189-1196). The depth/stencil readback emulation isDepthStencilSamplingReadImpl(DirectGLES.cpp:8117-8578, entered from:8617and:8721), withScopedDepthBlitStateat:6985.
2. The texture object twin
2.1 The class
TextureImpl::BackendTextureObject — Managers.h:1341-1517. Public surface :1343-1417; the members
that are the real server state:
| member | line | note |
|---|---|---|
m_backendTextureId |
1422 |
driver name |
m_bufferImageSplitViewId |
1441 |
second buffer-texture name for glBindImageTexture (the rg32f→r32f split) |
m_viewSourceBackendTextureId |
1447 |
for a glTextureView: the ES name it was made a view OF |
m_contextGeneration |
1455 |
|
m_isInitialized, m_imageBindableStorageRequired, m_backendStorageImmutable |
1456-1458 |
|
m_bufferTextureUnsupportedReported |
1463 |
one-shot report latch |
m_prevTextureInfo (StateTextureBasicInfo, Managers.h:1320-1338) |
1464 |
format/w/h/d/levels/bufferExternalIndex/samples/fixedSampleLocations |
m_syncedContentVersion |
1468 |
|
m_syncedShapeContextId, m_syncedShapeGeneration, m_syncedShapeParamsVersion |
1481-1483 |
the cheap-gate trio |
m_cacheSamplerParameters, m_cacheLodRange |
1484-1485 |
|
m_cacheBorderColor / I / UI / Form |
1490-1493 |
all four, because none alone identifies the driver's border colour |
m_cacheSwizzleParams |
1494-1495 |
|
m_cacheDepthStencilTextureMode |
1501 |
|
m_syncedSamplerVersion, m_syncedTextureParamsVersion |
1502-1503 |
|
m_forceTextureParamsResync, m_forceSamplerResync |
1507, 1516 |
the two re-mint overrides (MGPTextureParams::ForceResync mirrors the first) |
IsDrawSyncClean(t, contextId, samplingGeneration) — Managers.h:1399-1417: the aggregate gate that is
exactly the conjunction of the three per-draw sync calls' own early-outs (documented :1383-1394; it
must never be more permissive).
Unit binding shadow: g_boundTexturesCache[192][TextureTargetCount] (Managers.h:1530-1532, defined
Managers.cpp:6998-7000), g_activeTextureUnit (Managers.cpp:6997), ActivateTextureUnit
(Managers.cpp:6980-6986), UnbindTexture (:6988-6995), Bind(target, unit=TempTextureUnit=0)
(Managers.cpp:4729-4743, TempTextureUnit at Managers.h:1340).
2.2 SyncTextureObjectToBackend — DirectGLES.cpp:1501-1577
Find-or-create (:1507-1512), then a by-value SharedPtr copy of the twin
(:1514-1526) because the sync can re-enter (a texture view syncs its storage texture first) — on the
slot arm the copy is kept only to hold the twin alive. Then, in order:
RequireImageBindableStorage if asked (:1528-1530), SyncTextureParamsToBackend (:1531),
SyncBuiltinSamplerToBackend (:1532), SyncMipmapsToBackend (:1533), and — if the storage sync
re-minted the name — both parameter pushes again (:1541-1544, NeedsParameterResync()
Managers.h:1397). Handle arm re-resolves once (:1546-1566); legacy arm re-resolves and repairs
(:1568-1576).
2.3 The per-draw work lists and the unit-bindings epoch
DirectGLES.cpp:1579-1990 is the block P4a rewrites. In order:
- The identity snapshot.
UnitBindingsSnapshoton the push arm holds lifetime ids, not weak_ptrs (:1594-1622;LifetimeIdOf:1624-1630), with the legacy weak_ptr fields kept underMOBILEGL_PIPE_LEGACY_MEMOS(:1616-1621) and the arm chosen at runtime viaMGB_UNIT_BINDINGS_HANDLE_ARM(:1632-1636,#undefat:1694). Pull-build copy of the same two functions at:1696-1727.CaptureUnitBindings:1638-1664,UnitBindingsUnchanged:1666-1693. The design note at:1579-1593says explicitly what the snapshot does not cover (completeness flips, high-water mark, context identity, ES context generation, the program keys). This is the ~115-line derivation the task names. CurrentUnitBindingsEpoch(maxTouchedUnit)—:1746-1773, overg_observedUnitBindings/…ContextId/…Generation/…MaxUnit/g_unitBindingsEpoch(:1730-1734). Gate:(contextId, bindGeneration, maxTouchedUnit); on a miss it re-walks and bumps the epoch only when the bindings really moved. Stats gateGate::EsprytUnitBindingsEpoch(:1757,:1762).UnitTextureSyncEntry—:1801-1805:{const SharedPtr<ITextureObject>* slot, ITextureObject* texture, BackendTextureObject* backend}. Entries borrow, never own (rationale:1783-1800), andPairingsIntact(:1808-1813) is the structural net under the keys (a DSA by-name slot swap that never reached the bind generation would otherwise drive texture A's twin from texture B's state).g_unitTextureSyncList+ keys:1814-1820(Valid,ContextId,MaxUnit,ContextGeneration,Epoch,SamplingGeneration).g_fboTextureSyncList+ keys:1825-1830(Fbo,SlotVersion,ObjectVersion,ContextId,ContextGeneration).DrawTextureSyncKeys:1839-1844andCaptureDrawTextureSyncKeys():1846-1858(contextId,maxTouchedUnit,samplingGeneration,unitBindingsEpoch).SyncNeccessaryTextures(keys):1860-1988. Unit list hit path:1879-1901(per entry:IsDrawSyncCleanthen the three syncs); rebuild:1902-1930(skipsIsUndefinedDefaultTexture,:1918). FBO list:1944-1987walks the DRAW slot only (:1944), overcurrentFBO->GetAllAttachmentObjects()withattachment.IsTexture()(:1970-1976); a null draw FBO clears the list (:1984-1987). Stats gateGate::EsprytTextureSyncList(:1889,:1904). No-arg wrapper:1990used by every non-draw entry point.
2.4 SyncTextureParamsToBackend / SyncBuiltinSamplerToBackend
SyncBuiltinSamplerToBackend—Managers.cpp:6671-6780. Gatem_syncedSamplerVersion == version && !m_forceSamplerResync(:6684). Multisample targets: cache and return without any GL (:6706-6709). Then min/mag filter (:6732-6744, viaResolveBackendMinFilter), wrap S/T (:6749-6750), wrap R only whenSupportsWrapR(:6751-6755), compare func/mode (:6756-6757), min/max LOD (:6758-6765), max anisotropy behindSupportsTextureFilterAnisotropy(:6766-6775). All of it viaglTexParameter*on the texture object, afterBind(target)at:6711.SyncTextureParamsToBackend—Managers.cpp:6782-6978. Gatem_syncedTextureParamsVersion == version && !m_forceTextureParamsResync(:6794); multisample refresh-but-skip at:6821-6825;Bind(target):6827; base/max level:6837-6847; swizzle with the alpha-widening substitution:6852-6864and the image-widening substitution:6865-6879; border colour (all three forms + form,:~6900-6954);GL_DEPTH_STENCIL_TEXTURE_MODE:6956-6977.
Both Bind() on TempTextureUnit (unit 0), which is why BindCurrentTextures has to re-establish the
real unit bindings afterwards (DirectGLES.cpp:3687-3693).
2.5 SyncMipmapsToBackend — Managers.cpp:5672-…
- Texture-view short-circuit
:5683-5686→SyncTextureViewToBackend. - Cheap gate
:5700-5707:m_isInitialized && shapeContextId && ctx live && shapeContextId == GetTextureContextId() && shapeGeneration == GetSamplingResolutionGeneration() && contentVersion == GetContentVersion() && shapeParamsVersion == GetTextureParamsVersion() && StorageType == Mipmap. IsComplete() || HasAnyDefinedMipmapLevel()bail:5734-5738.- Second fast path
:5751-5778: content version unchanged + aStateTextureBasicInfoprobe equal tom_prevTextureInfo→ re-stamp the cheap gate and return without the scratch bind. Bind(target):5780;currentTextureInfobuilt:5785-5792.TextureStorageType::Mipmap:5794-6503:needsRegeneration:5800; immutable storage forcesRecreateBackendTexture():5801-5804.imageWidening=GetImageBindableStorageWidening(format)only whenm_imageBindableStorageRequired(:5810-5813).canAppendMipmaps:5815-5827(append-only level growth) → theglTexImage2D/3Dappend loop:5833-5910.- Full regeneration
:5912-…:ApplyImageBindableStorageWidening:5920; multisample clamp:5923-5931. - The per-level upload loop
:6184-6500— see §2.7.
TextureStorageType::Buffer:6504-…: callsBufferImpl::EnsureBufferResource(buffer)at:6520(the one place a texture sync pulls a buffer twin — and therefore the one texture path that reachesSyncPersistentMappedRange, see §5), thenCallTexBuffer/CallTexBufferRange(:6589-6598), and mints/pointsm_bufferImageSplitViewIdfor the image split (:6606-6630, rationale:6533-6549).
SyncTextureViewToBackend — Managers.cpp:5579-5670: syncs the storage owner first (:5600-5601,
by-value SharedPtr for the same rehash reason), short-circuits when
m_viewSourceBackendTextureId == storageBackendTextureId (:5613-5616), otherwise
RecreateBackendTexture() (:5620, which also bumps g_attachmentBackendIdGeneration) and calls
ResolveTextureViewEntryPoint() (:5630-5634). StampViewSyncKeys :5569-5577.
2.6 Where an attachment-only / image-unit / CopyImage texture gets — or misses — its parameters
This is the exact answer the task asks for.
| how the texture is reached | code path | gets SyncTextureParamsToBackend + SyncBuiltinSamplerToBackend? |
|---|---|---|
| bound to a sampler unit ≤ high-water mark | SyncNeccessaryTextures unit list, DirectGLES.cpp:1898-1900 (hit) / :1919-1920 → SyncTextureObjectToBackend (miss) |
yes |
| attachment of the DRAW framebuffer | SyncNeccessaryTextures FBO list, DirectGLES.cpp:1963-1965 (hit) / :1974-1975 (rebuild) |
yes |
| attachment of the READ framebuffer only | SyncCurrentFBO :2304-2310 → SyncToBackend → SyncAttachmentObject Managers.cpp:7142-7194, which calls SyncMipmapsToBackend at :7161 and nothing else |
NO — storage is synced, parameters are not. SyncNeccessaryTextures's attachment list reads only GetFramebufferBindingSlotChecked(FramebufferTarget::Draw) (DirectGLES.cpp:1944) |
| bound to an image unit | SyncImageTextureBinding → SyncTextureObjectToBackend(imageBinding.Texture, /*imageBindable=*/true) (DirectGLES.cpp:2071) |
yes, and RequireImageBindableStorage additionally forces m_forceTextureParamsResync = true (Managers.cpp:4787) so the swizzle override lands |
a glCopyImageSubData endpoint |
MakeGLESCopyImageEndpoint → SyncTextureObjectToBackend(endpoint.Texture) (DirectGLES.cpp:7588); renderbuffer endpoint → SyncRenderbufferObjectToBackend (:7547-7562, :7567) |
yes |
GenerateMipmap target |
DirectGLES.cpp:7433 / :7436 → SyncTextureObjectToBackend |
yes |
GetTexImage source |
DirectGLES.cpp:9678 SyncNeccessaryTextures() — i.e. only if it is unit-bound or a DRAW attachment; then a bare Find at :9693 |
only incidentally |
So today parameters ride on the unit binding and on the draw attachment set. MGPTextureParams
(MGPipeTypes.h:306-318) is per-Res and independent of any view, which is the fix — but the
implementer must not assume the current behaviour is complete: the READ-attachment-only case is a
pre-existing gap that P4a either preserves (bug-for-bug) or closes deliberately, and the choice must be
written down. The corresponding negative control is easy: a texture attached only as READ with a
non-default swizzle / DEPTH_STENCIL_TEXTURE_MODE, read back with glReadPixels.
2.7 Upload shapes — whole box vs rect vs jobs, and the PBO unpack ring
Per dirty (uploadTarget, level) in Managers.cpp:6185-6499:
- Conversion fallbacks first, each of which takes the level off the sub-rect path by pointing
uploadDataat a fresh buffer:PrepareFallbackUpload(:6215-6217),PreparePackedNormUpload(:6218-6220),PrepareImageWidenedUpload(:6225-6227, defined:5442). subRectEligible—:6241-6246:uploadData == mipData(unconverted) and a non-empty dirty region that does not cover the whole level and whole-texel byte size and the backend upload size equals the shadow texel size.bpp = byteSize / texelCount(:6247).- Union box (
GetStorageDirtyRegion,:6237) vs scatter rects (GetStorageDirtyRects,:6271-6273, bounded byMipmapStorage::kMaxDirtyRects).dirtyRectCountis forced to 0 whenever the unpack ring is available (:6280-6282) — the comment records the measured +6 ms/frame Mali cliff from ~100 one-rect jobs; "One box, one job". - Unpack-ring staging plan, decided once for the level (
:6290-6353): three shapes matching the three branches — N rects (:6310-6322), one box (:6323-6331), whole level verbatim (:6332-6353).StageBlocksIntoUnpackRing(Managers.cpp:4947-5001,UnpackStagingBlock:4935-4945) repacks tightly, which is why the ring path issues noglPixelStoreiat all — the surroundingScopedDefaultUnpackStatealready holds ROW_LENGTH/IMAGE_HEIGHT at 0 (:6296-6302).UnpackRingPixelOffset:5003-5006. ringStaged→BindPixelUnpackBufferId(UnpackRingBufferId()):6357-6359, restored to 0 at:6492-6497.- Stats
:6360-6395:ByteClass::StageTexture,CallClass::TextureUploadEmissions,TextureUploadRectEmissions/TextureUploadBoxEmissions,TextureUploadJobs(rects → N jobs, box → 1). "the box/rect split is counted separately from the bytes on purpose: SSIM is blind to it". - The dispatch
:6396-6491: 2D/CubeMapglTexSubImage2Din three shapes (:6399-6431), 3D/2DArray/CubeMapArrayglTexSubImage3Din three shapes (:6438-6485).
The ring itself is BufferImpl's: g_unpackRing (Managers.cpp:866-872), kUnpackRingInitialBytes
4 MiB / kUnpackRingMaxBytes 64 MiB / alignment 64 (:803-810), UnpackRingAvailable
(:3466-3469, honours MG_Config::Features.EsprytDisableUnpackRing), UnpackRingAllocate (:3471-3476),
UnpackRingMappedPtr/BufferId/MaxBytes (:3478-3480), UnpackRingOnPresent (:3482),
ResetRingForNewContext (:2577). ScopedDefaultUnpackState Managers.cpp:4851-4880.
Widening helpers: GetWidenableClientComponentCount / IsIntegerWidenableFormat /
PrepareChannelWidenedUpload / PreparePackedIntWidenedUpload (Managers.h:1292-1318);
ShouldUseCaveatTextureFormat, BackendTextureFormatAddsAlpha, GetImageBindableStorageWidening
(Utils.h:63, :69, :130-161; Utils.cpp:245, :270, :280-…).
Target mapping: MapToBackendTextureTarget (Managers.h:1243-1253), GetBackendUploadSize
(:1273-1278 — 1D arrays move layers from y to z).
2.8 Image units and the format recast
SyncImageTextureBinding(unit) — DirectGLES.cpp:2057-2135: high-water mark g_imageUnitHighWaterMark
(:2055), TrackWritableImageBufferUnit (:2062, counter at :2040-2047), unbind path :2066-2069,
SyncTextureObjectToBackend(tex, true) :2071, layered/layer resolution via
SupportsLayeredImageBinding (:2072-2074, rationale :1992-1999), the buffer split view
(:2120-2124), and the bind-format recast (:2126-2132, rationale :2075-2083: a GL_RG32F bind
is INVALID_VALUE on 19/26 non-core formats on Adreno and 25 on both Malis).
SyncImageTextureBindings() :2169-2179; the per-draw gate
SyncImageTextureBindingsForDraw(keys) :2202-2214, keyed on (contextId, samplingGeneration, g_backendContextGeneration) with the deliberate note (:2195-2201) that the key is the frontend
sampling-resolution generation and not the backend re-mint counter.
MarkWritableImageBufferTexturesGpuWritten() :2149-2167.
2.9 Re-mint and the FBO coupling
RecreateBackendTexture() — Managers.cpp:4790-4840: ScratchFBOImpl::NoteTextureIdDeleted
(:4792), ++FramebufferImpl::g_attachmentBackendIdGeneration (:4797), delete under the matching
context generation (:4798-4800), scrub g_boundTexturesCache (:4801-4807), regen (:4810), reset
m_isInitialized / m_backendStorageImmutable / m_prevTextureInfo (:4818-4820), reset every
parameter cache and set both force flags (:4829-4839).
RequireImageBindableStorage — Managers.cpp:4752-4788: sticky, clears m_isInitialized,
re-marks every already-uploaded level dirty (:4769-4779), sets m_forceTextureParamsResync
(:4787). g_attachmentBackendIdGeneration is defined at Managers.cpp:7763.
Destructor Managers.cpp:4693-4727 (note the /sdcard/MG/exp_leak_texture_deletes experiment latch at
:4711-4718 — an existing temp-experiment hook in the destructor).
2.10 CopyImage
CopyImageSubData — DirectGLES.cpp:7696-…; endpoint resolution MakeGLESCopyImageEndpoint
:7564-7603 (renderbuffer at :7566-7573, texture at :7587-7602, with the explicit no axis remap
for 1D arrays at :7592-7598); GLESCopyImageEndpoint at :7530-7542;
GetCopyImageEndpointFormat :7605-7608; CanMirrorCopyImageShadow :7613-7617 and the CPU-shadow
mirror below it (:7619-…, :7790 "the CPU shadow is what glGetTexImage answers from, so it has to
follow the same move"). The layered-blit substitute reuses glCopyImageSubData at
DirectGLES.cpp:6288, :6406.
2.11 GenerateMipmap
GenerateMipmap(target) — DirectGLES.cpp:7417-7462.
EnsureGenerateMipmapStorageAllocated (:6707-6752) for R11F_G11F_B10F / depth-only / RGB16F / RGB32F;
GenerateThreeChannelFloatMipmapOnCpu (:7432-7435, filtered on the CPU so dirty levels ride down with
the sync); GenerateDepthTexture2DMipmap (:7439), GenerateColorTexture2DMipmap (:7444);
ScopedDetachedTextureFramebufferAttachments (:7453) then ScopedCompleteFramebufferBinding (:7455)
then glGenerateMipmap (:7460).
3. The renderbuffer twin
RenderbufferImpl::BackendRenderbufferObject — Managers.h:2235-2255. Members:
m_backendRBOId, m_contextGeneration, m_isInitialized, m_cacheInternalFormat, m_cacheWidth,
m_cacheHeight, m_cacheSamples (:2248-2254).
- ctor
Managers.cpp:10585-10597(glGenRenderbuffers, stampsg_backendContextGeneration). - dtor
Managers.cpp:10599-10612; no binding shadow exists (:10606-10607—Bind()always issues). Bind()Managers.cpp:10614-10619— unconditionalglBindRenderbuffer.SyncToBackendManagers.cpp:10621-10693: storage respecify is memoised on the four-field cache (:10634-10640); otherwiseBind(),GenerateRenderbufferFormatInfo(:10650),ClampSamplesToBackendSupportfor the multisample form (:10664-10668) orglRenderbufferStorage(:10670-10671), and an explicitGL_OUT_OF_MEMORYprobe that reportsErrorCode::OutOfMemoryto the application (:10673-10683) — the deferred-allocation model means the error lands on whatever entry point triggered the sync (:10652-10658).- Table
Managers.cpp:10695-10696. Reached fromSyncAttachmentObject(Managers.cpp:7195-7213), fromSyncRenderbufferObjectToBackend(DirectGLES.cpp:7547-7562, used by CopyImage), and ticked bySyncCurrentFBO(DirectGLES.cpp:2235). - Lifetime id / death:
RenderbufferObjectraisesNotifyStateObjectDestroyed; the consumer arm isManagers.cpp:208-210.
Frontend members read: GetInternalFormat (7 sites), GetExternalIndex (4), GetWidth, GetHeight,
GetSamples (2 each).
4. Readback paths, and what a server without the frontend address space needs
4.1 ReadPixels — DirectGLES.cpp:9488-9636
IsLegacyNativeReadPixelsFormat / …Type / IsSupportedDepthStencilReadPixelsPair
(:9455-9486) decide useNativeReadback; GetReadbackChannelMapping + GetReadbackDstPixelSize
decide convertible (:9497-9499). Then:
SyncNeccessaryTextures() :9507, SyncCurrentFBO() :9510, BindCurrentFBO(Read) :9513,
ScopedPackState packParamsScope(PackStateFromContext()) :9516,
glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) :9518-9524.
packSwapBytes :9530, forceOpaqueAlpha = IsAlphaWidenedFallbackReadAttachment() :9533,
nativeFastPair :9537-9539; conversion path :9540-9550; depth/stencil helpers :9555-9560+;
the pack-PBO resolution GLuint packBufferId :9575-9585 (via
BufferImpl::EnsureBufferResource(pixelPackBufferObject) at :9577), ScopedPixelPackBuffer :9589,
glReadPixels :9593, the retry-on-error via conversion :9594-9617, and the
PBO → frontend shadow writeback :9618-9634 (WritebackFromBackend :9626 +
BumpBufferMutationEpoch() :9628).
4.2 GetTexImage — DirectGLES.cpp:9655-…
IsNativeGetTexImagePair :9640-9653; SyncNeccessaryTextures() :9678; SyncCurrentFBO() :9681;
resolves the texture off the active unit's binding slot (:9683-9691) and does a bare
g_backendTextureObjects.Find (:9693-9702) — it does not create a twin, it bails.
Then the cross-FBO resolve barrier note (:9704-9709), widening probes at :9810, :9818,
the temp-FBO + ReadPixelsViaFormatConversion route (:9933, :9956), the pack-PBO resolution
:10001-10011 and the PBO writeback :10029-10043.
The shadow fallback: GetTexImageViaShadowConversion — DirectGLES.cpp:9244-9300+. Valid only
while the CPU shadow is authoritative, which holds for non-renderable formats (:9246-9247). Reads
textureMipmapObject->MapMipmapData(uploadTarget, level) (:9268), takes the raw-packed shortcut when
IsRawPackedPixelTransfer (:9277-9287, so an RGB9_E5 shared exponent is not canonicalised), else
DecodeShadowDataToWideRGBA (:9292-9297) then ReadbackImpl::StoreWideRowsToClient.
ReadPixelsViaFormatConversion — DirectGLES.cpp:9015-9242; the fixed-point read clamp :9204-9231.
ReadbackImpl client-store walks: Utils.cpp:1973-2404, StoreWideRowsToClient Utils.cpp:2362,
StorePackedWordsToClient Utils.cpp:2387 (declarations Utils.h:188-246); the PBO writeback inside
the shared row walk is Utils.cpp:2343-2352.
StoreReadbackRowsToClient (depth/stencil destination walk) — DirectGLES.cpp:8040-8073, PBO writeback
:8062, epoch bump :8068.
4.3 The scoped driver-state guards (the readback-state-guards architecture)
DirectGLES.cpp:6545-6644 and :6795-6900+:
ScopedFramebufferBinding :6555-6573 (saves/restores through CurrentFramebufferBinding +
BindFramebufferId, never glGetIntegerv), ScopedPackState :6576-6588, PackStateFromContext
:6592-6596, ScopedPixelPackBuffer :6601-6607 (always returns the binding to 0),
ScopedScissorDisable :6612-6626 (reads the render-state shadow), TempFBOBinder :6631-6644,
ScopedCompleteFramebufferBinding :6795-6803,
ScopedDetachedTextureFramebufferAttachments :6805-… — the one direct-iteration site over a twin
table: push arm uses g_backendFramebufferObjects.ForEachLive (:6877-6884), legacy arm walks
begin()/end() and must test stateRef.expired() by hand (:6885-6895), pull arm keeps the pre-P2 walk
verbatim (:6896-6899). SlotTables.h:61-68 says the per-entry weak_ptr survives only for this
caller. ScopedDepthBlitState :6985.
PixelStoreImpl (PACK shadow): Managers.h:1792-1806, Managers.cpp:8004-8054
(ApplyPackState :8019).
ScratchFBOImpl: Managers.h:1734-1781, Managers.cpp:7767-8002
(EnsureId :7833, EnsureColorAttachment2D :7847, EnsureCompleteTinyFramebufferId :7960,
NoteTextureIdDeleted :7984).
4.4 What a server without the frontend address space needs
Enumerated so the implementer does not have to re-derive it:
- A destination that is not a client pointer. Every readback today writes either into the
application's
void* pixelsor into a frontendBufferObject's CPU shadow viaWritebackFromBackend(four sites, §5).MGPReadbackInfo(MGPipeTypes.h:748-756) already carriesDstOffset/DstSizeand iskReplySlot, so the reply slot is the destination — but the pack-PBO case is different: the driver writes into the backend buffer store and the frontend shadow then has to be refreshed. That refresh isWritebackFromBackend+BumpBufferMutationEpoch, both frontend calls, and is P4a's hardest readback item. - PACK pixel-store parameters as payload.
PackStateFromContext(DirectGLES.cpp:6592-6596) and the row walks readMGB_CTX->GetPixelStoreParameters(false)directly (DirectGLES.cpp:9530,:8042,Utils.cpp:~2300).SetPixelPackState/MGPPixelPackStateexists in the catalogue (PipeCalls.def:126) — P4a has to actually push it, including the four ES-less fields (IMAGE_HEIGHT,SKIP_IMAGES,SWAP_BYTES,LSB_FIRST;Managers.h:1788-1791says they are honoured on the CPU from frontend state). - The resolved read surface, not the read-buffer enum:
MGPFramebufferState::ReadSurface. The three helpersIsFixedPointFallbackReadAttachment,IsAlphaWidenedFallbackReadAttachment,GetReadColorAttachment(Managers.cpp:7280-7408) all currently walkMGB_CTX->GetFramebufferBindingSlot(Read); with the inlineInternalFormatonMGPSurface(MGPipeTypes.h:344) they become pure functions of the pushed record. - The texture CPU shadow.
GetTexImageViaShadowConversionreadsMapMipmapData(DirectGLES.cpp:9268) and the CopyImage mirror writes it (DirectGLES.cpp:~7790,MarkStorageDirty). A split server has no shadow. Either the shadow stays client-side and the server answers only from the driver, or a mirror is added — this is the texture analogue of P8's index host mirror and should be recorded, not solved, in P4a. - Error reporting.
BackendRenderbufferObject::SyncToBackendcallsMGB_CTX->RecordError(Managers.cpp:10677-10682);GenerateMipmapcallsRecordGLError(DirectGLES.cpp:7461). Both need the reverse channel.
5. SyncPersistentMappedRange / SyncGpuWrites sites still touching textures
Full census in MG_Backend/DirectGLES (the grep is exhaustive):
| site | file:line | texture-related? |
|---|---|---|
drawBuffer->SyncPersistentMappedRange() (indirect) |
DirectGLES.cpp:301, :5117-5118, :5219-5220 |
no (indirect buffers) |
indexBuffer->SyncPersistentMappedRange(); SyncGpuWrites() |
DirectGLES.cpp:4863-4864, MultiDraw.cpp:511-512 |
no |
bufferObject->SyncPersistentMappedRange() inside the ensure path |
Managers.cpp:2799, :2976 (D-N note :2703-2718) |
reached from the texture path: SyncMipmapsToBackend's buffer-texture branch calls BufferImpl::EnsureBufferResource(buffer) at Managers.cpp:6520 |
bufferObject->SyncGpuWrites() |
Managers.cpp:4424, :4549 (fp64 narrowing; ID-15 M-3 is the RULED P3a deviation) |
no |
MarkBufferGpuWritten / MarkGpuWritten for writable image buffer textures |
DirectGLES.cpp:2162-2164 (from MarkWritableImageBufferTexturesGpuWritten, :2149-2167) |
yes — the only texture-driven GPU-write announcement |
| SSBO / atomic-counter marks | DirectGLES.cpp:506-510, :556-558 |
no |
WritebackFromBackend (pack PBO) |
DirectGLES.cpp:8062, :9626, :10036, Utils.cpp:2343 |
yes — these are the readback writebacks, each paired with BumpBufferMutationEpoch() (:8068, :9628, :10038, Utils.cpp:2351) |
WritebackFromBackend (XFB capture) |
DirectGLES.cpp:1017, :1125 |
no |
Ops_ReadbackFromGpu |
Managers.cpp:1726-1761, tracked wrapper :1828-1829, table row :2300 |
no |
So for P4a: one texture-owned SyncPersistentMappedRange reach (the buffer-texture backing store, via
EnsureBufferResource), one texture-owned GPU-write mark (writable image buffer textures), and
four readback writebacks that are texture/framebuffer-triggered but land on buffers.
MarkBufferGpuWritten is Managers.cpp:2491-2520 (declared Managers.h:753-763); the
BufferImpl::CurrentBufferMutationEpoch / BumpBufferMutationEpoch contract is documented at length in
Managers.h:558-593 — note it explicitly lists "every pack-PBO WritebackFromBackend site
(glReadPixels/glGetTexImage)" (Managers.h:579) as a bumping path.
6. Tests and traces
6.1 Unit tests (label unit)
MG_Test/SanityTest.cpp(4378 lines) — the slot-table suiteDirectGLESSlotTable:ARecycledSlotIsANewHandleAndTheStaleOneResolvesToNothing:3316,RepeatedLookupsOfALiveObjectKeepOneHandle:3358,ANegativeLookupIsNotCachedAcrossAnotherHoldersAcquire:3387,FindNeverMutatesTheTable:3416,AWholeTableSavesResetsAndRestores:3450,TwoTablesOfTheSameKindShareOneSlotAndKeepTheirOwnTwin:3491,OneDeathNoticeDropsTheTwinInEveryHolderOfTheKind:3537,AnnouncedDeathKeepsObjectChurnFromAccumulatingWithoutASweep:3596,GetOrCreateToleratesANullStateObject:3635,AnAnnouncedDeathReturnsTheSlotWithoutASweep:3662,EveryReKeyedObjectClassAnnouncesItsOwnDeath:3711,TheHandleArmInstallsTheDeathNoticeConsumer:3777,TheTwinRegistryCasesInThisBinaryRunOnTheHandleArm:3795,EverySwitchedOverKindResolvesItsTwinThroughTheHandleArm:3878— drives Texture, Framebuffer, Renderbuffer, SamplerCso, ShaderCso, VertexElementsCso throughExpectTheHandleArmDrivesThisKind(:3888-3899) and then the seventh (Buffer) table by hand (:3908-3958). This is the case P4a extends for the kinds it converts toGetOrCreate(handle).ASavedCopyOfARealRegistryDropsTheTwinOnTheSameNotice:3963,AnArmlessKnobCombinationStopsInsteadOfSkippingTheLane:4004,TheArmlessCasesLeaveTheLogPathAndTheConfigAsTheyFoundThem:4061. FixtureScopedDirectGLESTextureBindings:218-260— the second live holder of kind Texture (savesg_backendTextureObjectsby value at:224, resets with= {}at:228, restores at:242), used at:353,:404,:2699,:3065.EsprytSlotArmEnvironment:90-103.MG_Test/Framebuffer/FramebufferTest.cpp— 60+FramebufferTest.*cases; the ones P4a's framebuffer record must keep green:NamedFramebufferTextureAttachesWithoutChangingBindings:244,FramebufferTextureBumpsAttachmentVersionOnlyOnce:289,ReadPixelsAllowsPersistentMappedPixelPackBuffer:310,NamedFramebufferDrawBuffersDoNotModifyDefaultFramebuffer:446,DefaultFramebufferProvidesTextureAttachmentsForFrontAndBackAliases:492,FramebufferTexture3DAttachesSliceWithLayerTracking:825,ThreeChannelColorAttachmentsAreCompleteThroughTheWidenedSubstitution:961,TwoAttachmentCompositeFramebufferMatchesIrisComplementaryPass:972,WidenedDrawBufferIsIdentifiedPerDrawBufferSlotNotPerAttachmentPoint:1194,DrawIntoAWidenedDrawBufferReachesTheDriverWithAlphaWritesMaskedOff:1227,ClearIntoAWidenedDrawBufferKeepsAlphaWritableAndSubstitutesOne:1263, plus thePackedReadbackEncodeTest.*block:645-769(pure readback encoders) andReadPixelsRejectsIntegerFormatMismatchWithReadBuffer:785.MG_Test/Texture/TextureTest.cpp— 192 cases. Directly load-bearing for P4a:DepthStencilTextureModeIsBackendVisibleThroughTheParamsVersion:2609,ShadowReadbackRefusesALayoutItCannotProduceInsteadOfOverrunningTheBuffer:1017,ShadowReadbackRefusesAPackStateItCannotHonour:1058,GetTexImageReadsALevelWhoseLowerLevelsWereNeverDefined:1971,GetTexImageOfOneCubeFacePacksIntoAOneFacePixelPackBuffer:2096,CopyImageSubDataRejectsALevelTheTextureDoesNotHave:2531,TextureStorage2DMultisampleTracksNamedObjectState:1921, the border-colour trio:610,:656,:698, and the compressed-upload/PBO block:2657-2812.MG_Test/Texture/TextureViewTest.cpp,MG_Test/Texture/VkClearManagerTest.cpp.MG_Test/Backend/DirectGLES/holds onlyBaseInstanceInjectionTest.cpp,EsslShaderPassTest.cpp,ViewportIndexRoutingTest.cpp— there is no backend-side framebuffer or texture unit test today; P4a's emitter tests belong inMG_Test/Pipe/besideResourceEmitTest.cppandVertexInputEmitTest.cpp(the P3a shape), which is also where thePipeCatalogueTest.cppsize pins live.
6.2 Integration scenarios (label integration-gpu / integration-verify)
Registered in MG_IntegrationTest/CMakeLists.txt; the framebuffer/texture/renderbuffer set with its
case names:
HandleRecycleScenario(:979-1015+, four+ pinned lanes):TheReproducerRecyclesEveryName,AVertexArrayAtARecycledAddressDoesNotInheritItsPredecessorsVertexInput,ABufferAtARecycledAddressDoesNotInheritItsPredecessorsContents,ATextureAtARecycledAddressDoesNotInheritItsPredecessorsTwin(Scenarios/HandleRecycleScenario.cpp:869),AFramebufferAtARecycledAddressDoesNotInheritItsPredecessorsTwin(:920),DestroyedVertexArraysReturnTheirVertexElementsSlots. P4a's G8 analogue: add a renderbuffer case and a sampler case here.LayeredAttachmentShapeScenario(8 cases:LayeredThreeDColorAttachmentReachesEverySlice,NonLayeredThreeDSliceAttachmentWritesOnlyThatSlice,LayeredCubeMapArrayColorAttachmentReachesEveryLayerFace,LayeredCubeMapArrayDepthAttachmentGatesEveryLayerFace,LayeredCubeMapArrayDepthStencilAttachmentGatesEveryLayerFace,LayeredCubeMapClearMaterialisedBySamplingReachesEveryFace,LayeredOneDArrayClearMaterialisedBySamplingReachesEveryLayer,CubeMapFaceReadbackAnswersTheFaceItWasAskedFor) — the direct gate onMGPSurface'sLayered/Level/Layer/UploadTarget.LayeredTextureReadbackScenario(2),LayeredAttachmentBarrierScenario.ThreeChannelAttachmentScenario(ThreeChannelColorAttachmentsReportComplete,WidenedAttachmentReadsBackOpaqueWhileItsNeighbourKeepsItsAlpha,DstAlphaBlendingSeesOneInAWidenedAttachment) — the alpha-widening triple.SnormAttachmentScenario,RenderbufferBlendFormatScenario(3 cases — the only renderbuffer-specific scenario),DepthStencilReadbackScenario+…MatrixScenario+…AttachmentShapeScenario(one lane,TEST_FILTER "DepthStencilReadback*Scenario.*"at:639),PackedWordReadbackScenario,PixelStoreSweepScenario,ClearThenReadPixelsScenario(5 cases, incl.ABlitIntoTheDefaultFramebufferSurvivesAnEarlierClearandAMultisampleResolveBlitIntoTheDefaultFramebufferKeepsItsOrientation),ClearTexImageUndefinedLevelZeroScenario,CopyImageLevelRangeScenario/CopyImageLayeredScenario/CopyImagePacked16Scenario(own lane:806),TextureViewScenario(8 cases, incl.BetterCloudsCoveragePipelineandCoherencyIsBidirectional),BufferTextureScenario,ImageSizeAfterRespecScenario(ADrawSeesTheNewSizeOfARespecifiedImageTexture— theRecreateBackendTexture/g_attachmentBackendIdGenerationgate),SampledSetStalenessScenario(2 cases — the sampling-resolution-generation gate),ImageTargetKindScenario,ImageLoadStoreSsoScenario,NonCoreImageFormatScenario,FormatlessImageBakeScenario,ImageFormatQualifierScenario,UnboundImageDescriptorScenario,IntegerBorderColorScenario(carries a known ASan stack-buffer-overflow, ID-21 follow-up),FragmentOutputArrayIndexScenario,OrientationScenario,FragCoordOriginScenario,IterationRP*Scenario×3,GuiBatchScenario.ResourceSubsystemControlScenario.ClearingTheP3aBitsStopsTheEmissionsAndNotThePixels— the G12 template P4a copies for its own bits;CsoContentAddressingScenario;PipeVerifyArmingScenario.
6.3 Traces most sensitive to this surface
From tools/trace_replay/trace_cases.json (40 cases; 79 desktop arms):
improved-transparency-minecraft-26.3— the OIT scratch-clear-FBO / draw-buffers-None/ read-buffer bug family lives here (Managers.cpp:7544-7550). First trace to break on a draw-buffer or read-buffer regression.minecraft-1.21.4-fabric-iris-*-in-world(17 shader packs: bsl, complementary-reimagined, complementary-unbound, sundial-lite, iterationt, iterationrp, photon-v1.1/v1.3b, mellow, nostalgia, bliss, chocapic-v6-lite, super-duper-vanilla, makeup-ultrafast, derivative-main, …) — heavy MRT, attachment relocation andglDrawBufferspermutations;TwoAttachmentCompositeFramebufferMatchesIrisComplementaryPassis the unit-test shadow of this.iterationrphas no DirectGLES arm (79 = 40×2 − 1).minecraft-1.21.4-rd12-odinlite-in-world— the VAO/buffer-heavy fixture that cost P3a +30%/+27% (ID-19/ID-24); also the one that aborts on Xiaomi/Magma pre-existing (ID-23). Texture-upload traffic is the other half of its frame.minecraft-1.21.4-fabric-sodium-in-world— sub-ms frames, the per-call overhead canary.minecraft-1.21.1-neoforge-create-instancing-in-world(2-frame fixture, not a benchmark) andcreate-indirect(pre-existingdevred, BRIEF-P3A.md's first caveat — keep it in the desktop SSIM corpus, out of the device A/B).minecraft-1.21.4-fabric-{rei,xaero-minimap,xaero-world-map,journeymap,modernui}-*and…-normal-world— GUI/atlas-heavy, i.e. theglTexSubImageunion-box vs scatter-rect shape and the unpack ring; a shape regression there is invisible to SSIM and shows only inTextureUploadBoxEmissions/TextureUploadRectEmissions/TextureUploadJobs(Managers.cpp:6385-6394).minecraft-1.17-main-menu-854,minecraft-1.21.4-fabric-iris-bsl-esc-menu-854,OpenRA— default framebuffer / blit-to-default paths.
7. Working notes for the P4a implementer
- Seven→ten tables. P3a converted only the Buffer kind to a call-carried handle
(
Managers.h:690-692). Framebuffer, Texture, Renderbuffer (and Sampler) still mint insideMG_BackendthroughTwinRegistry::GetOrCreate(StatePtr)(Managers.h:326-372) and still depend on the shared death notice (Managers.cpp:199-245). The buffer family's shape — client mints →GetOrCreate(handle)→ResourceDestroyretires → client frees the slot (SlotTables.h:337-361,Managers.cpp:233-243) — is the template. Note theGetOrCreate(handle)asymmetry: forward generations recycle, backward generations are refused (SlotTables.h:301-321). ForEachLiveis the blocker for droppingEntry::stateRefon Framebuffer.ScopedDetachedTextureFramebufferAttachments(DirectGLES.cpp:6805-6899) is the only direct-iteration site and it needs the frontendFramebufferObjectto read its attachment array. A handle-keyed FBO table has nostateRef(SlotTables.h:266-269), so that walk has to be re-expressed over the applier'sMGPFramebufferStaterecords before the FBO table can lose itsStatePtr.- The
Color[8]/DrawBuffers[8]vs 32 mismatch (§0) has to be ruled on before any emitter is written;MAX_COLOR_ATTACHMENT_SLOTSis 32 atManagers.h:1579-1581andRecomputeBackendColorSlotsis 32-wide (Managers.cpp:7443-7448). ContentHashsuppression onSetFramebufferStatemust not swallow thefragColorBroadcastCountinput (§1.6): the broadcast count is read from the frontend before the FBO sync precisely so a program relinks in the same draw. If the FBO record is suppressed, the broadcast count must still be derivable — either it becomes a field of the record or it stays a separate emission.ForceResynconMGPTextureParams(MGPipeTypes.h:312-313) is already the wire spelling ofm_forceTextureParamsResync; there is no wire spelling ofm_forceSamplerResync(Managers.h:1508-1516), and the failure mode it guards is not mis-filtering but an incomplete texture that samples(0,0,0,1). That gap is P4a's third contract ruling.- G5-style byte-identity. P3a's G5 protects eleven buffer-pool/ring functions
(ID-11/ID-13/ID-15). The P4a analogue candidates, if the phase wants one, are the unpack-ring
staging (
StageBlocksIntoUnpackRing,Managers.cpp:4947-5001),RecomputeBackendColorSlots(Managers.cpp:7432-7519) andScratchFBOImpl's attachment shadow (Managers.cpp:7767-8002) — all three are dense, well-tested and have nothing to gain from being rewritten. - Exit order (ID-18/ID-21).
EnsureProcessTeardownSentinel/InProcessTeardown(Managers.cpp:171-180) gate every twin destructor (:3556,:4693,:7026,:8085,:10432,:10600), andOnFrontendStateObjectDestroyeddrops notices during teardown (:200). Any new MGPipe singleton P4a adds must be heap-constructed and intentionally leaked, like the fourfde5fda3made never-destroyed.