From e006fcc6ee10d0c949c1f55bfeb005d85f87c556 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 22 May 2026 16:05:40 +1000 Subject: [PATCH] config cleanup --- README.md | 32 ++++++++----- config/runtime-host.json | 25 ++++++---- docs/CURRENT_SYSTEM_ARCHITECTURE.md | 2 +- docs/openapi.yaml | 47 ++++++++++++------- src/README.md | 19 ++++---- src/RenderCadenceCompositor.cpp | 20 ++++---- src/app/AppConfig.cpp | 18 +++---- src/app/AppConfig.h | 28 ++++++++--- src/app/AppConfigProvider.cpp | 40 +++++++++++++--- src/app/RenderCadenceApp.h | 12 +++-- src/app/VideoBackendFactory.cpp | 14 +++--- src/control/RuntimeStateJson.h | 42 +++++++++++------ src/video/core/VideoMode.cpp | 16 +++---- src/video/core/VideoMode.h | 4 +- src/video/decklink/DeckLinkSession.cpp | 2 +- ...adenceCompositorAppConfigProviderTests.cpp | 42 +++++++++++------ ...CadenceCompositorRuntimeStateJsonTests.cpp | 8 ++-- 17 files changed, 238 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index 4ad01cd..a12b55a 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/config/runtime-host.json b/config/runtime-host.json index 290c04f..4df5063 100644 --- a/config/runtime-host.json +++ b/config/runtime-host.json @@ -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 } diff --git a/docs/CURRENT_SYSTEM_ARCHITECTURE.md b/docs/CURRENT_SYSTEM_ARCHITECTURE.md index 8dd07f7..72230b5 100644 --- a/docs/CURRENT_SYSTEM_ARCHITECTURE.md +++ b/docs/CURRENT_SYSTEM_ARCHITECTURE.md @@ -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. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index b506e90..6fc0aac 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -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: diff --git a/src/README.md b/src/README.md index 16b1191..21b7f35 100644 --- a/src/README.md +++ b/src/README.md @@ -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. diff --git a/src/RenderCadenceCompositor.cpp b/src/RenderCadenceCompositor.cpp index 33f0e95..f34a6d9 100644 --- a/src/RenderCadenceCompositor.cpp +++ b/src/RenderCadenceCompositor.cpp @@ -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; diff --git a/src/app/AppConfig.cpp b/src/app/AppConfig.cpp index f8752ef..efce457 100644 --- a/src/app/AppConfig.cpp +++ b/src/app/AppConfig.cpp @@ -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; diff --git a/src/app/AppConfig.h b/src/app/AppConfig.h index 865dc57..6fae80f 100644 --- a/src/app/AppConfig.h +++ b/src/app/AppConfig.h @@ -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; diff --git a/src/app/AppConfigProvider.cpp b/src/app/AppConfigProvider.cpp index 2c25fe9..c62d415 100644 --- a/src/app/AppConfigProvider.cpp +++ b/src/app/AppConfigProvider.cpp @@ -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(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(); diff --git a/src/app/RenderCadenceApp.h b/src/app/RenderCadenceApp.h index 0e8de99..e5bfafc 100644 --- a/src/app/RenderCadenceApp.h +++ b/src/app/RenderCadenceApp.h @@ -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", diff --git a/src/app/VideoBackendFactory.cpp b/src/app/VideoBackendFactory.cpp index 72b3055..3ccf097 100644 --- a/src/app/VideoBackendFactory.cpp +++ b/src/app/VideoBackendFactory.cpp @@ -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 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("Unsupported videoOutputBackend: " + config.videoOutputBackend); + return std::make_unique("Unsupported output backend: " + config.output.backend); } std::unique_ptr StartVideoInputBackend( @@ -298,7 +298,7 @@ std::unique_ptr 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 StartVideoInputBackend( return backend->start(context); } - LogWarning("app", "Unsupported videoInputBackend '" + config.videoInputBackend + "'; runtime shaders will use fallback input."); - return std::make_unique(config.videoInputBackend); + LogWarning("app", "Unsupported input backend '" + config.input.backend + "'; runtime shaders will use fallback input."); + return std::make_unique(config.input.backend); } } diff --git a/src/control/RuntimeStateJson.h b/src/control/RuntimeStateJson.h index 5d2fb73..ed52e9b 100644 --- a/src/control/RuntimeStateJson.h +++ b/src/control/RuntimeStateJson.h @@ -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(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); diff --git a/src/video/core/VideoMode.cpp b/src/video/core/VideoMode.cpp index 394ec7f..b650058 100644 --- a/src/video/core/VideoMode.cpp +++ b/src/video/core/VideoMode.cpp @@ -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; } diff --git a/src/video/core/VideoMode.h b/src/video/core/VideoMode.h index 9963813..e34e8ea 100644 --- a/src/video/core/VideoMode.h +++ b/src/video/core/VideoMode.h @@ -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); diff --git a/src/video/decklink/DeckLinkSession.cpp b/src/video/decklink/DeckLinkSession.cpp index 22aac1b..47115c6 100644 --- a/src/video/decklink/DeckLinkSession.cpp +++ b/src/video/decklink/DeckLinkSession.cpp @@ -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); diff --git a/tests/RenderCadenceCompositorAppConfigProviderTests.cpp b/tests/RenderCadenceCompositorAppConfigProviderTests.cpp index 697b97d..ec9e36f 100644 --- a/tests/RenderCadenceCompositorAppConfigProviderTests.cpp +++ b/tests/RenderCadenceCompositorAppConfigProviderTests.cpp @@ -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); } diff --git a/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp b/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp index 407e2dd..63d3f42 100644 --- a/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp +++ b/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp @@ -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");