Introduce a Mesa pipe_resource-style PipeResource that owns a GL buffer's bytes
and its backend GPU resource, abstracting WHERE the authoritative bytes live:
- Shadow mode (non-persistent buffers): a CPU Vector; the backend keeps its own
GPU copy in sync via BufferBackendOps, exactly as before.
- Persistent mode (coherent GL_MAP_PERSISTENT maps): the backend's host-visible,
COHERENT, persistently-mapped GPU memory is the single source of truth. The app
writes into it directly, every reader resolves against it, and NO per-write
backend transfer happens. The CPU shadow is released.
BufferObject no longer owns a raw shadow Vector; it holds a PipeResource and
exposes one accessor, MappedData(), that all readers go through. Every buffer-data
consumer (UBO payload, PBO texture upload, indirect draws, resident/streamed
uploads, both backends) was migrated from GetDataReadOnly()->data() to
MappedData(), so a persistent buffer's readers see GPU memory - not a stale
shadow. That stale-shadow inconsistency is what corrupted rendering (wrong UBOs ->
misplaced/"lost" vertices) in the first zero-copy attempt (625c8a6, reverted in
896cafc); routing every consumer through one accessor makes it structurally
impossible.
Backends provide the map via BufferBackendOps::AcquirePersistentMap:
- DirectVulkan: a HOST_VISIBLE|HOST_COHERENT (required, not just requested),
persistently mapped resident VkBuffer carrying every usage, seeded from the
shadow, never recreated; AcquireResidentSlice binds it directly.
- DirectGLES: EXT_buffer_storage immutable persistent+coherent glMapBufferRange,
falling back to the shadow when the extension is absent.
Fixes the ~7GB GpuMemory OOM + 100%-CPU/ANR running modern Blaze3D Minecraft on
both Magma and Espryt (per-draw whole-buffer re-upload of the coherent persistent
ring buffer), without the coherency/stale-read hazards of the reverted attempt.
BufferTest: zero-copy stress guard (15,360 draws -> 0 per-draw transfers, and every
reader resolves to GPU memory) + a shadow-fallback test. Host suite: 203/203 pass.
Device verification pending.
- Track every graphics-queue submission with a real fence: pooled fences
for mid-frame flushes, the frame slot's fence for Present and readback.
Completion advances a submit counter via vkGetFenceStatus polls,
slot-fence waits, and device-idle points, and raises the buffer-manager
serial floor from the frame serial each submission carried.
- GL sync objects now capture the submission index that will carry the
commands recorded so far; ClientWaitSync honors
GL_SYNC_FLUSH_COMMANDS_BIT with a mid-frame submit (gated on the index
still being unsubmitted so poll loops cannot split the render pass), and
blocking waits flush then vkWaitForFences with the caller timeout.
- FlushPendingCommands retires the submitted command buffer and restarts
recording on a fresh one; retired buffers are freed once the slot fence
is next waited, so an executing buffer is never reset.
- Rewind descriptor-set cursors exactly once per frame in Present (after
the slot-fence wait), plus after the synchronous readback drain,
replacing the ten lazy per-draw-path rewinds.
Verified: host tests 168/168, trace-replay 70/70.
Create 6 / Flywheel 1.0.6 now renders correctly with both flywheel:instancing
and flywheel:indirect on DirectGLES and DirectVulkan (verified in-game on
Adreno 830: waterwheels and cogwheels solid, animated, correct pairing, no
crashes across all four combinations).
- MG_State/MG_Impl: sync explicitly-ranged SSBO bindings of FLUSH_EXPLICIT
persistent maps to the backend before compute dispatches. Flywheel writes
its scatter-copy descriptors into the staging ring's persistent map and
never flushes that span (UB per spec, works on drivers whose maps alias
GPU-visible memory); our maps alias the CPU shadow, so the descriptors
never reached the GPU: the scatter compute copied nothing (GLES: empty
draw commands) or stale garbage (Vulkan: wild indirect commands ending in
VK_ERROR_DEVICE_LOST).
- MG_Impl/MG_Backend: real glFenceSync objects backed by backend fences
(GLES: native ES syncs guarded by context generation and owner thread;
Vulkan: buffer-manager frame serials), replacing always-signaled stubs
that let Flywheel reclaim staging memory the GPU still reads.
- MG_Backend/DirectGLES: compute dispatches now run the same per-program
resource sync as draws (uniform-block bindings and sampler units must be
re-established through the API because layout(binding) is stripped from
transpiled ESSL) and rebind texture units afterwards; the cull shader
used to read a stale _FlwFrameUniforms binding and the depth-pyramid
downsample sampled a stale unit-0 texture, zeroing the Hi-Z pyramid and
occlusion-culling all Flywheel geometry. Image uniforms are excluded from
glUniform1i (ES bakes their unit via layout(binding)); image-unit sync is
clamped to the device limit; eliminated/SSBO-classified uniform blocks
are skipped.
- MG_Backend/DirectGLES: gl_BaseInstance in native indirect draws reads the
GPU-written command buffer through an injected mg_IndirectParams SSBO
view addressed per draw instead of the zero CPU shadow; layout(binding)
is preserved for SSBO/image declarations (ES has no API rebinding for
them); the ES context ownership claim moved to a global atomic owner
thread with an EGL ground-truth check, and deferred buffer op state is
mutex-guarded, so ops cannot silently no-op after context migration.
- MG_Backend/DirectVulkan: new RebaseInstanceIndexPass rewrites vertex
InstanceIndex loads to (InstanceIndex - BaseInstance). glslang's relaxed
Vulkan mode aliases gl_InstanceID to InstanceIndex, which includes
firstInstance, but GL's gl_InstanceID is zero-based - draws with nonzero
baseInstance paired meshes with wrong instance data (cogwheel drawn as a
waterwheel, another wheel collapsed invisible). Gated on the
shaderDrawParameters device feature. Sampled-read barriers additionally
cover the compute stage (the Hi-Z downsample samples the depth
attachment from compute), and short uniform-buffer ranges keep the
existing zero-padding.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Support cube map face uploads with cube-compatible images and per-face array layers
- Add uniform texel buffer descriptor support for samplerBuffer bindings
- Cache transient vertex/index buffer uploads per frame to avoid VMA allocation failures