Repository navigation
OpenGL Persistent Mapped Geometry Streaming
Lifecycle: Performance - Unproven · Type: Streaming-buffer architecture
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579
State: Current orphan/subdata uploads are source-confirmed. Persistent mapping has not been implemented or benchmarked in this fork.
OpenGL candidate directory · OpenGL Renderer · Vulkan Transient-Buffer Growth
The OpenGL backend already uses buffer orphaning for inline-array uploads:
glBufferData(GL_ARRAY_BUFFER, buffer_size, NULL, GL_STREAM_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, pg->inline_array);Other inline streams similarly redefine or upload buffer storage. This is not a naive implementation—buffer orphaning gives the driver permission to provide fresh storage rather than waiting for the old allocation.
The candidate tests a different ownership model: allocate a bounded persistently mapped arena once, suballocate slices explicitly, and retire each slice after its last GPU use. The goal is to remove repeated storage re-specification/upload API calls and make reuse waits measurable.
-
hw/xbox/nv2a/pgraph/gl/vertex.cpgraph_gl_bind_inline_array()- per-attribute inline buffers
- vertex attribute pointer setup
-
hw/xbox/nv2a/pgraph/gl/draw.c- inline-elements and draw-array batching routes
- OpenGL capability discovery and buffer cleanup in renderer initialization/finalization.
flowchart TD
A[Inline geometry ready] --> B[Bind stream buffer]
B --> C[glBufferData NULL to orphan storage]
C --> D[glBufferSubData or glBufferData with bytes]
D --> E[Set attribute pointers at offset zero]
E --> F[Draw]
F --> G[Driver decides storage lifetime/reuse]
The driver may implement this efficiently. The candidate is not automatically faster; it replaces implicit driver allocation with explicit ring ownership.
flowchart TD
A[Inline geometry ready] --> B[Calculate aligned slice size]
B --> C{Free completed slice available?}
C -->|Yes| D[Copy bytes directly to mapped address]
C -->|No| E{Arena can wrap/grow safely?}
E -->|Yes| D
E -->|No| F[Wait for oldest required fence or use orphaning fallback]
F --> D
D --> G[Set attribute pointers to slice offset]
G --> H[Draw]
H --> I[Associate slice with retirement fence/ticket]
I --> J[Recycle only after GPU completion]
Setup sketch:
GLbitfield storage_flags =
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT |
GL_MAP_COHERENT_BIT |
GL_DYNAMIC_STORAGE_BIT;
GLuint buffer;
glCreateBuffers(1, &buffer);
glNamedBufferStorage(buffer, capacity, NULL, storage_flags);
void *mapped = glMapNamedBufferRange(
buffer, 0, capacity,
GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT |
GL_MAP_COHERENT_BIT);Per-draw sketch:
StreamSlice slice = stream_acquire(bytes, alignment);
memcpy((uint8_t *)mapped + slice.offset, source, bytes);
for (unsigned attr = 0; attr < active_attributes; attr++) {
glVertexAttribPointer(attr, count[attr], type[attr], normalized[attr],
stride, (void *)(slice.offset + attr_offset[attr]));
}
draw();
stream_retire_after_current_gpu_work(slice);Non-coherent mapping would require explicit flushes and should be a separate capability route.
A safe arena tracks:
capacity
write cursor
wrap generation
alignment
slice offset/size
draw or fence that last consumes each slice
high-water usage
oldest unretired slice
The simplest implementation can use one fence per coarse batch rather than one fence per draw. Every slice allocated during a batch inherits the batch’s retirement fence.
-
GL_ARB_buffer_storageor equivalent core functionality is present. - Mapping flags are supported by the selected context.
- Slice alignment satisfies every active vertex/index requirement.
- Bytes are visible to the GPU according to coherent/non-coherent rules.
- A slice is never overwritten before every consuming draw completes.
- Context reset or buffer replacement invalidates all mapped pointers and slices.
- Oversized uploads have a bounded fallback rather than unbounded arena growth.
GL_MAP_COHERENT_BIT controls visibility. It does not authorize reuse.
Incorrect sequence:
copy red vertices to slice 0
submit draw using slice 0
immediately overwrite slice 0 with blue vertices
GPU may execute red draw with blue bytes
Correct sequence:
copy red vertices to slice 0
submit draw
associate slice 0 with completion batch N
allocate another slice for blue
recycle slice 0 only after batch N completes
Retain it as:
- fallback when persistent mapping is unavailable;
- comparison route for A/B tests;
- emergency path for uploads larger than the bounded arena;
- compatibility route for problematic drivers.
The large gl_memory_buffer is a different stream. This candidate should start with inline geometry because its short lifetime and known source bytes make ownership easier to prove.
A later stage can place all active streams in one arena or one arena per usage class. Combining every path initially would obscure which upload route improved.
- Repeated
glBufferData(..., NULL, GL_STREAM_DRAW)storage re-specification for admitted streams. - Repeated
glBufferSubData()or data-bearingglBufferData()upload calls. - Driver-side allocation and rename decisions on every upload.
- Immediate reuse stalls hidden inside the current upload API calls.
- Per-stream object churn when several small attributes can share one prepared arena.
The byte copy itself does not disappear. The CPU still writes each newly produced inline stream into host-visible memory unless another producer route is introduced.
- Copying every required source byte before the draw that consumes it.
- Correct attribute format, stride, normalization, binding, and offset state.
- Visibility handling for coherent or explicitly flushed non-coherent mappings.
- Retirement tracking until every consuming GPU command completes.
- A bounded capacity, wrap, growth, and oversize policy.
- Safe teardown, reset, snapshot, and context-recreation behavior.
- The existing orphaning route as a fallback and comparison path.
- Removes repeated
glBufferData(NULL)andglBufferSubData()calls on admitted uploads. - Makes memory capacity and reuse waits explicit and countable.
- Can reduce driver allocation/validation work.
- Supports batching several small streams into one mapped region.
- Provides a clear path to bounded capacity experiments.
- Additional fence, ring, wrap, and teardown logic.
- Persistent coherent memory can have different performance characteristics across drivers.
- Tiny uploads may not benefit enough to justify ring bookkeeping.
- Too-small arenas cause waits; too-large arenas increase memory use and latency.
- Attribute pointer offsets and VAO state can become stale after buffer replacement.
- A stalled GPU can pin the whole arena and force fallback.
stream_uploads
stream_upload_bytes
orphan_calls
subdata_calls
persistent_slice_allocations
persistent_bytes_written
arena_wraps
arena_growths
arena_high_water_bytes
slice_reuse_waits
slice_reuse_wait_us
oversize_fallbacks
driver_fallbacks
upload_prepare_us
Compare API count and CPU time, but treat frame/guest progress as the acceptance metric.
| Scenario | Required result |
|---|---|
| Tiny frequent inline draws | Correct attributes and no per-upload wait |
| Large inline upload | Safe arena allocation or bounded fallback |
| Deliberately small arena | Correct wrap and retirement behavior |
| Artificially delayed GPU | No early overwrite |
| Alternating red/blue vertex data | Every draw observes its own bytes |
| Multiple active attributes and strides | Correct offsets/alignment |
| Abrupt workload-size changes | Stable growth/fallback behavior |
| Renderer reset/context recreation | Old mapped pointer never reused |
| Snapshot and teardown | All fences/slices retired or abandoned safely |
| Multiple vendors | Capability and performance recorded per driver |
Promote to Researched when route use is frequent, premature-reuse stress tests pass, and an uninstrumented Release build improves upload CPU or frame tails over the existing orphaning route without excessive memory or wait growth.
Reject or limit the path if the driver’s orphaning implementation is as fast or faster, if arena bookkeeping introduces frequent waits, or if compatibility varies too widely without a reliable capability fallback.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues