Initialize the desktop replay Request from the three iterationRP environment flags before loading MobileGL. Without this, the Request defaults caused the replay core to unset CI's exported flags, leaving all repairs disabled despite the workflow configuration.
Run the lavapipe Program 203 golden test with the same subgroup scratch, derived topology, and missing-barrier repairs as the iterationRP retrace matrix. This keeps the prerequisite integration job from failing before retrace jobs can start.
Program 203 reuses prefixSumCache for a second subgroup reduction before every workgroup invocation has consumed the first result. Add a fingerprint-gated SPIR-V pass that inserts the missing Workgroup acquire-release barrier while preserving native subgroup operations.
Keep the repair opt-in behind MOBILEGL_ITERATIONRP_FIX_BARRIER, cover insertion, pass-through, and idempotence, and enable it together with the existing iterationRP subgroup repairs for the matching Linux and Android CI retraces.
Add a deterministic iterationRP Program 203 fixture that dispatches the original shader and compares every RG16F texel against fixed half-float golden bits. This catches both a wrong exposure result and collateral writes without retaining a serial reference shader.
Make MobileGLIntegrationTest runnable as a standalone Android executable by linking the shared MobileGL library and backing EGL with an AImageReader window; desktop keeps its static-library pbuffer path.
Validation: Adreno 830 passes with 0/262656 mismatches; lavapipe reproduces the current reduction defect with 1/262656 mismatches at the exposure texel.
Final audit round over the DirectGLES scratch/shadow mechanisms; three verified
defects fixed:
- ~ScopedEmulationDrawState restored the APPLICATION's per-buffer colour masks,
not what SyncRenderState actually pushed: a widened attachment's alpha-off
doctoring (g_syncedColorMaskAlphaWidenMask) was dropped while the memo still
claimed it applied, so the next sync early-outed and draws wrote fragment
alpha into the widened buffer - breaking the stored-alpha==1.0 invariant the
widen discipline exists to protect. The restore now re-applies the doctoring.
- The same restore loop gated on the core glColorMaski name only, while the sync
push falls back to glColorMaskiEXT/OES: EXT/OES-only devices were left holding
buffer 0's mask broadcast across every draw buffer with the shadow recording
the divergent set (never repaired). The restore now uses the same three-way
pointer fallback.
- ResolveThenBlit ran its resolve-into-scratch staging blit under the
application's scissor: a box not covering the scratch-origin rect clipped the
resolve silently (no GL error), and the second blit then copied stale scratch
renderbuffer texels into the destination. The staging blit now runs scissor-off
(shadow-tracked, like ScopedScissorDisable); the caller-visible blit keeps its
native scissor semantics.
Six more verified defects from the residual memo/cache mechanisms:
- Program resource cache (DirectVulkan reflection): glShaderStorageBlockBinding
deliberately does not bump the backend state version, and the SSO pipeline
composite is unnamed so the by-name in-place patch can never reach its slot -
the composite kept serving pre-rebind SSBO bindings. The cache now keys on the
program's block-binding version; a binding-only change re-applies the overrides
by name instead of re-running spirv-reflect. SetShaderStorageBlockBinding also
gains the equality bail-out its uniform-block sibling has, so the composite
mirror's replay stops churning the version every draw.
- LinkProgram's allowVSOnlyPrograms function-static latch never set its own
initialized flag (dead memo, re-read every call) - and completing it would have
frozen a per-backend capability across re-initialization. Replaced with a fresh
per-link read from the null-checked active backend.
- Query object registry: drained at full library teardown (DestroyAllQueryObjects,
mirroring DestroyAllSyncObjects) - undeleted queries and their backend wrappers
leaked across Destroy/Initialize cycles, stale ids stayed IsQuery == GL_TRUE in
the re-initialized library, and a later delete could hand the old backend's
wrapper to a different backend's DeleteBackendQuery.
- Converted vertex streams and the host-side EBO max-index scan now SyncGpuWrites
before reading the coherent mapping: XFB/SSBO/image writes are merely recorded
at that point, so the conversion read pre-write bytes (the restart-index
rewrite already synced; these two host reads did not).
- Zero-stride converted bindings: both converters rejected stride 0, making the
factory's documented single-element conversion unreachable and silently
dropping every draw using such a binding; the stride is substituted with the
element size for the one-element case.
- DemoteFloat64Pass block relayout: measurement queued into the module eagerly,
so a mid-struct failure left a half-relaid-out block (compacted offsets before
the failing member, 64-bit offsets after) while claiming the block was left
alone. Decoration writes are now collected and committed only when the whole
block measures successfully.
Audit of every memoization implementation; sixteen verified defects fixed:
DirectGLES backend:
- Broadcast draw-buffer memo: cleared at MakeCurrent/DestroyEGLContext like its
sibling shadows; its identity+version key is only monotonic within one GLContext,
so a library teardown + re-init could false-hit on a recycled FBO address.
- Backend texture id re-mint (RecreateBackendTexture) now bumps an attachment
generation that the SyncCurrentFBO gate and every FBO twin compare, so driver
FBOs re-attach instead of keeping the deleted texture name; the attachment walk
re-enters until the generation is quiescent (a walk itself can re-mint).
- Buffer id re-mint (persistent-map adoption, immutable-store retire) now bumps a
generation the VAO twin sync compares, forcing a full re-emit of the baked
glVertexAttribPointer / element-array bindings that frontend versions cannot see.
- VAO element-array sync memo: bound-object identity joins the wrapping Uint16
slot version (same pairing the ResolvedDrawBuffers IBO memo already uses).
DirectVulkan backend:
- EBO slice memo gains the mapped-buffer guard its vertex-binding sibling has: a
shadow-backed persistent map mutates with no epoch bump, so a hit must decline.
- VkClearManager::MergeClearPayload keeps colorEncoding/colorInt/colorUint with
the color, so deferred glClearBufferiv/uiv no longer degrade to all-zero float.
- GetOrCreateComputePipeline no longer memoizes a failed creation (same contract
as PipelineFactory): a transient driver failure was permanently disabling every
dispatch of that program.
- Explicit-LOD-0 verdict memo keys on the sampling-resolution generation; sampler
filter/aniso/LOD setters bump only that counter, so the old key served a stale
verdict (wrong SPIR-V variant) after glTexParameter/glSamplerParameter changes.
- SetupDraw fast path declines instead of re-arming on a moved sampling-resolution
generation (the snapshot bakes the LOD verdict into its pipeline), and
recomputes the XfbCapture bit so the first draw after glBeginTransformFeedback
cannot bind the undecorated variant and silently capture nothing.
- VertexInputStateFactory eviction epoch is drawn from a process-wide source: VAO
state-pointer memos outlive the factory across renderer recreation, and a fresh
factory restarting at epoch 1 would dereference a dead factory's entry.
- Cached render passes re-read the live renderbuffer clear payload at begin (the
clear VALUE is not in the pass hash; the entry's inline snapshot replayed the
creation-time color and dropped the newly queued one).
- FramebufferObject gains a never-reused lifetime id, keyed into the render-pass
fast-path memo and the SetupDraw snapshot beside the raw pointer + Uint16
version pair, which address reuse plus fresh version counts could equal.
- SyncTextureResource's preserved-content image goes through the deferred-release
ring on both failure paths instead of a synchronous destructor under the GPU.
MG_State frontend:
- Layer-1 compile memo is env-disciplined like layers 2/3: a node computed against
a dead CompileEnv (e.g. pre-capability fallback limits) no longer answers
glCompileShader forever once the environment's content changes.
- Pipeline composite cache rebuilds from each stage program's last-link shader
snapshot (new LinkedShaderRef list + pinned link inputs) instead of the live
attach list and current compile nodes: post-link glAttachShader/glCompileShader
must not leak into the composite while the (lifetimeId, linkVersion) signature
still hits - GL's "as last linked" rule.
The previous commit's fingerprint was pinned to one array's incidental
dimensions - workgroup exactly 32x16x1, element exactly vec2, length
exactly 32 - which is the auto-exposure reduction and nothing else. The
pack ships the same idiom twice:
- auto-exposure: 32x16 (512 invocations), shared vec2 prefixSumCache[32]
- RTW warp: 1024 invocations, shared float prefixSumCache[64]
so the warp kept writing 128 subgroups into 64 entries on an 8-lane
device and the retrace stayed bit-identically wrong (ssim 0.027902).
Key the fingerprint on the pack's idiom instead of one array's shape: a
workgroup array of 32-bit floats indexed by gl_SubgroupID, fed by a
subgroup scan, whose declared length is below ceil(invocations / native
width). Three properties keep that a targeted repair rather than a
general array resizer:
- the index must BE gl_SubgroupID (through OpCopyObject, a signedness
OpBitcast, or a spill whose every store is that id), so an index
masked or clamped into range is left alone;
- the >= 16-lane early-out is retained, so every module on the devices
the pack was written for passes through byte-identical;
- growth is certified against maxComputeSharedMemorySize using a
natural-alignment layout model, and declined outright when a
declaration cannot be sized, so a patched module can never fail
pipeline creation where the original would not have.
Verified against the shaders the CI trace actually contains: of the 14
compute modules in the fixture exactly these two change, the other
twelve are byte-identical, and all fourteen pass spirv-val. The
integration scenario grows a second case for the 1024-invocation shape;
both abort with heap corruption when the patch is disabled.
iterationRP's Program 203 declares shared vec2 prefixSumCache[32] for a
512-invocation workgroup indexed by gl_SubgroupID; any device narrower
than 16 lanes partitions into more than 32 subgroups and the pack writes
shared memory out of bounds (heap corruption on lavapipe's CPU
rasterizer, ssim 0.028 on the CI retrace). Fix it where the fault lies -
in the fixture - and keep the GL contract sound everywhere else:
- FixIterationRPSubgroupScratchPass: fingerprint-gated SPIR-V pass that
grows exactly that array to ceil(invocations/width) entries on sub-16-lane devices; every other module passes through byte-identical.
- DeriveNumSubgroupsPass stays default-on for the Adreno topology bug
and is made spec-sound: pipelines request REQUIRE_FULL_SUBGROUPS
whenever the workgroup shape makes the flag legal (computeFullSubgroups
enabled, local_size_x a multiple of the native width, subgroup count
within maxComputeWorkgroupSubgroups).
- EmulateSubgroupsPass: 32-lane virtual-subgroup lowering kept in-tree
as a last resort, enabled only by MOBILEGL_MAGMA_EMULATE_SUBGROUP=1 on
devices with no native subgroup support; fails closed on extended
subgroup instructions and on modules whose added scratch would exceed
maxComputeSharedMemorySize.
- IterationRPFirstReductionScenario skips gracefully outside the pack's
16..256-lane source domain; the new IterationRPScratchFixScenario runs
the fixture-shaped reduction on any width and asserts the exact
width-independent total. DriverPost keeps reporting FAIL on
out-of-domain devices.
- Program203 -> IterationRP rename throughout; the per-trace
num_subgroups_quirk plumbing is removed from the trace replayer, JNI
chain, and CI workflows.