clean split
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
|
||||
26
src/app/RuntimeContentController.h
Normal file
26
src/app/RuntimeContentController.h
Normal 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;
|
||||
};
|
||||
}
|
||||
67
src/app/ShaderRuntimeContentController.cpp
Normal file
67
src/app/ShaderRuntimeContentController.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
34
src/app/ShaderRuntimeContentController.h
Normal file
34
src/app/ShaderRuntimeContentController.h
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user