config cleanup
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Failing after 2m30s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 16:05:40 +10:00
parent 2058f94193
commit e006fcc6ee
17 changed files with 238 additions and 133 deletions

View File

@@ -154,23 +154,32 @@ Current native test coverage includes:
"oscBindAddress": "127.0.0.1",
"oscPort": 9000,
"oscSmoothing": 0.18,
"inputVideoFormat": "1080p",
"inputFrameRate": "59.94",
"outputVideoFormat": "1080p",
"outputFrameRate": "59.94",
"videoInputBackend": "decklink",
"videoOutputBackend": "decklink",
"input": {
"backend": "decklink",
"device": "default",
"resolution": "1080p",
"frameRate": "59.94"
},
"output": {
"backend": "decklink",
"device": "default",
"resolution": "1080p",
"frameRate": "59.94",
"keying": {
"external": true,
"alphaRequired": false
}
},
"autoReload": true,
"maxTemporalHistoryFrames": 12,
"enableExternalKeying": true
"maxTemporalHistoryFrames": 12
}
```
`videoInputBackend` and `videoOutputBackend` select the concrete video I/O backend. Today the app supports `decklink` and `none`; future backends such as NDI, Spout, or file playback can be added behind the same factory boundary.
`input.backend` and `output.backend` select the concrete video I/O backend. Today the app supports `decklink` and `none`; future backends such as NDI, Spout, or file playback can be added behind the same factory boundary. `device` is currently accepted as a backend-neutral selector placeholder; DeckLink still chooses the first compatible device.
`inputVideoFormat`/`inputFrameRate` select the video capture mode. `outputVideoFormat`/`outputFrameRate` select the playout mode through a backend-neutral mode description; the current DeckLink backend maps that mode to a `BMDDisplayMode` at the DeckLink boundary. Supported modes still 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`.
`input.resolution`/`input.frameRate` select the video capture mode. `output.resolution`/`output.frameRate` select the playout mode through a backend-neutral mode description; the current DeckLink backend maps that mode to a `BMDDisplayMode` at the DeckLink boundary. Supported modes still 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 checked-in config uses the nested `input` and `output` objects as the supported shape.
The control UI is available at:
@@ -290,7 +299,6 @@ If `SLANG_ROOT` or `MSDF_ATLAS_GEN_ROOT` is not set, the workflow falls back to
- Audio.
- Genlock.
- Logs.
- Add more video I/O backends now that the DeckLink path is isolated under `src/video/decklink/`.
- Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt)
- Add WebView2 for an embedded native control surface.
- More shader-library organisation and filtering as the built-in library grows.

View File

@@ -4,15 +4,24 @@
"oscBindAddress": "0.0.0.0",
"oscPort": 9000,
"oscSmoothing": 0.18,
"inputVideoFormat": "1080p",
"inputFrameRate": "59.94",
"outputVideoFormat": "1080p",
"outputFrameRate": "59.94",
"videoInputBackend": "decklink",
"videoOutputBackend": "decklink",
"input": {
"backend": "decklink",
"device": "default",
"resolution": "1080p",
"frameRate": "59.94"
},
"output": {
"backend": "decklink",
"device": "default",
"resolution": "1080p",
"frameRate": "59.94",
"keying": {
"external": true,
"alphaRequired": false
}
},
"autoReload": true,
"maxTemporalHistoryFrames": 12,
"previewEnabled": true,
"previewFps": 59.94,
"enableExternalKeying": true
"previewFps": 59.94
}

View File

@@ -133,7 +133,7 @@ When a runtime shader build completes, the app publishes a render-layer artifact
## Video And Preview
Video input and output are optional edges. `videoInputBackend` and `videoOutputBackend` select the concrete backend through the app-side backend factory. DeckLink is the current concrete backend, and `none` disables that edge. Configured video modes are represented in `src/video/core` and translated to DeckLink display modes only inside `src/video/decklink`.
Video input and output are optional edges. `input.backend` and `output.backend` select the concrete backend through the app-side backend factory. DeckLink is the current concrete backend, and `none` disables that edge. `input` and `output` also carry the device selector plus resolution/frame-rate settings. Configured video modes are represented in `src/video/core` and translated to DeckLink display modes only inside `src/video/decklink`.
The input edge writes CPU frames into `InputFrameMailbox`. The current DeckLink backend captures BGRA8 directly where possible, or raw UYVY8 for render-thread GPU decode. The input edge does not call GL, render, preview, screenshot, shader, or output scheduling code.

