Profile and preinitialize viewport rendering
This commit is contained in:
100
docs/renderer-performance.md
Normal file
100
docs/renderer-performance.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Renderer and viewport performance
|
||||
|
||||
Measured on 2026-08-23 with the primary account in Broceliande, a 1024 m x
|
||||
1024 m varregion, using the default 64 m view distance. The visible scene had
|
||||
211 objects, 427,829 triangles, and five unavailable grid textures. Release
|
||||
numbers are the performance baseline; debug timings are intentionally omitted.
|
||||
|
||||
## Current capture path
|
||||
|
||||
| Phase | Warm release time | Can run ahead? | Required change |
|
||||
|---|---:|---|---|
|
||||
| Read current simulator objects and discover assets | 8 ms | Yes | Apply incoming object/avatar/terrain events to a persistent world instead of scanning the simulator maps per capture. |
|
||||
| Fetch cached/missing assets | 151 ms | Yes | Bounded asset workers continuously fetch visible dirty assets. Raw immutable assets remain in the 2 GiB disk LRU. |
|
||||
| Decode textures and derive scene geometry | 176 ms warm; 8.03 s on the first scene | Yes | Retain decoded visible textures and derived meshes in memory. Add a versioned persistent derived-mesh cache only for the measured 8 s cold-start cost; decoded RGBA textures should normally remain memory-only because they expand substantially. |
|
||||
| Collect and sort renderables | 1 ms | Yes | Persistent renderer entities remove this per-frame list build. |
|
||||
| Validate scene | 1 ms | Yes | Validate assets and changes when admitted, not every frame. |
|
||||
| Copy render command | 21 ms | Yes | Send stable IDs and dirty updates; do not clone the complete scene for a frame. |
|
||||
| Re-identify cached textures by content hash | 30 ms | Yes | Key immutable texture resources by grid asset UUID. |
|
||||
| Re-identify cached meshes by content hash | 14 ms | Yes | Key derived meshes by stable object/asset signature and LOD. |
|
||||
| Recreate frame entities | <1 ms | Yes | Keep Bevy entities and material handles alive; update only dirty components. |
|
||||
| Render plus synchronous readback | 46 ms | Partly | Render continuously into a persistent target. A GUI presents that target directly; readback happens only for CPU consumers. |
|
||||
| JPEG encoding | 41 ms | Yes, and not part of viewport FPS | A separate latest-frame worker encodes only when an LLM/image consumer asks for it. |
|
||||
|
||||
The existing end-to-end warm capture takes 525 ms. A repeated renderer call
|
||||
alone takes 113 ms (8.8 FPS), including 67 ms of avoidable copying, hashing,
|
||||
and cache synchronization. Its measured render/readback segment is 46 ms
|
||||
(21.9 FPS equivalent). A persistent GPU scene therefore makes the initial
|
||||
10 FPS viewport target realistic on the measured hardware.
|
||||
|
||||
The 46 ms value still includes synchronous CPU readback. It is not a pure GPU
|
||||
timestamp. Continuous viewport rendering without readback should be faster and
|
||||
must be measured separately once the persistent render target exists.
|
||||
|
||||
## Startup
|
||||
|
||||
The optimized cold-start profile was:
|
||||
|
||||
| Phase | Time | Scheduling |
|
||||
|---|---:|---|
|
||||
| Client owner initialization | 2 ms | Startup thread |
|
||||
| Grid login | 3.37 s | Parallel with renderer initialization |
|
||||
| wgpu/Bevy initialization | 452 ms | Parallel with grid login; completed before agent readiness |
|
||||
| Test scene convergence window | 30.04 s | World events continue asynchronously; this is not a system-readiness gate. |
|
||||
| First full visible-scene build from persistent raw assets | 8.35 s | Background asset/scene workers; publish partial complete frames as content converges. |
|
||||
|
||||
System readiness must require configuration, renderer initialization (or an
|
||||
explicitly reported fallback), grid login, region dimensions, and the running
|
||||
world/update loops. It must not wait until every user-created asset inside the
|
||||
view radius has decoded: that would turn missing or slow grid assets into a
|
||||
permanent login stall. Visual readiness is a separate completeness signal.
|
||||
|
||||
## Game loop
|
||||
|
||||
The agent needs four independent paths:
|
||||
|
||||
1. Grid callbacks enqueue compact object, avatar, terrain, region, and camera
|
||||
changes immediately. They never perform asset I/O or rendering.
|
||||
2. A fixed update loop drains those events, updates the authoritative CPU world,
|
||||
recalculates 64 m visibility, and emits stable dirty IDs. Grid event handling
|
||||
must remain faster than the render tick.
|
||||
3. Bounded asset workers fetch, decode, and derive only dirty visible content.
|
||||
Completed resources update the CPU world and enqueue GPU changes.
|
||||
4. A render loop applies GPU changes and updates a persistent viewport at an
|
||||
initial 10 FPS target. It retains the latest complete frame. GUI presentation
|
||||
uses the GPU target directly; snapshot readback, resize, JPEG encoding, and
|
||||
LLM upload are independent latest-frame consumers.
|
||||
|
||||
Simulation/update and rendering use separate clocks. Slow asset downloads,
|
||||
JPEG encoding, LLM requests, and subscribers must never hold either loop.
|
||||
|
||||
### Frame publication and readback
|
||||
|
||||
The persistent viewport renders into a GPU texture. A GUI presents it without
|
||||
CPU readback. When a CPU image is requested, the render graph schedules a copy
|
||||
after that frame's render commands into the next free staging buffer. GPU queue
|
||||
ordering makes the copied frame coherent even when the render target is reused
|
||||
for the following frame.
|
||||
|
||||
Use a ring of three staging buffers. Mapping and CPU consumption happen
|
||||
asynchronously; a buffer is reused only after its completion signal. If all
|
||||
three are busy, skip that readback rather than stall rendering. Publish only the
|
||||
latest completed immutable CPU frame, tagged with frame sequence, camera pose,
|
||||
and observation time. JPEG and LLM workers use that published frame and discard
|
||||
superseded work. The current synchronous Bevy screenshot plus `device.poll(Wait)`
|
||||
path is only a diagnostic bridge and must not remain in the game loop.
|
||||
|
||||
## Instrumentation
|
||||
|
||||
Hot paths record only monotonic start/end timestamps and emit fixed-size,
|
||||
fixed-cardinality timing signals with `try_send`. A bounded receiver owned by a
|
||||
dedicated profiling worker aggregates counts, totals, maxima, and percentile
|
||||
histograms and publishes observability records. A full queue drops profiling
|
||||
signals and increments a drop counter; it never backpressures grid or rendering.
|
||||
Formatting, serialization, journal I/O, and subscriber delivery remain outside
|
||||
the measured thread.
|
||||
|
||||
Required fixed phases are startup/config, client creation, renderer creation,
|
||||
login, grid readiness, event ingest, update tick, visibility, asset disk read,
|
||||
asset network fetch, texture decode, geometry derive, GPU change application,
|
||||
render submission, GPU completion, optional readback, and optional JPEG encode.
|
||||
Reference in New Issue
Block a user