Skip to content

RUNE-002: Architecture Proposed

Updated2026-08-23

Summary

Rune's current public architecture centers on Story and the managed engine API. Managed callers author scene state and layout, commit immutable render snapshots on the UI thread, then the native render thread resolves animation and replays those snapshots. Context submission, not a context-owned node tree, decides which committed root a surface renders.

Motivation

Traditional rendering stacks often pay per-frame UI-thread cost for animated size, transform, and visual state. Rune instead keeps layout and command authoring on state changes, publishes immutable snapshots, and leaves per-frame sampling plus replay on the render thread.

Design

Current Product Layers

  1. Story / managed callers - author scene state, layout, timing, and command recording against the managed API.
  2. Managed engine API (.NET) - RuneEngine, RuneSurface, RuneContext, and RuneNode expose explicit runtime, surface, context, and snapshot boundaries.
  3. Native render engine (C++) - owns commit publication, render-thread animation sampling, immutable replay data, whole-target clear, replay, and presentation bookkeeping.
  4. Backend (current audited backend: Direct2D) - owns window/offscreen surfaces, effects, text shaping integration, and final presentation.

Runtime, Surface, Context, and Node

  • RuneEngine owns the device, render thread, and clock source.
  • RuneSurface owns output size, backend surface state, resize/readback/export behavior, and at most one bound context.
  • RuneContext owns one submitted root snapshot for one surface plus render bookkeeping such as last rendered root and force-render.
  • RuneNode owns mutable authoring state on the UI thread and produces immutable snapshots through rune_node_commit().
Runtime modeTargetUse case
AutomaticWindowInteractive windowed app
AutomaticOffscreenRender-to-texture
ManualOffscreenTests, video, screenshots
ManualWindowStep debugging

Data Flow

text
Story / managed caller
  -> Build or update scene state
  -> Record commands into RuneNode authoring state
  -> node.Commit() produces immutable snapshot tree
  -> context.Submit(snapshot)
  -> Render thread: skip decision -> resolve -> clear full target -> replay root -> present

node.Commit() is pure data handoff. It publishes the next immutable snapshot tree for one node root; it does not resolve animations or perform presentation-time replay work.

Render Thread Responsibilities

For each frame, the render thread:

  1. atomically loads the latest submitted root;
  2. decides whether the context can be skipped;
  3. resolves animated and derived RuneValues at time t;
  4. clears the whole target when rendering proceeds;
  5. replays the complete committed root;
  6. stores successful frame bookkeeping and presents.

The checked-in Direct2D backend does not implement damage tracking or partial redraw. Correctness comes from whole-target clear plus full-root replay.

API Surface

c
RuneEngine* rune_engine_create(uint8_t device);
uint8_t rune_engine_device(RuneEngine* rt);
void rune_engine_set_frame_time(RuneEngine* rt, uint64_t time_ns);
RuneStatus rune_engine_start_loop(RuneEngine* rt);
RuneStatus rune_engine_render_frame(RuneEngine* rt, uint64_t time_ns);

RuneSurface* rune_surface_create_offscreen(RuneEngine* rt, uint32_t w, uint32_t h);
RuneStatus rune_surface_resize(RuneSurface* target, uint32_t w, uint32_t h);
RuneStatus rune_surface_read_pixels(RuneSurface* target, uint8_t* rgba, uint32_t w, uint32_t h);
RuneContext* rune_context_create(RuneEngine* rt, RuneSurface* target);
void rune_context_submit(RuneContext* ctx, RuneSnapshot* snapshot);
RuneNode* rune_node_create(RuneEngine* engine, RuneRenderCallback on_render, void* user_data);
RuneSnapshot* rune_node_commit(RuneNode* node);

Managed wrappers mirror this split with RuneEngine, Surface, RuneContext, RuneNode, and explicit Snapshot submission.

Performance

  • render-thread animation removes managed layout-per-frame work for animated values
  • immutable commit lets clean subtrees be reused across frames
  • shell snapshots let dirty-child updates avoid parent command-buffer rebuilds
  • manual runtime + offscreen surface enables deterministic golden-image tests with the same architecture

Alternatives Considered

  • Per-frame managed-tree rebuild / reconciliation: rejected because it pushes animation cost back onto the UI thread.
  • Single object combining runtime and target: rejected because window, offscreen, realtime, and manual modes need orthogonal composition.
  • Context-owned node trees: rejected because commit/submission separation gives explicit immutable roots that can be captured, reused, or redirected.
  • Current backend damage tracking: not part of the audited implementation baseline.

Dependencies

  • RUNE-001 Spec Standard

Test Strategy

  1. verify all runtime/target combinations can create and render a submitted snapshot tree
  2. use manual runtime + offscreen surface for deterministic golden-image tests
  3. validate state change flow: managed update -> layout/record -> node commit -> context submit -> render-thread replay
  4. confirm settled-root skip still renders one final frame at or after the effective settle time

Rune Project brings Rune Story authoring together with the Rune Engine rendering foundation.