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

@@ -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);