12 KiB
Video Shader
Native video shader host with an OpenGL render path, pluggable video I/O boundary, DeckLink backend, Slang shader packages, and a local React control UI.
The app loads shader packages from shaders/, compiles Slang to GLSL at runtime, renders a configurable layer stack, and exposes a browser-based control surface over a local HTTP/WebSocket server. Shader compilation is prepared off the frame path where possible, then committed on the render thread so editing shader files does not block video output for the whole compile.
Repository Layout
src/: native C++ host app.shaders/: shader packages, each withshader.jsonandshader.slang.ui/: Vite/React control UI.config/runtime-host.json: runtime configuration.runtime/templates/: tracked shader wrapper templates.runtime/: ignored generated runtime cache/state output. Seeruntime/README.md.tests/: focused native tests for pure runtime logic..gitea/workflows/ci.yml: Gitea Actions CI for Windows native tests and Ubuntu UI build.
Native app internals are grouped by boundary:
app/: startup/shutdown orchestration and runtime layer controller.control/: HTTP/WebSocket server, command parsing, and runtime-state JSON presentation.frames/: system-memory frame exchange and input mailbox handoff.render/: render thread, readback, runtime render scene, and shared-context shader program preparation.runtime/: shader catalog support, layer model, Slang build bridge, font atlas build, and runtime-state persistence.shader/: shader package parsing and Slang compilation helpers.video/: DeckLink input/output edges, format helpers, and scheduling.
Requirements
- Windows with Visual Studio 2022 C++ tooling.
- CMake 3.24 or newer.
- Node.js and npm for the control UI.
- Blackmagic Desktop Video drivers and a DeckLink device for the current production video I/O backend.
- Slang binary release with
slangc.exe,slang-compiler.dll,slang-glslang.dll, andLICENSE. msdf-atlas-genWindows binary release withmsdf-atlas-gen.exe,LICENSE.txt, and any adjacent runtime DLLs for font atlas generation.
Default expected Slang path:
3rdParty/slang-2026.8-windows-x86_64
Default expected msdf-atlas-gen path:
3rdParty/msdf-atlas-gen
Override example:
cmake --preset vs2022-x64-debug -DSLANG_ROOT="D:/SDKs/slang-2026.8-windows-x86_64"
Build
Configure and build the native app:
cmake --preset vs2022-x64-debug
cmake --build --preset build-debug --parallel
Build the React control UI:
cd ui
npm ci
npm run build
The native app serves ui/dist when it exists, otherwise it falls back to the source UI directory during development.
The control UI provides:
- A searchable shader library for adding layers.
- Compact parameter rows with inline descriptions and intended OSC route copy controls.
- Manual shader reload.
Package
Build the UI, build the native Release target, then install into a portable runtime folder:
cd ui
npm ci
npm run build
cd ..
cmake --preset vs2022-x64-release
cmake --build --preset build-release --parallel
cmake --install build/vs2022-x64-release --config Release --prefix dist/VideoShader
The package folder will contain:
dist/VideoShader/
RenderCadenceCompositor.exe
config/
shaders/
3rdParty/slang/bin/
3rdParty/msdf-atlas-gen/
ui/dist/
docs/
SHADER_CONTRACT.md
runtime/templates/
third_party_notices/
You can run RenderCadenceCompositor.exe directly from that folder. In packaged mode, the app resolves config/, shaders/, 3rdParty/slang/bin/slangc.exe, 3rdParty/msdf-atlas-gen/msdf-atlas-gen.exe, ui/dist/, and runtime/templates/ relative to the exe folder. In development mode, it still falls back to repo-root discovery.
The install step copies only the Slang runtime files required by the shader compiler (slangc.exe, slang-compiler.dll, and slang-glslang.dll) plus third_party_notices/SLANG_LICENSE.txt. It also copies msdf-atlas-gen.exe, any adjacent msdf-atlas-gen DLLs, and the third_party_notices/MSDF_ATLAS_GEN_LICENSE.txt and third_party_notices/MSDF_ATLAS_GEN_README.md notice files. It does not copy full third-party release folders.
Create a zip for distribution:
Compress-Archive -Path dist/VideoShader/* -DestinationPath dist/VideoShader.zip -Force
Tests
Run native tests:
cmake --build --preset build-debug --target RUN_TESTS --parallel
Run the UI production build check:
cd ui
npm run build
Current native test coverage includes:
- JSON parsing and serialization.
- Parameter normalization and preset filename safety.
- Shader manifest parsing, temporal manifest validation, and package registry scanning.
- Video I/O format helpers, v210/Ay10 row-byte math, v210 pack/unpack math, playout scheduler timing, and fake backend contract coverage.
- Slang validation for every available shader package.
Runtime Configuration
config/runtime-host.json controls host behavior:
{
"shaderLibrary": "shaders",
"serverPort": 8080,
"oscBindAddress": "127.0.0.1",
"oscPort": 9000,
"oscSmoothing": 0.18,
"inputVideoFormat": "1080p",
"inputFrameRate": "59.94",
"outputVideoFormat": "1080p",
"outputFrameRate": "59.94",
"autoReload": true,
"maxTemporalHistoryFrames": 12,
"enableExternalKeying": true
}
inputVideoFormat/inputFrameRate select the video capture mode. outputVideoFormat/outputFrameRate select the playout mode. With the current DeckLink backend, supported modes depend on the installed card and driver. The shader stack runs at input resolution and the final rendered frame is scaled once into the configured output mode. Common examples include 720p/50, 720p/59.94, 1080i/50, 1080i/59.94, 1080p/25, 1080p/50, 1080p/59.94, and 2160p/59.94.
Legacy videoFormat and frameRate keys are still accepted and apply to both input and output unless the explicit input/output keys are present.
The control UI is available at:
http://127.0.0.1:<serverPort>
Runtime State
The current layer stack is autosaved to runtime/runtime_state.json whenever durable UI/API layer changes are accepted: add/remove, shader assignment, bypass state, ordering, parameter updates, parameter reset, and reload compatibility refreshes. Saves are debounced and written on a background worker, with a final flush during shutdown.
On startup, the host tries to reload runtime/runtime_state.json before compiling the stack. Valid saved layers are rebuilt in saved order, with shader id, bypass state, and parameter values restored. Missing shader packages are skipped, invalid saved parameter values fall back to shader defaults, and if the file is missing or unusable the app falls back to the configured default shader.
Manual stack preset and screenshot routes are still present in the UI/OpenAPI surface, but they are not implemented by the current native command path yet. runtime_state.json is the supported latest-working-state mechanism for now.
Control API
The local REST control API is documented as an OpenAPI/Swagger spec:
docs/openapi.yaml
When the control server is running, the same spec is also served at:
http://127.0.0.1:<serverPort>/docs/openapi.yaml
http://127.0.0.1:<serverPort>/openapi.yaml
A Swagger UI page is available at:
http://127.0.0.1:<serverPort>/docs
Use those docs to inspect the /api/state, layer control, and reload endpoints. Live state updates are also sent over the /ws WebSocket.
The control UI has a Reload shaders button. It rescans shaders/, re-reads manifests, refreshes shader availability/errors, updates active layer parameter definitions from changed manifests, and queues recompilation for every catalog-valid layer in the active stack. Missing shader packages are marked failed, and the previous working render stack remains active where possible until replacement builds commit successfully.
Each parameter row still exposes the intended OSC route in the UI, but OSC ingress is not wired in the current native host.
The control UI currently still shows preset and screenshot controls from the intended route surface. Those endpoints return an unimplemented action result in the native host until their backend paths are wired.
The planned screenshot output directory is:
runtime/screenshots/
OSC Control
OSC fields are present in config/runtime-host.json and /api/state for compatibility with the intended control surface, but the current native host does not start an OSC listener yet.
The intended route shape is:
/VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID}
For now, use the REST layer parameter endpoints or the control UI for live parameter changes. Future OSC-driven parameter changes should stay out of autosave unless an explicit persistence policy is added.
Shader Packages
Each shader package lives under:
shaders/<id>/
shader.json
shader.slang
optional-extra-pass.slang
optional-font-or-texture-assets
See SHADER_CONTRACT.md for the manifest schema, parameter types, texture assets, font/text assets, temporal history support, optional render-pass declarations, and the Slang entry point contract. shaders/text-overlay/ is the reference live text package and bundles Roboto Regular with its OFL license. Broken shader packages are shown as unavailable in the selector with their error text instead of preventing the app from launching.
Generated Files
Runtime-generated files are intentionally ignored:
runtime/shader_cache/active_shader_wrapper.slangruntime/shader_cache/active_shader.raw.fragruntime/shader_cache/active_shader.fragruntime/runtime_state.jsonautosaved latest stack and parameter state.runtime/stack_presets/*.jsonplanned manual preset output; preset routes are not implemented in the native host yet.runtime/screenshots/*.pngplanned screenshot output; screenshot capture is not implemented in the native host yet.
Only runtime/templates/ and runtime/README.md are tracked.
CI
The Gitea workflow expects two act runners:
windows-2022: builds the native app and runs native tests.ubuntu-latest: installs UI dependencies and runs the Vite build.
The Windows jobs validate native third-party dependencies before configuring CMake. Because 3rdParty/ is ignored, configure this path on the runner or in a Gitea repository variable:
SLANG_ROOT: path to the Slang binary release folder containingbin/slangc.exe.MSDF_ATLAS_GEN_ROOT: path to themsdf-atlas-genbinary release folder containingmsdf-atlas-gen.exe.
The Windows runner also needs the Visual Studio ATL component installed. In Visual Studio Build Tools 2022, add C++ ATL for latest v143 build tools (x86 & x64), component ID Microsoft.VisualStudio.Component.VC.ATL.
Example runner paths:
D:\SDKs\slang-2026.8-windows-x86_64
D:\SDKs\msdf-atlas-gen
If SLANG_ROOT or MSDF_ATLAS_GEN_ROOT is not set, the workflow falls back to the repo-local defaults under 3rdParty/.
Still Todo
- Audio.
- Genlock.
- Logs.
- Add more video I/O backends now that the DeckLink path is behind
videoio/. - Support a separate sound shader
.slangfile in shader packages. (https://www.shadertoy.com/view/XsBXWt) - Add WebView2 for an embedded native control surface.
- MSDF typography rasterisation
- More shader-library organisation and filtering as the built-in library grows.
- Optional linear-light compositing mode.
- compute shaders or a small 1x1 or nx1 RGBA16f render target for arbitrary data storage
- allow shaders to read other shaders data store based on name? or output over OSC
- Mipmapping for shader-declared textures
- Anotate included shaders
- allow 3 vector exposed controls
- add nearest sampling to the extra shader pass