148 lines
5.0 KiB
Markdown
148 lines
5.0 KiB
Markdown
# Render Cadence Golden Rules
|
|
|
|
These are the non-negotiable rules for the new render-cadence architecture.
|
|
|
|
They exist because the old app drifted into a place where DeckLink timing, render work, shader build work, state coordination, readback, and recovery behavior all influenced each other. The new app should stay boring, explicit, and easy to reason about.
|
|
|
|
## 1. The Render Thread Owns Its GL Context
|
|
|
|
Only the render thread may bind and use its primary OpenGL context.
|
|
|
|
Allowed on the render thread:
|
|
|
|
- GL resource creation and destruction for resources it owns
|
|
- GL shader/program commit from an already-prepared artifact
|
|
- drawing the next frame
|
|
- async readback queueing and completion polling
|
|
- publishing completed system-memory frames
|
|
|
|
Not allowed on the render thread:
|
|
|
|
- Slang compiler invocation
|
|
- manifest scanning/parsing
|
|
- filesystem discovery
|
|
- image/font/LUT decoding
|
|
- persistence
|
|
- network/API/OSC handling
|
|
- DeckLink scheduling
|
|
- blocking console logging
|
|
- config file discovery or parsing
|
|
|
|
If future GL preparation needs to happen off-thread, use an explicit shared-context GL prepare thread. Do not smuggle non-render work back into the cadence loop.
|
|
|
|
## 2. Render Cadence Does Not Chase Buffers
|
|
|
|
The render thread runs at the selected render cadence.
|
|
|
|
It must not speed up to fill a DeckLink/system-memory buffer, and it must not slow down because a consumer is late. If the GPU is genuinely overloaded, record that as render overrun telemetry.
|
|
|
|
Buffers absorb timing differences. They do not control render cadence.
|
|
|
|
## 3. Video I/O Never Renders
|
|
|
|
DeckLink output consumes already-rendered system-memory frames.
|
|
|
|
The output/scheduling side may:
|
|
|
|
- schedule completed frames
|
|
- release frames after DeckLink completion
|
|
- report late/dropped/schedule telemetry
|
|
- record app-side poll misses
|
|
|
|
It must not:
|
|
|
|
- render fallback frames
|
|
- invoke GL
|
|
- compile shaders
|
|
- block the render cadence waiting for DeckLink
|
|
|
|
If no completed frame is available, record the miss and keep the ownership boundary intact.
|
|
|
|
## 4. Runtime Build Work Produces Artifacts
|
|
|
|
Runtime shader work is split into two phases:
|
|
|
|
1. CPU/build phase outside the render thread
|
|
2. GL commit phase on the render thread
|
|
|
|
The CPU/build phase may parse manifests, invoke Slang, validate package shape, and prepare CPU-side data.
|
|
|
|
The render thread receives a completed artifact and either commits it at a frame boundary or rejects it. A failed artifact must not disturb the current renderer.
|
|
|
|
The display/render layer model is app-owned. It may track requested shaders, build state, display metadata, and render-ready artifacts, but it must not perform GL work or drive render cadence directly.
|
|
|
|
## 5. No Hidden Blocking In The Cadence Path
|
|
|
|
The render loop must not do work with unbounded or OS-dependent latency.
|
|
|
|
Examples to avoid:
|
|
|
|
- file reads
|
|
- directory scans
|
|
- image decoding
|
|
- process launches
|
|
- waits on worker threads
|
|
- blocking locks around slow code
|
|
- synchronous GPU readback waits
|
|
- console I/O
|
|
|
|
Short mutex use for exchanging small already-prepared objects is acceptable. Holding a lock while doing heavy work is not.
|
|
|
|
## 6. System Memory Frames Are A Handoff, Not A Render Driver
|
|
|
|
The system-memory frame exchange stores the latest rendered frames and protects frames scheduled to DeckLink.
|
|
|
|
It may drop old completed, unscheduled frames when the render thread needs a free slot. It must never force the render thread to wait for the output side to consume a frame.
|
|
|
|
## 7. Startup Uses Warmup, Not Burst Rendering
|
|
|
|
DeckLink playback starts only after the render thread has produced enough real frames for preroll.
|
|
|
|
Warmup should happen at normal render cadence. Do not temporarily accelerate the renderer to fill buffers.
|
|
|
|
## 8. Telemetry Must Name Ownership Clearly
|
|
|
|
Counters should say which subsystem had the event.
|
|
|
|
Good examples:
|
|
|
|
- `renderFps`
|
|
- `scheduleFps`
|
|
- `completedPollMisses`
|
|
- `scheduleFailures`
|
|
- `decklinkBuffered`
|
|
- `shaderCommitted`
|
|
- `shaderFailures`
|
|
|
|
Avoid ambiguous names like `underrun` unless it is clear whether it means app-ready underrun, DeckLink buffered-frame underrun, render overrun, or schedule failure.
|
|
|
|
## 9. Keep Files Small And Role-Based
|
|
|
|
A file should have one clear reason to change.
|
|
|
|
Preferred boundaries:
|
|
|
|
- app orchestration
|
|
- render cadence/thread ownership
|
|
- GL rendering
|
|
- runtime artifact build/bridge
|
|
- app-owned display/render layer model
|
|
- parameter packing
|
|
- system-memory frame exchange
|
|
- DeckLink output scheduling
|
|
- telemetry
|
|
- local control/API edge
|
|
- config loading
|
|
- JSON presentation/serialization
|
|
- logging
|
|
|
|
If a file starts coordinating multiple subsystems and doing detailed work for each of them, split it before it becomes the new old app.
|
|
|
|
## 10. Prefer Explicit Unsupported States
|
|
|
|
If a feature needs storage, timing behavior, or ownership we have not designed yet, reject it clearly.
|
|
|
|
For example, in the current new app it is better to reject texture/LUT/text/temporal/feedback shaders than to quietly load files or allocate history state on the render thread.
|
|
|
|
Unsupported is healthy when it protects the architecture.
|