From 659fbef1a53618a79635f0f3dda2153a9ecbbf1a Mon Sep 17 00:00:00 2001 From: Aiden Date: Wed, 3 Jun 2026 01:08:43 -0400 Subject: [PATCH] OCIO and Fullscreen stubs --- README.md | 28 ++++ config/runtime-host.json | 25 ++++ config/runtime-host.schema.json | 135 ++++++++++++++++++ src/app/AppConfig.cpp | 21 +++ src/app/AppConfig.h | 31 ++++ src/app/AppConfigJson.cpp | 88 ++++++++++++ src/app/AppConfigProvider.cpp | 53 +++++++ src/app/VideoBackendFactory.cpp | 7 + src/control/RuntimeStateJson.h | 27 ++++ ...adenceCompositorAppConfigProviderTests.cpp | 54 +++++++ ...CadenceCompositorRuntimeStateJsonTests.cpp | 3 + 11 files changed, 472 insertions(+) diff --git a/README.md b/README.md index 0825760..0d83e7d 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,34 @@ http://127.0.0.1: `/api/state` exposes backend-neutral output telemetry in `videoOutput`. Use `videoOutput.enabled`, `videoOutput.backend`, and `videoOutput.scheduleFailures` for portable status. Backend-specific counters live in `videoOutput.backendMetrics`. +### Borderless Window / Fullscreen Output Plan + +The preview window is a best-effort diagnostic view and is allowed to drop or reuse frames without disturbing cadence. A borderless window or fullscreen display output should be implemented as a real `output.backend`, separate from preview, so it can target full configured resolution and frame rate. + +Rough plan: + +- Add a `window` video output backend behind the existing `IVideoOutput` factory, selectable with `"output": { "backend": "window", ... }`. +- Give it its own device/display selection, fullscreen/borderless mode, window position, vsync policy, and optional monitor refresh validation in `runtime-host.json`. +- Consume `SystemFrameExchange` frames like the other output backends, on an output-owned thread, without blocking the render thread. +- Upload/present frames through a dedicated GL/DX window context or shared-context path, with a bounded mailbox so late window presentation never back-pressures render cadence. +- Report backend-neutral telemetry through `videoOutput` and put display/window-specific details under `videoOutput.backendMetrics`. +- Keep the existing preview window as an operator confidence monitor only, not as the full-quality display output path. + +### OCIO / Linear 16-Bit Float Pipeline Plan + +The render backend should move toward a true linear-light `RGBA16F` pipeline, with OpenColorIO used for explicit color transforms at the video I/O and display edges. Shader packages should be able to assume a stable working space instead of guessing whether input pixels are display-referred, log, Rec.709, HDR, or already linear. + +Rough plan: + +- Add OCIO as an optional third-party dependency and package its required runtime DLLs, config files, and license notices with Release builds. +- Extend `runtime-host.json` with color settings for input, working, output, and preview/display transforms, including OCIO config path, input color space, working color space, output color space, view, look, exposure, and gamma. +- Convert captured/input frames into the configured linear working space before shader layers sample them. +- Allocate runtime render targets, temporal history, feedback buffers, and intermediate layer buffers as `RGBA16F` by default, with explicit fallbacks for unsupported hardware. +- Keep shader parameter uploads and texture assets explicit about color intent: data textures stay data, image/video textures can opt into OCIO or known transfer conversion. +- Apply the output transform only at backend edges: DeckLink/NDI/window output, screenshots, and preview each get the correct display/output transform for their target. +- Prebuild OCIO GPU shader/lut resources off the render thread and commit prepared resources to the render thread only when ready, following the render cadence golden rules. +- Add test coverage for config parsing, color-pipeline defaults, fallback behavior, and shader/runtime state reporting. + ## Runtime State The current layer stack is autosaved to `runtime/runtime_state.json` whenever durable UI/API layer changes are accepted: add/remove, shader assignment, bypass state, ordering, parameter updates, parameter reset, and reload compatibility refreshes. Saves are debounced and written on a background worker, with a final flush during shutdown. diff --git a/config/runtime-host.json b/config/runtime-host.json index a1bedf7..288dd3d 100644 --- a/config/runtime-host.json +++ b/config/runtime-host.json @@ -22,6 +22,31 @@ "pixelFormat": "auto", "resolution": "1080p" }, + "windowOutput": { + "fullscreen": true, + "borderless": true, + "display": "default", + "x": 0, + "y": 0, + "width": 0, + "height": 0, + "vsync": true, + "allowTearing": false + }, + "colorPipeline": { + "ocioEnabled": false, + "ocioConfig": "", + "inputColorSpace": "scene_linear", + "workingColorSpace": "scene_linear", + "outputColorSpace": "rec709", + "display": "sRGB", + "view": "Rec.709", + "look": "", + "exposure": 0, + "gamma": 1, + "workingFormat": "rgba16f", + "linearWorkingSpace": true + }, "previewEnabled": true, "previewFps": 59.94, "runtimeShaderId": "", diff --git a/config/runtime-host.schema.json b/config/runtime-host.schema.json index 9c6ce3e..31e5d64 100644 --- a/config/runtime-host.schema.json +++ b/config/runtime-host.schema.json @@ -46,6 +46,12 @@ "output": { "$ref": "#/$defs/output" }, + "windowOutput": { + "$ref": "#/$defs/windowOutput" + }, + "colorPipeline": { + "$ref": "#/$defs/colorPipeline" + }, "autoReload": { "type": "boolean", "default": true, @@ -86,6 +92,9 @@ "enum": [ "decklink", "ndi", + "window", + "fullscreen", + "borderless", "none", "disabled", "off" @@ -221,6 +230,132 @@ "resolution", "frameRate" ] + }, + "windowOutput": { + "type": "object", + "additionalProperties": false, + "description": "Reserved settings for a future full-resolution/full-frame-rate borderless or fullscreen video output backend. These are not used by the preview window.", + "properties": { + "fullscreen": { + "type": "boolean", + "default": true, + "description": "When the future window backend is implemented, request exclusive/fullscreen-style presentation." + }, + "borderless": { + "type": "boolean", + "default": true, + "description": "When true, the future window backend should create a borderless output window." + }, + "display": { + "type": "string", + "default": "default", + "description": "Display/monitor selector for the future window backend." + }, + "x": { + "type": "integer", + "default": 0, + "description": "Window X position used when fullscreen is false or when a backend needs explicit placement." + }, + "y": { + "type": "integer", + "default": 0, + "description": "Window Y position used when fullscreen is false or when a backend needs explicit placement." + }, + "width": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "Optional window width override. Zero means use output resolution." + }, + "height": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "Optional window height override. Zero means use output resolution." + }, + "vsync": { + "type": "boolean", + "default": true, + "description": "Presentation sync request for the future window backend." + }, + "allowTearing": { + "type": "boolean", + "default": false, + "description": "Allow tearing for the future window backend when the graphics API and OS support it." + } + } + }, + "colorPipeline": { + "type": "object", + "additionalProperties": false, + "description": "Reserved color-management settings for a future OCIO-backed linear RGBA16F render pipeline.", + "properties": { + "ocioEnabled": { + "type": "boolean", + "default": false, + "description": "Enable future OpenColorIO transforms. Currently parsed and reported only." + }, + "ocioConfig": { + "type": "string", + "default": "", + "description": "Path to an OCIO config file or package-relative config identifier." + }, + "inputColorSpace": { + "type": "string", + "default": "scene_linear", + "description": "Input/video color space to convert from at the input edge." + }, + "workingColorSpace": { + "type": "string", + "default": "scene_linear", + "description": "Linear working space shader packages should be able to assume." + }, + "outputColorSpace": { + "type": "string", + "default": "rec709", + "description": "Output transform target color space for scheduled video output." + }, + "display": { + "type": "string", + "default": "sRGB", + "description": "OCIO display name for preview/window/display transforms." + }, + "view": { + "type": "string", + "default": "Rec.709", + "description": "OCIO view name for preview/window/display transforms." + }, + "look": { + "type": "string", + "default": "", + "description": "Optional OCIO look name." + }, + "exposure": { + "type": "number", + "default": 0, + "description": "Reserved display transform exposure adjustment." + }, + "gamma": { + "type": "number", + "exclusiveMinimum": 0, + "default": 1, + "description": "Reserved display transform gamma adjustment." + }, + "workingFormat": { + "type": "string", + "enum": [ + "rgba16f", + "rgba32f" + ], + "default": "rgba16f", + "description": "Future render target/intermediate working format." + }, + "linearWorkingSpace": { + "type": "boolean", + "default": true, + "description": "Documents the intended linear-light working pipeline." + } + } } } } diff --git a/src/app/AppConfig.cpp b/src/app/AppConfig.cpp index 4f10047..8009416 100644 --- a/src/app/AppConfig.cpp +++ b/src/app/AppConfig.cpp @@ -17,6 +17,27 @@ AppConfig DefaultAppConfig() config.output.externalKeyingEnabled = false; config.output.outputAlphaRequired = false; config.output.systemFramePixelFormat = VideoIOPixelFormat::Bgra8; + config.windowOutput.fullscreen = true; + config.windowOutput.borderless = true; + config.windowOutput.display = "default"; + config.windowOutput.x = 0; + config.windowOutput.y = 0; + config.windowOutput.width = 0; + config.windowOutput.height = 0; + config.windowOutput.vsync = true; + config.windowOutput.allowTearing = false; + config.colorPipeline.ocioEnabled = false; + config.colorPipeline.ocioConfig.clear(); + config.colorPipeline.inputColorSpace = "scene_linear"; + config.colorPipeline.workingColorSpace = "scene_linear"; + config.colorPipeline.outputColorSpace = "rec709"; + config.colorPipeline.display = "sRGB"; + config.colorPipeline.view = "Rec.709"; + config.colorPipeline.look.clear(); + config.colorPipeline.exposure = 0.0; + config.colorPipeline.gamma = 1.0; + config.colorPipeline.workingFormat = "rgba16f"; + config.colorPipeline.linearWorkingSpace = true; config.outputThread.targetBufferedFrames = 4; config.telemetry.interval = std::chrono::seconds(1); config.logging.minimumLevel = LogLevel::Log; diff --git a/src/app/AppConfig.h b/src/app/AppConfig.h index 1a33be3..6630b1d 100644 --- a/src/app/AppConfig.h +++ b/src/app/AppConfig.h @@ -34,10 +34,41 @@ struct VideoOutputAppConfig VideoFormat videoMode; }; +struct WindowOutputAppConfig +{ + bool fullscreen = true; + bool borderless = true; + std::string display = "default"; + int x = 0; + int y = 0; + unsigned width = 0; + unsigned height = 0; + bool vsync = true; + bool allowTearing = false; +}; + +struct ColorPipelineAppConfig +{ + bool ocioEnabled = false; + std::string ocioConfig; + std::string inputColorSpace = "scene_linear"; + std::string workingColorSpace = "scene_linear"; + std::string outputColorSpace = "rec709"; + std::string display = "sRGB"; + std::string view = "Rec.709"; + std::string look; + double exposure = 0.0; + double gamma = 1.0; + std::string workingFormat = "rgba16f"; + bool linearWorkingSpace = true; +}; + struct AppConfig { VideoInputAppConfig input; VideoOutputAppConfig output; + WindowOutputAppConfig windowOutput; + ColorPipelineAppConfig colorPipeline; VideoOutputThreadConfig outputThread; TelemetryHealthMonitorConfig telemetry; LoggerConfig logging; diff --git a/src/app/AppConfigJson.cpp b/src/app/AppConfigJson.cpp index 9925698..ff2df2c 100644 --- a/src/app/AppConfigJson.cpp +++ b/src/app/AppConfigJson.cpp @@ -40,6 +40,20 @@ void ApplySize(const JsonValue& root, const char* key, std::size_t& target) target = static_cast(value->asNumber()); } +void ApplyInt(const JsonValue& root, const char* key, int& target) +{ + const JsonValue* value = Find(root, key); + if (value && value->isNumber()) + target = static_cast(value->asNumber()); +} + +void ApplyUnsigned(const JsonValue& root, const char* key, unsigned& target) +{ + const JsonValue* value = Find(root, key); + if (value && value->isNumber() && value->asNumber() >= 0.0) + target = static_cast(value->asNumber()); +} + void ApplyPort(const JsonValue& root, const char* key, unsigned short& target) { const JsonValue* value = Find(root, key); @@ -108,6 +122,43 @@ void ApplyOutputConfig(const JsonValue& root, AppConfig& config) config.output.externalKeyingEnabled = false; } +void ApplyWindowOutputConfig(const JsonValue& root, AppConfig& config) +{ + const JsonValue* window = Find(root, "windowOutput"); + if (!window || !window->isObject()) + return; + + ApplyBool(*window, "fullscreen", config.windowOutput.fullscreen); + ApplyBool(*window, "borderless", config.windowOutput.borderless); + ApplyString(*window, "display", config.windowOutput.display); + ApplyInt(*window, "x", config.windowOutput.x); + ApplyInt(*window, "y", config.windowOutput.y); + ApplyUnsigned(*window, "width", config.windowOutput.width); + ApplyUnsigned(*window, "height", config.windowOutput.height); + ApplyBool(*window, "vsync", config.windowOutput.vsync); + ApplyBool(*window, "allowTearing", config.windowOutput.allowTearing); +} + +void ApplyColorPipelineConfig(const JsonValue& root, AppConfig& config) +{ + const JsonValue* color = Find(root, "colorPipeline"); + if (!color || !color->isObject()) + return; + + ApplyBool(*color, "ocioEnabled", config.colorPipeline.ocioEnabled); + ApplyString(*color, "ocioConfig", config.colorPipeline.ocioConfig); + ApplyString(*color, "inputColorSpace", config.colorPipeline.inputColorSpace); + ApplyString(*color, "workingColorSpace", config.colorPipeline.workingColorSpace); + ApplyString(*color, "outputColorSpace", config.colorPipeline.outputColorSpace); + ApplyString(*color, "display", config.colorPipeline.display); + ApplyString(*color, "view", config.colorPipeline.view); + ApplyString(*color, "look", config.colorPipeline.look); + ApplyDouble(*color, "exposure", config.colorPipeline.exposure); + ApplyDouble(*color, "gamma", config.colorPipeline.gamma); + ApplyString(*color, "workingFormat", config.colorPipeline.workingFormat); + ApplyBool(*color, "linearWorkingSpace", config.colorPipeline.linearWorkingSpace); +} + JsonValue InputConfigToJson(const VideoInputAppConfig& input) { JsonValue value = JsonValue::MakeObject(); @@ -133,6 +184,39 @@ JsonValue OutputConfigToJson(const VideoOutputAppConfig& output) value.set("keying", keying); return value; } + +JsonValue WindowOutputConfigToJson(const WindowOutputAppConfig& window) +{ + JsonValue value = JsonValue::MakeObject(); + value.set("fullscreen", JsonValue(window.fullscreen)); + value.set("borderless", JsonValue(window.borderless)); + value.set("display", JsonValue(window.display)); + value.set("x", JsonValue(static_cast(window.x))); + value.set("y", JsonValue(static_cast(window.y))); + value.set("width", JsonValue(static_cast(window.width))); + value.set("height", JsonValue(static_cast(window.height))); + value.set("vsync", JsonValue(window.vsync)); + value.set("allowTearing", JsonValue(window.allowTearing)); + return value; +} + +JsonValue ColorPipelineConfigToJson(const ColorPipelineAppConfig& color) +{ + JsonValue value = JsonValue::MakeObject(); + value.set("ocioEnabled", JsonValue(color.ocioEnabled)); + value.set("ocioConfig", JsonValue(color.ocioConfig)); + value.set("inputColorSpace", JsonValue(color.inputColorSpace)); + value.set("workingColorSpace", JsonValue(color.workingColorSpace)); + value.set("outputColorSpace", JsonValue(color.outputColorSpace)); + value.set("display", JsonValue(color.display)); + value.set("view", JsonValue(color.view)); + value.set("look", JsonValue(color.look)); + value.set("exposure", JsonValue(color.exposure)); + value.set("gamma", JsonValue(color.gamma)); + value.set("workingFormat", JsonValue(color.workingFormat)); + value.set("linearWorkingSpace", JsonValue(color.linearWorkingSpace)); + return value; +} } bool ApplyAppConfigJson(const JsonValue& root, AppConfig& config, std::string* error) @@ -151,6 +235,8 @@ bool ApplyAppConfigJson(const JsonValue& root, AppConfig& config, std::string* e ApplyDouble(root, "oscSmoothing", config.oscSmoothing); ApplyInputConfig(root, config); ApplyOutputConfig(root, config); + ApplyWindowOutputConfig(root, config); + ApplyColorPipelineConfig(root, config); ApplyBool(root, "autoReload", config.autoReload); ApplySize(root, "maxTemporalHistoryFrames", config.maxTemporalHistoryFrames); ApplyBool(root, "previewEnabled", config.previewEnabled); @@ -186,6 +272,8 @@ JsonValue AppConfigToJsonValue(const AppConfig& config) root.set("oscSmoothing", NumberValue(config.oscSmoothing)); root.set("input", InputConfigToJson(config.input)); root.set("output", OutputConfigToJson(config.output)); + root.set("windowOutput", WindowOutputConfigToJson(config.windowOutput)); + root.set("colorPipeline", ColorPipelineConfigToJson(config.colorPipeline)); root.set("autoReload", JsonValue(config.autoReload)); root.set("maxTemporalHistoryFrames", SizeValue(config.maxTemporalHistoryFrames)); root.set("previewEnabled", JsonValue(config.previewEnabled)); diff --git a/src/app/AppConfigProvider.cpp b/src/app/AppConfigProvider.cpp index bcb9420..0fcca3a 100644 --- a/src/app/AppConfigProvider.cpp +++ b/src/app/AppConfigProvider.cpp @@ -70,6 +70,20 @@ void ApplySize(const JsonValue& root, const char* key, std::size_t& target) target = static_cast(value->asNumber()); } +void ApplyInt(const JsonValue& root, const char* key, int& target) +{ + const JsonValue* value = Find(root, key); + if (value && value->isNumber()) + target = static_cast(value->asNumber()); +} + +void ApplyUnsigned(const JsonValue& root, const char* key, unsigned& target) +{ + const JsonValue* value = Find(root, key); + if (value && value->isNumber() && value->asNumber() >= 0.0) + target = static_cast(value->asNumber()); +} + void ApplyPort(const JsonValue& root, const char* key, unsigned short& target) { const JsonValue* value = Find(root, key); @@ -127,6 +141,43 @@ void ApplyOutputConfig(const JsonValue& root, AppConfig& config) if (config.output.backend != "decklink") config.output.externalKeyingEnabled = false; } + +void ApplyWindowOutputConfig(const JsonValue& root, AppConfig& config) +{ + const JsonValue* window = Find(root, "windowOutput"); + if (!window || !window->isObject()) + return; + + ApplyBool(*window, "fullscreen", config.windowOutput.fullscreen); + ApplyBool(*window, "borderless", config.windowOutput.borderless); + ApplyString(*window, "display", config.windowOutput.display); + ApplyInt(*window, "x", config.windowOutput.x); + ApplyInt(*window, "y", config.windowOutput.y); + ApplyUnsigned(*window, "width", config.windowOutput.width); + ApplyUnsigned(*window, "height", config.windowOutput.height); + ApplyBool(*window, "vsync", config.windowOutput.vsync); + ApplyBool(*window, "allowTearing", config.windowOutput.allowTearing); +} + +void ApplyColorPipelineConfig(const JsonValue& root, AppConfig& config) +{ + const JsonValue* color = Find(root, "colorPipeline"); + if (!color || !color->isObject()) + return; + + ApplyBool(*color, "ocioEnabled", config.colorPipeline.ocioEnabled); + ApplyString(*color, "ocioConfig", config.colorPipeline.ocioConfig); + ApplyString(*color, "inputColorSpace", config.colorPipeline.inputColorSpace); + ApplyString(*color, "workingColorSpace", config.colorPipeline.workingColorSpace); + ApplyString(*color, "outputColorSpace", config.colorPipeline.outputColorSpace); + ApplyString(*color, "display", config.colorPipeline.display); + ApplyString(*color, "view", config.colorPipeline.view); + ApplyString(*color, "look", config.colorPipeline.look); + ApplyDouble(*color, "exposure", config.colorPipeline.exposure); + ApplyDouble(*color, "gamma", config.colorPipeline.gamma); + ApplyString(*color, "workingFormat", config.colorPipeline.workingFormat); + ApplyBool(*color, "linearWorkingSpace", config.colorPipeline.linearWorkingSpace); +} } AppConfigProvider::AppConfigProvider() : @@ -174,6 +225,8 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err ApplyDouble(root, "oscSmoothing", mConfig.oscSmoothing); ApplyInputConfig(root, mConfig); ApplyOutputConfig(root, mConfig); + ApplyWindowOutputConfig(root, mConfig); + ApplyColorPipelineConfig(root, mConfig); ApplyBool(root, "autoReload", mConfig.autoReload); ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames); ApplyBool(root, "previewEnabled", mConfig.previewEnabled); diff --git a/src/app/VideoBackendFactory.cpp b/src/app/VideoBackendFactory.cpp index 7645875..ed9bddb 100644 --- a/src/app/VideoBackendFactory.cpp +++ b/src/app/VideoBackendFactory.cpp @@ -288,6 +288,12 @@ std::unique_ptr CreateNdiOutputBackend(const AppConfig& config return std::make_unique(config.output.device); } +std::unique_ptr CreateWindowOutputBackend(const AppConfig&) +{ + return std::make_unique( + "Window output backend is reserved but not implemented yet. Use preview for best-effort monitoring or DeckLink/NDI for scheduled output."); +} + std::unique_ptr StartNoInputBackend(const VideoInputBackendStartContext&) { Log("app", "Video input backend disabled by config."); @@ -329,6 +335,7 @@ const std::vector& VideoOutputBackendRegistry() static const std::vector registry = { VideoOutputBackendRegistration{ { "decklink" }, true, &CreateDeckLinkOutputBackend }, VideoOutputBackendRegistration{ { "ndi" }, false, &CreateNdiOutputBackend }, + VideoOutputBackendRegistration{ { "window", "fullscreen", "borderless" }, false, &CreateWindowOutputBackend }, VideoOutputBackendRegistration{ { "none", "disabled", "off" }, false, &CreateDisabledOutputBackend }, }; return registry; diff --git a/src/control/RuntimeStateJson.h b/src/control/RuntimeStateJson.h index db270b9..22860cf 100644 --- a/src/control/RuntimeStateJson.h +++ b/src/control/RuntimeStateJson.h @@ -420,6 +420,33 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input) writer.KeyBool("alphaRequired", input.config.output.outputAlphaRequired); writer.EndObject(); writer.EndObject(); + writer.Key("windowOutput"); + writer.BeginObject(); + writer.KeyBool("fullscreen", input.config.windowOutput.fullscreen); + writer.KeyBool("borderless", input.config.windowOutput.borderless); + writer.KeyString("display", input.config.windowOutput.display); + writer.KeyInt("x", input.config.windowOutput.x); + writer.KeyInt("y", input.config.windowOutput.y); + writer.KeyUInt("width", input.config.windowOutput.width); + writer.KeyUInt("height", input.config.windowOutput.height); + writer.KeyBool("vsync", input.config.windowOutput.vsync); + writer.KeyBool("allowTearing", input.config.windowOutput.allowTearing); + writer.EndObject(); + writer.Key("colorPipeline"); + writer.BeginObject(); + writer.KeyBool("ocioEnabled", input.config.colorPipeline.ocioEnabled); + writer.KeyString("ocioConfig", input.config.colorPipeline.ocioConfig); + writer.KeyString("inputColorSpace", input.config.colorPipeline.inputColorSpace); + writer.KeyString("workingColorSpace", input.config.colorPipeline.workingColorSpace); + writer.KeyString("outputColorSpace", input.config.colorPipeline.outputColorSpace); + writer.KeyString("display", input.config.colorPipeline.display); + writer.KeyString("view", input.config.colorPipeline.view); + writer.KeyString("look", input.config.colorPipeline.look); + writer.KeyDouble("exposure", input.config.colorPipeline.exposure); + writer.KeyDouble("gamma", input.config.colorPipeline.gamma); + writer.KeyString("workingFormat", input.config.colorPipeline.workingFormat); + writer.KeyBool("linearWorkingSpace", input.config.colorPipeline.linearWorkingSpace); + writer.EndObject(); writer.EndObject(); writer.Key("runtime"); diff --git a/tests/RenderCadenceCompositorAppConfigProviderTests.cpp b/tests/RenderCadenceCompositorAppConfigProviderTests.cpp index 49c454e..404f4a7 100644 --- a/tests/RenderCadenceCompositorAppConfigProviderTests.cpp +++ b/tests/RenderCadenceCompositorAppConfigProviderTests.cpp @@ -49,6 +49,31 @@ std::filesystem::path WriteConfigFixture() << " \"alphaRequired\": true\n" << " }\n" << " },\n" + << " \"windowOutput\": {\n" + << " \"fullscreen\": false,\n" + << " \"borderless\": true,\n" + << " \"display\": \"Display 2\",\n" + << " \"x\": 1920,\n" + << " \"y\": 0,\n" + << " \"width\": 1920,\n" + << " \"height\": 1080,\n" + << " \"vsync\": true,\n" + << " \"allowTearing\": false\n" + << " },\n" + << " \"colorPipeline\": {\n" + << " \"ocioEnabled\": true,\n" + << " \"ocioConfig\": \"config/ocio/studio.ocio\",\n" + << " \"inputColorSpace\": \"rec709\",\n" + << " \"workingColorSpace\": \"acescg\",\n" + << " \"outputColorSpace\": \"rec709_display\",\n" + << " \"display\": \"sRGB\",\n" + << " \"view\": \"Filmic\",\n" + << " \"look\": \"none\",\n" + << " \"exposure\": 0.5,\n" + << " \"gamma\": 1.1,\n" + << " \"workingFormat\": \"rgba16f\",\n" + << " \"linearWorkingSpace\": true\n" + << " },\n" << " \"autoReload\": false,\n" << " \"maxTemporalHistoryFrames\": 8,\n" << " \"previewEnabled\": true,\n" @@ -92,6 +117,27 @@ void TestLoadsRuntimeHostConfig() Expect(config.runtimeShaderId == "solid-color", "runtime shader id loads"); Expect(config.output.externalKeyingEnabled, "external keying loads"); Expect(config.output.outputAlphaRequired, "output alpha requirement loads"); + Expect(!config.windowOutput.fullscreen, "window output fullscreen loads"); + Expect(config.windowOutput.borderless, "window output borderless loads"); + Expect(config.windowOutput.display == "Display 2", "window output display loads"); + Expect(config.windowOutput.x == 1920, "window output x loads"); + Expect(config.windowOutput.y == 0, "window output y loads"); + Expect(config.windowOutput.width == 1920, "window output width loads"); + Expect(config.windowOutput.height == 1080, "window output height loads"); + Expect(config.windowOutput.vsync, "window output vsync loads"); + Expect(!config.windowOutput.allowTearing, "window output tearing flag loads"); + Expect(config.colorPipeline.ocioEnabled, "OCIO enabled loads"); + Expect(config.colorPipeline.ocioConfig == "config/ocio/studio.ocio", "OCIO config path loads"); + Expect(config.colorPipeline.inputColorSpace == "rec709", "input color space loads"); + Expect(config.colorPipeline.workingColorSpace == "acescg", "working color space loads"); + Expect(config.colorPipeline.outputColorSpace == "rec709_display", "output color space loads"); + Expect(config.colorPipeline.display == "sRGB", "OCIO display loads"); + Expect(config.colorPipeline.view == "Filmic", "OCIO view loads"); + Expect(config.colorPipeline.look == "none", "OCIO look loads"); + Expect(config.colorPipeline.exposure == 0.5, "OCIO exposure loads"); + Expect(config.colorPipeline.gamma == 1.1, "OCIO gamma loads"); + Expect(config.colorPipeline.workingFormat == "rgba16f", "working format loads"); + Expect(config.colorPipeline.linearWorkingSpace, "linear working space flag loads"); std::filesystem::remove(path); } @@ -154,6 +200,10 @@ void TestConfigJsonRoundTrip() config.output.backend = "ndi"; config.output.device = "Program"; config.output.pixelFormat = "uyvy8"; + config.windowOutput.fullscreen = false; + config.windowOutput.display = "Display 3"; + config.colorPipeline.ocioEnabled = true; + config.colorPipeline.workingColorSpace = "acescg"; config.previewEnabled = true; config.runtimeShaderId = "solid-color"; @@ -166,6 +216,10 @@ void TestConfigJsonRoundTrip() Expect(parsed.output.backend == "ndi", "output backend round trips"); Expect(parsed.output.device == "Program", "output device round trips"); Expect(parsed.output.pixelFormat == "uyvy8", "output pixel format round trips"); + Expect(!parsed.windowOutput.fullscreen, "window output fullscreen round trips"); + Expect(parsed.windowOutput.display == "Display 3", "window output display round trips"); + Expect(parsed.colorPipeline.ocioEnabled, "OCIO enabled round trips"); + Expect(parsed.colorPipeline.workingColorSpace == "acescg", "working color space round trips"); Expect(parsed.previewEnabled, "preview enabled round trips"); Expect(parsed.runtimeShaderId == "solid-color", "runtime shader id round trips"); } diff --git a/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp b/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp index b72de59..5f8ba82 100644 --- a/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp +++ b/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp @@ -131,6 +131,9 @@ int main() ExpectContains(json, "\"height\":1080", "state JSON should expose output height"); 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\",\"pixelFormat\":\"auto\",\"systemFramePixelFormat\":\"8-bit BGRA\",\"keying\"", "state JSON should expose nested output config"); + ExpectContains(json, "\"windowOutput\":{\"fullscreen\":true,\"borderless\":true,\"display\":\"default\"", "state JSON should expose window output stub config"); + ExpectContains(json, "\"colorPipeline\":{\"ocioEnabled\":false,\"ocioConfig\":\"\",\"inputColorSpace\":\"scene_linear\"", "state JSON should expose color pipeline stub config"); + ExpectContains(json, "\"workingFormat\":\"rgba16f\",\"linearWorkingSpace\":true", "state JSON should expose linear 16-bit float pipeline intent"); ExpectContains(json, "\"osc\":{\"configured\":true,\"listening\":false", "state JSON should expose OSC stub status"); 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");