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.
Architecture
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, anderrorlevels - 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
cmake --build --preset build-debug --target RenderCadenceCompositor -- /m:1
The executable is:
build\vs2022-x64-debug\Debug\RenderCadenceCompositor.exe
Run
Run from VS Code with:
Debug RenderCadenceCompositor
Or from a terminal:
build\vs2022-x64-debug\Debug\RenderCadenceCompositor.exe
Press Enter to stop.
To test a different compatible shader package:
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:
serverPortshaderLibraryoscBindAddressoscPortoscSmoothinginputVideoFormatinputFrameRateoutputVideoFormatoutputFrameRateautoReloadmaxTemporalHistoryFramespreviewFpsenableExternalKeying
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 <shader-id>--no-shader--port <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 underperformance.cadenceGET /docs/openapi.yamlandGET /openapi.yaml: serves the OpenAPI documentGET /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:
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
renderFpsis close to the selected cadencescheduleFpsis close to the selected cadence after warmupscheduledstays near 4decklinkBufferedstays near 4 when availablelateanddroppeddo not increase continuouslyscheduleFailuresdoes not increaseshaderCommittedbecomes1after the background Happy Accident compile completesshaderFailuresremains0
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
gVideoInputandgLayerInputare 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:
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
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 handoffplatform/: COM/Win32/hidden GL context supportrender/: cadence, simple rendering, PBO readbackcontrol/: local HTTP API edgejson/: compact JSON serialization helpersvideo/: DeckLink output wrapper and scheduling threadtelemetry/: cadence telemetryapp/: startup/shutdown orchestrationapp/AppConfigProvider: startup config loading and CLI overrides
Next Porting Steps
Only after this app matches the probe's smooth output:
- replace
SimpleMotionRendererwith a render-scene interface - port shader package rendering
- port runtime snapshots/live state
- add control services
- add preview/screenshot from system-memory frames
- add DeckLink input as a CPU latest-frame mailbox