#pragma once #include "../app/AppConfig.h" #include "../app/AppConfigProvider.h" #include "../control/osc/OscControlServer.h" #include "../json/JsonWriter.h" #include "RuntimeLayerModel.h" #include "SupportedShaderCatalog.h" #include "../telemetry/CadenceTelemetryJson.h" #include #include #include #include namespace RenderCadenceCompositor { struct RuntimeStateJsonInput { const AppConfig& config; const CadenceTelemetrySnapshot& telemetry; unsigned short serverPort = 0; bool videoOutputEnabled = false; std::string videoOutputStatus; const OscControlServerState* osc = nullptr; std::function writeRuntimeJson; std::function writeCatalogJson; std::function writeLayersJson; }; inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input) { writer.BeginObject(); 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.output.externalKeyingEnabled); writer.KeyBool("externalKeyingActive", input.videoOutputEnabled && input.config.output.externalKeyingEnabled); writer.KeyString("statusMessage", input.videoOutputStatus); writer.EndObject(); } inline void WriteVideoOutputBackendMetricsJson(JsonWriter& writer, const RuntimeStateJsonInput& input) { writer.BeginObject(); if (input.config.output.backend == "decklink") { writer.KeyBool("bufferedAvailable", input.telemetry.deckLinkBufferedAvailable); writer.Key("buffered"); if (input.telemetry.deckLinkBufferedAvailable) writer.UInt(input.telemetry.deckLinkBuffered); else writer.Null(); writer.KeyDouble("scheduleCallMs", input.telemetry.deckLinkScheduleCallMilliseconds); writer.KeyBool("scheduleLeadAvailable", input.telemetry.deckLinkScheduleLeadAvailable); writer.Key("scheduleLeadFrames"); if (input.telemetry.deckLinkScheduleLeadAvailable) writer.Int(input.telemetry.deckLinkScheduleLeadFrames); else writer.Null(); writer.KeyUInt("playbackFrameIndex", input.telemetry.deckLinkPlaybackFrameIndex); writer.KeyUInt("nextScheduleFrameIndex", input.telemetry.deckLinkNextScheduleFrameIndex); writer.KeyInt("playbackStreamTime", input.telemetry.deckLinkPlaybackStreamTime); writer.KeyUInt("scheduleRealignments", input.telemetry.deckLinkScheduleRealignments); } writer.EndObject(); } inline void WriteVideoOutputTelemetryJson(JsonWriter& writer, const RuntimeStateJsonInput& input) { writer.BeginObject(); writer.KeyBool("enabled", input.videoOutputEnabled); writer.KeyString("backend", input.config.output.backend); writer.KeyString("statusMessage", input.videoOutputStatus); writer.KeyUInt("scheduleFailures", input.telemetry.scheduleFailures); writer.KeyUInt("completions", input.telemetry.completions); writer.KeyUInt("late", input.telemetry.displayedLate); writer.KeyUInt("dropped", input.telemetry.dropped); writer.Key("backendMetrics"); WriteVideoOutputBackendMetricsJson(writer, input); writer.EndObject(); } inline void OutputDimensions(const RuntimeStateJsonInput& input, unsigned& width, unsigned& height) { VideoFormatDimensions(input.config.output.resolution, width, height); } inline const char* ShaderParameterTypeName(ShaderParameterType type) { switch (type) { case ShaderParameterType::Float: return "float"; case ShaderParameterType::Vec2: return "vec2"; case ShaderParameterType::Color: return "color"; case ShaderParameterType::Boolean: return "bool"; case ShaderParameterType::Enum: return "enum"; case ShaderParameterType::Text: return "text"; case ShaderParameterType::Trigger: return "trigger"; } return "unknown"; } inline void WriteNumberArray(JsonWriter& writer, const std::vector& values) { writer.BeginArray(); for (double value : values) writer.Double(value); writer.EndArray(); } inline void WriteDefaultParameterValue(JsonWriter& writer, const ShaderParameterDefinition& parameter) { switch (parameter.type) { case ShaderParameterType::Boolean: writer.Bool(parameter.defaultBoolean); return; case ShaderParameterType::Enum: writer.String(parameter.defaultEnumValue); return; case ShaderParameterType::Text: writer.String(parameter.defaultTextValue); return; case ShaderParameterType::Trigger: writer.Double(0.0); return; case ShaderParameterType::Float: writer.Double(parameter.defaultNumbers.empty() ? 0.0 : parameter.defaultNumbers.front()); return; case ShaderParameterType::Vec2: case ShaderParameterType::Color: WriteNumberArray(writer, parameter.defaultNumbers); return; } writer.Null(); } inline void WriteParameterValue(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue& value) { switch (parameter.type) { case ShaderParameterType::Boolean: writer.Bool(value.booleanValue); return; case ShaderParameterType::Enum: writer.String(value.enumValue); return; case ShaderParameterType::Text: writer.String(value.textValue); return; case ShaderParameterType::Trigger: case ShaderParameterType::Float: writer.Double(value.numberValues.empty() ? 0.0 : value.numberValues.front()); return; case ShaderParameterType::Vec2: case ShaderParameterType::Color: WriteNumberArray(writer, value.numberValues); return; } writer.Null(); } inline void WriteTemporalJson(JsonWriter& writer, const TemporalSettings& temporal) { writer.BeginObject(); writer.KeyBool("enabled", temporal.enabled); writer.KeyString("historySource", "none"); writer.KeyUInt("requestedHistoryLength", temporal.requestedHistoryLength); writer.KeyUInt("effectiveHistoryLength", temporal.effectiveHistoryLength); writer.EndObject(); } inline void WriteFeedbackJson(JsonWriter& writer, const FeedbackSettings& feedback) { writer.BeginObject(); writer.KeyBool("enabled", feedback.enabled); writer.KeyString("writePass", feedback.writePassId); writer.EndObject(); } inline std::string ShaderAssetUrl(const std::string& shaderId, const std::string& assetPath) { return "/shader-assets/" + shaderId + "/" + assetPath; } inline void WriteShaderUiJson(JsonWriter& writer, const std::string& shaderId, const ShaderUiDefinition& ui) { if (!ui.enabled) { writer.Null(); return; } writer.BeginObject(); writer.KeyString("type", ui.type); writer.KeyString("entry", ui.entryPath); writer.KeyString("tag", ui.customElementTag); writer.KeyString("assetUrl", ShaderAssetUrl(shaderId, ui.entryPath)); writer.EndObject(); } inline const char* RuntimeLayerBuildStateName(RuntimeLayerBuildState state) { switch (state) { case RuntimeLayerBuildState::Pending: return "pending"; case RuntimeLayerBuildState::Ready: return "ready"; case RuntimeLayerBuildState::Failed: return "failed"; } return "unknown"; } inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue* value) { writer.BeginObject(); writer.KeyString("id", parameter.id); writer.KeyString("label", parameter.label.empty() ? parameter.id : parameter.label); writer.KeyString("description", parameter.description); writer.KeyString("type", ShaderParameterTypeName(parameter.type)); writer.Key("defaultValue"); WriteDefaultParameterValue(writer, parameter); writer.Key("value"); if (value) WriteParameterValue(writer, parameter, *value); else WriteDefaultParameterValue(writer, parameter); if (!parameter.minNumbers.empty()) { writer.Key("min"); WriteNumberArray(writer, parameter.minNumbers); } if (!parameter.maxNumbers.empty()) { writer.Key("max"); WriteNumberArray(writer, parameter.maxNumbers); } if (!parameter.stepNumbers.empty()) { writer.Key("step"); WriteNumberArray(writer, parameter.stepNumbers); } if (parameter.type == ShaderParameterType::Enum) { writer.Key("options"); writer.BeginArray(); for (const ShaderParameterOption& option : parameter.enumOptions) { writer.BeginObject(); writer.KeyString("value", option.value); writer.KeyString("label", option.label.empty() ? option.value : option.label); writer.EndObject(); } writer.EndArray(); } if (parameter.type == ShaderParameterType::Text) { writer.KeyUInt("maxLength", parameter.maxLength); if (!parameter.fontId.empty()) writer.KeyString("font", parameter.fontId); if (!parameter.fontParameterId.empty()) writer.KeyString("fontParameter", parameter.fontParameterId); } writer.EndObject(); } inline void WriteShaderRuntimeJson(JsonWriter& writer, const RuntimeLayerModelSnapshot& runtimeLayers) { writer.BeginObject(); writer.KeyUInt("layerCount", static_cast(runtimeLayers.displayLayers.size())); writer.KeyBool("compileSucceeded", runtimeLayers.compileSucceeded); writer.KeyString("compileMessage", runtimeLayers.compileMessage); writer.EndObject(); } inline void WriteEmptyRuntimeJson(JsonWriter& writer) { writer.BeginObject(); writer.KeyUInt("layerCount", 0); writer.KeyBool("compileSucceeded", true); writer.KeyString("compileMessage", "No runtime content controller is active."); writer.EndObject(); } inline void WriteShaderCatalogJson(JsonWriter& writer, const SupportedShaderCatalog& shaderCatalog) { writer.BeginArray(); for (const SupportedShaderSummary& shader : shaderCatalog.Shaders()) { writer.BeginObject(); writer.KeyString("id", shader.id); writer.KeyString("name", shader.name); writer.KeyString("description", shader.description); writer.KeyString("category", shader.category); writer.KeyBool("available", true); writer.KeyNull("error"); writer.Key("ui"); WriteShaderUiJson(writer, shader.id, shader.ui); writer.EndObject(); } writer.EndArray(); } inline void WriteEmptyCatalogJson(JsonWriter& writer) { writer.BeginArray(); writer.EndArray(); } inline void WriteRuntimeShaderLayersJson( JsonWriter& writer, const SupportedShaderCatalog& shaderCatalog, const RuntimeLayerModelSnapshot& runtimeLayers) { writer.BeginArray(); for (const RuntimeLayerReadModel& layer : runtimeLayers.displayLayers) { const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId); writer.BeginObject(); writer.KeyString("id", layer.id); writer.KeyString("shaderId", layer.shaderId); writer.KeyString("shaderName", layer.shaderName); writer.KeyBool("bypass", layer.bypass); writer.KeyString("buildState", RuntimeLayerBuildStateName(layer.buildState)); writer.KeyBool("renderReady", layer.renderReady); writer.KeyString("message", layer.message); writer.Key("ui"); if (shaderPackage) WriteShaderUiJson(writer, shaderPackage->id, shaderPackage->ui); else writer.Null(); writer.Key("temporal"); if (shaderPackage) WriteTemporalJson(writer, shaderPackage->temporal); else WriteTemporalJson(writer, TemporalSettings()); writer.Key("feedback"); if (shaderPackage) WriteFeedbackJson(writer, shaderPackage->feedback); else WriteFeedbackJson(writer, FeedbackSettings()); writer.Key("parameters"); writer.BeginArray(); for (const ShaderParameterDefinition& parameter : layer.parameterDefinitions) { const auto valueIt = layer.parameterValues.find(parameter.id); WriteParameterDefinitionJson(writer, parameter, valueIt == layer.parameterValues.end() ? nullptr : &valueIt->second); } writer.EndArray(); writer.EndObject(); } writer.EndArray(); } inline void WriteEmptyLayersJson(JsonWriter& writer) { writer.BeginArray(); writer.EndArray(); } inline void WriteOscJson(JsonWriter& writer, const RuntimeStateJsonInput& input) { const bool configured = input.osc ? input.osc->configured : input.config.oscPort != 0; const bool listening = input.osc ? input.osc->listening : false; const std::string bindAddress = input.osc ? input.osc->bindAddress : input.config.oscBindAddress; const unsigned short port = input.osc ? input.osc->port : input.config.oscPort; const double smoothing = input.osc ? input.osc->smoothing : input.config.oscSmoothing; const std::string status = input.osc ? input.osc->statusMessage : (configured ? "OSC ingress is not implemented yet." : "OSC ingress disabled by config."); writer.BeginObject(); writer.KeyBool("configured", configured); writer.KeyBool("listening", listening); writer.KeyString("bindAddress", bindAddress); writer.KeyUInt("port", port); writer.KeyDouble("smoothing", smoothing); writer.KeyString("statusMessage", status); writer.EndObject(); } inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input) { JsonWriter writer; writer.BeginObject(); writer.Key("app"); writer.BeginObject(); writer.KeyUInt("serverPort", input.serverPort); writer.KeyUInt("oscPort", input.config.oscPort); writer.KeyString("oscBindAddress", input.config.oscBindAddress); writer.KeyDouble("oscSmoothing", input.config.oscSmoothing); writer.KeyBool("autoReload", input.config.autoReload); writer.KeyUInt("maxTemporalHistoryFrames", static_cast(input.config.maxTemporalHistoryFrames)); writer.KeyDouble("previewFps", input.config.previewFps); writer.Key("osc"); WriteOscJson(writer, input); 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.KeyString("pixelFormat", input.config.output.pixelFormat); writer.KeyString("systemFramePixelFormat", VideoIOPixelFormatName(input.config.output.systemFramePixelFormat)); writer.Key("keying"); writer.BeginObject(); writer.KeyBool("outputAlpha", input.config.output.outputAlphaRequired || input.config.output.externalKeyingEnabled); writer.KeyBool("external", input.config.output.externalKeyingEnabled); writer.KeyBool("alphaRequired", input.config.output.outputAlphaRequired); writer.EndObject(); writer.EndObject(); writer.EndObject(); writer.Key("runtime"); if (input.writeRuntimeJson) input.writeRuntimeJson(writer); else WriteEmptyRuntimeJson(writer); writer.Key("video"); writer.BeginObject(); unsigned outputWidth = 0; unsigned outputHeight = 0; OutputDimensions(input, outputWidth, outputHeight); writer.KeyBool("hasSignal", input.videoOutputEnabled); writer.KeyUInt("width", outputWidth); writer.KeyUInt("height", outputHeight); writer.KeyString("modeName", input.config.output.resolution + " output-only"); writer.EndObject(); writer.Key("videoOutput"); WriteVideoOutputTelemetryJson(writer, input); writer.Key("decklink"); WriteVideoIoStatusJson(writer, input); writer.Key("videoIO"); WriteVideoIoStatusJson(writer, input); writer.Key("performance"); writer.BeginObject(); writer.KeyDouble("frameBudgetMs", FrameDurationMillisecondsFromRateString(input.config.output.frameRate)); writer.KeyDouble("renderMs", input.telemetry.renderFrameMilliseconds); writer.KeyNull("smoothedRenderMs"); writer.KeyDouble("budgetUsedPercent", input.telemetry.renderFrameBudgetUsedPercent); writer.KeyNull("completionIntervalMs"); writer.KeyNull("smoothedCompletionIntervalMs"); writer.KeyNull("maxCompletionIntervalMs"); writer.KeyUInt("lateFrameCount", input.telemetry.displayedLate); writer.KeyUInt("droppedFrameCount", input.telemetry.dropped); writer.KeyNull("flushedFrameCount"); writer.Key("cadence"); WriteCadenceTelemetryJson(writer, input.telemetry); writer.EndObject(); writer.KeyNull("backendPlayout"); writer.KeyNull("runtimeEvents"); writer.Key("shaders"); if (input.writeCatalogJson) input.writeCatalogJson(writer); else WriteEmptyCatalogJson(writer); writer.Key("stackPresets"); writer.BeginArray(); writer.EndArray(); writer.Key("layers"); if (input.writeLayersJson) input.writeLayersJson(writer); else WriteEmptyLayersJson(writer); writer.EndObject(); return writer.StringValue(); } }