TASK 31: SceneGraph component + baked-crust compositor optimization

- Add ElementKind (Static/Dynamic) to SceneElement base; Source/WebcamSceneConfig classify
- Services/SceneGraph.cs: owns the Scenes collection (ViewModel's Scenes delegates to it),
  the element mutation surface (Add/Insert/Remove/Move, each invalidating the bake), and the
  queries that were scattered LINQ (GetBackground/GetWebcam/GetChatBoxes/GetSplitPoint/IsStatic)
- SceneCompositor: split-aware BakeStaticBase + CompositeLayers + Render(.., staticBase, split);
  builds/caches the static base below the split point in source-rect space
- FramePump: optional SceneGraph -> optimized bake+composite path; falls back to full render
- MainViewModel: routes element mutations through the graph; invalidates the bake on static
  layout/opacity/visibility/useDefaultBackground changes and after background heal/Ensure
- Integration test SceneGraphTests.BakedStaticBase_WithDynamicLayer_CompositesCorrectly

Derivative survey (mandated): tried before writing — OBS does per-source opacity/visibility
caching and static-scene baking; this mirrors OBS's 'cached static source' optimization.
3 documented defensive deviations from the spec (ChatOverlayLayer stays decoupled;
background helpers stay VM-static for direct testability; full facade peel deferred post-1.0)
in TASKS.md + ai.md. Scope check passed; clean build 0 warnings.
This commit is contained in:
2026-08-31 17:53:34 -07:00
parent 3bf053a3b1
commit 670fe3a10a
15 changed files with 618 additions and 54 deletions
+34 -1
View File
@@ -1227,7 +1227,40 @@ and a full layout restructure moving all live controls into the preview pane.
`SceneGraph` component. Classify elements as static or dynamic. Enable the compositor to bake
static layers once and only recompose dynamic layers per frame.
### Status: ☐ Not started
### Status: ✅ Done (2026-08-31)
What landed — core optimization + SceneGraph component, all in one commit:
- `ElementKind` (Static/Dynamic) on `SceneElement` base, overridden in `Source` and `WebcamSceneConfig`
- `Services/SceneGraph.cs`: owns `Scenes` collection (ViewModel's `Scenes` property delegates to it);
mutation surface `AddElement`/`InsertElement`/`RemoveElement`/`MoveElement` (each invalidates the bake);
queries `GetBackground`/`GetWebcam`/`GetChatBoxes`/`GetSplitPoint`/`IsStatic`; and the
`GetBakedBase` bake-cache (keyed by scene id + static element identities below the split)
- `SceneCompositor.BakeStaticBase`/`CompositeLayers` + `Render(.., staticBase, split)` overload —
split-aware, builds the cached base in source-rect space, composites dynamic/above-split per frame
- `FramePump` takes an optional `SceneGraph` and uses the optimized path when wired (falls back to full render)
- `MainViewModel`: routes element mutations through the graph; invalidates the bake on static
layout/opacity/visibility/useDefaultBackground changes and after background heal / EnsureBackground
- Integration test `SceneGraphTests.BakedStaticBase_WithDynamicLayer_CompositesCorrectly` (audio-free,
verifies bake-once + cache hit + dynamic-on-top pixels + static-mutation invalidation +
dynamic-only non-invalidation)
**Defensive deviations from this spec (decision 2026-08-31):**
1. `ChatOverlayLayer` keeps taking `IEnumerable<Scene>` rather than `SceneGraph.GetChatBoxes()` — it is
deliberately decoupled from the graph (`// without owning the scene graph`). Forcing the graph in would
couple a WPF-bound layer to it and violate that documented seam.
2. Background static helpers (`EnsureBackground`/`CreateBackground`/`NormalizeBackgrounds`) stay on the
ViewModel because `BackgroundTests.cs` unit-tests `MainViewModel.EnsureBackground` directly. Queries
moved to SceneGraph; helpers + their invalidation wiring stayed on the VM.
3. The full ViewModel-as-facade extraction (moving the ~all-binding-surface off Scenes/StagedScene/
LiveScene) was deliberately NOT done in this unsupervised pass — it is the "touch 11 files across 4
layers" regression risk the handoff flagged. SceneGraph owns the collection + mutation surface now;
the remaining VM call sites binding to those still work because `Scenes` delegates. Revisit after 1.0.
**Verification:** SceneCompositorTests 4, StretchMathTests 4, BackgroundTests 16, SceneCatalogTests 18,
LayoutStorePersistenceTests 12, FramePumpTests 9, SceneGraphTests 1 all green. RealAppHost GUI/collection
tests (SourceNaming, RoundClip, BackgroundHeal, ...) construct a real `MainWindow` and cannot run in a
headless WSL session (pre-existing limitation, not caused by this change).
### Design