101 lines
6.3 KiB
Markdown
101 lines
6.3 KiB
Markdown
# 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.
|