wgpu streaming (3/4): --streaming scaffold + applyCachedModelStreaming

Wires the metadata-only reader (commit 1) through a parallel streaming
load path. With --streaming on:

  - loadSidecar routes through readSidecarMetadataOnly: reads header +
    mesh dict + instance dict + georef + elements upfront. Skips
    vertex bytes entirely.
  - applyCachedModelStreaming computes the same chunk plan as the
    non-streaming path, allocates the small per-chunk buffers
    (visible_draws + prefix_sums + per_chunk_uniform), allocates the
    model-shared mesh + instance + index buffers, but leaves each
    chunk's vertex_storage NULL and is_resident=false.
  - Stores streaming_file_path + vertex_section_offset on the model so
    the per-frame loader can range-read chunks later.
  - Computes per-chunk world AABB by walking instances → mesh → chunk;
    used by both cull (chunk-level frustum reject, future) and the
    streaming loader (proximity-prioritised fetch, future).

Index buffer is still loaded upfront in stage 1 (small relative to
vertex data: ~1/2 of vertex bytes on real scenes). Stage 2 may defer
it too if measurements suggest it's worth the extra plumbing.

Render + pick already gate on c.bind_group (null when non-resident),
so the existing guards correctly skip non-resident chunks without
further changes.

With this commit alone, --streaming mode shows an EMPTY scene (just
background colour) because no chunk ever becomes resident. Commit 4
adds the per-frame loader that triggers chunk load when cull marks
them visible — that's the commit where rendering kicks in and the
OOM fix actually lands.

Default behaviour (no --streaming): legacy synchronous full-load.
Pixel-identical to the prior commit on basic.ifc.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-05-28 09:15:53 +10:00
parent d368ee449d
commit f6d888d42b
3 changed files with 382 additions and 25 deletions
+19 -1
View File
@@ -61,7 +61,8 @@ public:
// Synchronous load + GPU upload. Requires wgpu init to have completed
// (i.e. the window has been exposed at least once). Returns the
// assigned model_id, or 0 on failure.
// assigned model_id, or 0 on failure. Routes through the streaming
// loader when streaming_enabled_, else the legacy full-load path.
uint32_t loadSidecar(const QString& path);
// Restore a finalised model from a SidecarData struct: allocate wgpu
@@ -69,6 +70,15 @@ public:
// models_gpu_. Replaces any existing state for model_id.
void applyCachedModel(uint32_t model_id, SidecarData data);
// Streaming variant: takes a StreamingSidecar (metadata only — no
// vertex / index bytes). Allocates per-chunk small buffers and the
// model-shared index / mesh / instance storage upfront, but leaves
// each chunk's vertex_storage null and is_resident=false. The per-
// frame loader (commit 4 of streaming) brings chunks resident on
// demand as cull flags them visible.
void applyCachedModelStreaming(uint32_t model_id,
struct StreamingSidecar metadata);
void removeModel(uint32_t model_id);
void resetScene();
@@ -366,6 +376,14 @@ public:
// Real default-on requires further tuning — see task #15.
bool bvh_enabled_ = false;
// Streaming load (task #16). When enabled, queueLoadSidecar routes
// through the metadata-only reader: mesh dict + instance dict + georef
// load immediately; per-chunk vertex bytes are read + uploaded on
// demand by the per-frame loader as chunks become frustum-visible.
// Default OFF so existing behaviour (synchronous full load) is
// preserved; --streaming opts in.
bool streaming_enabled_ = false;
private:
// Switch to LOD1 when an instance's projected bounding-sphere radius