Skip to content

[Feature] Virtual geometry streaming — pool-based GPU-resident VB/IB with page eviction #622

Description

@JeanPhilippeKernel

Type

  • Feature

Overview

The current global VB/IB (PR #611) is a static packed buffer: all mesh geometry is uploaded at load time and lives in VRAM until explicitly removed. This does not scale to large scenes where total geometry exceeds the VRAM budget.

A virtual geometry streaming system replaces the static buffer with a fixed-size GPU-resident pool. Geometry pages are streamed into the pool on demand (driven by visibility and LOD) and evicted when cold. The total VRAM footprint stays within a configurable budget regardless of scene size.

Context

The global buffer design was chosen in PR #611 specifically to compose with this future system:

  • Phase 1 (done) — packed VB/IB, offset-based instanced draws, free-list compaction on removal
  • Phase 2 (pending [Task] Build Outliner UI wired to the ECS Actor system #604 area) — draw-indirect batching, frustum culling, material sort
  • Phase 3 (this issue) — pool becomes a streaming arena; pages stream in/out based on visibility and budget

The free-list + deferred-compact design in Phase 1 is the correct foundation: a streaming system needs the same mechanics (free slots, fast reclaim, batch uploads). A simpler copy-on-remove design would require a full redesign at this phase.

Related:

What needs to be done

1. Pool allocator for the VB/IB

Replace the current grow-to-fit buffer with a fixed-capacity ring/slab:

  • GeometryPool struct: capacity_vertices, capacity_indices, free-list of fixed-size pages (e.g. 64 KB vertex pages, 64 KB index pages)
  • Allocation returns a GeometryPageHandle (offset + size), not a raw offset
  • Eviction policy: LRU or clock-hand; mark pages as resident/non-resident

2. Streaming manager

  • GeometryStreamingManager: background worker that services load/evict requests
  • Queue of StreamRequest { MeshUUID, LODLevel, Priority } fed by the visibility system
  • On load: read mesh data from VFS, upload via RRM::UpdateBuffer into the pool slot
  • On evict: mark slot free; mesh is flagged non-resident in AssetManager

3. Visibility-driven priority

  • Integrate with the frustum culler (Phase 2): meshes outside frustum get lower priority
  • Distance-based LOD selection feeds into page size (lower LOD = smaller page)
  • Actor with MeshComponent carries a StreamingState flag: Unloaded | Pending | Resident

4. Render-side fallback

  • If a mesh is requested for draw but its page is not yet resident, skip the draw call or substitute a proxy mesh
  • Avoid stalls: the render thread never waits on streaming I/O

5. Budget management

  • Configurable VRAM budget (streaming.max_geometry_mb in project config)
  • GeometryPool::Stats() reports resident pages, eviction count, upload bandwidth

Resources

  • UE5 Nanite streaming overview (cluster-based, but same eviction concepts)
  • GPU Gems 3, Chapter 5 — Efficient Rendering with Hardware Tessellation (LOD streaming)
  • ZEngine/ZEngine/Rendering/RenderResourceManager.h — current upload path
  • ZEngine/ZEngine/ECS/Components/MeshComponent.h — component that will carry StreamingState

Testing

cd Result.Darwin.arm64.Debug
# Unit tests for pool allocator and eviction policy
./ZEngine/tests/Debug/ZEngineTests --gtest_filter="GeometryPoolTest.*:StreamingManagerTest.*"

# Integration: load a scene exceeding the VRAM budget, verify no crash,
# verify visible meshes render, verify eviction stats are non-zero

Acceptance criteria

  • GeometryPool allocates and evicts pages within a fixed VRAM budget
  • GeometryStreamingManager uploads pages off the render thread
  • Render thread never stalls waiting for a streaming upload
  • MeshComponent::StreamingState reflects resident/pending/unloaded correctly
  • Scenes larger than the pool budget render without crash (missing meshes are skipped)
  • GeometryPool::Stats() reports budget usage and eviction count
  • Builds without warnings in Debug and Release
  • All relevant unit tests pass
  • No regression in existing rendering tests

Estimated effort

2–3 weeks (pool allocator + streaming manager + render integration)

Metadata

Metadata

Projects

Status
No status

Relationships

None yet

Development

No branches or pull requests

Issue actions