Repository navigation
OpenGL Asynchronous Display Handoff
Lifecycle: Performance - Unproven · Type: Host synchronization architecture
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579
State: The two client-side waits are source-confirmed. Their cost and the feasibility of a fully asynchronous frontend contract remain unmeasured.
OpenGL candidate directory · Completed display reuse · OpenGL Renderer
The OpenGL renderer uses separate shared render and display contexts. When the frontend asks for the framebuffer texture, the current path asks the PGRAPH thread to prepare the source, waits for the render context to finish, switches to the display context, renders the display/PVIDEO composition, waits again, then signals the frontend and returns a texture ID.
The candidate keeps producer/consumer ordering but replaces CPU-blocking waits with GPU-side cross-context dependencies and explicit output-slot ownership.
This is not a proposal to remove every glFinish() or every guest-visible completion boundary. It targets waits that exist because the host interface requires an already-completed texture before returning.
-
hw/xbox/nv2a/pgraph/gl/display.cgl_fence()pgraph_gl_sync()pgraph_gl_get_framebuffer_surface()- display-context composition
-
hw/xbox/nv2a/pgraph/gl/renderer.c- render/display context creation
-
pgraph_gl_flip_stall()and its guest-visibleglFinish()
- Frontend framebuffer ownership and presentation interface must be updated to accept readiness and release dependencies, not only a texture ID.
Current fence helper:
static void gl_fence(void)
{
GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
int result = glClientWaitSync(
fence, GL_SYNC_FLUSH_COMMANDS_BIT, 5000000000ULL);
assert(result == GL_CONDITION_SATISFIED ||
result == GL_ALREADY_SIGNALED);
glDeleteSync(fence);
}The five-second value is a maximum timeout, not evidence of a five-second normal delay.
sequenceDiagram
participant UI as Frontend/UI
participant PF as PGRAPH/render context
participant DC as Display context
participant GPU as Host GPU
UI->>PF: request framebuffer texture
PF->>PF: upload/prepare source surface
PF->>GPU: fence render-context work
PF->>PF: glClientWaitSync
PF->>DC: switch context
DC->>GPU: render display/PVIDEO composition
DC->>DC: glClientWaitSync
DC->>PF: switch context back
PF-->>UI: signal sync_complete + texture ID
The CPU does not only submit ordering; it waits for both stages before publishing the result.
sequenceDiagram
participant UI as Frontend/UI
participant PF as Render context
participant DC as Display context
participant GPU as Host GPU
PF->>GPU: render source
PF->>GPU: create render_ready fence + glFlush
PF->>DC: enqueue display job with source and render_ready
DC->>GPU: glWaitSync(render_ready)
DC->>GPU: compose into free output slot
DC->>GPU: create display_ready fence + glFlush
DC-->>UI: publish {texture, display_ready, slot_generation}
UI->>GPU: wait/import dependency at consumption boundary
UI-->>DC: publish consumer_finished for slot
DC->>DC: recycle slot after consumer completion
Illustrative interfaces:
typedef struct GLDisplayFrame {
GLuint texture;
GLsync ready;
uint64_t slot_generation;
unsigned width;
unsigned height;
} GLDisplayFrame;
GLDisplayFrame pgraph_gl_acquire_display_frame(void);
void pgraph_gl_release_display_frame(
uint64_t slot_generation, GLsync consumer_finished);An implementation may use explicit server waits in the frontend context rather than passing raw sync ownership across arbitrary threads. The contract is the important part: readiness and lifetime travel with the image.
- CPU time inside the first
glClientWaitSync(). - CPU time inside the display-context completion wait.
- Render-thread/frontend round trips caused only by the “ready texture ID” interface.
- GPU idle periods caused by preventing later independent render work from being queued.
- The display pass must not sample the source before rendering/upload completes.
- The frontend must not sample a display output before composition completes.
- The renderer must not overwrite or destroy the source while display composition uses it.
- The display context must not overwrite an output slot while the frontend uses it.
- Guest-visible flip-stall behavior must retain its real completion semantics.
- Resize, reset, shutdown, snapshot, and context loss must drain or abandon jobs safely.
A practical design needs at least two or three display output slots:
FREE
-> RECORDING
-> SUBMITTED
-> READY_FOR_FRONTEND
-> FRONTEND_IN_USE
-> RETIRING
-> FREE
Each state transition needs a generation or ticket. A texture name alone is insufficient because the same texture object may be recycled for a later frame.
The source surface also needs a retained reference or version. The display job must not rely on “same address” as proof that the source is still the same image.
- Preserves GPU ordering without forcing the application CPU to wait immediately.
- Allows render work, display composition, and frontend presentation to overlap.
- Makes ownership explicit rather than hidden in synchronous behavior.
- Creates the foundation needed for completed-output reuse and a future native presentation path.
- Keeps guest-visible completion waits separate from host-interface waits.
- Requires a framebuffer interface change across renderer and frontend.
- Adds slot, fence, and teardown state.
- Shared-context sync behavior must be tested on every supported platform/driver family.
-
glWaitSync()still serializes dependent GPU work; it only removes the client wait. - A ring that is too small can reintroduce waits under another name.
- A ring that is unbounded can hide a leak or create latency growth.
- Immediate surface reuse, resize, or context recreation can expose stale references.
render red into surface A
queue display composition of A
render blue into reused A before display samples it
frontend unexpectedly sees blue
display frame N into slot 0
frontend still sampling slot 0
renderer composes frame N+2 into slot 0
tearing or mixed image
Replacing glClientWaitSync() with glWaitSync() but immediately waiting for a free output slot can move the same stall from the fence helper into slot acquisition. Evidence must measure total useful overlap.
render_to_display_client_wait_calls
render_to_display_client_wait_us
display_to_frontend_client_wait_calls
display_to_frontend_client_wait_us
server_waits_enqueued
display_slots_in_use_peak
slot_reuse_waits
source_lifetime_waits
frames_published
frames_consumed
frames_dropped_or_replaced
presentation_latency_us
fixed guest progress and host frame p95/p99
| Scenario | Required result |
|---|---|
| Artificially delayed render context | Display waits correctly without stale source |
| Artificially delayed frontend | Output slot is not overwritten |
| Alternating red/blue frames | Exact frame/version observed |
| Host display faster than guest | No unnecessary recomposition or stale publication |
| Host display slower than guest | Defined queue/drop/backpressure behavior |
| Resize/fullscreen/minimize | Old slots retired before destruction |
| PVIDEO enable/disable | Overlay state belongs to correct frame |
| Snapshot save/load | Pending jobs drained or invalidated |
| Renderer reset/shutdown | No wait cycle or leaked sync object |
| Guest flip stall | Required guest completion preserved |
Promote to Researched only when route counters show meaningful client-wait time, the asynchronous version preserves frame identity under delayed producer/consumer tests, and Release measurements improve useful guest progress or CPU/tail behavior without growing latency or memory unboundedly.
Reject or redesign when waits merely move to slot acquisition, when the frontend cannot safely consume readiness dependencies, or when preserving source/output lifetimes removes the expected overlap.
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