Frame Life Cycle - limztudio/NWBLot GitHub Wiki
Frame Life Cycle
This page explains what happens when an NWBLot project starts and renders frames.
Short Version
loader startup -> project startup -> ECS update -> renderer prepare -> graphics submit -> present
A frame is one iteration of engine work. It processes platform/input state, updates project systems, prepares render work, submits graphics commands, presents the backbuffer, and records diagnostics.
Startup Before The First Frame
Before the first frame, the loader creates the common runtime environment.
The startup path is:
- install crash capture
- parse runtime options
- initialize logging
- ask project code for window title and frame size
- create
Core::Frame - call
ConfigureProjectRuntime, then apply loader graphics options - initialize frame, window, graphics, input, CPU/GPU task schedulers, and telemetry
- resolve the runtime resource directory
- mount the cooked
graphicsvolume throughIFilesystem - register asset codecs
- load shader archive records
- build
ProjectRuntimeContext - create project callbacks
- call project startup
- show the frame
- enter the frame loop
Crash capture is installed early so failures during startup can still produce a useful report.
Project Startup
Project startup is where the engine becomes a specific application.
The loader calls into project-provided callbacks. In the current repository, Testbed implements those callbacks in CoolStuff/Testbed/entry.cpp and CoolStuff/Testbed/runtime.cpp.
Testbed creates an ECS world, registers engine systems, sets up renderer integration, creates startup scene entities, and installs runtime UI/control behavior.
The project does not create its own graphics backend or asset manager. It receives those services through ProjectRuntimeContext.
One Frame In Simple Terms
Once the frame loop is running, each frame looks like this:
- Update platform state.
- Update input state.
- Pump main-thread CPU tasks, then call the project update callback (the project ticks its own ECS world here).
- Let registered render passes prepare resources.
- Record and submit graphics commands.
- Present the swapchain backbuffer.
- Collect timing, telemetry, and frame graph data.
If the project requests quit or an unrecoverable step fails, the frame loop exits through the engine shutdown path.
The startup path feeds the frame loop, and the loop repeats until quit or failure sends execution into ordered shutdown:
flowchart TD
Startup["Loader startup"]
ProjectStartup["Project startup"]
Platform["Platform and input update"]
ProjectUpdate["Project update callback"]
WorldTick["Project-owned ECS world tick"]
Prepare["Render pass prepare"]
Render["Render pass command recording"]
Submit["Graphics submit"]
Present["Present"]
Diagnostics["Telemetry and frame graph"]
Quit{"Quit requested or fatal failure?"}
Shutdown["Ordered shutdown"]
Startup --> ProjectStartup --> Platform --> ProjectUpdate --> WorldTick --> Prepare --> Render --> Submit --> Present --> Diagnostics --> Quit
Quit -->|no| Platform
Quit -->|yes| Shutdown
ECS Update
The ECS world owns entities, components, systems, and messages. The frame does not tick the world directly; the project ticks the world it owns from inside its update callback. Testbed does this in onUpdate.
During update, systems inspect components and perform work. Some systems update simulation-like state. Other systems prepare renderer-facing state from components.
Examples:
- scene transform systems maintain world-space transforms
- model systems expand model assets into renderable runtime state
- skinning systems update skeleton pose and GPU skinning data
- renderer systems gather renderable state and material/mesh resources
- UI systems update runtime controls
The point of ECS update is to make current scene state explicit before render work is prepared.
Renderer Validation And Prepare
Before per-frame preparation, render passes validate resources for the current framebuffer size and sample count. Validation creates or reuses command lists, targets, buffers, push-constant layouts, global-heap handles, and pipelines. Preparation then consumes the validated resources and builds frame-local state.
Renderer preparation also gathers scene data:
- camera and light state
- material surfaces
- mesh and model draw data
- skinned mesh data
- CSG receiver and cutter state
- transparent object state
- shadow and ray tracing resources
This phase bridges ECS state and GPU execution.
Graphics Submit And Present
After preparation, the renderer declares one bounded GpuTaskGraph for setup, G-buffer/normalization, shadows, caustics, surfel GI, AVBOIT, deferred lighting, composite, presentation, and any required history/readback/recovery tails. The compiler reads one coherent declaration revision. The first packet recording claim freezes that declaration until every packet in the attempt is accepted or rejected. Native packet recording publishes an immutable command list only after close and resource retention, and submission consumes that exact publication.
Queue choice is capability-based. A host without a dedicated Compute or Transfer queue can run compatible tasks on Graphics; a host with dedicated queues can overlap independent declared work while the graph preserves dependency and ownership rules. Accepted packet callbacks publish temporal and CPU-visible state, while rejected work is discarded rather than treated as a completed handoff. If an observer throws after native acceptance, the transaction commits the accepted token before the exception continues and abandons the remaining attempt callback-free, preventing replay or ambiguous GPU state. See GPU Task Graph and Renderer Frame Flow for the scheduling and recovery contract.
The graphics backend handles Vulkan objects such as command buffers, barriers, pipelines, buffers, textures, acceleration structures, shader tables, swapchain images, and GPU timing queries.
Presentation displays the final backbuffer image.
Shutdown
Shutdown is ordered because systems reference each other.
On normal exit, the loader joins its project CPU task scope before and after calling onShutdown. Testbed destroys its world inside that callback: it removes frame graph contributors and render passes, requires a successful GraphicsRuntime::waitForIdle() join or terminal device loss, joins the world CPU task scope while clearing the world, and releases it. Loader-owned services then leave scope. Native worker exceptions remain terminal at their worker boundary; joining does not deliver deferred worker failures.
An unexpected exception follows a different terminal path. RAII guards skip new throwing callbacks, drain outstanding CPU scheduler captures without rethrowing a second failure, perform callback-free platform cleanup, and let the original exception reach the common application-entry boundary. The runtime does not catch an unexpected exception in the middle, convert it to a normal frame failure, and continue. See CPU Task Scheduler.
Where To Go Deeper
Read Runtime Boot for loader startup detail.
Read Project API for project callbacks and ProjectRuntimeContext.
Read Runtime and ECS for world/system/component detail.
Read CPU Task Scheduler for shared CPU execution and shutdown drain rules.
Read Renderer Frame Flow for the detailed rendering sequence.