View File

@@ -561,22 +561,37 @@ components:
type: number
previewFps:
type: number
enableExternalKeying:
type: boolean
videoInputBackend:
type: string
enum: [decklink, none]
videoOutputBackend:
type: string
enum: [decklink, none]
inputVideoFormat:
type: string
inputFrameRate:
type: string
outputVideoFormat:
type: string
outputFrameRate:
type: string
input:
type: object
properties:
backend:
type: string
enum: [decklink, none]
device:
type: string
resolution:
type: string
frameRate:
type: string
output:
type: object
properties:
backend:
type: string
enum: [decklink, none]
device:
type: string
resolution:
type: string
frameRate:
type: string
keying:
type: object
properties:
external:
type: boolean
alphaRequired:
type: boolean
RuntimeStatus:
type: object
properties:

View File

@@ -200,19 +200,22 @@ Currently consumed fields:
- `oscBindAddress` (reported for compatibility; OSC ingress is not wired yet)
- `oscPort` (reported for compatibility; OSC ingress is not wired yet)
- `oscSmoothing` (reported for compatibility; OSC ingress is not wired yet)
- `inputVideoFormat`
- `inputFrameRate`
- `outputVideoFormat`
- `outputFrameRate`
- `videoInputBackend`
- `videoOutputBackend`
- `input.backend`
- `input.device`
- `input.resolution`
- `input.frameRate`
- `output.backend`
- `output.device`
- `output.resolution`
- `output.frameRate`
- `output.keying.external`
- `output.keying.alphaRequired`
- `autoReload`
- `maxTemporalHistoryFrames`
- `previewEnabled`
- `previewFps`
- `enableExternalKeying`
`videoInputBackend` and `videoOutputBackend` currently support `decklink` and `none`. Backend creation is routed through the app-side video backend factory, so new concrete backends can be added without making `main` or the render cadence path own their startup details.
`input.backend` and `output.backend` currently support `decklink` and `none`. Backend creation is routed through the app-side video backend factory, so new concrete backends can be added without making `main` or the render cadence path own their startup details.
When `previewEnabled` is true, the preview window runs on `PreviewWindowThread`. It paints BGRA8 system-memory frames with Win32/GDI after render readback has already completed, so it does not bind GL and does not consume frames from DeckLink output. `previewFps` controls the preview repaint cadence; the default is 60 fps and `config/runtime-host.json` tracks the shipped 59.94 output cadence.

View File

@@ -80,7 +80,7 @@ int main(int argc, char** argv)
SystemFrameExchangeConfig frameExchangeConfig;
VideoFormatDimensions(
appConfig.outputVideoFormat,
appConfig.output.resolution,
frameExchangeConfig.width,
frameExchangeConfig.height);
frameExchangeConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
@@ -95,7 +95,7 @@ int main(int argc, char** argv)
InputFrameMailboxConfig inputMailboxConfig;
VideoFormatDimensions(
appConfig.inputVideoFormat,
appConfig.input.resolution,
inputMailboxConfig.width,
inputMailboxConfig.height);
inputMailboxConfig.pixelFormat = VideoIOPixelFormat::Bgra8;
@@ -107,24 +107,24 @@ int main(int argc, char** argv)
VideoFormat inputVideoMode;
VideoFormat outputVideoMode;
std::string inputVideoModeError;
const bool inputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.inputVideoFormat, appConfig.inputFrameRate, inputVideoMode);
const bool outputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.outputVideoFormat, appConfig.outputFrameRate, outputVideoMode);
const bool inputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.input.resolution, appConfig.input.frameRate, inputVideoMode);
const bool outputVideoModeResolved = ResolveConfiguredVideoFormat(appConfig.output.resolution, appConfig.output.frameRate, outputVideoMode);
if (!inputVideoModeResolved)
{
inputVideoModeError = "Unsupported inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
appConfig.inputVideoFormat + " / " + appConfig.inputFrameRate;
inputVideoModeError = "Unsupported input resolution/frameRate in config/runtime-host.json: " +
appConfig.input.resolution + " / " + appConfig.input.frameRate;
RenderCadenceCompositor::LogWarning("app", inputVideoModeError);
}
if (!outputVideoModeResolved)
{
RenderCadenceCompositor::LogWarning(
"app",
"Unsupported outputVideoFormat/outputFrameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
appConfig.outputVideoFormat + " / " + appConfig.outputFrameRate);
"Unsupported output resolution/frameRate in config/runtime-host.json; render cadence will use parsed frame-rate fallback: " +
appConfig.output.resolution + " / " + appConfig.output.frameRate);
}
else
{
appConfig.deckLink.outputVideoMode = outputVideoMode;
appConfig.output.videoMode = outputVideoMode;
}
auto inputBackend = RenderCadenceCompositor::StartVideoInputBackend(
@@ -137,7 +137,7 @@ int main(int argc, char** argv)
RenderThread::Config renderConfig;
renderConfig.width = frameExchangeConfig.width;
renderConfig.height = frameExchangeConfig.height;
const double fallbackFrameDurationMilliseconds = FrameDurationMillisecondsFromRateString(appConfig.outputFrameRate);
const double fallbackFrameDurationMilliseconds = FrameDurationMillisecondsFromRateString(appConfig.output.frameRate);
renderConfig.frameDurationMilliseconds = outputVideoModeResolved
? outputVideoMode.frameDurationMilliseconds
: fallbackFrameDurationMilliseconds;

View File

@@ -5,8 +5,16 @@ namespace RenderCadenceCompositor
AppConfig DefaultAppConfig()
{
AppConfig config;
config.deckLink.externalKeyingEnabled = false;
config.deckLink.outputAlphaRequired = false;
config.input.backend = "decklink";
config.input.device = "default";
config.input.resolution = "1080p";
config.input.frameRate = "59.94";
config.output.backend = "decklink";
config.output.device = "default";
config.output.resolution = "1080p";
config.output.frameRate = "59.94";
config.output.externalKeyingEnabled = false;
config.output.outputAlphaRequired = false;
config.outputThread.targetBufferedFrames = 4;
config.telemetry.interval = std::chrono::seconds(1);
config.logging.minimumLevel = LogLevel::Log;
@@ -22,12 +30,6 @@ AppConfig DefaultAppConfig()
config.oscBindAddress = "0.0.0.0";
config.oscPort = 9000;
config.oscSmoothing = 0.18;
config.inputVideoFormat = "1080p";
config.inputFrameRate = "59.94";
config.outputVideoFormat = "1080p";
config.outputFrameRate = "59.94";
config.videoInputBackend = "decklink";
config.videoOutputBackend = "decklink";
config.autoReload = true;
config.maxTemporalHistoryFrames = 12;
config.previewEnabled = false;

View File

@@ -13,9 +13,29 @@
namespace RenderCadenceCompositor
{
struct VideoInputAppConfig
{
std::string backend = "decklink";
std::string device = "default";
std::string resolution = "1080p";
std::string frameRate = "59.94";
};
struct VideoOutputAppConfig
{
std::string backend = "decklink";
std::string device = "default";
std::string resolution = "1080p";
std::string frameRate = "59.94";
bool externalKeyingEnabled = false;
bool outputAlphaRequired = false;
VideoFormat videoMode;
};
struct AppConfig
{
VideoOutputEdgeConfig deckLink;
VideoInputAppConfig input;
VideoOutputAppConfig output;
VideoOutputThreadConfig outputThread;
TelemetryHealthMonitorConfig telemetry;
LoggerConfig logging;
@@ -24,12 +44,6 @@ struct AppConfig
std::string oscBindAddress = "0.0.0.0";
unsigned short oscPort = 9000;
double oscSmoothing = 0.18;
std::string inputVideoFormat = "1080p";
std::string inputFrameRate = "59.94";
std::string outputVideoFormat = "1080p";
std::string outputFrameRate = "59.94";
std::string videoInputBackend = "decklink";
std::string videoOutputBackend = "decklink";
bool autoReload = true;
std::size_t maxTemporalHistoryFrames = 12;
bool previewEnabled = false;

View File

@@ -79,6 +79,37 @@ void ApplyPort(const JsonValue& root, const char* key, unsigned short& target)
if (port >= 1.0 && port <= 65535.0)
target = static_cast<unsigned short>(port);
}
void ApplyInputConfig(const JsonValue& root, AppConfig& config)
{
const JsonValue* input = Find(root, "input");
if (!input || !input->isObject())
return;
ApplyString(*input, "backend", config.input.backend);
ApplyString(*input, "device", config.input.device);
ApplyString(*input, "resolution", config.input.resolution);
ApplyString(*input, "frameRate", config.input.frameRate);
}
void ApplyOutputConfig(const JsonValue& root, AppConfig& config)
{
const JsonValue* output = Find(root, "output");
if (!output || !output->isObject())
return;
ApplyString(*output, "backend", config.output.backend);
ApplyString(*output, "device", config.output.device);
ApplyString(*output, "resolution", config.output.resolution);
ApplyString(*output, "frameRate", config.output.frameRate);
const JsonValue* keying = Find(*output, "keying");
if (keying && keying->isObject())
{
ApplyBool(*keying, "external", config.output.externalKeyingEnabled);
ApplyBool(*keying, "alphaRequired", config.output.outputAlphaRequired);
}
}
}
AppConfigProvider::AppConfigProvider() :
@@ -124,17 +155,12 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
ApplyString(root, "oscBindAddress", mConfig.oscBindAddress);
ApplyPort(root, "oscPort", mConfig.oscPort);
ApplyDouble(root, "oscSmoothing", mConfig.oscSmoothing);
ApplyString(root, "inputVideoFormat", mConfig.inputVideoFormat);
ApplyString(root, "inputFrameRate", mConfig.inputFrameRate);
ApplyString(root, "outputVideoFormat", mConfig.outputVideoFormat);
ApplyString(root, "outputFrameRate", mConfig.outputFrameRate);
ApplyString(root, "videoInputBackend", mConfig.videoInputBackend);
ApplyString(root, "videoOutputBackend", mConfig.videoOutputBackend);
ApplyInputConfig(root, mConfig);
ApplyOutputConfig(root, mConfig);
ApplyBool(root, "autoReload", mConfig.autoReload);
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
ApplyBool(root, "previewEnabled", mConfig.previewEnabled);
ApplyDouble(root, "previewFps", mConfig.previewFps);
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
mLoadedFromFile = true;
error.clear();

View File

@@ -137,7 +137,7 @@ public:
private:
void StartOptionalVideoOutput()
{
if (mConfig.videoOutputBackend == "none")
if (mConfig.output.backend == "none")
{
mVideoOutputEnabled = false;
mVideoOutputStatus = "Video output backend disabled by config.";
@@ -146,9 +146,13 @@ private:
}
std::string outputError;
Log("app", "Initializing optional video output backend: " + mConfig.videoOutputBackend + ".");
Log("app", "Initializing optional video output backend: " + mConfig.output.backend + ".");
VideoOutputEdgeConfig outputConfig;
outputConfig.outputVideoMode = mConfig.output.videoMode;
outputConfig.externalKeyingEnabled = mConfig.output.externalKeyingEnabled;
outputConfig.outputAlphaRequired = mConfig.output.outputAlphaRequired;
if (!mOutput->Initialize(
mConfig.deckLink,
outputConfig,
[this](const VideoIOCompletion& completion) {
mFrameExchange.ReleaseScheduledByBytes(completion.outputFrameBuffer);
},
@@ -180,7 +184,7 @@ private:
}
mVideoOutputEnabled = true;
mVideoOutputStatus = mConfig.videoOutputBackend + " scheduled output running.";
mVideoOutputStatus = mConfig.output.backend + " scheduled output running.";
Log("app", mVideoOutputStatus);
Log(
"app",

View File

@@ -274,8 +274,8 @@ const VideoInputBackendRegistration* FindVideoInputBackend(std::string_view back
bool VideoBackendsRequireCom(const AppConfig& config)
{
const std::string inputBackend = NormalizeBackendName(config.videoInputBackend);
const std::string outputBackend = NormalizeBackendName(config.videoOutputBackend);
const std::string inputBackend = NormalizeBackendName(config.input.backend);
const std::string outputBackend = NormalizeBackendName(config.output.backend);
const VideoInputBackendRegistration* input = FindVideoInputBackend(inputBackend);
const VideoOutputBackendRegistration* output = FindVideoOutputBackend(outputBackend);
return (input != nullptr && input->requiresCom) || (output != nullptr && output->requiresCom);
@@ -283,12 +283,12 @@ bool VideoBackendsRequireCom(const AppConfig& config)
std::unique_ptr<IVideoOutputEdge> CreateVideoOutputBackend(const AppConfig& config)
{
const std::string backendName = NormalizeBackendName(config.videoOutputBackend);
const std::string backendName = NormalizeBackendName(config.output.backend);
const VideoOutputBackendRegistration* backend = FindVideoOutputBackend(backendName);
if (backend != nullptr && backend->create != nullptr)
return backend->create();
return std::make_unique<DisabledVideoOutputEdge>("Unsupported videoOutputBackend: " + config.videoOutputBackend);
return std::make_unique<DisabledVideoOutputEdge>("Unsupported output backend: " + config.output.backend);
}
std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
@@ -298,7 +298,7 @@ std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
const VideoFormat& inputVideoMode,
bool inputVideoModeResolved)
{
const std::string backendName = NormalizeBackendName(config.videoInputBackend);
const std::string backendName = NormalizeBackendName(config.input.backend);
const VideoInputBackendRegistration* backend = FindVideoInputBackend(backendName);
if (backend != nullptr && backend->start != nullptr)
{
@@ -306,7 +306,7 @@ std::unique_ptr<VideoInputBackendSession> StartVideoInputBackend(
return backend->start(context);
}
LogWarning("app", "Unsupported videoInputBackend '" + config.videoInputBackend + "'; runtime shaders will use fallback input.");
return std::make_unique<NoInputBackendSession>(config.videoInputBackend);
LogWarning("app", "Unsupported input backend '" + config.input.backend + "'; runtime shaders will use fallback input.");
return std::make_unique<NoInputBackendSession>(config.input.backend);
}
}

View File

@@ -27,13 +27,13 @@ struct RuntimeStateJsonInput
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
{
writer.BeginObject();
writer.KeyString("backend", input.config.videoOutputBackend);
writer.KeyString("backend", input.config.output.backend);
writer.KeyNull("modelName");
writer.KeyBool("supportsInternalKeying", false);
writer.KeyBool("supportsExternalKeying", false);
writer.KeyBool("keyerInterfaceAvailable", false);
writer.KeyBool("externalKeyingRequested", input.config.deckLink.externalKeyingEnabled);
writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.deckLink.externalKeyingEnabled);
writer.KeyBool("externalKeyingRequested", input.config.output.externalKeyingEnabled);
writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.output.externalKeyingEnabled);
writer.KeyString("statusMessage", input.videoOutputStatus);
writer.EndObject();
}
@@ -41,7 +41,7 @@ inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInp
inline void WriteVideoOutputBackendMetricsJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
{
writer.BeginObject();
if (input.config.videoOutputBackend == "decklink")
if (input.config.output.backend == "decklink")
{
writer.KeyBool("bufferedAvailable", input.telemetry.deckLinkBufferedAvailable);
writer.Key("buffered");
@@ -68,7 +68,7 @@ inline void WriteVideoOutputTelemetryJson(JsonWriter& writer, const RuntimeState
{
writer.BeginObject();
writer.KeyBool("enabled", input.videoOutputEnabled);
writer.KeyString("backend", input.config.videoOutputBackend);
writer.KeyString("backend", input.config.output.backend);
writer.KeyString("statusMessage", input.videoOutputStatus);
writer.KeyUInt("scheduleFailures", input.telemetry.scheduleFailures);
writer.KeyUInt("completions", input.telemetry.completions);
@@ -81,7 +81,7 @@ inline void WriteVideoOutputTelemetryJson(JsonWriter& writer, const RuntimeState
inline void OutputDimensions(const RuntimeStateJsonInput& input, unsigned& width, unsigned& height)
{
VideoFormatDimensions(input.config.outputVideoFormat, width, height);
VideoFormatDimensions(input.config.output.resolution, width, height);
}
inline const char* ShaderParameterTypeName(ShaderParameterType type)
@@ -291,13 +291,25 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.KeyBool("autoReload", input.config.autoReload);
writer.KeyUInt("maxTemporalHistoryFrames", static_cast<uint64_t>(input.config.maxTemporalHistoryFrames));
writer.KeyDouble("previewFps", input.config.previewFps);
writer.KeyBool("enableExternalKeying", input.config.deckLink.externalKeyingEnabled);
writer.KeyString("videoInputBackend", input.config.videoInputBackend);
writer.KeyString("videoOutputBackend", input.config.videoOutputBackend);
writer.KeyString("inputVideoFormat", input.config.inputVideoFormat);
writer.KeyString("inputFrameRate", input.config.inputFrameRate);
writer.KeyString("outputVideoFormat", input.config.outputVideoFormat);
writer.KeyString("outputFrameRate", input.config.outputFrameRate);
writer.Key("input");
writer.BeginObject();
writer.KeyString("backend", input.config.input.backend);
writer.KeyString("device", input.config.input.device);
writer.KeyString("resolution", input.config.input.resolution);
writer.KeyString("frameRate", input.config.input.frameRate);
writer.EndObject();
writer.Key("output");
writer.BeginObject();
writer.KeyString("backend", input.config.output.backend);
writer.KeyString("device", input.config.output.device);
writer.KeyString("resolution", input.config.output.resolution);
writer.KeyString("frameRate", input.config.output.frameRate);
writer.Key("keying");
writer.BeginObject();
writer.KeyBool("external", input.config.output.externalKeyingEnabled);
writer.KeyBool("alphaRequired", input.config.output.outputAlphaRequired);
writer.EndObject();
writer.EndObject();
writer.EndObject();
writer.Key("runtime");
@@ -315,7 +327,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.KeyBool("hasSignal", input.videoOutputEnabled);
writer.KeyUInt("width", outputWidth);
writer.KeyUInt("height", outputHeight);
writer.KeyString("modeName", input.config.outputVideoFormat + " output-only");
writer.KeyString("modeName", input.config.output.resolution + " output-only");
writer.EndObject();
writer.Key("videoOutput");
@@ -327,7 +339,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.Key("performance");
writer.BeginObject();
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.outputFrameRate));
writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.output.frameRate));
writer.KeyDouble("renderMs", input.telemetry.renderFrameMilliseconds);
writer.KeyNull("smoothedRenderMs");
writer.KeyDouble("budgetUsedPercent", input.telemetry.renderFrameBudgetUsedPercent);

View File

@@ -135,24 +135,24 @@ bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::str
}
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputResolution,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputResolution,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error)
{
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
if (!ResolveConfiguredVideoFormat(inputResolution, inputFrameRate, videoModes.input))
{
error = "Unsupported inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
inputVideoFormat + " / " + inputFrameRate;
error = "Unsupported input resolution/frameRate in config/runtime-host.json: " +
inputResolution + " / " + inputFrameRate;
return false;
}
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
if (!ResolveConfiguredVideoFormat(outputResolution, outputFrameRate, videoModes.output))
{
error = "Unsupported outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
outputVideoFormat + " / " + outputFrameRate;
error = "Unsupported output resolution/frameRate in config/runtime-host.json: " +
outputResolution + " / " + outputFrameRate;
return false;
}

View File

@@ -40,9 +40,9 @@ double FrameDurationMillisecondsFromRateString(const std::string& rateText, doub
void VideoFormatDimensions(const std::string& formatName, unsigned& width, unsigned& height);
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode);
bool ResolveConfiguredVideoFormats(
const std::string& inputVideoFormat,
const std::string& inputResolution,
const std::string& inputFrameRate,
const std::string& outputVideoFormat,
const std::string& outputResolution,
const std::string& outputFrameRate,
VideoFormatSelection& videoModes,
std::string& error);

View File

@@ -360,7 +360,7 @@ bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoF
}
else if (mState.supportsExternalKeying)
{
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
mState.statusMessage = "Selected DeckLink output supports external keying. Set output.keying.external to true in runtime-host.json to request it.";
}
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);

View File

@@ -31,17 +31,26 @@ std::filesystem::path WriteConfigFixture()
<< " \"oscBindAddress\": \"127.0.0.1\",\n"
<< " \"oscPort\": 9100,\n"
<< " \"oscSmoothing\": 0.25,\n"
<< " \"inputVideoFormat\": \"720p\",\n"
<< " \"inputFrameRate\": \"50\",\n"
<< " \"outputVideoFormat\": \"2160p\",\n"
<< " \"outputFrameRate\": \"60\",\n"
<< " \"videoInputBackend\": \"none\",\n"
<< " \"videoOutputBackend\": \"decklink\",\n"
<< " \"input\": {\n"
<< " \"backend\": \"none\",\n"
<< " \"device\": \"input-card-1\",\n"
<< " \"resolution\": \"720p\",\n"
<< " \"frameRate\": \"50\"\n"
<< " },\n"
<< " \"output\": {\n"
<< " \"backend\": \"decklink\",\n"
<< " \"device\": \"output-card-1\",\n"
<< " \"resolution\": \"2160p\",\n"
<< " \"frameRate\": \"60\",\n"
<< " \"keying\": {\n"
<< " \"external\": true,\n"
<< " \"alphaRequired\": true\n"
<< " }\n"
<< " },\n"
<< " \"autoReload\": false,\n"
<< " \"maxTemporalHistoryFrames\": 8,\n"
<< " \"previewEnabled\": true,\n"
<< " \"previewFps\": 24,\n"
<< " \"enableExternalKeying\": true\n"
<< " \"previewFps\": 24\n"
<< "}\n";
return path;
}
@@ -64,17 +73,20 @@ void TestLoadsRuntimeHostConfig()
Expect(config.oscBindAddress == "127.0.0.1", "OSC bind address loads");
Expect(config.oscPort == 9100, "OSC port loads");
Expect(config.oscSmoothing == 0.25, "OSC smoothing loads");
Expect(config.inputVideoFormat == "720p", "input format loads");
Expect(config.inputFrameRate == "50", "input frame rate loads");
Expect(config.outputVideoFormat == "2160p", "output format loads");
Expect(config.outputFrameRate == "60", "output frame rate loads");
Expect(config.videoInputBackend == "none", "video input backend loads");
Expect(config.videoOutputBackend == "decklink", "video output backend loads");
Expect(config.input.resolution == "720p", "input resolution loads");
Expect(config.input.frameRate == "50", "input frame rate loads");
Expect(config.input.device == "input-card-1", "input device loads");
Expect(config.output.resolution == "2160p", "output resolution loads");
Expect(config.output.frameRate == "60", "output frame rate loads");
Expect(config.output.device == "output-card-1", "output device loads");
Expect(config.input.backend == "none", "video input backend loads");
Expect(config.output.backend == "decklink", "video output backend loads");
Expect(!config.autoReload, "auto reload loads");
Expect(config.maxTemporalHistoryFrames == 8, "history length loads");
Expect(config.previewEnabled, "preview enabled toggle loads");
Expect(config.previewFps == 24.0, "preview fps loads");
Expect(config.deckLink.externalKeyingEnabled, "external keying loads");
Expect(config.output.externalKeyingEnabled, "external keying loads");
Expect(config.output.outputAlphaRequired, "output alpha requirement loads");
std::filesystem::remove(path);
}

