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/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.
|
- `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
|
## Requirements
|
||||||
|
|
||||||
- Windows with Visual Studio 2022 C++ tooling.
|
- 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 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.
|
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/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/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/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
|
## 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/render/runtime`: current runtime shader scene, renderer, text texture cache, and shared-context shader preparation.
|
||||||
- `src/runtime/shader`: background Slang package build bridge.
|
- `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/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.
|
- `shaders/`: bundled shader package library.
|
||||||
- `runtime/templates/shader_wrapper.slang.in`: only needed for the current Slang package pipeline.
|
- `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.
|
- `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 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.
|
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
|
## 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 `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.
|
- 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.
|
- 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.
|
- 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 `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.
|
- 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
|
## 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`.
|
- 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`.
|
- 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.
|
- `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.
|
- `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
|
## Recommended Fork Sequence
|
||||||
|
|
||||||
1. Make the hygiene fixes above in this repo or immediately after the fork.
|
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.
|
2. Keep `IRuntimeContentController` as the app-side boundary and replace `ShaderRuntimeContentController` with the fork's D3D engine control/state implementation.
|
||||||
3. Keep `RuntimeShaderRenderContent` as the baseline implementation until the fork's renderer exists.
|
3. Keep `IRenderContent` as the render-thread draw-call boundary only if the fork remains GL-backed or uses a GL/D3D bridge.
|
||||||
4. Add the new renderer beside it or replace the default adapter.
|
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.
|
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.
|
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 display/render layer model for shader build readiness
|
||||||
- app-owned submission of a completed shader artifact
|
- app-owned submission of a completed shader artifact
|
||||||
- render-thread-owned render-content interface with runtime shader content as the default implementation
|
- 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
|
- 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
|
- shared-context GL prepare worker for runtime shader program compile/link
|
||||||
- render-thread-only GL program swap once a prepared program is ready
|
- 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.
|
`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
|
## Optional DeckLink Output
|
||||||
|
|
||||||
DeckLink output is an optional edge service in this app.
|
DeckLink output is an optional edge service in this app.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "app/AppConfigProvider.h"
|
#include "app/AppConfigProvider.h"
|
||||||
#include "app/AppRestart.h"
|
#include "app/AppRestart.h"
|
||||||
#include "app/RenderCadenceApp.h"
|
#include "app/RenderCadenceApp.h"
|
||||||
|
#include "app/ShaderRuntimeContentController.h"
|
||||||
#include "app/VideoBackendFactory.h"
|
#include "app/VideoBackendFactory.h"
|
||||||
#include "frames/InputFrameMailbox.h"
|
#include "frames/InputFrameMailbox.h"
|
||||||
#include "frames/SystemFrameExchange.h"
|
#include "frames/SystemFrameExchange.h"
|
||||||
@@ -16,8 +17,10 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -196,11 +199,16 @@ int main(int argc, char** argv)
|
|||||||
RenderThread renderThread(frameExchange, &inputMailbox, renderConfig);
|
RenderThread renderThread(frameExchange, &inputMailbox, renderConfig);
|
||||||
|
|
||||||
auto outputBackend = RenderCadenceCompositor::CreateVideoOutputBackend(appConfig);
|
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(
|
RenderCadenceCompositor::RenderCadenceApp<RenderThread, SystemFrameExchange> app(
|
||||||
renderThread,
|
renderThread,
|
||||||
frameExchange,
|
frameExchange,
|
||||||
appConfig,
|
appConfig,
|
||||||
std::move(outputBackend),
|
std::move(outputBackend),
|
||||||
|
std::move(runtimeContent),
|
||||||
configProvider.SourcePath());
|
configProvider.SourcePath());
|
||||||
app.SetVideoInputMetricsProvider([inputBackend = inputBackend.get()]() {
|
app.SetVideoInputMetricsProvider([inputBackend = inputBackend.get()]() {
|
||||||
return inputBackend ? inputBackend->Metrics() : RenderCadenceCompositor::VideoInputEdgeMetrics();
|
return inputBackend ? inputBackend->Metrics() : RenderCadenceCompositor::VideoInputEdgeMetrics();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "AppConfigProvider.h"
|
#include "AppConfigProvider.h"
|
||||||
#include "AppRestart.h"
|
#include "AppRestart.h"
|
||||||
#include "RenderCadenceHttpRoutes.h"
|
#include "RenderCadenceHttpRoutes.h"
|
||||||
#include "RuntimeLayerController.h"
|
#include "RuntimeContentController.h"
|
||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
#include "../control/RuntimeStateJson.h"
|
#include "../control/RuntimeStateJson.h"
|
||||||
#include "../control/osc/OscControlServer.h"
|
#include "../control/osc/OscControlServer.h"
|
||||||
@@ -65,20 +65,19 @@ public:
|
|||||||
SystemFrameExchange& frameExchange,
|
SystemFrameExchange& frameExchange,
|
||||||
AppConfig config,
|
AppConfig config,
|
||||||
std::unique_ptr<IVideoOutputEdge> output,
|
std::unique_ptr<IVideoOutputEdge> output,
|
||||||
|
std::unique_ptr<IRuntimeContentController> runtimeContent,
|
||||||
std::filesystem::path configPath = std::filesystem::path()) :
|
std::filesystem::path configPath = std::filesystem::path()) :
|
||||||
mRenderThread(renderThread),
|
mRenderThread(renderThread),
|
||||||
mFrameExchange(frameExchange),
|
mFrameExchange(frameExchange),
|
||||||
mConfig(config),
|
mConfig(config),
|
||||||
mConfigPath(std::move(configPath)),
|
mConfigPath(std::move(configPath)),
|
||||||
mOutput(std::move(output)),
|
mOutput(std::move(output)),
|
||||||
|
mRuntimeContent(std::move(runtimeContent)),
|
||||||
mOutputThread(*mOutput, mFrameExchange, VideoOutputThreadConfig{
|
mOutputThread(*mOutput, mFrameExchange, VideoOutputThreadConfig{
|
||||||
mConfig.outputThread.targetBufferedFrames,
|
mConfig.outputThread.targetBufferedFrames,
|
||||||
mConfig.outputThread.idleSleep
|
mConfig.outputThread.idleSleep
|
||||||
}),
|
}),
|
||||||
mTelemetryHealth(mConfig.telemetry),
|
mTelemetryHealth(mConfig.telemetry)
|
||||||
mRuntimeLayers([this](const std::vector<RuntimeRenderLayerModel>& layers) {
|
|
||||||
mRenderThread.SubmitRuntimeRenderLayers(layers);
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +91,8 @@ public:
|
|||||||
|
|
||||||
bool Start(std::string& error)
|
bool Start(std::string& error)
|
||||||
{
|
{
|
||||||
mRuntimeLayers.Initialize(
|
if (mRuntimeContent)
|
||||||
mConfig.shaderLibrary,
|
mRuntimeContent->Initialize(mConfig);
|
||||||
static_cast<unsigned>(mConfig.maxTemporalHistoryFrames),
|
|
||||||
mConfig.runtimeShaderId);
|
|
||||||
|
|
||||||
Log("app", "Starting render thread.");
|
Log("app", "Starting render thread.");
|
||||||
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
||||||
@@ -104,7 +101,8 @@ public:
|
|||||||
Stop();
|
Stop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mRuntimeLayers.StartStartupBuild(mConfig.runtimeShaderId);
|
if (mRuntimeContent)
|
||||||
|
mRuntimeContent->Start();
|
||||||
|
|
||||||
if (!BuildSettledOutputReserve(error))
|
if (!BuildSettledOutputReserve(error))
|
||||||
{
|
{
|
||||||
@@ -131,7 +129,8 @@ public:
|
|||||||
mPreviewWindow.Stop();
|
mPreviewWindow.Stop();
|
||||||
mOutputThread.Stop();
|
mOutputThread.Stop();
|
||||||
mOutput->Stop();
|
mOutput->Stop();
|
||||||
mRuntimeLayers.Stop();
|
if (mRuntimeContent)
|
||||||
|
mRuntimeContent->Stop();
|
||||||
mRenderThread.Stop();
|
mRenderThread.Stop();
|
||||||
mOutput->ReleaseResources();
|
mOutput->ReleaseResources();
|
||||||
if (mStarted)
|
if (mStarted)
|
||||||
@@ -260,23 +259,15 @@ private:
|
|||||||
routeCallbacks.getNdiSourcesJson = [this]() {
|
routeCallbacks.getNdiSourcesJson = [this]() {
|
||||||
return BuildNdiSourcesJson();
|
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) {
|
routeCallbacks.executePost = [this](const std::string& path, const std::string& body) {
|
||||||
if (path == "/api/config/save")
|
if (path == "/api/config/save")
|
||||||
return HandleConfigSave(body);
|
return HandleConfigSave(body);
|
||||||
if (path == "/api/app/restart")
|
if (path == "/api/app/restart")
|
||||||
return HandleAppRestart();
|
return HandleAppRestart();
|
||||||
|
|
||||||
RuntimeControlCommand command;
|
if (mRuntimeContent)
|
||||||
std::string error;
|
return mRuntimeContent->HandlePost(path, body);
|
||||||
if (!ParseRuntimeControlCommand(path, body, command, error))
|
return ControlActionResult{ false, "No runtime content controller is active." };
|
||||||
return ControlActionResult{ false, error };
|
|
||||||
return mRuntimeLayers.HandleControlCommand(command);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpControlServerCallbacks callbacks;
|
HttpControlServerCallbacks callbacks;
|
||||||
@@ -320,17 +311,27 @@ private:
|
|||||||
{
|
{
|
||||||
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, *mOutput, mOutputThread, mRenderThread);
|
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, *mOutput, mOutputThread, mRenderThread);
|
||||||
ApplyVideoInputMetrics(telemetry);
|
ApplyVideoInputMetrics(telemetry);
|
||||||
RuntimeLayerModelSnapshot layerSnapshot = mRuntimeLayers.Snapshot(telemetry);
|
RuntimeStateJsonInput stateInput{
|
||||||
return RuntimeStateToJson(RuntimeStateJsonInput{
|
|
||||||
mConfig,
|
mConfig,
|
||||||
telemetry,
|
telemetry,
|
||||||
mHttpServer.Port(),
|
mHttpServer.Port(),
|
||||||
mVideoOutputEnabled,
|
mVideoOutputEnabled,
|
||||||
mVideoOutputStatus,
|
mVideoOutputStatus,
|
||||||
mRuntimeLayers.ShaderCatalog(),
|
|
||||||
layerSnapshot,
|
|
||||||
&mOscServer.State()
|
&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()
|
void StartOscServer()
|
||||||
@@ -460,13 +461,13 @@ private:
|
|||||||
AppConfig mConfig;
|
AppConfig mConfig;
|
||||||
std::filesystem::path mConfigPath;
|
std::filesystem::path mConfigPath;
|
||||||
std::unique_ptr<IVideoOutputEdge> mOutput;
|
std::unique_ptr<IVideoOutputEdge> mOutput;
|
||||||
|
std::unique_ptr<IRuntimeContentController> mRuntimeContent;
|
||||||
VideoOutputThread<SystemFrameExchange> mOutputThread;
|
VideoOutputThread<SystemFrameExchange> mOutputThread;
|
||||||
TelemetryHealthMonitor mTelemetryHealth;
|
TelemetryHealthMonitor mTelemetryHealth;
|
||||||
CadenceTelemetry mHttpTelemetry;
|
CadenceTelemetry mHttpTelemetry;
|
||||||
HttpControlServer mHttpServer;
|
HttpControlServer mHttpServer;
|
||||||
OscControlServer mOscServer;
|
OscControlServer mOscServer;
|
||||||
PreviewWindowThread mPreviewWindow;
|
PreviewWindowThread mPreviewWindow;
|
||||||
RuntimeLayerController mRuntimeLayers;
|
|
||||||
std::function<VideoInputEdgeMetrics()> mVideoInputMetricsProvider;
|
std::function<VideoInputEdgeMetrics()> mVideoInputMetricsProvider;
|
||||||
uint64_t mLastInputCapturedFrames = 0;
|
uint64_t mLastInputCapturedFrames = 0;
|
||||||
std::atomic<bool> mRestartRequired{ false };
|
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 "../telemetry/CadenceTelemetryJson.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -21,9 +22,10 @@ struct RuntimeStateJsonInput
|
|||||||
unsigned short serverPort = 0;
|
unsigned short serverPort = 0;
|
||||||
bool videoOutputEnabled = false;
|
bool videoOutputEnabled = false;
|
||||||
std::string videoOutputStatus;
|
std::string videoOutputStatus;
|
||||||
const SupportedShaderCatalog& shaderCatalog;
|
|
||||||
const RuntimeLayerModelSnapshot& runtimeLayers;
|
|
||||||
const OscControlServerState* osc = nullptr;
|
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)
|
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
@@ -244,12 +246,56 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
|
|||||||
writer.EndObject();
|
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();
|
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.BeginObject();
|
||||||
writer.KeyString("id", layer.id);
|
writer.KeyString("id", layer.id);
|
||||||
writer.KeyString("shaderId", layer.shaderId);
|
writer.KeyString("shaderId", layer.shaderId);
|
||||||
@@ -281,6 +327,12 @@ inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& inp
|
|||||||
writer.EndArray();
|
writer.EndArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void WriteEmptyLayersJson(JsonWriter& writer)
|
||||||
|
{
|
||||||
|
writer.BeginArray();
|
||||||
|
writer.EndArray();
|
||||||
|
}
|
||||||
|
|
||||||
inline void WriteOscJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
inline void WriteOscJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
{
|
{
|
||||||
const bool configured = input.osc ? input.osc->configured : input.config.oscPort != 0;
|
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.EndObject();
|
||||||
|
|
||||||
writer.Key("runtime");
|
writer.Key("runtime");
|
||||||
writer.BeginObject();
|
if (input.writeRuntimeJson)
|
||||||
writer.KeyUInt("layerCount", static_cast<uint64_t>(input.runtimeLayers.displayLayers.size()));
|
input.writeRuntimeJson(writer);
|
||||||
writer.KeyBool("compileSucceeded", input.runtimeLayers.compileSucceeded);
|
else
|
||||||
writer.KeyString("compileMessage", input.runtimeLayers.compileMessage);
|
WriteEmptyRuntimeJson(writer);
|
||||||
writer.EndObject();
|
|
||||||
|
|
||||||
writer.Key("video");
|
writer.Key("video");
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
@@ -386,24 +437,18 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
|||||||
writer.KeyNull("backendPlayout");
|
writer.KeyNull("backendPlayout");
|
||||||
writer.KeyNull("runtimeEvents");
|
writer.KeyNull("runtimeEvents");
|
||||||
writer.Key("shaders");
|
writer.Key("shaders");
|
||||||
writer.BeginArray();
|
if (input.writeCatalogJson)
|
||||||
for (const SupportedShaderSummary& shader : input.shaderCatalog.Shaders())
|
input.writeCatalogJson(writer);
|
||||||
{
|
else
|
||||||
writer.BeginObject();
|
WriteEmptyCatalogJson(writer);
|
||||||
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();
|
|
||||||
writer.Key("stackPresets");
|
writer.Key("stackPresets");
|
||||||
writer.BeginArray();
|
writer.BeginArray();
|
||||||
writer.EndArray();
|
writer.EndArray();
|
||||||
writer.Key("layers");
|
writer.Key("layers");
|
||||||
WriteLayersJson(writer, input);
|
if (input.writeLayersJson)
|
||||||
|
input.writeLayersJson(writer);
|
||||||
|
else
|
||||||
|
WriteEmptyLayersJson(writer);
|
||||||
|
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
return writer.StringValue();
|
return writer.StringValue();
|
||||||
|
|||||||
@@ -100,15 +100,24 @@ int main()
|
|||||||
layerModel.MarkBuildReady(artifact, error);
|
layerModel.MarkBuildReady(artifact, error);
|
||||||
const RenderCadenceCompositor::RuntimeLayerModelSnapshot layerSnapshot = layerModel.Snapshot();
|
const RenderCadenceCompositor::RuntimeLayerModelSnapshot layerSnapshot = layerModel.Snapshot();
|
||||||
|
|
||||||
const std::string json = RenderCadenceCompositor::RuntimeStateToJson(RenderCadenceCompositor::RuntimeStateJsonInput{
|
RenderCadenceCompositor::RuntimeStateJsonInput stateInput{
|
||||||
config,
|
config,
|
||||||
telemetry,
|
telemetry,
|
||||||
8080,
|
8080,
|
||||||
true,
|
true,
|
||||||
"DeckLink scheduled output running.",
|
"DeckLink scheduled output running.",
|
||||||
shaderCatalog,
|
nullptr
|
||||||
layerSnapshot
|
};
|
||||||
});
|
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, "\"shaders\":[{\"id\":\"solid-color\"", "state JSON should include supported shaders");
|
||||||
ExpectContains(json, "\"layerCount\":1", "state JSON should expose the display layer count");
|
ExpectContains(json, "\"layerCount\":1", "state JSON should expose the display layer count");
|
||||||
|
|||||||
Reference in New Issue
Block a user