#include "RuntimeStateJson.h" #include #include #include #include #include namespace { int gFailures = 0; void ExpectContains(const std::string& text, const std::string& fragment, const std::string& message) { if (text.find(fragment) != std::string::npos) return; ++gFailures; std::cerr << "FAIL: " << message << "\n"; } std::filesystem::path MakeTestRoot() { const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); const std::filesystem::path root = std::filesystem::temp_directory_path() / ("render-cadence-state-json-tests-" + std::to_string(stamp)); std::filesystem::create_directories(root); return root; } void WriteFile(const std::filesystem::path& path, const std::string& contents) { std::filesystem::create_directories(path.parent_path()); std::ofstream output(path, std::ios::binary); output << contents; } } int main() { RenderCadenceCompositor::AppConfig config = RenderCadenceCompositor::DefaultAppConfig(); config.output.resolution = "1080p"; config.output.frameRate = "59.94"; RenderCadenceCompositor::CadenceTelemetrySnapshot telemetry; telemetry.renderFps = 59.94; telemetry.renderFrameMilliseconds = 2.5; telemetry.renderFrameBudgetUsedPercent = 15.0; telemetry.renderFrameMaxMilliseconds = 4.0; telemetry.readbackQueueMilliseconds = 0.6; telemetry.completedReadbackCopyMilliseconds = 1.2; telemetry.completedDrops = 3; telemetry.acquireMisses = 4; telemetry.scheduleFailures = 2; telemetry.completions = 12; telemetry.displayedLate = 1; telemetry.shaderBuildsCommitted = 1; telemetry.deckLinkBufferedAvailable = true; telemetry.deckLinkBuffered = 4; telemetry.deckLinkScheduleCallMilliseconds = 1.25; telemetry.deckLinkScheduleLeadAvailable = true; telemetry.deckLinkScheduleLeadFrames = 4; telemetry.deckLinkPlaybackFrameIndex = 10; telemetry.deckLinkNextScheduleFrameIndex = 14; telemetry.deckLinkPlaybackStreamTime = 10010; telemetry.deckLinkScheduleRealignments = 1; const std::filesystem::path root = MakeTestRoot(); WriteFile(root / "solid-color" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n"); WriteFile(root / "solid-color" / "ui" / "controls.js", "customElements.define('solid-color-controls', class extends HTMLElement {});\n"); WriteFile(root / "solid-color" / "shader.json", R"({ "id": "solid-color", "name": "Solid Color", "description": "A single color shader.", "category": "Generator", "entryPoint": "shadeVideo", "ui": { "type": "webComponent", "entry": "ui/controls.js", "tag": "solid-color-controls" }, "parameters": [ { "id": "color", "label": "Color", "description": "Output color.", "type": "color", "default": [1.0, 0.25, 0.5, 1.0], "min": [0.0, 0.0, 0.0, 0.0], "max": [1.0, 1.0, 1.0, 1.0], "step": [0.01, 0.01, 0.01, 0.01] } ] })"); RenderCadenceCompositor::SupportedShaderCatalog shaderCatalog; std::string error; ExpectContains(shaderCatalog.Load(root, 4, error) ? "loaded" : error, "loaded", "test shader catalog should load"); RenderCadenceCompositor::RuntimeLayerModel layerModel; layerModel.InitializeSingleLayer(shaderCatalog, "solid-color", error); RuntimeShaderArtifact artifact; artifact.shaderId = "solid-color"; artifact.displayName = "Solid Color"; artifact.fragmentShaderSource = "void main(){}"; artifact.message = "Runtime shader committed."; layerModel.MarkBuildReady(artifact, error); const RenderCadenceCompositor::RuntimeLayerModelSnapshot layerSnapshot = layerModel.Snapshot(); RenderCadenceCompositor::RuntimeStateJsonInput stateInput{ config, telemetry, 8080, true, "DeckLink scheduled output running.", nullptr }; stateInput.writeRuntimeJson = [&layerSnapshot](RenderCadenceCompositor::JsonWriter& writer) { RenderCadenceCompositor::WriteShaderRuntimeJson(writer, layerSnapshot); }; stateInput.writeCatalogJson = [&shaderCatalog](RenderCadenceCompositor::JsonWriter& writer) { RenderCadenceCompositor::WriteShaderCatalogJson(writer, shaderCatalog); }; stateInput.writeLayersJson = [&shaderCatalog, &layerSnapshot](RenderCadenceCompositor::JsonWriter& writer) { RenderCadenceCompositor::WriteRuntimeShaderLayersJson(writer, shaderCatalog, layerSnapshot); }; const std::string json = RenderCadenceCompositor::RuntimeStateToJson(stateInput); ExpectContains(json, "\"shaders\":[{\"id\":\"solid-color\"", "state JSON should include supported shaders"); ExpectContains(json, "\"ui\":{\"type\":\"webComponent\",\"entry\":\"ui/controls.js\",\"tag\":\"solid-color-controls\",\"assetUrl\":\"/shader-assets/solid-color/ui/controls.js\"}", "state JSON should expose shader custom UI metadata"); ExpectContains(json, "\"layerCount\":1", "state JSON should expose the display layer count"); ExpectContains(json, "\"layers\":[{\"id\":\"runtime-layer-1\"", "state JSON should expose the active display layer"); ExpectContains(json, "\"parameters\":[{\"id\":\"color\"", "state JSON should expose active shader parameters"); 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, "\"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, "\"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"); ExpectContains(json, "\"backendMetrics\":{\"bufferedAvailable\":true,\"buffered\":4", "state JSON should expose backend-specific video output metrics"); ExpectContains(json, "\"scheduleLeadFrames\":4", "state JSON should expose backend-specific schedule lead"); ExpectContains(json, "\"renderMs\":2.5", "state JSON should expose top-level render timing"); ExpectContains(json, "\"budgetUsedPercent\":15", "state JSON should expose top-level render budget percentage"); ExpectContains(json, "\"renderFrameMs\":2.5", "state JSON should expose cadence render timing"); ExpectContains(json, "\"readbackQueueMs\":0.6", "state JSON should expose readback queue timing"); ExpectContains(json, "\"completedReadbackCopyMs\":1.2", "state JSON should expose completed readback copy timing"); ExpectContains(json, "\"completedDrops\":3", "state JSON should expose completed drop count"); ExpectContains(json, "\"acquireMisses\":4", "state JSON should expose acquire miss count"); std::filesystem::remove_all(root); if (gFailures != 0) { std::cerr << gFailures << " RenderCadenceCompositorRuntimeStateJson test failure(s).\n"; return 1; } std::cout << "RenderCadenceCompositorRuntimeStateJson tests passed.\n"; return 0; }