-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Context
Follow-up to #74. Roadmap Phase 2 item (see #57). Terrain LOD (#78), instanced geometry LOD (#80), and hybrid cluster/standard GAS (#81) each use fixed distance thresholds. These work well for a given scene scale but don't adapt to scene complexity — a 10M-triangle city scene needs more aggressive LOD than a sparse rural DEM. A budget system closes the loop: measure actual frame time, adjust LOD thresholds to hit a target.
Goal
Automatic LOD threshold adjustment that maintains a target frame time (e.g. 33ms for 30fps) by pushing LOD transitions closer or farther based on measured render performance.
Current State
TerrainLODManagerusescompute_lod_distances()with a fixedlod_distance_factor— all thresholds scale linearly from tile diagonal- Instanced geometry LOD (Instanced geometry LOD: mesh LOD chains and billboard imposters #80) will use
compute_lod_level()with fixed thresholds - Frame timing is available in the viewer tick loop (
_update_frame()returns, GLFW providesglfwGetTime()) - No feedback loop exists between render cost and LOD parameters
Design
Feedback Loop
measure frame_time
→ compare to target_frame_time
→ compute error = frame_time - target
→ adjust lod_distance_factor (controls all LOD thresholds)
→ LOD managers recompute distances on next update
Controller
A simple exponential moving average + proportional controller:
ema_frame_time = α * frame_time + (1-α) * ema_frame_time(α ≈ 0.1, smooth out spikes)- If
ema_frame_time > target * 1.1: decreaselod_distance_factorby step (push LOD transitions closer → fewer triangles) - If
ema_frame_time < target * 0.9: increaselod_distance_factorby step (push transitions farther → more detail) - Dead band (±10%) prevents oscillation when frame time is close to target
- Step size proportional to error magnitude, clamped to avoid large jumps
Bounds
min_lod_distance_factor: floor to prevent quality collapse (e.g. 1.0 — LOD transitions no closer than 1× tile diagonal)max_lod_distance_factor: ceiling to prevent wasted work when GPU is fast (e.g. 10.0)- Per-frame adjustment clamped to ±5% of current factor — smooth, no pops
Budget Metrics (Beyond Frame Time)
Optional secondary metrics for more precise control:
| Metric | Source | Use |
|---|---|---|
| Total triangle count | Sum of active GAS triangle counts | Hard cap: if > budget, force LOD up regardless of frame time |
| Total instance count | IAS instance count | Similar hard cap |
| GPU memory pressure | cuMemGetInfo free/total |
Force aggressive LOD when memory is tight |
Frame time is the primary metric. Triangle/instance/memory caps are safety valves, not the main control loop.
Integration Points
TerrainLODManager: already acceptslod_distance_factor— budget system adjusts this value, manager recomputes_lod_distancesand re-evaluates tiles on nextupdate()- Instanced LOD (Instanced geometry LOD: mesh LOD chains and billboard imposters #80): same
lod_distance_factorparameter, or a separate per-layer multiplier - Hybrid GAS (Hybrid cluster/standard GAS selection by distance #81): cluster distance threshold scales with the same factor
- Viewer tick loop: budget controller runs once per frame after render, before present
LODBudgetController API
class LODBudgetController:
def __init__(self, target_fps=30, min_factor=1.0, max_factor=10.0,
alpha=0.1, dead_band=0.1, step_scale=0.02):
...
def update(self, frame_time_ms):
"""Called once per frame. Returns adjusted lod_distance_factor."""
...
@property
def current_factor(self):
...
@property
def ema_frame_time(self):
...Viewer Integration
- Toggle: Shift+B or similar keybinding — enables/disables auto-budget
- HUD: show target FPS, current EMA frame time, current
lod_distance_factor, and direction indicator (↑ increasing detail / ↓ decreasing detail / = stable) - When disabled,
lod_distance_factorstays at its last value (no snap back to default) - Manual LOD distance override (if added later) disables auto-budget
Implementation Plan
LODBudgetControllerclass — inrtxpy/lod.py, pure logic, no viewer dependencies, fully unit-testable- Frame time measurement — capture render time per frame in the tick loop (wall clock, not including present/vsync)
- Wire controller to TerrainLODManager — adjust
lod_distance_factor, callcompute_lod_distances()to update thresholds - Wire to instanced LOD (Instanced geometry LOD: mesh LOD chains and billboard imposters #80) — same factor or per-layer multiplier
- Wire to hybrid GAS (Hybrid cluster/standard GAS selection by distance #81) — cluster distance threshold
- HUD display — budget status line in help overlay
- Toggle keybinding — Shift+B, add to SHIFT_BINDINGS table
- Triangle count safety cap — optional hard limit, force LOD up if exceeded
Scope Boundaries
- Proportional controller only — no PID (derivative/integral terms add complexity without clear benefit for this use case)
- Frame time is the primary signal; triangle count cap is stretch goal
- No per-geometry priority system (all geometry LOD adjusts uniformly)
- No network/disk streaming budget — this is purely GPU render budget
Key Files
rtxpy/lod.py—compute_lod_distances(), newLODBudgetControllerrtxpy/viewer/terrain_lod.py—TerrainLODManager(consumes adjusted factor)rtxpy/engine.py— tick loop, frame timing, budget controller instancertxpy/viewer/keybindings.py—SHIFT_BINDINGStable
References
- Parent issue: Level of detail: terrain LOD, instanced LOD, and hybrid cluster GAS #74
- Terrain LOD PR: Add distance-based terrain LOD system #78
- Instanced geometry LOD: Instanced geometry LOD: mesh LOD chains and billboard imposters #80
- Hybrid cluster/standard GAS: Hybrid cluster/standard GAS selection by distance #81
- Roadmap: Project roadmap: architecture, rendering, simulation, platform, digital twin #57 (Phase 2 — Level of Detail)