# RenderCadenceCompositor This app is the modular version of the working DeckLink render-cadence probe. Its job is to prove the production-facing foundation before the current compositor's shader/runtime/control features are ported over. Before adding features here, read the guardrails in [Render Cadence Golden Rules](../../docs/RENDER_CADENCE_GOLDEN_RULES.md). ## Architecture ```text RenderThread owns a hidden OpenGL context renders simple BGRA8 motion at selected cadence queues async PBO readback publishes completed frames into SystemFrameExchange SystemFrameExchange owns Free / Rendering / Completed / Scheduled slots drops old completed unscheduled frames when render needs space protects scheduled frames until DeckLink completion DeckLinkOutputThread consumes completed system-memory frames schedules them into DeckLink up to target depth never renders ``` Startup warms up real rendered frames before DeckLink scheduled playback starts. ## Current Scope Included now: - output-only DeckLink - non-blocking startup when DeckLink output is unavailable - hidden render-thread-owned OpenGL context - simple smooth-motion renderer - BGRA8-only output - async PBO readback - latest-N system-memory frame exchange - rendered-frame warmup - background Slang compile of `shaders/happy-accident` - app-owned display/render layer model for shader build readiness - app-owned submission of a completed shader artifact - render-thread-only GL commit once the artifact is ready - manifest-driven stateless single-pass shader packages - HTTP shader list populated from supported stateless single-pass shader packages - default float, vec2, color, boolean, enum, and trigger parameters - small JSON writer for future HTTP/WebSocket payloads - JSON serialization for cadence telemetry snapshots - background logging with `log`, `warning`, and `error` levels - local HTTP control server matching the OpenAPI route surface - startup config provider for `config/runtime-host.json` - quiet telemetry health monitor - non-GL frame-exchange tests Intentionally not included yet: - DeckLink input - multipass shader rendering - temporal/history/feedback shader storage - texture/LUT asset upload - text-parameter rasterization - runtime state - OSC/API control - preview - screenshots - persistence Those features should be ported only after the cadence spine is stable. ## Build ```powershell cmake --build --preset build-debug --target RenderCadenceCompositor -- /m:1 ``` The executable is: ```text build\vs2022-x64-debug\Debug\RenderCadenceCompositor.exe ``` ## Run Run from VS Code with: ```text Debug RenderCadenceCompositor ``` Or from a terminal: ```powershell build\vs2022-x64-debug\Debug\RenderCadenceCompositor.exe ``` Press Enter to stop. To test a different compatible shader package: ```powershell build\vs2022-x64-debug\Debug\RenderCadenceCompositor.exe --shader solid-color ``` Use `--no-shader` to keep the simple motion fallback only. ## Startup Config On startup the app loads `config/runtime-host.json` through `AppConfigProvider`, then applies explicit CLI overrides. Currently consumed fields: - `serverPort` - `shaderLibrary` - `oscBindAddress` - `oscPort` - `oscSmoothing` - `inputVideoFormat` - `inputFrameRate` - `outputVideoFormat` - `outputFrameRate` - `autoReload` - `maxTemporalHistoryFrames` - `previewFps` - `enableExternalKeying` The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently. Supported CLI overrides: - `--shader ` - `--no-shader` - `--port ` ## Expected Telemetry Startup, shutdown, shader-build, and render-thread event messages are written through the app logger. Telemetry is intentionally separate and remains a compact once-per-second cadence line. The logger writes to the console, `OutputDebugStringA`, and `logs/render-cadence-compositor.log` by default. Render-thread log calls use the non-blocking path so diagnostics do not become cadence blockers. ## HTTP Control Server The app starts a local HTTP control server on `http://127.0.0.1:8080` by default, searching nearby ports if that one is busy. Current endpoints: - `GET /` and UI asset paths: serve the bundled control UI from `ui/dist` - `GET /api/state`: returns OpenAPI-shaped display data with cadence telemetry, supported shaders, output status, and a read-only current runtime layer - `GET /docs/openapi.yaml` and `GET /openapi.yaml`: serves the OpenAPI document - `GET /docs`: serves Swagger UI - `POST /api/layers/add` and `POST /api/layers/remove` mutate the app-owned display layer model only - other OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }` The HTTP server runs on its own thread. It serves static UI/docs files, samples/copies telemetry through callbacks, and does not call render work or DeckLink scheduling. ## Optional DeckLink Output DeckLink output is an optional edge service in this app. Startup order is: 1. start render thread 2. warm up rendered system-memory frames 3. try to attach DeckLink output 4. start telemetry and HTTP either way If DeckLink discovery or output setup fails, the app logs a warning and continues running without starting the output scheduler or scheduled playback. This keeps render cadence, runtime shader testing, HTTP state, and logging available on machines without DeckLink hardware or drivers. `/api/state` reports the output status in `videoIO.statusMessage`. The app samples telemetry once per second. Normal cadence samples are available through `GET /api/state` and are not printed to the console. The telemetry monitor only logs health events: - warning when DeckLink late/dropped-frame counters increase - warning when schedule failures increase - error when the app/DeckLink output buffer is starved Healthy first-run signs: - visible DeckLink output is smooth - `renderFps` is close to the selected cadence - `scheduleFps` is close to the selected cadence after warmup - `scheduled` stays near 4 - `decklinkBuffered` stays near 4 when available - `late` and `dropped` do not increase continuously - `scheduleFailures` does not increase - `shaderCommitted` becomes `1` after the background Happy Accident compile completes - `shaderFailures` remains `0` `completedPollMisses` means the DeckLink scheduling thread woke up before a completed frame was available. It is not a DeckLink playout underrun by itself. Treat it as healthy polling noise when `scheduled`, `decklinkBuffered`, `late`, `dropped`, and `scheduleFailures` remain stable. ## Runtime Slang Shader Test On startup the app begins compiling the selected shader package on a background thread owned by the app orchestration layer. The default is `shaders/happy-accident`. The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. It only receives a completed shader artifact and attempts the OpenGL shader compile/link at a frame boundary. If either the Slang build or GL commit fails, the app keeps rendering the simple motion fallback. Current runtime shader support is deliberately limited to stateless single-pass packages: - one pass only - no temporal history - no feedback storage - no texture/LUT assets yet - no text parameters yet - manifest defaults are used for parameters - `gVideoInput` and `gLayerInput` are bound to a small fallback source texture until DeckLink input is added The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as multipass, temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now. Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Stage 1 add/remove POST controls mutate this app-owned model and may start background shader builds, but multi-layer render-scene handoff is not ported yet. Successful handoff signs: - telemetry shows `shaderCommitted=1` - output changes from the simple motion pattern to the Happy Accident shader - render/schedule cadence remains near 60 fps during and after the handoff - DeckLink buffer remains stable ## Baseline Result Date: 2026-05-12 User-visible result: - output was smooth - DeckLink held a 4-frame buffer Representative telemetry: ```text renderFps=59.9 scheduleFps=59.9 free=8 completed=0 scheduled=4 completedPollMisses=30 scheduleFailures=0 completions=720 late=0 dropped=0 decklinkBuffered=4 scheduleCallMs=1.2 renderFps=59.8 scheduleFps=59.8 free=7 completed=1 scheduled=4 completedPollMisses=36 scheduleFailures=0 completions=1080 late=0 dropped=0 decklinkBuffered=4 scheduleCallMs=4.7 renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 completedPollMisses=86 scheduleFailures=0 completions=1381 late=0 dropped=0 decklinkBuffered=4 scheduleCallMs=2.1 ``` Read: - render cadence and DeckLink schedule cadence both held roughly 60 fps - app scheduled depth stayed at 4 - actual DeckLink buffered depth stayed at 4 - no late frames, dropped frames, or schedule failures were observed - completed poll misses were benign because playout remained fully fed ## Tests ```powershell cmake --build --preset build-debug --target RenderCadenceCompositorFrameExchangeTests -- /m:1 ctest --test-dir build\vs2022-x64-debug -C Debug -R RenderCadenceCompositorFrameExchangeTests --output-on-failure ``` ## Relationship To The Probe `apps/DeckLinkRenderCadenceProbe` proved the timing model in one compact file. This app keeps the same core behavior but splits it into modules that can grow: - `frames/`: system-memory handoff - `platform/`: COM/Win32/hidden GL context support - `render/`: cadence, simple rendering, PBO readback - `runtime/`: app-owned shader layer readiness model, runtime Slang build bridge, and completed artifact handoff - `control/`: local HTTP API edge and runtime-state JSON presentation - `json/`: compact JSON serialization helpers - `video/`: DeckLink output wrapper and scheduling thread - `telemetry/`: cadence telemetry - `telemetry/TelemetryHealthMonitor`: quiet health event logging from telemetry samples - `app/`: startup/shutdown orchestration - `app/AppConfigProvider`: startup config loading and CLI overrides ## Next Porting Steps Only after this app matches the probe's smooth output: 1. replace `SimpleMotionRenderer` with a render-scene interface 2. port shader package rendering 3. port runtime snapshots/live state 4. add control services 5. add preview/screenshot from system-memory frames 6. add DeckLink input as a CPU latest-frame mailbox