Skip to content

reuse staging buffer when read_pixel gets perf: 18x faster read_pixels #397

Open
haixuanTao wants to merge 1 commit into
dimforge:masterfrom
haixuanTao:feat/persistent-readback-staging
Open

reuse staging buffer when read_pixel gets perf: 18x faster read_pixels #397
haixuanTao wants to merge 1 commit into
dimforge:masterfrom
haixuanTao:feat/persistent-readback-staging

Conversation

@haixuanTao

Copy link
Copy Markdown

read_pixels indexed the mapped staging buffer byte-by-byte during BGRA->RGB conversion. Mapped readback memory is uncached (write-combined), so scalar reads run at ~10 MB/s — 99 ms for a 640x480 frame on an RTX 5080, dwarfing the 4 ms render. memcpy each row into a cached local buffer first and convert from there: 5.4 ms (18x).

Also: reuse the staging buffer across calls (grown on demand) instead of allocating per call, and wait on the copy's submission index instead of polling the device indefinitely.

Per-frame capture loop (sim + render + snap, 640x480): 113 ms -> 12 ms.

…ing buffer

read_pixels indexed the mapped staging buffer byte-by-byte during
BGRA->RGB conversion. Mapped readback memory is uncached
(write-combined), so scalar reads run at ~10 MB/s — 99 ms for a 640x480
frame on an RTX 5080, dwarfing the 4 ms render. memcpy each row into a
cached local buffer first and convert from there: 5.4 ms (18x).

Also: reuse the staging buffer across calls (grown on demand) instead of
allocating per call, and wait on the copy's submission index instead of
polling the device indefinitely.

Per-frame capture loop (sim + render + snap, 640x480): 113 ms -> 12 ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
haixuanTao added a commit to haixuanTao/kiss3d that referenced this pull request Jul 8, 2026
…back, fast read_pixels

Off-screen video capture improvements, in three parts:

- read_pixels 18x faster: convert BGRA->RGB from cached memory (mapped
  readback memory is uncached, ~10 MB/s scalar reads), reuse the staging
  buffer across calls, and wait on the copy's submission index instead of
  polling the whole device.

- Window::new_headless_with_setup(width, height, setup): a full-featured
  Window backed by no OS window and no swapchain (same headless canvas as
  OffscreenSurface, but exposing the whole Window API — custom renderers,
  ray tracer, snap* readbacks). Never throttled by display vsync; works
  without a display server.

- snap_begin()/snap_finish(): pipelined readback that enqueues the
  texture->buffer copy and map without waiting, so frame N's copy is
  collected after frame N+1 renders instead of stalling the GPU pipeline
  each frame. read_pixels() is refactored onto the same begin/finish path
  with unchanged behavior.

Motivation: a windowed capture loop (render_frame + blocking snap) is
vsync-locked — the blocking readback defeats swapchain frame pipelining,
so every iteration eats ~2 vblanks (~30 fps at 60 Hz) regardless of GPU
load. Headless + pipelined readback took a downstream nexus3d capture
loop from 33 to 92 fps at 640x480 (2.7 fps before the read_pixels fix).

Supersedes dimforge#397 (included here).
haixuanTao added a commit to haixuanTao/kiss3d that referenced this pull request Jul 8, 2026
Two additions for off-screen video capture:

- Window::new_headless_with_setup(width, height, setup): a full-featured
  Window backed by no OS window and no swapchain (same headless canvas as
  OffscreenSurface, but exposing the whole Window API — custom renderers,
  ray tracer, snap* readbacks). Rendering is never throttled by the
  display's vsync and works without a display server.

- snap_begin()/snap_finish() (begin_read_pixels/finish_read_pixels on the
  canvas): pipelined readback that enqueues the texture->buffer copy and
  buffer map without waiting, so the copy of frame N can be collected after
  frame N+1 renders instead of stalling the GPU pipeline every frame the
  way the blocking snap()/read_pixels() does. read_pixels() is refactored
  to begin+finish with unchanged behavior.

Motivation: a windowed capture loop (render_frame + blocking snap) is
vsync-locked — the blocking readback defeats swapchain frame pipelining, so
every iteration eats ~2 vblanks (~30 fps at 60 Hz) regardless of GPU load.
Headless + pipelined readback took a downstream nexus3d capture loop from
33 to 92 fps at 640x480.

Builds on feat/persistent-readback-staging (dimforge#397).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant