Files
MetaCrate/docs/renderer-performance.md
Chili Palmer 42f0c62b6b
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled
Gate readiness on stable viewport frames
2026-08-23 13:48:06 +02:00

119 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent runtime 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 rendered scene had
211 objects, 427,253 triangles, and five unavailable grid textures. All timing
numbers below are optimized release builds.
## Runtime ownership
- `metacrate-grid-world` owns native OpenSim/Second Life event subscriptions,
distance filtering, attachment world transforms, terse movement application,
event-drop accounting, and immutable published world snapshots.
- `metacrate-game-loop` owns the fixed update schedule, renderer lifetime,
persistent GPU scene, fixed-rate viewport loop, completed-frame buffers, and
timing signals. Readers receive an immutable `Arc<[u8]>`; they never read a
render target while Bevy writes it.
- `metacrate-rendering-wgpu` owns Bevy/wgpu resources and supports separate
scene loading and camera/frame rendering. Shared immutable scene arrays cross
the renderer thread without copying their contents.
- `metacrate-grid-agent` supplies policy, conversation, tools, and lifecycle
triggers. It consumes published world/viewport state instead of synchronizing
render buffers or individual native event channels.
Grid callbacks never perform asset I/O or render work. The world loop drains a
bounded 4,096-event queue in batches, coalesces dirty IDs, and publishes only
complete snapshots. Object, avatar, attachment, and terrain admission uses the
configured view distance; 64 m is the default. The live reference contained
280 visible prims, one avatar, and 51 nearby terrain patches, with zero dropped
events.
## Readiness
Transport connection is not agent readiness. A generation remains degraded and
chat, tools, movement, and autonomous behavior remain fenced while the terminal
reports:
```text
INITIALIZING generation=N waiting_for=world_state,viewport target_fps=10
```
The framework gate opens only after the region-scoped world state exists, the
first scene/assets have converged, and at least two complete viewport frames
have been published after the last missed deadline, within the frame budget,
and without render errors. One-time warm-up misses are reported separately and
do not contaminate the post-`READY` stability window. The terminal then emits
`INITIALIZED ...` followed by one unambiguous `READY ...` line when the session
and agent services are released. The default readiness allowance is two
minutes; the measured scene convergence window was about 30.05 seconds.
## Measured steady state
| Phase | Measured time |
|---|---:|
| Bevy/wgpu initialization | 476 ms |
| First persistent GPU scene load and frame | 1.02 s |
| Cached complete scene refresh | 86 ms |
| Stable warm render plus coherent CPU readback | 3136 ms |
| JPEG encoding on demand | 37 ms |
| Repeated capture using the already published viewport, including JPEG | 45 ms |
The viewport uses a 90 ms scheduling interval, providing margin above the
minimum 10 FPS requirement. The live two-second soak completed 22 frames at
10.99 FPS with zero missed deadlines and zero render errors. JPEG encoding and
LLM transfer are not in this loop.
A cached full-scene refresh completed in 86 ms, inside the 90 ms interval:
validation 1.08 ms, shared command handoff 0.001 ms, texture cache sync 30.47
ms, mesh cache sync 15.21 ms, scene setup 0.47 ms, and render/readback 35.70 ms.
Unchanged frames skip validation, command transfer, texture sync, and mesh sync.
## Startup work
| Phase | Reference time | Scheduling |
|---|---:|---|
| Client owner initialization | 2 ms | Startup thread |
| Grid login | 3.37 s | Parallel with renderer initialization |
| Bevy/wgpu initialization | 0.49 s | Completed before agent readiness |
| Initial world/asset convergence | 30.05 s | Asynchronous Grid and asset workers |
| First persistent GPU scene and frame | 1.02 s | Before the readiness gate opens |
The slower first start is intentional: the agent does not claim readiness until
it can provide stable world and visual state. Raw immutable assets persist in
the configured 2 GiB LRU under the platform cache directory
(`$HOME/.cache/metacrate` on Linux), so later starts reuse them.
## Frame publication and future GUI path
The current agent path continuously renders and publishes the latest complete
RGBA frame. A requested snapshot reads that immutable frame and performs JPEG
encoding independently. Camera or scene submissions replace stale pending
input; consumers never block the viewport loop.
The current Bevy screenshot API still performs synchronous CPU readback, which
accounts for most of the 3136 ms warm frame cost. This meets the current
headless-agent target, but a GUI viewer should present the GPU target directly.
For higher-rate CPU capture, use a ring of three staging buffers: render, enqueue
GPU copy, map asynchronously, and publish only after completion. If all staging
buffers are occupied, skip that readback instead of stalling rendering.
## Instrumentation and observed limits
Hot paths record monotonic timestamps and fixed-cardinality counters only.
World event drops, completed frames, missed deadlines, render errors, and last
render duration are available without formatting or I/O on the loop threads.
Observed limiting factors:
- Initial scene/asset convergence, about 30 seconds, dominates readiness.
- First GPU admission is about one second; it occurs before `READY`.
- A cached full-scene refresh is 86 ms and is currently the closest operation to
the 90 ms frame budget. Incremental per-entity GPU updates are the next useful
optimization if denser or highly animated regions exceed that budget.
- Synchronous readback costs roughly 3136 ms per frame. A direct GUI path or
asynchronous staging ring removes it from presentation.
- Five referenced textures were unavailable from the test grid. Missing assets
do not deadlock readiness; completeness remains explicit.
- The successful live run used 280 visible prims, one avatar, 51 terrain
patches, 427,253 render triangles, 4,096 event slots, and dropped zero events.