clean split
All checks were successful
CI / React UI Build (push) Successful in 12s
CI / Native Windows Build And Tests (push) Successful in 2m14s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-30 21:22:03 +10:00
parent fbc2851ccb
commit 56883439a6
10 changed files with 269 additions and 63 deletions

View File

@@ -30,6 +30,8 @@ Native app internals are grouped by boundary:
- `video/playout/`: backend-adjacent playout policy, queues, frame pools, and scheduling helpers.
- `video/legacy/`: older backend pipeline pieces kept separate while the new edge model settles.
The runtime shader stack is plugged into the host through `src/app/RuntimeContentController.h`. The checked-in app wires `ShaderRuntimeContentController` into that slot; a fork can provide another runtime-content controller for a larger render engine while keeping the cadence/video I/O shell.
## Requirements
- Windows with Visual Studio 2022 C++ tooling.

View File

@@ -6,7 +6,9 @@ This note captures the fork-readiness review for using this repository as a base
The repository is clean enough for an internal fork, but it needs a small hygiene pass before it becomes a comfortable long-lived base repo.
The important architecture is already in place: render cadence, video input/output, frame exchange, readback, preview, control, and shader build work are mostly separated by role. The main replacement point is the render-content adapter in `src/render/RuntimeShaderRenderContent.*`, where the current shader-package renderer decides what to draw into the framebuffer handed to it by `RenderThread`.
The important architecture is already in place: render cadence, video input/output, frame exchange, readback, preview, control, and shader build work are mostly separated by role. The app-side replacement point is now `IRuntimeContentController` in `src/app/RuntimeContentController.h`. The current shader package stack is plugged in by `ShaderRuntimeContentController`, while a fork can provide a D3D-engine controller without making the host app own Slang, shader catalogs, or shader layer state.
The current render-thread implementation still uses OpenGL for its context and readback path. If the fork's renderer is truly D3D rather than GL content, keep the app/video/frame-exchange boundary and replace the render-thread/content implementation that publishes completed system-memory frames.
For a new repo, keep the cadence and frame handoff machinery, then replace or narrow the runtime shader rendering layer.
@@ -23,6 +25,7 @@ These parts are the useful base for the fork:
- `src/app`: startup, config, video backend factory, runtime layer orchestration, preview, telemetry, and HTTP server hookup.
- `src/control/http`, `src/control/osc`, `src/telemetry`, `src/logging`, and `ui`: useful if the new repo still wants a local control surface. `control/osc` is currently a status/lifecycle stub, not a UDP listener.
- `src/app/RenderCadenceHttpRoutes.*`: useful only if the new repo keeps this app's current `/api/...` control surface.
- `src/app/RuntimeContentController.h`: the app-side runtime-content boundary. Keep the interface shape and provide a fork-owned implementation for D3D engine control/state.
## Replace Or Rework
@@ -31,6 +34,7 @@ These are most likely to change when the fork renders something other than shade
- `src/render/runtime`: current runtime shader scene, renderer, text texture cache, and shared-context shader preparation.
- `src/runtime/shader`: background Slang package build bridge.
- `src/shader`: shader package manifest parsing and Slang wrapper generation, unless the new renderer keeps the same shader package contract.
- `src/app/ShaderRuntimeContentController.*`: current Slang shader-stack implementation of the app runtime-content boundary.
- `shaders/`: bundled shader package library.
- `runtime/templates/shader_wrapper.slang.in`: only needed for the current Slang package pipeline.
- `src/app/RenderCadenceHttpRoutes.*`: replace this with a fork-owned route module if the new renderer has different controls, while keeping `src/control/http/HttpControlServer.*` as the socket/static/WebSocket shell.
@@ -38,6 +42,8 @@ These are most likely to change when the fork renders something other than shade
The first fork step is now in place: `RenderThread` preserves the cadence/readback shell and calls a narrow render-content interface behind the draw call. A new repo can swap that implementation without touching video I/O scheduling.
The shader stack is also separated from `RenderCadenceApp` now. The app owns only an `IRuntimeContentController`; `RenderCadenceCompositor.cpp` wires the current `ShaderRuntimeContentController` into that slot. A D3D fork should wire a D3D runtime-content controller there and then replace the render-thread content/readback implementation as needed.
The HTTP control boundary is also split now. `HttpControlServer` owns transport, static-file helpers, OpenAPI helper serving, and WebSocket state transport; `RenderCadenceHttpRoutes` owns this app's REST endpoint map. A fork that wants the same browser/server plumbing can provide its own route callback and leave the Render Cadence-specific endpoints behind.
## Current Swap Point
@@ -68,17 +74,18 @@ Before cutting a long-lived fork, fix or decide these items:
- Keep `runtimeShaderId` empty in checked-in config unless this repo intentionally wants a default startup shader again.
- Keep runtime third-party discovery aligned with CMake. Font atlas generation and Slang compilation now both check explicit tool roots, `THIRD_PARTY_ROOT`, the private `video-io-3rdParty` bundle, and legacy or packaged `3rdParty` layouts.
- Make `config/runtime-host.json` portable. Current checked-in defaults include a local NDI source name and DeckLink output.
- Decide whether the fork keeps the Slang shader package contract. If not, retire or clearly isolate `shaders/SHADER_CONTRACT.md`, shader package UI, and shader manifest tests.
- Decide whether the fork keeps the Slang shader package contract. If not, replace `ShaderRuntimeContentController`, retire or clearly isolate `shaders/SHADER_CONTRACT.md`, shader package UI, and shader manifest tests.
- For a D3D engine fork, decide whether to bridge D3D output into the existing GL/readback path temporarily or replace `RenderThread`/readback with a D3D-native publisher that still writes completed `SystemFrameExchange` frames.
- Mark older docs that reference `apps/LoopThroughWithOpenGLCompositing` as historical, or update them to point at the current `src/` implementation.
- Keep `runtime/` generated output ignored, and keep only `runtime/templates/` plus `runtime/README.md` tracked.
- Keep the private SDK bundle as a submodule only if the new repo is intended for the same org/build environment. External forks should use ignored `3rdParty/` or explicit CMake SDK paths.
## Verification Snapshot
Last checked locally on 2026-05-30 after the Slang lookup alignment:
Last checked locally on 2026-05-30 after the runtime-content controller split:
- UI production build passed with `npm.cmd run build`.
- Native debug build passed for `RenderCadenceCompositor`, `ShaderSlangValidationTests`, and `ShaderCompilerLookupTests`.
- Native debug build passed for `RenderCadenceCompositor`.
- Native tests passed 26 of 26 with `ctest --test-dir build\vs2022-x64-debug -C Debug --output-on-failure`.
- `FontAtlasBuilderTests` now passes with `msdf-atlas-gen.exe` supplied by the private `video-io-3rdParty/msdf-atlas-gen` bundle.
- `ShaderCompilerLookupTests` covers `SLANG_ROOT`, `THIRD_PARTY_ROOT`, repo `video-io-3rdParty`, legacy `3rdParty`, and packaged `3rdParty/slang` Slang layouts.
@@ -88,9 +95,9 @@ The generated Visual Studio `RUN_TESTS` target did not build missing test execut
## Recommended Fork Sequence
1. Make the hygiene fixes above in this repo or immediately after the fork.
2. Keep `IRenderContent` as the boundary behind the `RenderThread` draw call.
3. Keep `RuntimeShaderRenderContent` as the baseline implementation until the fork's renderer exists.
4. Add the new renderer beside it or replace the default adapter.
2. Keep `IRuntimeContentController` as the app-side boundary and replace `ShaderRuntimeContentController` with the fork's D3D engine control/state implementation.
3. Keep `IRenderContent` as the render-thread draw-call boundary only if the fork remains GL-backed or uses a GL/D3D bridge.
4. If the fork is D3D-native, replace the render-thread/readback implementation while preserving completed-frame publication into `SystemFrameExchange`.
5. Verify that video output still consumes completed frames and never requests rendering directly.
6. Only then remove shader package pieces that the new repo no longer needs.

