#206

Follow-up: compilation orchestration for SceneView

Openmatiasngfopened 3mo ago

Status

Blocked by the Phase 2 MVP in #SUB4. This issue freezes the future compilation/orchestration contract; implementation must not invent per-node policies.

Reserved API

type PendingPipelines = "skip" | "sync";
interface SceneView {
  // constructor default, render override; precedence render > view > "skip"
  render(camera: SceneCamera, o?: { frame?: Frame; pendingPipelines?: PendingPipelines }): void;
  compile(node?: SceneNode): Promise<void>; // missing fingerprints + geometry uploads
  readonly stats: { pendingPipelines:number; syncCompiles:number; /* MVP stats */ };
}

skip uses createRenderPipelineAsync; a node draws only when its draw/pipeline/uploads are ready. sync blocks deterministically (tests/mocks). compile(node?) is incremental: no-op for known fingerprints, and with a node compiles only that subtree including uploads.

Atomic draw semantics

Skip is atomic per draw, never per subtree. Parent ready + child pending still draws parent; child ready + parent pending still draws child because transforms are CPU. visible=false remains the separate subtree-pruning mechanism. There is no per-node pending policy.

// streaming while loop continues: pending children are skipped individually
const root = scene(); const ship = mesh(box(), mat); root.add(ship);
const view = createSceneView(gpu, root, { target, pendingPipelines: "skip" });
frameLoop(gpu, f => view.render(camera, { frame: f }));
// later: root.add(mesh(importedMesh, newMaterial)); // ready siblings keep drawing
// child introduces a new material under ready parent
parent.add(mesh(box(), brandNewMaterial));
view.render(camera, { pendingPipelines: "skip" }); // parent draw is present; only child may skip
// reveal a composed model atomically
node.visible = false; root.add(node);
await view.compile(node); // shaders + geometry uploads for subtree
node.visible = true; // first visible frame has all draws ready
// transition between views
const next = createSceneView(gpu, nextRoot, { target, pendingPipelines: "sync" });
await next.compile();
active = next; // swap only after complete compile
// post-processing shader swap
const nextFx = effect(gpu, nextShader);
await nextFx.compile(target);
frame(gpu, f => f.pass(target, nextFx));

Stats and errors

stats.pendingPipelines reports currently skipped draws; stats.syncCompiles counts blocking compiles for jank diagnostics. Compile deduplicates shared fingerprints: two material objects with same shader/layout/state compile once, while bindings remain distinct. Opacity crossing 1 re-keys and may enqueue a new pipeline. Dispose/cancel during compile or frame must settle safely; post-dispose calls throw VGPU-SCENE-VIEW-DISPOSED.

const a = shaderMaterial({ source, opacity: 1 });
const b = shaderMaterial({ source, opacity: 1 }); // same fingerprint
await view.compile(); // one pipeline compile, two binding records
b.set({ opacity: .5 }); // transparent re-key; compile only new fingerprint

Acceptance

Add deterministic mock tests for skip/sync precedence (render > view > skip), per-draw readiness, incremental compile/uploads, stats, shared fingerprints, reveal atomicity, streaming, transition swap, and cancellation. Keep view.reveal(node) sugar explicitly Phase 3.

Activity

1
matiasngf3mo ago

Note: this issue's target surface has moved to the new renderTree/sceneView vocabulary (no more createSceneView/SceneView) following the Scene API redesign in #219. The reserved orchestration contract (pendingPipelines, view.compile(), view.stats, atomic-per-draw skip) still applies, now layered on top of renderTree()'s kernel-owned retained state instead of a constructed view object. See #219 (parent) and #215 (renderTree sub-issue) for full context.