# 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 - 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 submission of a completed shader artifact - render-thread-only GL commit once the artifact is ready - manifest-driven 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` - compact telemetry - 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 /api/state`: returns an OpenAPI-shaped state scaffold with cadence telemetry under `performance.cadence` - `GET /docs/openapi.yaml` and `GET /openapi.yaml`: serves the OpenAPI document - `GET /docs`: serves Swagger UI - 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 samples/copies telemetry through callbacks and does not call render work or DeckLink scheduling. The app prints one line per second: ```text renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 completedPollMisses=0 scheduleFailures=0 completions=119 late=0 dropped=0 shaderCommitted=1 shaderFailures=0 decklinkBuffered=4 scheduleCallMs=0.0 ``` 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 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 - `control/`: local HTTP API edge - `json/`: compact JSON serialization helpers - `video/`: DeckLink output wrapper and scheduling thread - `telemetry/`: cadence telemetry - `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