8.2 KiB
Forking The Render Cadence Base
This note captures the fork-readiness review for using this repository as a base where the render cadence and video I/O work are kept, but the GPU content being rendered is replaced in a separate repository.
Verdict
The repository is clean enough for an internal fork, but it needs a small hygiene pass before it becomes a comfortable long-lived base repo.
The important architecture is already in place: render cadence, video input/output, frame exchange, readback, preview, control, and shader build work are mostly separated by role. The app-side replacement point is now IRuntimeContentController in src/app/RuntimeContentController.h. The current shader package stack is plugged in by ShaderRuntimeContentController, while a fork can provide a D3D-engine controller without making the host app own Slang, shader catalogs, or shader layer state.
The current render-thread implementation still uses OpenGL for its context and readback path. If the fork's renderer is truly D3D rather than GL content, keep the app/video/frame-exchange boundary and replace the render-thread/content implementation that publishes completed system-memory frames.
For a new repo, keep the cadence and frame handoff machinery, then replace or narrow the runtime shader rendering layer.
Keep
These parts are the useful base for the fork:
src/frames: CPU frame handoff, input mailbox, and completed-frame exchange.src/video/core: backend-neutral input/output contracts.src/video/decklink,src/video/ndi, andsrc/video/playout: concrete video I/O edges and scheduling support.src/render/thread: render cadence ownership, readback pumping, runtime render-layer commit point, and metrics.src/render/readback: BGRA8/UYVY8 PBO readback and completed-frame publication.src/platform: hidden GL window/context support.src/app: startup, config, video backend factory, runtime layer orchestration, preview, telemetry, and HTTP server hookup.src/control/http,src/control/osc,src/telemetry,src/logging, andui: useful if the new repo still wants a local control surface.control/oscis currently a status/lifecycle stub, not a UDP listener.src/app/RenderCadenceHttpRoutes.*: useful only if the new repo keeps this app's current/api/...control surface.src/app/RuntimeContentController.h: the app-side runtime-content boundary. Keep the interface shape and provide a fork-owned implementation for D3D engine control/state.
Replace Or Rework
These are most likely to change when the fork renders something other than shader packages:
src/render/runtime: current runtime shader scene, renderer, text texture cache, and shared-context shader preparation.src/runtime/shader: background Slang package build bridge.src/shader: shader package manifest parsing and Slang wrapper generation, unless the new renderer keeps the same shader package contract.src/app/ShaderRuntimeContentController.*: current Slang shader-stack implementation of the app runtime-content boundary.shaders/: bundled shader package library.runtime/templates/shader_wrapper.slang.in: only needed for the current Slang package pipeline.src/app/RenderCadenceHttpRoutes.*: replace this with a fork-owned route module if the new renderer has different controls, while keepingsrc/control/http/HttpControlServer.*as the socket/static/WebSocket shell.- Shader-specific UI affordances in
ui, if the new renderer has a different control model.
The first fork step is now in place: RenderThread preserves the cadence/readback shell and calls a narrow render-content interface behind the draw call. A new repo can swap that implementation without touching video I/O scheduling.
The shader stack is also separated from RenderCadenceApp now. The app owns only an IRuntimeContentController; RenderCadenceCompositor.cpp wires the current ShaderRuntimeContentController into that slot. A D3D fork should wire a D3D runtime-content controller there and then replace the render-thread content/readback implementation as needed.
The HTTP control boundary is also split now. HttpControlServer owns transport, static-file helpers, OpenAPI helper serving, and WebSocket state transport; RenderCadenceHttpRoutes owns this app's REST endpoint map. A fork that wants the same browser/server plumbing can provide its own route callback and leave the Render Cadence-specific endpoints behind.
Current Swap Point
The render cadence loop now calls IRenderContent inside the readback queue call in src/render/thread/RenderThread.cpp:
renderContent.RenderFrame(RenderContentFrame{
index,
mConfig.width,
mConfig.height,
videoInputTexture
});
The default implementation is RuntimeShaderRenderContent, which owns the existing runtime shader scene plus simple fallback renderer. That is the practical boundary for a fork:
- keep the tick clock, input upload, readback queueing, and
SystemFrameExchangepublication around it - replace what draws into the current GL framebuffer
- keep video output consuming already completed system-memory frames
Do not move DeckLink, NDI, file I/O, shader compilation, or control handling into the cadence path while doing the replacement.
Fork Hygiene Checklist
Before cutting a long-lived fork, fix or decide these items:
- Keep
runtimeShaderIdempty in checked-in config unless this repo intentionally wants a default startup shader again. - Keep runtime third-party discovery aligned with CMake. Font atlas generation and Slang compilation now both check explicit tool roots,
THIRD_PARTY_ROOT, the privatevideo-io-3rdPartybundle, and legacy or packaged3rdPartylayouts. - Make
config/runtime-host.jsonportable. Current checked-in defaults include a local NDI source name and DeckLink output. - Decide whether the fork keeps the Slang shader package contract. If not, replace
ShaderRuntimeContentController, retire or clearly isolateshaders/SHADER_CONTRACT.md, shader package UI, and shader manifest tests. - For a D3D engine fork, decide whether to bridge D3D output into the existing GL/readback path temporarily or replace
RenderThread/readback with a D3D-native publisher that still writes completedSystemFrameExchangeframes. - Mark older docs that reference
apps/LoopThroughWithOpenGLCompositingas historical, or update them to point at the currentsrc/implementation. - Keep
runtime/generated output ignored, and keep onlyruntime/templates/plusruntime/README.mdtracked. - Keep the private SDK bundle as a submodule only if the new repo is intended for the same org/build environment. External forks should use ignored
3rdParty/or explicit CMake SDK paths.
Verification Snapshot
Last checked locally on 2026-05-30 after the runtime-content controller split:
- UI production build passed with
npm.cmd run build. - Native debug build passed for
RenderCadenceCompositor. - Native tests passed 26 of 26 with
ctest --test-dir build\vs2022-x64-debug -C Debug --output-on-failure. FontAtlasBuilderTestsnow passes withmsdf-atlas-gen.exesupplied by the privatevideo-io-3rdParty/msdf-atlas-genbundle.ShaderCompilerLookupTestscoversSLANG_ROOT,THIRD_PARTY_ROOT, repovideo-io-3rdParty, legacy3rdParty, and packaged3rdParty/slangSlang layouts.
The generated Visual Studio RUN_TESTS target did not build missing test executables by itself during the first local run; building the debug preset first produced the test binaries.
Recommended Fork Sequence
- Make the hygiene fixes above in this repo or immediately after the fork.
- Keep
IRuntimeContentControlleras the app-side boundary and replaceShaderRuntimeContentControllerwith the fork's D3D engine control/state implementation. - Keep
IRenderContentas the render-thread draw-call boundary only if the fork remains GL-backed or uses a GL/D3D bridge. - If the fork is D3D-native, replace the render-thread/readback implementation while preserving completed-frame publication into
SystemFrameExchange. - Verify that video output still consumes completed frames and never requests rendering directly.
- Only then remove shader package pieces that the new repo no longer needs.
That sequence preserves the hard-won cadence/video I/O behavior while giving the fork a clean place to become its own renderer.