View File

@@ -38,8 +38,8 @@ void WriteFile(const std::filesystem::path& path, const std::string& contents)
int main()
{
RenderCadenceCompositor::AppConfig config = RenderCadenceCompositor::DefaultAppConfig();
config.outputVideoFormat = "1080p";
config.outputFrameRate = "59.94";
config.output.resolution = "1080p";
config.output.frameRate = "59.94";
RenderCadenceCompositor::CadenceTelemetrySnapshot telemetry;
telemetry.renderFps = 59.94;
@@ -117,8 +117,8 @@ int main()
ExpectContains(json, "\"type\":\"color\"", "state JSON should serialize parameter types for the UI");
ExpectContains(json, "\"width\":1920", "state JSON should expose output width");
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
ExpectContains(json, "\"videoInputBackend\":\"decklink\"", "state JSON should expose input backend");
ExpectContains(json, "\"videoOutputBackend\":\"decklink\"", "state JSON should expose output backend");
ExpectContains(json, "\"input\":{\"backend\":\"decklink\",\"device\":\"default\",\"resolution\":\"1080p\",\"frameRate\":\"59.94\"}", "state JSON should expose nested input config");
ExpectContains(json, "\"output\":{\"backend\":\"decklink\",\"device\":\"default\",\"resolution\":\"1080p\",\"frameRate\":\"59.94\",\"keying\"", "state JSON should expose nested output config");
ExpectContains(json, "\"videoOutput\":{\"enabled\":true,\"backend\":\"decklink\"", "state JSON should expose neutral video output status");
ExpectContains(json, "\"scheduleFailures\":2", "state JSON should expose neutral video output schedule failures");
ExpectContains(json, "\"backendMetrics\":{\"bufferedAvailable\":true,\"buffered\":4", "state JSON should expose backend-specific video output metrics");