Skip to content

OpenGL Persistent Mapped Geometry Streaming

github-actions[bot] edited this page Sep 15, 2026 · 1 revision

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

Purpose

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.

Source seams

Current execution flow

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]
Loading

The driver may implement this efficiently. The candidate is not automatically faster; it replaces implicit driver allocation with explicit ring ownership.

Candidate execution flow

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]
Loading

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.

Arena design

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.

Required guard

  • GL_ARB_buffer_storage or 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.

Why persistent mapping does not solve lifetime automatically

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

Interaction with existing routes

Current orphaning route

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.

Guest-memory vertex buffer

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.

Per-attribute inline buffers

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.

What may disappear

  • Repeated glBufferData(..., NULL, GL_STREAM_DRAW) storage re-specification for admitted streams.
  • Repeated glBufferSubData() or data-bearing glBufferData() 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.

What remains mandatory

  • 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.

Advantages

  • Removes repeated glBufferData(NULL) and glBufferSubData() 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.

Costs and risks

  • 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.

Required counters

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.

Qualification matrix

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

Acceptance rule

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.

Rejection rule

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.

Related

Clone this wiki locally