prometeu-runtime/discussion/lessons/DSC-0040-vm-render-parallel-execution-boundary/LSN-0048-render-workers-need-a-closed-packet-contract.md

6.3 KiB

id ticket title created tags
LSN-0048 vm-render-parallel-execution-boundary Render Workers Need a Closed Packet Contract 2026-06-06
runtime
renderer
vm
concurrency
handoff
telemetry

Context

DSC-0040 took the runtime from a local "close and immediately render" model toward an architecture that can later support a real render worker, separate thread, separate core, or host-provided renderer.

The important result was not a real worker thread. The result was the contract a real worker must obey: the VM/logical side produces closed render submissions; the render side consumes those submissions without relying on live mutable VM, Hardware, Gfx, or FrameComposer state.

This work built on DSC-0038, where typed render submissions became the domain boundary. DSC-0040 made that boundary operational by adding ownership, pacing, handoff, AppMode policy, shutdown semantics, and telemetry.

Key Decisions

Game Frames Are Closed Submissions

What: RenderSubmissionPacket::Game2D is the render consumer's complete frame description. The consumer must compose scene/layers/sprites first and then apply gfx2d primitives as the final overlay.

Why: A worker cannot safely reconstruct a frame from live runtime state. If an active-scene path bypasses the packet and calls a live FrameComposer path, the packet is no longer a true handoff boundary.

Trade-offs: Keeping packets small means heavy resources are referenced by stable IDs rather than copied per frame. That requires disciplined resource lifetime and read-only access APIs.

Handoff Is Single-Slot Latest-Wins

What: The producer writes one pending submission. A newer submission replaces an older unconsumed one, and the replacement is telemetry.

Why: Render queues should not accumulate stale game frames. Latest-wins bounds memory, latency, and stale presentation risk.

Trade-offs: The system does not guarantee that every produced frame is consumed or presented. VM semantics must not depend on present acknowledgement.

Frame Pacing Is Not Render ACK

What: Game logical frames are paced by a frame scheduler, not by render completion. FRAME_SYNC remains the logical frame boundary.

Why: Letting the worker decide when the VM may advance mixes display/raster completion with logical simulation authority. The VM should not run freely, but it also should not wait for a specific render ACK.

Trade-offs: A delayed render consumer repeats the last valid frame, while logical frames remain sequential. Overruns are diagnostics and telemetry, not hidden catch-up simulation.

AppMode Owns Render Policy

What: Game workloads are frame-paced and worker-capable with local fallback. Shell/System UI remains lifecycle-driven and local/synchronous by default.

Why: A Shell app has a different lifecycle from a game loop. Treating Shell as just another game renderer would force unnecessary frame pacing and worker semantics into UI flows.

Trade-offs: The packet boundary is shared, but execution policy differs by AppMode. Future Shell worker support should be a separate decision, not an accidental inheritance from Game.

Epoch Guards Presentation

What: Submissions carry render ownership. Foreground visual-owner changes update the active render epoch, and stale submissions are discarded before present.

Why: A worker may finish old work after the visible owner has changed. Without an epoch check, old Game, Shell, splash, or crash frames can be presented after they are no longer valid.

Trade-offs: Epoch protects correctness, but it does not solve foreground stack policy. Game pause/resume and VM-backed Shell coexistence belong to lifecycle/SystemOS discussions.

Patterns and Algorithms

The durable pattern is:

logical core / VM
  -> close typed RenderSubmission
  -> publish to single pending slot
  -> render consumer takes owned submission
  -> validate ownership/epoch
  -> rasterize from packet + read-only resources
  -> present or repeat last valid frame
  -> report telemetry

The render manager is a coordinator, not a renderer. It owns policy, frame IDs, handoff, ownership, shutdown state, and telemetry. Concrete raster/present capability belongs to a render surface, backend, host, HAL, or future worker implementation.

The current LocalRenderWorker is intentionally a prototype. It proves the same policy path can select a worker-capable consumer, but it does not prove thread ownership, bounded join, stop token behavior for in-flight work, or a real 60Hz present loop.

Pitfalls

Treating overlay commands as background setup

Once gfx2d is a final overlay, gfx2d.clear in a scene-backed game frame clears over the composed scene and sprites. That is a cartridge/framework discipline issue: scene-backed games should not use final overlay clear as world background initialization.

Consulting live composer state during packet consumption

The active-scene consumer must not decide from live FrameComposer state whether a packet has a scene. It must read packet.composer.bound_scene. Otherwise the worker boundary silently depends on mutable state that may already belong to a different logical frame.

Counting telemetry without behavior

Counters such as repeated presents and shutdown discards are useful only if later worker work connects them to real display cadence and real in-flight cancellation. The local implementation establishes the surface; a real worker still needs behavior behind it.

Assuming read-only banks solve viewport cache

Read-only glyph and scene bank access is only part of the worker boundary. The viewport cache and resolver path still need a concrete read-only or ownership model before a real threaded worker can rasterize without live FrameComposer.

Takeaways

  • A render worker is first a contract problem, then a threading problem.
  • Closed packets must be complete enough to render without live VM/runtime state.
  • Latest-wins handoff keeps render latency bounded and makes dropped frames observable.
  • Game pacing belongs to the frame scheduler; render completion is telemetry, not VM semantics.
  • Shell UI can share the packet concept without inheriting the Game frame loop.
  • Epoch checks are the guardrail that prevents obsolete visual owners from presenting stale work.
  • The real worker should be introduced as a separate discussion and plan, using this contract rather than reopening it.