mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-15 18:14:08 +00:00
wgpu streaming: background-thread chunk I/O kills render-thread stutters
The sync chunk-read on the render thread was causing 100-300 ms spikes during orbit whenever a new chunk needed to scatter-gather its mesh bytes from disk. p99 was 326 ms on the close-camera benchmark. New WgpuStreamingThread: one worker thread with a condvar-protected request/result queue. driveStreamingLoads becomes drain-then-enqueue: 1. Drain any results the worker pushed since last frame. For each, pool-allocate slices + queueWriteBuffer + build the chunk bind group (still main-thread because wgpu queue ops aren't thread-safe). 2. Walk visible non-resident chunks (sorted by distance), evict to make pool room, and enqueue the request. Chunk gains is_loading flag to prevent re-enqueueing while in flight. loadChunkBytesAndUploadGpu becomes the sync fallback path, used only when a screenshot is pending — the deferred-capture wait would otherwise let the window manager re-layout the window between frames and the test framework would capture at the wrong size. Normal streaming always goes through the worker. Bench warm-gate / requestUpdate gating updated to consider streaming_thread_.inFlightApprox() so we don't declare "converged" while a worker read is still in flight, and the render loop stays alive until the worker queue is empty. Refactored loadChunkBytesAndUploadGpu into two helpers: - makeChunkRequest: builds the worker request from chunk metadata - applyStreamedChunk: pool.alloc + queueWriteBuffer + bind group Both the sync and async paths share applyStreamedChunk. Benchmark (big federation, --streaming): close camera: avg 24 fps p99 47 ms (was 27/326) default camera: avg 24 fps p99 46 ms (was 31/186) stream time: ~2 ms (was 8-12) cull is now the bottleneck (20 ms median) — task #17 (GPU compute cull) is the next frontier. Pixel-identical to non-streaming on basic.ifc on both paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
#include "WgpuBufferPool.h"
|
||||
#include "WgpuModelGpuData.h"
|
||||
#include "WgpuSelectionState.h"
|
||||
#include "WgpuStreamingThread.h"
|
||||
#include "WgpuVisibilityState.h"
|
||||
|
||||
// Stage-2 wgpu viewport: opens a native QWindow, brings up a wgpu instance/
|
||||
@@ -137,6 +138,13 @@ private:
|
||||
// expected to have already evicted enough). No-op (returns true)
|
||||
// when already resident.
|
||||
bool loadChunkBytesAndUploadGpu(WgpuModelGpuData& m, size_t chunk_idx);
|
||||
// Pool-allocate + queueWriteBuffer + build bind group for a chunk
|
||||
// whose vbytes/idx have already been read (by either the worker
|
||||
// thread's drained result or the sync fallback). Returns false on
|
||||
// pool OOM. Toggles is_resident=true / is_loading=false on success.
|
||||
bool applyStreamedChunk(WgpuModelGpuData& m, size_t chunk_idx,
|
||||
const std::vector<uint8_t>& vbytes,
|
||||
const std::vector<uint32_t>& idx);
|
||||
// Release a resident chunk's pool ranges + bind group; flip
|
||||
// is_resident=false. The chunk's CPU metadata (offsets, AABB,
|
||||
// visible-draw scratch) is retained so a subsequent
|
||||
@@ -414,6 +422,13 @@ public:
|
||||
// the old hand-picked streaming_vram_budget_bytes_ knob entirely.
|
||||
WgpuBufferPool pool_;
|
||||
|
||||
// Background worker that does scatter-gather chunk reads off the
|
||||
// render thread. driveStreamingLoads enqueues requests for visible
|
||||
// non-resident chunks and drains completed results into the pool
|
||||
// on subsequent frames. Kills the 100-300 ms per-frame stutters
|
||||
// that synchronous disk reads caused during orbit.
|
||||
WgpuStreamingThread streaming_thread_;
|
||||
|
||||
// Per-frame streaming activity, written by driveStreamingLoads,
|
||||
// consumed by the benchmark harness to delay the orbit sweep until
|
||||
// the initial cold-load settles. `loads` = chunks brought resident
|
||||
|
||||
Reference in New Issue
Block a user