Phase 4 complete
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m39s
CI / Windows Release Package (push) Successful in 2m45s

This commit is contained in:
Aiden
2026-05-11 18:39:02 +10:00
parent f141d20026
commit 761df3b2d0
5 changed files with 68 additions and 97 deletions

View File

@@ -8,7 +8,7 @@ Phase checklist:
- [x] Introduce an internal event model
- [x] Split `RuntimeHost`
- [x] Finish live-state and service-facing coordination
- [ ] Make the render thread the sole GL owner
- [x] Make the render thread the sole GL owner
- [ ] Refactor live state layering into an explicit composition model
- [ ] Move persistence onto a background snapshot writer
- [ ] Make DeckLink/backend lifecycle explicit with a state machine
@@ -18,8 +18,9 @@ Checklist note:
- The checked Phase 1 item means the subsystem vocabulary, dependency direction, state categories, design package, and runtime implementation foothold are in place.
- The checked Phase 2 item means the internal event model substrate is complete enough for later phases: the typed event vocabulary, app-owned dispatcher, coalesced event pump, reload bridge events, production bridges, and pure event tests are in place. Remaining items in [PHASE_2_INTERNAL_EVENT_MODEL_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_2_INTERNAL_EVENT_MODEL_DESIGN.md) are narrow follow-ups, mainly completion/failure observations and later replacement of the runtime-store poll fallback with real file-watch events.
- The checked Phase 3 item means the render-facing state path now has named live-state, composition, frame-state, resolver, and service-bridge boundaries. `OpenGLComposite::renderEffect()` is reduced to runtime work, frame input construction, and frame rendering. This prepares Phase 4 but does not yet move GL work onto a dedicated render thread.
- It does not mean the whole app is fully extracted. Sole-owner render threading, deeper live-state layering, background persistence, backend lifecycle, and richer telemetry continue through later phases.
- The checked Phase 3 item means the render-facing state path now has named live-state, composition, frame-state, resolver, and service-bridge boundaries. `OpenGLComposite::renderEffect()` is reduced to runtime work, frame input construction, and frame rendering.
- The checked Phase 4 item means normal runtime GL work is now owned by a dedicated `RenderEngine` render thread. Input upload, output render, preview, screenshot capture, render-local resets, and shader application enter through render-thread queue/request paths instead of caller-thread context borrowing. The remaining output timing risk is callback-coupled synchronous output production, which is intentionally tracked for the later DeckLink/backend lifecycle and playout-queue work.
- It does not mean the whole app is fully extracted. Deeper live-state layering, background persistence, backend lifecycle/playout queue policy, and richer telemetry continue through later phases.
## Timing Review
@@ -28,7 +29,7 @@ The recent OSC work removed several control-path stalls, but the app still has a
- output playout is still effectively render-on-demand from the DeckLink completion callback
- output buffering and preroll are now larger, but the buffering model is still static and only loosely related to actual render cost
- GPU readback is partly asynchronous, but the fallback path still returns to synchronous readback on any miss
- preview presentation is still tied to the playout render path
- preview presentation is best-effort and render-thread queued, but still shares the same render-thread budget as playout
- background service timing is partially event-driven; runtime-store scanning still uses a bounded compatibility poll fallback
Those points are important because they affect not just average performance, but how the app behaves under brief spikes, device jitter, or load bursts.
@@ -58,23 +59,23 @@ Recommended direction:
- separate status/telemetry updates from control mutation paths
- make render consume snapshots rather than sharing a large mutable authority object
### 2. OpenGL ownership is still centralized behind one shared lock
### 2. OpenGL ownership has moved to the render thread
Even after recent timing improvements, preview, input upload, and playout rendering still rely on one shared GL context protected by one `CRITICAL_SECTION`.
Phase 4 removed normal runtime dependence on the old shared GL `CRITICAL_SECTION`. `RenderEngine` now owns a dedicated render thread and binds the GL context there for normal input upload, output rendering, preview presentation, screenshot capture, shader application, and render-local reset work.
Relevant code:
- [OpenGLComposite.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.h:93)
- [OpenGLComposite.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp:253)
- [OpenGLVideoIOBridge.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp:70)
- [RenderEngine.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/RenderEngine.cpp:36)
- [OpenGLVideoIOBridge.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/pipeline/OpenGLVideoIOBridge.cpp:11)
- [OpenGLComposite.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp:168)
This is still a central choke point and limits timing isolation.
This removes cross-thread GL context borrowing as the central correctness model. The remaining timing risk is that output frame production is still synchronous from the DeckLink completion path, so a render/readback spike can still reduce playout headroom.
Recommended direction:
- use one dedicated render thread as the sole GL owner
- have input/output/control threads queue work instead of performing GL work directly
- remove ad hoc GL use from callback threads
- keep the render thread as the sole GL owner
- replace synchronous output request/response with a bounded producer/consumer playout queue
- keep preview and screenshot subordinate to output deadline pressure
### 3. Control flow is spread across polling and shared-memory patterns
@@ -179,7 +180,7 @@ Relevant timing code:
Why this matters:
- the output completion path currently requests a scheduled render through `OpenGLVideoIOBridge::RenderScheduledFrame()`, which still takes the shared GL path, renders, reads back, and schedules the next frame in one callback-driven flow.
- the output completion path currently requests a scheduled render through `OpenGLVideoIOBridge::RenderScheduledFrame()`, which asks the render thread to render/read back synchronously and then schedules the next frame in one callback-driven flow.
- `VideoPlayoutScheduler::AccountForCompletionResult()` currently reacts to both late and dropped frames by blindly advancing the schedule index by `2`, which is simple but not especially robust.
- `kPrerollFrameCount` is now `12`, but `DeckLinkSession::ConfigureOutput()` still creates a fixed pool of `10` mutable output frames. That mismatch suggests the buffering model is not being sized from one coherent source of truth.
@@ -203,8 +204,8 @@ That means the completion callback is currently responsible for:
- frame pacing accounting
- acquiring the next output buffer
- taking the GL critical section
- rendering the composite
- requesting render-thread output production
- waiting for render/readback completion
- performing output readback
- scheduling the next frame
@@ -286,7 +287,7 @@ Add lightweight tracing for:
- input callback latency
- input upload skip count
- GL lock wait time
- render-thread request latency
- render queue depth
- render time
- pass build/compile latency
@@ -502,6 +503,11 @@ Dedicated design note:
- [PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/PHASE_4_RENDER_THREAD_OWNERSHIP_DESIGN.md)
Status:
- complete for GL ownership
- remaining playout-headroom work is tracked under Phase 7/backend lifecycle
Target behavior:
- one thread owns the GL context
@@ -610,7 +616,6 @@ This phase should happen after the main ownership changes so the telemetry can r
Recommended coverage:
- render queue depth
- GL lock wait time, if any shared lock remains
- input callback latency
- input upload skip count
- output scheduling lag
@@ -662,7 +667,7 @@ This order tries to avoid doing foundational work twice.
## Short Version
The app is in a much better place than it was before the OSC timing work, but the main remaining architectural risk is still shared ownership around the shared GL path. The most sensible path forward is:
The app is in a much better place than it was before the OSC timing work. The shared-GL ownership risk has now been addressed by Phase 4; the main remaining live-resilience risk is output playout headroom because DeckLink callbacks still synchronously request render-thread output production. The most sensible path forward is:
1. define boundaries
2. establish an event model