View File

@@ -66,6 +66,7 @@ Included now:
- app-owned display/render layer model for shader build readiness
- app-owned submission of a completed shader artifact
- render-thread-owned render-content interface with runtime shader content as the default implementation
- app-owned runtime-content controller interface with the Slang shader stack as the default implementation
- render-thread-owned runtime render scene for ready shader layers inside the default render-content adapter
- shared-context GL prepare worker for runtime shader program compile/link
- render-thread-only GL program swap once a prepared program is ready
@@ -265,6 +266,12 @@ The HTTP server runs on its own thread. `control/http/HttpControlServer` owns so
`control/osc/OscControlServer` is currently a lifecycle/status stub. It consumes the startup OSC config and reports whether OSC is configured or disabled, but it deliberately does not open a UDP socket or dispatch parameter changes yet.
## Runtime Content Boundary
`app/RenderCadenceApp` owns startup, video output, preview, telemetry, OSC status, and HTTP server lifetime. It does not own the Slang shader stack directly. Runtime content is supplied through `IRuntimeContentController` in `app/RuntimeContentController.h`.
The default implementation is `ShaderRuntimeContentController`. It wraps `RuntimeLayerController`, exposes shader catalog/layer state to `/api/state`, handles the shader layer POST commands, and publishes render-ready shader layers to the current render thread. A fork that uses a D3D engine should replace this controller with a D3D engine controller and decide separately whether to keep the current GL render/readback thread as a bridge or replace it with a D3D-native frame publisher.
## Optional DeckLink Output
DeckLink output is an optional edge service in this app.

