Files
MetaCrate/docs/renderer-performance.md
Chili Palmer 33d1cf6827
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled
Centralize world and viewport game loops
2026-08-23 13:42:01 +02:00

5.8 KiB
Raw Blame History

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:

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 without render errors. 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.