View File

@@ -2,6 +2,7 @@
#include "app/AppConfigProvider.h"
#include "app/AppRestart.h"
#include "app/RenderCadenceApp.h"
#include "app/ShaderRuntimeContentController.h"
#include "app/VideoBackendFactory.h"
#include "frames/InputFrameMailbox.h"
#include "frames/SystemFrameExchange.h"
@@ -16,8 +17,10 @@
#include <chrono>
#include <cctype>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
namespace
{
@@ -196,11 +199,16 @@ int main(int argc, char** argv)
RenderThread renderThread(frameExchange, &inputMailbox, renderConfig);
auto outputBackend = RenderCadenceCompositor::CreateVideoOutputBackend(appConfig);
auto runtimeContent = std::make_unique<RenderCadenceCompositor::ShaderRuntimeContentController>(
[&renderThread](const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers) {
renderThread.SubmitRuntimeRenderLayers(layers);
});
RenderCadenceCompositor::RenderCadenceApp<RenderThread, SystemFrameExchange> app(
renderThread,
frameExchange,
appConfig,
std::move(outputBackend),
std::move(runtimeContent),
configProvider.SourcePath());
app.SetVideoInputMetricsProvider([inputBackend = inputBackend.get()]() {
return inputBackend ? inputBackend->Metrics() : RenderCadenceCompositor::VideoInputEdgeMetrics();

View File

@@ -5,7 +5,7 @@
#include "AppConfigProvider.h"
#include "AppRestart.h"
#include "RenderCadenceHttpRoutes.h"
#include "RuntimeLayerController.h"
#include "RuntimeContentController.h"
#include "../logging/Logger.h"
#include "../control/RuntimeStateJson.h"
#include "../control/osc/OscControlServer.h"
@@ -65,20 +65,19 @@ public:
SystemFrameExchange& frameExchange,
AppConfig config,
std::unique_ptr<IVideoOutputEdge> output,
std::unique_ptr<IRuntimeContentController> runtimeContent,
std::filesystem::path configPath = std::filesystem::path()) :
mRenderThread(renderThread),
mFrameExchange(frameExchange),
mConfig(config),
mConfigPath(std::move(configPath)),
mOutput(std::move(output)),
mRuntimeContent(std::move(runtimeContent)),
mOutputThread(*mOutput, mFrameExchange, VideoOutputThreadConfig{
mConfig.outputThread.targetBufferedFrames,
mConfig.outputThread.idleSleep
}),
mTelemetryHealth(mConfig.telemetry),
mRuntimeLayers([this](const std::vector<RuntimeRenderLayerModel>& layers) {
mRenderThread.SubmitRuntimeRenderLayers(layers);
})
mTelemetryHealth(mConfig.telemetry)
{
}
@@ -92,10 +91,8 @@ public:
bool Start(std::string& error)
{
mRuntimeLayers.Initialize(
mConfig.shaderLibrary,
static_cast<unsigned>(mConfig.maxTemporalHistoryFrames),
mConfig.runtimeShaderId);
if (mRuntimeContent)
mRuntimeContent->Initialize(mConfig);
Log("app", "Starting render thread.");
if (!detail::StartRenderThread(mRenderThread, error, 0))
@@ -104,7 +101,8 @@ public:
Stop();
return false;
}
mRuntimeLayers.StartStartupBuild(mConfig.runtimeShaderId);
if (mRuntimeContent)
mRuntimeContent->Start();
if (!BuildSettledOutputReserve(error))
{
@@ -131,7 +129,8 @@ public:
mPreviewWindow.Stop();
mOutputThread.Stop();
mOutput->Stop();
mRuntimeLayers.Stop();
if (mRuntimeContent)
mRuntimeContent->Stop();
mRenderThread.Stop();
mOutput->ReleaseResources();
if (mStarted)
@@ -260,23 +259,15 @@ private:
routeCallbacks.getNdiSourcesJson = [this]() {
return BuildNdiSourcesJson();
};
routeCallbacks.addLayer = [this](const std::string& body) {
return mRuntimeLayers.HandleAddLayer(body);
};
routeCallbacks.removeLayer = [this](const std::string& body) {
return mRuntimeLayers.HandleRemoveLayer(body);
};
routeCallbacks.executePost = [this](const std::string& path, const std::string& body) {
if (path == "/api/config/save")
return HandleConfigSave(body);
if (path == "/api/app/restart")
return HandleAppRestart();
RuntimeControlCommand command;
std::string error;
if (!ParseRuntimeControlCommand(path, body, command, error))
return ControlActionResult{ false, error };
return mRuntimeLayers.HandleControlCommand(command);
if (mRuntimeContent)
return mRuntimeContent->HandlePost(path, body);
return ControlActionResult{ false, "No runtime content controller is active." };
};
HttpControlServerCallbacks callbacks;
@@ -320,17 +311,27 @@ private:
{
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, *mOutput, mOutputThread, mRenderThread);
ApplyVideoInputMetrics(telemetry);
RuntimeLayerModelSnapshot layerSnapshot = mRuntimeLayers.Snapshot(telemetry);
return RuntimeStateToJson(RuntimeStateJsonInput{
RuntimeStateJsonInput stateInput{
mConfig,
telemetry,
mHttpServer.Port(),
mVideoOutputEnabled,
mVideoOutputStatus,
mRuntimeLayers.ShaderCatalog(),
layerSnapshot,
&mOscServer.State()
});
};
if (mRuntimeContent)
{
stateInput.writeRuntimeJson = [this, telemetry](JsonWriter& writer) {
mRuntimeContent->WriteRuntimeJson(writer, telemetry);
};
stateInput.writeCatalogJson = [this](JsonWriter& writer) {
mRuntimeContent->WriteCatalogJson(writer);
};
stateInput.writeLayersJson = [this, telemetry](JsonWriter& writer) {
mRuntimeContent->WriteLayersJson(writer, telemetry);
};
}
return RuntimeStateToJson(stateInput);
}
void StartOscServer()
@@ -460,13 +461,13 @@ private:
AppConfig mConfig;
std::filesystem::path mConfigPath;
std::unique_ptr<IVideoOutputEdge> mOutput;
std::unique_ptr<IRuntimeContentController> mRuntimeContent;
VideoOutputThread<SystemFrameExchange> mOutputThread;
TelemetryHealthMonitor mTelemetryHealth;
CadenceTelemetry mHttpTelemetry;
HttpControlServer mHttpServer;
OscControlServer mOscServer;
PreviewWindowThread mPreviewWindow;
RuntimeLayerController mRuntimeLayers;
std::function<VideoInputEdgeMetrics()> mVideoInputMetricsProvider;
uint64_t mLastInputCapturedFrames = 0;
std::atomic<bool> mRestartRequired{ false };

View File

@@ -0,0 +1,26 @@
#pragma once
#include "AppConfig.h"
#include "../control/ControlActionResult.h"
#include "../json/JsonWriter.h"
#include "../telemetry/CadenceTelemetry.h"
#include <string>
namespace RenderCadenceCompositor
{
class IRuntimeContentController
{
public:
virtual ~IRuntimeContentController() = default;
virtual void Initialize(AppConfig& config) = 0;
virtual void Start() = 0;
virtual void Stop() = 0;
virtual ControlActionResult HandlePost(const std::string& path, const std::string& body) = 0;
virtual void WriteRuntimeJson(JsonWriter& writer, const CadenceTelemetrySnapshot& telemetry) const = 0;
virtual void WriteCatalogJson(JsonWriter& writer) const = 0;
virtual void WriteLayersJson(JsonWriter& writer, const CadenceTelemetrySnapshot& telemetry) const = 0;
};
}

View File

@@ -0,0 +1,67 @@
#include "ShaderRuntimeContentController.h"
#include "../control/RuntimeControlCommand.h"
#include "../control/RuntimeStateJson.h"
#include <utility>
namespace RenderCadenceCompositor
{
ShaderRuntimeContentController::ShaderRuntimeContentController(RenderLayerPublisher publisher) :
mRuntimeLayers(std::move(publisher))
{
}
ShaderRuntimeContentController::~ShaderRuntimeContentController()
{
Stop();
}
void ShaderRuntimeContentController::Initialize(AppConfig& config)
{
mRuntimeLayers.Initialize(
config.shaderLibrary,
static_cast<unsigned>(config.maxTemporalHistoryFrames),
config.runtimeShaderId);
mStartupShaderId = config.runtimeShaderId;
}
void ShaderRuntimeContentController::Start()
{
mRuntimeLayers.StartStartupBuild(mStartupShaderId);
}
void ShaderRuntimeContentController::Stop()
{
mRuntimeLayers.Stop();
}
ControlActionResult ShaderRuntimeContentController::HandlePost(const std::string& path, const std::string& body)
{
if (path == "/api/layers/add")
return mRuntimeLayers.HandleAddLayer(body);
if (path == "/api/layers/remove")
return mRuntimeLayers.HandleRemoveLayer(body);
RuntimeControlCommand command;
std::string error;
if (!ParseRuntimeControlCommand(path, body, command, error))
return ControlActionResult{ false, error };
return mRuntimeLayers.HandleControlCommand(command);
}
void ShaderRuntimeContentController::WriteRuntimeJson(JsonWriter& writer, const CadenceTelemetrySnapshot& telemetry) const
{
WriteShaderRuntimeJson(writer, mRuntimeLayers.Snapshot(telemetry));
}
void ShaderRuntimeContentController::WriteCatalogJson(JsonWriter& writer) const
{
WriteShaderCatalogJson(writer, mRuntimeLayers.ShaderCatalog());
}
void ShaderRuntimeContentController::WriteLayersJson(JsonWriter& writer, const CadenceTelemetrySnapshot& telemetry) const
{
WriteRuntimeShaderLayersJson(writer, mRuntimeLayers.ShaderCatalog(), mRuntimeLayers.Snapshot(telemetry));
}
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "RuntimeContentController.h"
#include "RuntimeLayerController.h"
#include <string>
#include <vector>
namespace RenderCadenceCompositor
{
class ShaderRuntimeContentController final : public IRuntimeContentController
{
public:
using RenderLayerPublisher = RuntimeLayerController::RenderLayerPublisher;
explicit ShaderRuntimeContentController(RenderLayerPublisher publisher = RenderLayerPublisher());
ShaderRuntimeContentController(const ShaderRuntimeContentController&) = delete;
ShaderRuntimeContentController& operator=(const ShaderRuntimeContentController&) = delete;
~ShaderRuntimeContentController() override;
void Initialize(AppConfig& config) override;
void Start() override;
void Stop() override;
ControlActionResult HandlePost(const std::string& path, const std::string& body) override;
void WriteRuntimeJson(JsonWriter& writer, const CadenceTelemetrySnapshot& telemetry) const override;
void WriteCatalogJson(JsonWriter& writer) const override;
void WriteLayersJson(JsonWriter& writer, const CadenceTelemetrySnapshot& telemetry) const override;
private:
RuntimeLayerController mRuntimeLayers;
std::string mStartupShaderId;
};
}

View File

@@ -9,6 +9,7 @@
#include "../telemetry/CadenceTelemetryJson.h"
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
@@ -21,9 +22,10 @@ struct RuntimeStateJsonInput
unsigned short serverPort = 0;
bool videoOutputEnabled = false;
std::string videoOutputStatus;
const SupportedShaderCatalog& shaderCatalog;
const RuntimeLayerModelSnapshot& runtimeLayers;
const OscControlServerState* osc = nullptr;
std::function<void(JsonWriter&)> writeRuntimeJson;
std::function<void(JsonWriter&)> writeCatalogJson;
std::function<void(JsonWriter&)> writeLayersJson;
};
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
@@ -244,12 +246,56 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
writer.EndObject();
}
inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
inline void WriteShaderRuntimeJson(JsonWriter& writer, const RuntimeLayerModelSnapshot& runtimeLayers)
{
writer.BeginObject();
writer.KeyUInt("layerCount", static_cast<uint64_t>(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 RuntimeLayerReadModel& layer : input.runtimeLayers.displayLayers)
for (const SupportedShaderSummary& shader : shaderCatalog.Shaders())
{
const ShaderPackage* shaderPackage = input.shaderCatalog.FindPackage(layer.shaderId);
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.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);
@@ -281,6 +327,12 @@ inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& inp
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;
@@ -343,11 +395,10 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.EndObject();
writer.Key("runtime");
writer.BeginObject();
writer.KeyUInt("layerCount", static_cast<uint64_t>(input.runtimeLayers.displayLayers.size()));
writer.KeyBool("compileSucceeded", input.runtimeLayers.compileSucceeded);
writer.KeyString("compileMessage", input.runtimeLayers.compileMessage);
writer.EndObject();
if (input.writeRuntimeJson)
input.writeRuntimeJson(writer);
else
WriteEmptyRuntimeJson(writer);
writer.Key("video");
writer.BeginObject();
@@ -386,24 +437,18 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.KeyNull("backendPlayout");
writer.KeyNull("runtimeEvents");
writer.Key("shaders");
writer.BeginArray();
for (const SupportedShaderSummary& shader : input.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.EndObject();
}
writer.EndArray();
if (input.writeCatalogJson)
input.writeCatalogJson(writer);
else
WriteEmptyCatalogJson(writer);
writer.Key("stackPresets");
writer.BeginArray();
writer.EndArray();
writer.Key("layers");
WriteLayersJson(writer, input);
if (input.writeLayersJson)
input.writeLayersJson(writer);
else
WriteEmptyLayersJson(writer);
writer.EndObject();
return writer.StringValue();

View File

@@ -100,15 +100,24 @@ int main()
layerModel.MarkBuildReady(artifact, error);
const RenderCadenceCompositor::RuntimeLayerModelSnapshot layerSnapshot = layerModel.Snapshot();
const std::string json = RenderCadenceCompositor::RuntimeStateToJson(RenderCadenceCompositor::RuntimeStateJsonInput{
RenderCadenceCompositor::RuntimeStateJsonInput stateInput{
config,
telemetry,
8080,
true,
"DeckLink scheduled output running.",
shaderCatalog,
layerSnapshot
});
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, "\"layerCount\":1", "state JSON should expose the display layer count");