More http post end points filled
This commit is contained in:
@@ -333,6 +333,8 @@ set(RENDER_CADENCE_APP_SOURCES
|
|||||||
"${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.h"
|
"${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderParams.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.cpp"
|
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h"
|
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeShaderBridge.cpp"
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeShaderBridge.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeShaderBridge.h"
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeShaderBridge.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.cpp"
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.cpp"
|
||||||
@@ -821,6 +823,27 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
|
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
|
||||||
|
|
||||||
|
add_executable(RenderCadenceCompositorRuntimeLayerModelTests
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||||
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RenderCadenceCompositorRuntimeLayerModelTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
|
"${APP_DIR}/shader"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RenderCadenceCompositorRuntimeLayerModelTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RenderCadenceCompositorRuntimeLayerModelTests COMMAND RenderCadenceCompositorRuntimeLayerModelTests)
|
||||||
|
|
||||||
add_executable(RenderCadenceCompositorSupportedShaderCatalogTests
|
add_executable(RenderCadenceCompositorSupportedShaderCatalogTests
|
||||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
@@ -873,9 +896,12 @@ add_test(NAME RenderCadenceCompositorJsonWriterTests COMMAND RenderCadenceCompos
|
|||||||
|
|
||||||
add_executable(RenderCadenceCompositorRuntimeStateJsonTests
|
add_executable(RenderCadenceCompositorRuntimeStateJsonTests
|
||||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
|
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
|
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
|
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeLayerModel.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ Included now:
|
|||||||
- latest-N system-memory frame exchange
|
- latest-N system-memory frame exchange
|
||||||
- rendered-frame warmup
|
- rendered-frame warmup
|
||||||
- background Slang compile of `shaders/happy-accident`
|
- background Slang compile of `shaders/happy-accident`
|
||||||
|
- 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-only GL commit once the artifact is ready
|
- render-thread-only GL commit once the artifact is ready
|
||||||
- manifest-driven stateless single-pass shader packages
|
- manifest-driven stateless single-pass shader packages
|
||||||
@@ -149,7 +150,8 @@ Current endpoints:
|
|||||||
- `GET /api/state`: returns OpenAPI-shaped display data with cadence telemetry, supported shaders, output status, and a read-only current runtime layer
|
- `GET /api/state`: returns OpenAPI-shaped display data with cadence telemetry, supported shaders, output status, and a read-only current runtime layer
|
||||||
- `GET /docs/openapi.yaml` and `GET /openapi.yaml`: serves the OpenAPI document
|
- `GET /docs/openapi.yaml` and `GET /openapi.yaml`: serves the OpenAPI document
|
||||||
- `GET /docs`: serves Swagger UI
|
- `GET /docs`: serves Swagger UI
|
||||||
- OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
|
- `POST /api/layers/add` and `POST /api/layers/remove` mutate the app-owned display layer model only
|
||||||
|
- other OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
|
||||||
|
|
||||||
The HTTP server runs on its own thread. It serves static UI/docs files, samples/copies telemetry through callbacks, and does not call render work or DeckLink scheduling.
|
The HTTP server runs on its own thread. It serves static UI/docs files, samples/copies telemetry through callbacks, and does not call render work or DeckLink scheduling.
|
||||||
|
|
||||||
@@ -208,7 +210,7 @@ Current runtime shader support is deliberately limited to stateless single-pass
|
|||||||
|
|
||||||
The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as multipass, temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now.
|
The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as multipass, temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now.
|
||||||
|
|
||||||
The current runtime shader is also exposed as a read-only display layer with manifest parameter defaults. POST control endpoints still intentionally return "not implemented" responses until the control/state ownership model is ported.
|
Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Stage 1 add/remove POST controls mutate this app-owned model and may start background shader builds, but multi-layer render-scene handoff is not ported yet.
|
||||||
|
|
||||||
Successful handoff signs:
|
Successful handoff signs:
|
||||||
|
|
||||||
@@ -258,6 +260,7 @@ This app keeps the same core behavior but splits it into modules that can grow:
|
|||||||
- `frames/`: system-memory handoff
|
- `frames/`: system-memory handoff
|
||||||
- `platform/`: COM/Win32/hidden GL context support
|
- `platform/`: COM/Win32/hidden GL context support
|
||||||
- `render/`: cadence, simple rendering, PBO readback
|
- `render/`: cadence, simple rendering, PBO readback
|
||||||
|
- `runtime/`: app-owned shader layer readiness model, runtime Slang build bridge, and completed artifact handoff
|
||||||
- `control/`: local HTTP API edge and runtime-state JSON presentation
|
- `control/`: local HTTP API edge and runtime-state JSON presentation
|
||||||
- `json/`: compact JSON serialization helpers
|
- `json/`: compact JSON serialization helpers
|
||||||
- `video/`: DeckLink output wrapper and scheduling thread
|
- `video/`: DeckLink output wrapper and scheduling thread
|
||||||
|
|||||||
@@ -3,15 +3,19 @@
|
|||||||
#include "AppConfig.h"
|
#include "AppConfig.h"
|
||||||
#include "AppConfigProvider.h"
|
#include "AppConfigProvider.h"
|
||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
|
#include "../runtime/RuntimeLayerModel.h"
|
||||||
#include "../runtime/RuntimeShaderBridge.h"
|
#include "../runtime/RuntimeShaderBridge.h"
|
||||||
#include "../runtime/SupportedShaderCatalog.h"
|
#include "../runtime/SupportedShaderCatalog.h"
|
||||||
#include "../control/RuntimeStateJson.h"
|
#include "../control/RuntimeStateJson.h"
|
||||||
#include "../telemetry/TelemetryHealthMonitor.h"
|
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||||
#include "../video/DeckLinkOutput.h"
|
#include "../video/DeckLinkOutput.h"
|
||||||
#include "../video/DeckLinkOutputThread.h"
|
#include "../video/DeckLinkOutputThread.h"
|
||||||
|
#include "RuntimeJson.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@@ -71,6 +75,7 @@ public:
|
|||||||
bool Start(std::string& error)
|
bool Start(std::string& error)
|
||||||
{
|
{
|
||||||
LoadSupportedShaderCatalog();
|
LoadSupportedShaderCatalog();
|
||||||
|
InitializeRuntimeLayerModel();
|
||||||
|
|
||||||
Log("app", "Starting render thread.");
|
Log("app", "Starting render thread.");
|
||||||
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
||||||
@@ -174,6 +179,12 @@ private:
|
|||||||
callbacks.getStateJson = [this]() {
|
callbacks.getStateJson = [this]() {
|
||||||
return BuildStateJson();
|
return BuildStateJson();
|
||||||
};
|
};
|
||||||
|
callbacks.addLayer = [this](const std::string& body) {
|
||||||
|
return HandleAddLayer(body);
|
||||||
|
};
|
||||||
|
callbacks.removeLayer = [this](const std::string& body) {
|
||||||
|
return HandleRemoveLayer(body);
|
||||||
|
};
|
||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!mHttpServer.Start(
|
if (!mHttpServer.Start(
|
||||||
@@ -191,17 +202,15 @@ private:
|
|||||||
std::string BuildStateJson()
|
std::string BuildStateJson()
|
||||||
{
|
{
|
||||||
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||||
RuntimeDisplayState runtimeState = CopyRuntimeDisplayState(telemetry);
|
RuntimeLayerModelSnapshot layerSnapshot = CopyRuntimeLayerSnapshot(telemetry);
|
||||||
return RuntimeStateToJson(RuntimeStateJsonInput{
|
return RuntimeStateToJson(RuntimeStateJsonInput{
|
||||||
mConfig,
|
mConfig,
|
||||||
telemetry,
|
telemetry,
|
||||||
mHttpServer.Port(),
|
mHttpServer.Port(),
|
||||||
mVideoOutputEnabled,
|
mVideoOutputEnabled,
|
||||||
mVideoOutputStatus,
|
mVideoOutputStatus,
|
||||||
mShaderCatalog.Shaders(),
|
mShaderCatalog,
|
||||||
runtimeState.compileSucceeded,
|
layerSnapshot
|
||||||
runtimeState.compileMessage,
|
|
||||||
runtimeState.activeShaderPackage
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,17 +235,9 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
|
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
|
||||||
SetRuntimeDisplayState(true, "Runtime Slang build started for shader '" + mConfig.runtimeShaderId + "'.", mConfig.runtimeShaderId);
|
const std::string layerId = FirstRuntimeLayerId();
|
||||||
mShaderBridge.Start(
|
if (!layerId.empty())
|
||||||
mConfig.runtimeShaderId,
|
StartLayerShaderBuild(layerId, mConfig.runtimeShaderId, true);
|
||||||
[this](const RuntimeShaderArtifact& artifact) {
|
|
||||||
SetRuntimeDisplayState(true, artifact.message.empty() ? "Runtime shader artifact is ready." : artifact.message, artifact.shaderId);
|
|
||||||
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
|
||||||
},
|
|
||||||
[this](const std::string& message) {
|
|
||||||
SetRuntimeDisplayState(false, message);
|
|
||||||
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadSupportedShaderCatalog()
|
void LoadSupportedShaderCatalog()
|
||||||
@@ -252,41 +253,185 @@ private:
|
|||||||
Log("runtime-shader", "Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) + " shader(s).");
|
Log("runtime-shader", "Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) + " shader(s).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InitializeRuntimeLayerModel()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
std::string error;
|
||||||
|
if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, mConfig.runtimeShaderId, error))
|
||||||
|
{
|
||||||
|
LogWarning("runtime-shader", error + " Runtime shader build disabled.");
|
||||||
|
mConfig.runtimeShaderId.clear();
|
||||||
|
mRuntimeLayerModel.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void StopRuntimeShaderBuild()
|
void StopRuntimeShaderBuild()
|
||||||
{
|
{
|
||||||
mShaderBridge.Stop();
|
StopAllRuntimeShaderBuilds();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RuntimeDisplayState
|
void MarkRuntimeBuildStarted(const std::string& message)
|
||||||
{
|
{
|
||||||
bool compileSucceeded = true;
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
std::string compileMessage;
|
std::string error;
|
||||||
const ShaderPackage* activeShaderPackage = nullptr;
|
const std::string layerId = mRuntimeLayerModel.FirstLayerId();
|
||||||
};
|
if (!layerId.empty())
|
||||||
|
mRuntimeLayerModel.MarkBuildStarted(layerId, message, error);
|
||||||
void SetRuntimeDisplayState(bool compileSucceeded, const std::string& compileMessage, const std::string& activeShaderId = std::string())
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mRuntimeDisplayMutex);
|
|
||||||
mRuntimeCompileSucceeded = compileSucceeded;
|
|
||||||
mRuntimeCompileMessage = compileMessage;
|
|
||||||
if (!activeShaderId.empty())
|
|
||||||
mActiveShaderId = activeShaderId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeDisplayState CopyRuntimeDisplayState(const CadenceTelemetrySnapshot& telemetry) const
|
void MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mRuntimeDisplayMutex);
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
RuntimeDisplayState state;
|
std::string error;
|
||||||
state.compileSucceeded = mRuntimeCompileSucceeded && telemetry.shaderBuildFailures == 0;
|
if (!mRuntimeLayerModel.MarkBuildReady(artifact, error))
|
||||||
state.compileMessage = mRuntimeCompileMessage;
|
LogWarning("runtime-shader", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MarkRuntimeBuildFailed(const std::string& message)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
if (!mRuntimeLayerModel.MarkBuildFailedForShader(mConfig.runtimeShaderId, message))
|
||||||
|
LogWarning("runtime-shader", "Runtime shader failed without a matching display layer: " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MarkRuntimeBuildFailedForLayer(const std::string& layerId, const std::string& message)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
std::string error;
|
||||||
|
if (!mRuntimeLayerModel.MarkBuildFailed(layerId, message, error))
|
||||||
|
LogWarning("runtime-shader", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FirstRuntimeLayerId() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
return mRuntimeLayerModel.FirstLayerId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool submitToRender)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
std::string error;
|
||||||
|
mRuntimeLayerModel.MarkBuildStarted(layerId, "Runtime Slang build started for shader '" + shaderId + "'.", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bridge = std::make_unique<RuntimeShaderBridge>();
|
||||||
|
RuntimeShaderBridge* bridgePtr = bridge.get();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||||
|
auto existingIt = mShaderBuilds.find(layerId);
|
||||||
|
if (existingIt != mShaderBuilds.end())
|
||||||
|
existingIt->second->Stop();
|
||||||
|
mShaderBuilds[layerId] = std::move(bridge);
|
||||||
|
}
|
||||||
|
|
||||||
|
bridgePtr->Start(
|
||||||
|
layerId,
|
||||||
|
shaderId,
|
||||||
|
[this, submitToRender](const RuntimeShaderArtifact& artifact) {
|
||||||
|
MarkRuntimeBuildReady(artifact);
|
||||||
|
if (submitToRender)
|
||||||
|
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
||||||
|
},
|
||||||
|
[this, layerId](const std::string& message) {
|
||||||
|
MarkRuntimeBuildFailedForLayer(layerId, message);
|
||||||
|
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopLayerShaderBuild(const std::string& layerId)
|
||||||
|
{
|
||||||
|
std::unique_ptr<RuntimeShaderBridge> bridge;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||||
|
auto bridgeIt = mShaderBuilds.find(layerId);
|
||||||
|
if (bridgeIt == mShaderBuilds.end())
|
||||||
|
return;
|
||||||
|
bridge = std::move(bridgeIt->second);
|
||||||
|
mShaderBuilds.erase(bridgeIt);
|
||||||
|
}
|
||||||
|
bridge->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopAllRuntimeShaderBuilds()
|
||||||
|
{
|
||||||
|
std::map<std::string, std::unique_ptr<RuntimeShaderBridge>> builds;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
|
||||||
|
builds.swap(mShaderBuilds);
|
||||||
|
}
|
||||||
|
for (auto& entry : builds)
|
||||||
|
entry.second->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlActionResult HandleAddLayer(const std::string& body)
|
||||||
|
{
|
||||||
|
std::string shaderId;
|
||||||
|
std::string error;
|
||||||
|
if (!ExtractStringField(body, "shaderId", shaderId, error))
|
||||||
|
return { false, error };
|
||||||
|
|
||||||
|
std::string layerId;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
if (!mRuntimeLayerModel.AddLayer(mShaderCatalog, shaderId, layerId, error))
|
||||||
|
return { false, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
StartLayerShaderBuild(layerId, shaderId, false);
|
||||||
|
return { true, std::string() };
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlActionResult HandleRemoveLayer(const std::string& body)
|
||||||
|
{
|
||||||
|
std::string layerId;
|
||||||
|
std::string error;
|
||||||
|
if (!ExtractStringField(body, "layerId", layerId, error))
|
||||||
|
return { false, error };
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
if (!mRuntimeLayerModel.RemoveLayer(layerId, error))
|
||||||
|
return { false, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
StopLayerShaderBuild(layerId);
|
||||||
|
return { true, std::string() };
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error)
|
||||||
|
{
|
||||||
|
JsonValue root;
|
||||||
|
std::string parseError;
|
||||||
|
if (!ParseJson(body.empty() ? "{}" : body, root, parseError) || !root.isObject())
|
||||||
|
{
|
||||||
|
error = parseError.empty() ? "Request body must be a JSON object." : parseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonValue* field = root.find(fieldName);
|
||||||
|
if (!field || !field->isString() || field->asString().empty())
|
||||||
|
{
|
||||||
|
error = std::string("Request field '") + fieldName + "' must be a non-empty string.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = field->asString();
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLayerModelSnapshot CopyRuntimeLayerSnapshot(const CadenceTelemetrySnapshot& telemetry) const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||||
|
RuntimeLayerModelSnapshot snapshot = mRuntimeLayerModel.Snapshot();
|
||||||
if (telemetry.shaderBuildFailures > 0)
|
if (telemetry.shaderBuildFailures > 0)
|
||||||
state.compileMessage = "Runtime shader GL commit failed; see logs for details.";
|
{
|
||||||
if (state.compileMessage.empty())
|
snapshot.compileSucceeded = false;
|
||||||
state.compileMessage = mConfig.runtimeShaderId.empty()
|
snapshot.compileMessage = "Runtime shader GL commit failed; see logs for details.";
|
||||||
? "Runtime shader build disabled."
|
}
|
||||||
: "Runtime shader build has not completed yet.";
|
return snapshot;
|
||||||
state.activeShaderPackage = mShaderCatalog.FindPackage(mActiveShaderId);
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderThread& mRenderThread;
|
RenderThread& mRenderThread;
|
||||||
@@ -297,12 +442,11 @@ private:
|
|||||||
TelemetryHealthMonitor mTelemetryHealth;
|
TelemetryHealthMonitor mTelemetryHealth;
|
||||||
CadenceTelemetry mHttpTelemetry;
|
CadenceTelemetry mHttpTelemetry;
|
||||||
HttpControlServer mHttpServer;
|
HttpControlServer mHttpServer;
|
||||||
RuntimeShaderBridge mShaderBridge;
|
|
||||||
SupportedShaderCatalog mShaderCatalog;
|
SupportedShaderCatalog mShaderCatalog;
|
||||||
mutable std::mutex mRuntimeDisplayMutex;
|
mutable std::mutex mRuntimeLayerMutex;
|
||||||
bool mRuntimeCompileSucceeded = true;
|
RuntimeLayerModel mRuntimeLayerModel;
|
||||||
std::string mRuntimeCompileMessage;
|
std::mutex mShaderBuildMutex;
|
||||||
std::string mActiveShaderId;
|
std::map<std::string, std::unique_ptr<RuntimeShaderBridge>> mShaderBuilds;
|
||||||
bool mStarted = false;
|
bool mStarted = false;
|
||||||
bool mVideoOutputEnabled = false;
|
bool mVideoOutputEnabled = false;
|
||||||
std::string mVideoOutputStatus = "DeckLink output not started.";
|
std::string mVideoOutputStatus = "DeckLink output not started.";
|
||||||
|
|||||||
@@ -266,6 +266,18 @@ HttpControlServer::HttpResponse HttpControlServer::ServePost(const HttpRequest&
|
|||||||
if (!IsKnownPostEndpoint(request.path))
|
if (!IsKnownPostEndpoint(request.path))
|
||||||
return TextResponse("404 Not Found", "Not Found");
|
return TextResponse("404 Not Found", "Not Found");
|
||||||
|
|
||||||
|
if (request.path == "/api/layers/add" && mCallbacks.addLayer)
|
||||||
|
{
|
||||||
|
const ControlActionResult result = mCallbacks.addLayer(request.body);
|
||||||
|
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.path == "/api/layers/remove" && mCallbacks.removeLayer)
|
||||||
|
{
|
||||||
|
const ControlActionResult result = mCallbacks.removeLayer(request.body);
|
||||||
|
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"400 Bad Request",
|
"400 Bad Request",
|
||||||
"application/json",
|
"application/json",
|
||||||
|
|||||||
@@ -19,9 +19,17 @@ struct HttpControlServerConfig
|
|||||||
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10);
|
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ControlActionResult
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
std::string error;
|
||||||
|
};
|
||||||
|
|
||||||
struct HttpControlServerCallbacks
|
struct HttpControlServerCallbacks
|
||||||
{
|
{
|
||||||
std::function<std::string()> getStateJson;
|
std::function<std::string()> getStateJson;
|
||||||
|
std::function<ControlActionResult(const std::string&)> addLayer;
|
||||||
|
std::function<ControlActionResult(const std::string&)> removeLayer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UniqueSocket
|
class UniqueSocket
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "../app/AppConfig.h"
|
#include "../app/AppConfig.h"
|
||||||
#include "../app/AppConfigProvider.h"
|
#include "../app/AppConfigProvider.h"
|
||||||
#include "../json/JsonWriter.h"
|
#include "../json/JsonWriter.h"
|
||||||
|
#include "../runtime/RuntimeLayerModel.h"
|
||||||
#include "../runtime/SupportedShaderCatalog.h"
|
#include "../runtime/SupportedShaderCatalog.h"
|
||||||
#include "../telemetry/CadenceTelemetryJson.h"
|
#include "../telemetry/CadenceTelemetryJson.h"
|
||||||
|
|
||||||
@@ -19,10 +20,8 @@ struct RuntimeStateJsonInput
|
|||||||
unsigned short serverPort = 0;
|
unsigned short serverPort = 0;
|
||||||
bool videoOutputEnabled = false;
|
bool videoOutputEnabled = false;
|
||||||
std::string videoOutputStatus;
|
std::string videoOutputStatus;
|
||||||
const std::vector<SupportedShaderSummary>& shaders;
|
const SupportedShaderCatalog& shaderCatalog;
|
||||||
bool runtimeCompileSucceeded = true;
|
const RuntimeLayerModelSnapshot& runtimeLayers;
|
||||||
std::string runtimeCompileMessage;
|
|
||||||
const ShaderPackage* activeShaderPackage = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
@@ -112,6 +111,17 @@ inline void WriteFeedbackJson(JsonWriter& writer, const FeedbackSettings& feedba
|
|||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const char* RuntimeLayerBuildStateName(RuntimeLayerBuildState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case RuntimeLayerBuildState::Pending: return "pending";
|
||||||
|
case RuntimeLayerBuildState::Ready: return "ready";
|
||||||
|
case RuntimeLayerBuildState::Failed: return "failed";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter)
|
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter)
|
||||||
{
|
{
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
@@ -164,22 +174,34 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
|
|||||||
inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
{
|
{
|
||||||
writer.BeginArray();
|
writer.BeginArray();
|
||||||
if (input.activeShaderPackage)
|
for (const RuntimeLayerReadModel& layer : input.runtimeLayers.displayLayers)
|
||||||
{
|
{
|
||||||
const ShaderPackage& shaderPackage = *input.activeShaderPackage;
|
const ShaderPackage* shaderPackage = input.shaderCatalog.FindPackage(layer.shaderId);
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
writer.KeyString("id", "runtime-layer-1");
|
writer.KeyString("id", layer.id);
|
||||||
writer.KeyString("shaderId", shaderPackage.id);
|
writer.KeyString("shaderId", layer.shaderId);
|
||||||
writer.KeyString("shaderName", shaderPackage.displayName.empty() ? shaderPackage.id : shaderPackage.displayName);
|
writer.KeyString("shaderName", layer.shaderName);
|
||||||
writer.KeyBool("bypass", false);
|
writer.KeyBool("bypass", layer.bypass);
|
||||||
|
writer.KeyString("buildState", RuntimeLayerBuildStateName(layer.buildState));
|
||||||
|
writer.KeyBool("renderReady", layer.renderReady);
|
||||||
|
writer.KeyString("message", layer.message);
|
||||||
writer.Key("temporal");
|
writer.Key("temporal");
|
||||||
WriteTemporalJson(writer, shaderPackage.temporal);
|
if (shaderPackage)
|
||||||
|
WriteTemporalJson(writer, shaderPackage->temporal);
|
||||||
|
else
|
||||||
|
WriteTemporalJson(writer, TemporalSettings());
|
||||||
writer.Key("feedback");
|
writer.Key("feedback");
|
||||||
WriteFeedbackJson(writer, shaderPackage.feedback);
|
if (shaderPackage)
|
||||||
|
WriteFeedbackJson(writer, shaderPackage->feedback);
|
||||||
|
else
|
||||||
|
WriteFeedbackJson(writer, FeedbackSettings());
|
||||||
writer.Key("parameters");
|
writer.Key("parameters");
|
||||||
writer.BeginArray();
|
writer.BeginArray();
|
||||||
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
|
if (shaderPackage)
|
||||||
|
{
|
||||||
|
for (const ShaderParameterDefinition& parameter : shaderPackage->parameters)
|
||||||
WriteParameterDefinitionJson(writer, parameter);
|
WriteParameterDefinitionJson(writer, parameter);
|
||||||
|
}
|
||||||
writer.EndArray();
|
writer.EndArray();
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
}
|
}
|
||||||
@@ -209,9 +231,9 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
|||||||
|
|
||||||
writer.Key("runtime");
|
writer.Key("runtime");
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
writer.KeyUInt("layerCount", input.activeShaderPackage ? 1 : 0);
|
writer.KeyUInt("layerCount", static_cast<uint64_t>(input.runtimeLayers.displayLayers.size()));
|
||||||
writer.KeyBool("compileSucceeded", input.runtimeCompileSucceeded);
|
writer.KeyBool("compileSucceeded", input.runtimeLayers.compileSucceeded);
|
||||||
writer.KeyString("compileMessage", input.runtimeCompileMessage);
|
writer.KeyString("compileMessage", input.runtimeLayers.compileMessage);
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
|
|
||||||
writer.Key("video");
|
writer.Key("video");
|
||||||
@@ -250,7 +272,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
|||||||
writer.KeyNull("runtimeEvents");
|
writer.KeyNull("runtimeEvents");
|
||||||
writer.Key("shaders");
|
writer.Key("shaders");
|
||||||
writer.BeginArray();
|
writer.BeginArray();
|
||||||
for (const SupportedShaderSummary& shader : input.shaders)
|
for (const SupportedShaderSummary& shader : input.shaderCatalog.Shaders())
|
||||||
{
|
{
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
writer.KeyString("id", shader.id);
|
writer.KeyString("id", shader.id);
|
||||||
|
|||||||
224
apps/RenderCadenceCompositor/runtime/RuntimeLayerModel.cpp
Normal file
224
apps/RenderCadenceCompositor/runtime/RuntimeLayerModel.cpp
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
#include "RuntimeLayerModel.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
if (shaderId.empty())
|
||||||
|
{
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
||||||
|
if (!shaderPackage)
|
||||||
|
{
|
||||||
|
error = "Shader '" + shaderId + "' is not in the supported shader catalog.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layer layer;
|
||||||
|
layer.id = AllocateLayerId();
|
||||||
|
layer.shaderId = shaderPackage->id;
|
||||||
|
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||||
|
layer.buildState = RuntimeLayerBuildState::Pending;
|
||||||
|
layer.message = "Runtime Slang build is waiting to start.";
|
||||||
|
mLayers.push_back(std::move(layer));
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::AddLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& layerId, std::string& error)
|
||||||
|
{
|
||||||
|
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
|
||||||
|
if (!shaderPackage)
|
||||||
|
{
|
||||||
|
error = "Shader '" + shaderId + "' is not in the supported shader catalog.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Layer layer;
|
||||||
|
layer.id = AllocateLayerId();
|
||||||
|
layer.shaderId = shaderPackage->id;
|
||||||
|
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||||
|
layer.buildState = RuntimeLayerBuildState::Pending;
|
||||||
|
layer.message = "Runtime Slang build is waiting to start.";
|
||||||
|
layerId = layer.id;
|
||||||
|
mLayers.push_back(std::move(layer));
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::RemoveLayer(const std::string& layerId, std::string& error)
|
||||||
|
{
|
||||||
|
for (auto layerIt = mLayers.begin(); layerIt != mLayers.end(); ++layerIt)
|
||||||
|
{
|
||||||
|
if (layerIt->id != layerId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mLayers.erase(layerIt);
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = "Unknown runtime layer id: " + layerId;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeLayerModel::Clear()
|
||||||
|
{
|
||||||
|
mLayers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error)
|
||||||
|
{
|
||||||
|
Layer* layer = FindLayer(layerId);
|
||||||
|
if (!layer)
|
||||||
|
{
|
||||||
|
error = "Unknown runtime layer id: " + layerId;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer->buildState = RuntimeLayerBuildState::Pending;
|
||||||
|
layer->message = message;
|
||||||
|
layer->renderReady = false;
|
||||||
|
layer->artifact = RuntimeShaderArtifact();
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error)
|
||||||
|
{
|
||||||
|
Layer* layer = artifact.layerId.empty() ? FindFirstLayerForShader(artifact.shaderId) : FindLayer(artifact.layerId);
|
||||||
|
if (!layer)
|
||||||
|
{
|
||||||
|
error = artifact.layerId.empty()
|
||||||
|
? "No runtime layer is waiting for shader artifact: " + artifact.shaderId
|
||||||
|
: "No runtime layer is waiting for shader artifact on layer: " + artifact.layerId;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer->shaderName = artifact.displayName.empty() ? artifact.shaderId : artifact.displayName;
|
||||||
|
layer->buildState = RuntimeLayerBuildState::Ready;
|
||||||
|
layer->message = artifact.message;
|
||||||
|
layer->renderReady = true;
|
||||||
|
layer->artifact = artifact;
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::MarkBuildFailedForShader(const std::string& shaderId, const std::string& message)
|
||||||
|
{
|
||||||
|
Layer* layer = FindFirstLayerForShader(shaderId);
|
||||||
|
if (!layer)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string error;
|
||||||
|
return MarkBuildFailed(layer->id, message, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error)
|
||||||
|
{
|
||||||
|
Layer* layer = FindLayer(layerId);
|
||||||
|
if (!layer)
|
||||||
|
{
|
||||||
|
error = "Unknown runtime layer id: " + layerId;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer->buildState = RuntimeLayerBuildState::Failed;
|
||||||
|
layer->message = message;
|
||||||
|
layer->renderReady = false;
|
||||||
|
layer->artifact = RuntimeShaderArtifact();
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeLayerModel::MarkRenderCommitFailed(const std::string& layerId, const std::string& message, std::string& error)
|
||||||
|
{
|
||||||
|
return MarkBuildFailed(layerId, message, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
|
||||||
|
{
|
||||||
|
RuntimeLayerModelSnapshot snapshot;
|
||||||
|
snapshot.compileSucceeded = true;
|
||||||
|
|
||||||
|
for (const Layer& layer : mLayers)
|
||||||
|
{
|
||||||
|
snapshot.displayLayers.push_back(ToReadModel(layer));
|
||||||
|
if (!layer.message.empty() && snapshot.compileMessage.empty())
|
||||||
|
snapshot.compileMessage = layer.message;
|
||||||
|
if (layer.buildState == RuntimeLayerBuildState::Failed)
|
||||||
|
snapshot.compileSucceeded = false;
|
||||||
|
if (layer.renderReady)
|
||||||
|
{
|
||||||
|
RuntimeRenderLayerModel renderLayer;
|
||||||
|
renderLayer.id = layer.id;
|
||||||
|
renderLayer.shaderId = layer.shaderId;
|
||||||
|
renderLayer.artifact = layer.artifact;
|
||||||
|
snapshot.renderLayers.push_back(std::move(renderLayer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.compileMessage.empty())
|
||||||
|
snapshot.compileMessage = mLayers.empty() ? "Runtime shader build disabled." : "Runtime shader build has not completed yet.";
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeLayerModel::FirstLayerId() const
|
||||||
|
{
|
||||||
|
return mLayers.empty() ? std::string() : mLayers.front().id;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLayerModel::Layer* RuntimeLayerModel::FindLayer(const std::string& layerId)
|
||||||
|
{
|
||||||
|
for (Layer& layer : mLayers)
|
||||||
|
{
|
||||||
|
if (layer.id == layerId)
|
||||||
|
return &layer;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RuntimeLayerModel::Layer* RuntimeLayerModel::FindLayer(const std::string& layerId) const
|
||||||
|
{
|
||||||
|
for (const Layer& layer : mLayers)
|
||||||
|
{
|
||||||
|
if (layer.id == layerId)
|
||||||
|
return &layer;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLayerModel::Layer* RuntimeLayerModel::FindFirstLayerForShader(const std::string& shaderId)
|
||||||
|
{
|
||||||
|
for (Layer& layer : mLayers)
|
||||||
|
{
|
||||||
|
if (layer.shaderId == shaderId)
|
||||||
|
return &layer;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string RuntimeLayerModel::AllocateLayerId()
|
||||||
|
{
|
||||||
|
return "runtime-layer-" + std::to_string(mNextLayerNumber++);
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLayerReadModel RuntimeLayerModel::ToReadModel(const Layer& layer)
|
||||||
|
{
|
||||||
|
RuntimeLayerReadModel readModel;
|
||||||
|
readModel.id = layer.id;
|
||||||
|
readModel.shaderId = layer.shaderId;
|
||||||
|
readModel.shaderName = layer.shaderName;
|
||||||
|
readModel.bypass = layer.bypass;
|
||||||
|
readModel.buildState = layer.buildState;
|
||||||
|
readModel.message = layer.message;
|
||||||
|
readModel.renderReady = layer.renderReady;
|
||||||
|
return readModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
apps/RenderCadenceCompositor/runtime/RuntimeLayerModel.h
Normal file
84
apps/RenderCadenceCompositor/runtime/RuntimeLayerModel.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RuntimeShaderArtifact.h"
|
||||||
|
#include "SupportedShaderCatalog.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
enum class RuntimeLayerBuildState
|
||||||
|
{
|
||||||
|
Pending,
|
||||||
|
Ready,
|
||||||
|
Failed
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeLayerReadModel
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string shaderId;
|
||||||
|
std::string shaderName;
|
||||||
|
bool bypass = false;
|
||||||
|
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
|
||||||
|
std::string message;
|
||||||
|
bool renderReady = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeRenderLayerModel
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string shaderId;
|
||||||
|
RuntimeShaderArtifact artifact;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeLayerModelSnapshot
|
||||||
|
{
|
||||||
|
bool compileSucceeded = true;
|
||||||
|
std::string compileMessage;
|
||||||
|
std::vector<RuntimeLayerReadModel> displayLayers;
|
||||||
|
std::vector<RuntimeRenderLayerModel> renderLayers;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RuntimeLayerModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
bool AddLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& layerId, std::string& error);
|
||||||
|
bool RemoveLayer(const std::string& layerId, std::string& error);
|
||||||
|
bool MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error);
|
||||||
|
bool MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error);
|
||||||
|
bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message);
|
||||||
|
bool MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error);
|
||||||
|
bool MarkRenderCommitFailed(const std::string& layerId, const std::string& message, std::string& error);
|
||||||
|
|
||||||
|
RuntimeLayerModelSnapshot Snapshot() const;
|
||||||
|
std::string FirstLayerId() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Layer
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string shaderId;
|
||||||
|
std::string shaderName;
|
||||||
|
bool bypass = false;
|
||||||
|
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
|
||||||
|
std::string message;
|
||||||
|
bool renderReady = false;
|
||||||
|
RuntimeShaderArtifact artifact;
|
||||||
|
};
|
||||||
|
|
||||||
|
Layer* FindLayer(const std::string& layerId);
|
||||||
|
const Layer* FindLayer(const std::string& layerId) const;
|
||||||
|
Layer* FindFirstLayerForShader(const std::string& shaderId);
|
||||||
|
std::string AllocateLayerId();
|
||||||
|
static RuntimeLayerReadModel ToReadModel(const Layer& layer);
|
||||||
|
|
||||||
|
std::vector<Layer> mLayers;
|
||||||
|
uint64_t mNextLayerNumber = 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
struct RuntimeShaderArtifact
|
struct RuntimeShaderArtifact
|
||||||
{
|
{
|
||||||
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
std::string fragmentShaderSource;
|
std::string fragmentShaderSource;
|
||||||
|
|||||||
@@ -8,11 +8,17 @@ RuntimeShaderBridge::~RuntimeShaderBridge()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeShaderBridge::Start(const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError)
|
void RuntimeShaderBridge::Start(const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError)
|
||||||
|
{
|
||||||
|
Start(std::string(), shaderId, std::move(onArtifactReady), std::move(onError));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeShaderBridge::Start(const std::string& layerId, const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError)
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
if (shaderId.empty())
|
if (shaderId.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
mLayerId = layerId;
|
||||||
mOnArtifactReady = std::move(onArtifactReady);
|
mOnArtifactReady = std::move(onArtifactReady);
|
||||||
mOnError = std::move(onError);
|
mOnError = std::move(onError);
|
||||||
mStopping.store(false, std::memory_order_release);
|
mStopping.store(false, std::memory_order_release);
|
||||||
@@ -26,6 +32,7 @@ void RuntimeShaderBridge::Stop()
|
|||||||
if (mThread.joinable())
|
if (mThread.joinable())
|
||||||
mThread.join();
|
mThread.join();
|
||||||
mCompiler.Stop();
|
mCompiler.Stop();
|
||||||
|
mLayerId.clear();
|
||||||
mOnArtifactReady = ArtifactCallback();
|
mOnArtifactReady = ArtifactCallback();
|
||||||
mOnError = ErrorCallback();
|
mOnError = ErrorCallback();
|
||||||
}
|
}
|
||||||
@@ -39,6 +46,7 @@ void RuntimeShaderBridge::ThreadMain()
|
|||||||
{
|
{
|
||||||
if (build.succeeded)
|
if (build.succeeded)
|
||||||
{
|
{
|
||||||
|
build.artifact.layerId = mLayerId;
|
||||||
if (mOnArtifactReady)
|
if (mOnArtifactReady)
|
||||||
mOnArtifactReady(build.artifact);
|
mOnArtifactReady(build.artifact);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public:
|
|||||||
~RuntimeShaderBridge();
|
~RuntimeShaderBridge();
|
||||||
|
|
||||||
void Start(const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError);
|
void Start(const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError);
|
||||||
|
void Start(const std::string& layerId, const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError);
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -28,6 +29,7 @@ private:
|
|||||||
RuntimeSlangShaderCompiler mCompiler;
|
RuntimeSlangShaderCompiler mCompiler;
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
std::atomic<bool> mStopping{ false };
|
std::atomic<bool> mStopping{ false };
|
||||||
|
std::string mLayerId;
|
||||||
ArtifactCallback mOnArtifactReady;
|
ArtifactCallback mOnArtifactReady;
|
||||||
ErrorCallback mOnError;
|
ErrorCallback mOnError;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ The CPU/build phase may parse manifests, invoke Slang, validate package shape, a
|
|||||||
|
|
||||||
The render thread receives a completed artifact and either commits it at a frame boundary or rejects it. A failed artifact must not disturb the current renderer.
|
The render thread receives a completed artifact and either commits it at a frame boundary or rejects it. A failed artifact must not disturb the current renderer.
|
||||||
|
|
||||||
|
The display/render layer model is app-owned. It may track requested shaders, build state, display metadata, and render-ready artifacts, but it must not perform GL work or drive render cadence directly.
|
||||||
|
|
||||||
## 5. No Hidden Blocking In The Cadence Path
|
## 5. No Hidden Blocking In The Cadence Path
|
||||||
|
|
||||||
The render loop must not do work with unbounded or OS-dependent latency.
|
The render loop must not do work with unbounded or OS-dependent latency.
|
||||||
@@ -124,6 +126,7 @@ Preferred boundaries:
|
|||||||
- render cadence/thread ownership
|
- render cadence/thread ownership
|
||||||
- GL rendering
|
- GL rendering
|
||||||
- runtime artifact build/bridge
|
- runtime artifact build/bridge
|
||||||
|
- app-owned display/render layer model
|
||||||
- parameter packing
|
- parameter packing
|
||||||
- system-memory frame exchange
|
- system-memory frame exchange
|
||||||
- DeckLink output scheduling
|
- DeckLink output scheduling
|
||||||
|
|||||||
@@ -106,6 +106,39 @@ void TestKnownPostEndpointReturnsActionError()
|
|||||||
Expect(response.body.find("not implemented") != std::string::npos, "unimplemented post reports diagnostic");
|
Expect(response.body.find("not implemented") != std::string::npos, "unimplemented post reports diagnostic");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestLayerPostEndpointsUseCallbacks()
|
||||||
|
{
|
||||||
|
using namespace RenderCadenceCompositor;
|
||||||
|
|
||||||
|
HttpControlServer server;
|
||||||
|
HttpControlServerCallbacks callbacks;
|
||||||
|
callbacks.addLayer = [](const std::string& body) {
|
||||||
|
Expect(body.find("solid") != std::string::npos, "add callback receives request body");
|
||||||
|
return ControlActionResult{ true, std::string() };
|
||||||
|
};
|
||||||
|
callbacks.removeLayer = [](const std::string& body) {
|
||||||
|
Expect(body.find("runtime-layer-1") != std::string::npos, "remove callback receives request body");
|
||||||
|
return ControlActionResult{ false, "Unknown layer id." };
|
||||||
|
};
|
||||||
|
server.SetCallbacksForTest(callbacks);
|
||||||
|
|
||||||
|
HttpControlServer::HttpRequest addRequest;
|
||||||
|
addRequest.method = "POST";
|
||||||
|
addRequest.path = "/api/layers/add";
|
||||||
|
addRequest.body = "{\"shaderId\":\"solid\"}";
|
||||||
|
const HttpControlServer::HttpResponse addResponse = server.RouteRequestForTest(addRequest);
|
||||||
|
ExpectEquals(addResponse.status, "200 OK", "add layer callback success returns 200");
|
||||||
|
Expect(addResponse.body.find("\"ok\":true") != std::string::npos, "add layer callback returns action success");
|
||||||
|
|
||||||
|
HttpControlServer::HttpRequest removeRequest;
|
||||||
|
removeRequest.method = "POST";
|
||||||
|
removeRequest.path = "/api/layers/remove";
|
||||||
|
removeRequest.body = "{\"layerId\":\"runtime-layer-1\"}";
|
||||||
|
const HttpControlServer::HttpResponse removeResponse = server.RouteRequestForTest(removeRequest);
|
||||||
|
ExpectEquals(removeResponse.status, "400 Bad Request", "remove layer callback failure returns 400");
|
||||||
|
Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic");
|
||||||
|
}
|
||||||
|
|
||||||
void TestUnknownEndpointReturns404()
|
void TestUnknownEndpointReturns404()
|
||||||
{
|
{
|
||||||
using namespace RenderCadenceCompositor;
|
using namespace RenderCadenceCompositor;
|
||||||
@@ -126,6 +159,7 @@ int main()
|
|||||||
TestStateEndpointUsesCallback();
|
TestStateEndpointUsesCallback();
|
||||||
TestRootServesUiIndex();
|
TestRootServesUiIndex();
|
||||||
TestKnownPostEndpointReturnsActionError();
|
TestKnownPostEndpointReturnsActionError();
|
||||||
|
TestLayerPostEndpointsUseCallbacks();
|
||||||
TestUnknownEndpointReturns404();
|
TestUnknownEndpointReturns404();
|
||||||
|
|
||||||
if (gFailures != 0)
|
if (gFailures != 0)
|
||||||
|
|||||||
163
tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp
Normal file
163
tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include "RuntimeLayerModel.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void Expect(bool condition, const std::string& message)
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
++gFailures;
|
||||||
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path MakeTestRoot()
|
||||||
|
{
|
||||||
|
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
|
const std::filesystem::path root = std::filesystem::temp_directory_path() / ("render-cadence-layer-model-tests-" + std::to_string(stamp));
|
||||||
|
std::filesystem::create_directories(root);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteFile(const std::filesystem::path& path, const std::string& contents)
|
||||||
|
{
|
||||||
|
std::filesystem::create_directories(path.parent_path());
|
||||||
|
std::ofstream output(path, std::ios::binary);
|
||||||
|
output << contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog MakeCatalog(std::filesystem::path& root)
|
||||||
|
{
|
||||||
|
root = MakeTestRoot();
|
||||||
|
WriteFile(root / "solid" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
||||||
|
WriteFile(root / "solid" / "shader.json", R"({
|
||||||
|
"id": "solid",
|
||||||
|
"name": "Solid",
|
||||||
|
"description": "Solid test shader",
|
||||||
|
"category": "Tests",
|
||||||
|
"entryPoint": "shadeVideo",
|
||||||
|
"parameters": [
|
||||||
|
{ "id": "gain", "label": "Gain", "type": "float", "default": 0.5 }
|
||||||
|
]
|
||||||
|
})");
|
||||||
|
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog catalog;
|
||||||
|
std::string error;
|
||||||
|
Expect(catalog.Load(root, 4, error), error.empty() ? "catalog loads test shader" : error);
|
||||||
|
return catalog;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSingleLayerLifecycle()
|
||||||
|
{
|
||||||
|
std::filesystem::path root;
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||||
|
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||||
|
std::string error;
|
||||||
|
Expect(model.InitializeSingleLayer(catalog, "solid", error), "model initializes a supported startup shader");
|
||||||
|
Expect(model.FirstLayerId() == "runtime-layer-1", "startup layer id is stable");
|
||||||
|
|
||||||
|
Expect(model.MarkBuildStarted(model.FirstLayerId(), "build started", error), "build start updates the layer");
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
||||||
|
Expect(snapshot.displayLayers.size() == 1, "snapshot exposes the display layer");
|
||||||
|
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Pending, "started layer is pending");
|
||||||
|
Expect(!snapshot.displayLayers[0].renderReady, "started layer is not render-ready yet");
|
||||||
|
|
||||||
|
RuntimeShaderArtifact artifact;
|
||||||
|
artifact.shaderId = "solid";
|
||||||
|
artifact.displayName = "Solid";
|
||||||
|
artifact.fragmentShaderSource = "void main(){}";
|
||||||
|
artifact.message = "build ready";
|
||||||
|
Expect(model.MarkBuildReady(artifact, error), "ready artifact updates the matching layer");
|
||||||
|
|
||||||
|
snapshot = model.Snapshot();
|
||||||
|
Expect(snapshot.compileSucceeded, "ready layer reports compile success");
|
||||||
|
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Ready, "ready layer is marked ready");
|
||||||
|
Expect(snapshot.displayLayers[0].renderReady, "ready layer exposes render readiness");
|
||||||
|
Expect(snapshot.renderLayers.size() == 1, "ready layer produces one render layer artifact");
|
||||||
|
Expect(snapshot.renderLayers[0].artifact.shaderId == "solid", "render layer carries the artifact");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestRejectsUnsupportedStartupShader()
|
||||||
|
{
|
||||||
|
std::filesystem::path root;
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||||
|
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||||
|
std::string error;
|
||||||
|
Expect(!model.InitializeSingleLayer(catalog, "missing", error), "model rejects unsupported shader ids");
|
||||||
|
Expect(!error.empty(), "unsupported shader rejection explains the problem");
|
||||||
|
Expect(model.Snapshot().displayLayers.empty(), "rejected startup shader leaves no display layer");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestBuildFailureStaysDisplaySide()
|
||||||
|
{
|
||||||
|
std::filesystem::path root;
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||||
|
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||||
|
std::string error;
|
||||||
|
Expect(model.InitializeSingleLayer(catalog, "solid", error), "model initializes for failure test");
|
||||||
|
Expect(model.MarkBuildFailed(model.FirstLayerId(), "compile failed", error), "build failure updates the layer");
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
||||||
|
Expect(!snapshot.compileSucceeded, "failed layer reports compile failure");
|
||||||
|
Expect(snapshot.displayLayers[0].buildState == RenderCadenceCompositor::RuntimeLayerBuildState::Failed, "failed layer is marked failed");
|
||||||
|
Expect(!snapshot.displayLayers[0].renderReady, "failed layer is not render-ready");
|
||||||
|
Expect(snapshot.renderLayers.empty(), "failed layer does not produce a render artifact");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAddAndRemoveLayers()
|
||||||
|
{
|
||||||
|
std::filesystem::path root;
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||||
|
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||||
|
std::string error;
|
||||||
|
std::string firstLayerId;
|
||||||
|
std::string secondLayerId;
|
||||||
|
Expect(model.AddLayer(catalog, "solid", firstLayerId, error), "first layer can be added");
|
||||||
|
Expect(model.AddLayer(catalog, "solid", secondLayerId, error), "second layer can be added");
|
||||||
|
Expect(firstLayerId != secondLayerId, "added layers receive distinct ids");
|
||||||
|
Expect(model.Snapshot().displayLayers.size() == 2, "added layers appear in display snapshot");
|
||||||
|
|
||||||
|
Expect(model.RemoveLayer(firstLayerId, error), "existing layer can be removed");
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
||||||
|
Expect(snapshot.displayLayers.size() == 1, "removed layer leaves snapshot");
|
||||||
|
Expect(snapshot.displayLayers[0].id == secondLayerId, "remaining layer identity is preserved");
|
||||||
|
Expect(!model.RemoveLayer(firstLayerId, error), "removed layer cannot be removed twice");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TestSingleLayerLifecycle();
|
||||||
|
TestRejectsUnsupportedStartupShader();
|
||||||
|
TestBuildFailureStaysDisplaySide();
|
||||||
|
TestAddAndRemoveLayers();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " RenderCadenceCompositorRuntimeLayerModel test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RenderCadenceCompositorRuntimeLayerModel tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#include "RuntimeStateJson.h"
|
#include "RuntimeStateJson.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -17,29 +19,19 @@ void ExpectContains(const std::string& text, const std::string& fragment, const
|
|||||||
std::cerr << "FAIL: " << message << "\n";
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderPackage MakePackage()
|
std::filesystem::path MakeTestRoot()
|
||||||
{
|
{
|
||||||
ShaderPackage package;
|
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||||
package.id = "solid-color";
|
const std::filesystem::path root = std::filesystem::temp_directory_path() / ("render-cadence-state-json-tests-" + std::to_string(stamp));
|
||||||
package.displayName = "Solid Color";
|
std::filesystem::create_directories(root);
|
||||||
package.description = "A single color shader.";
|
return root;
|
||||||
package.category = "Generator";
|
}
|
||||||
|
|
||||||
ShaderPassDefinition pass;
|
void WriteFile(const std::filesystem::path& path, const std::string& contents)
|
||||||
pass.id = "main";
|
{
|
||||||
package.passes.push_back(pass);
|
std::filesystem::create_directories(path.parent_path());
|
||||||
|
std::ofstream output(path, std::ios::binary);
|
||||||
ShaderParameterDefinition color;
|
output << contents;
|
||||||
color.id = "color";
|
|
||||||
color.label = "Color";
|
|
||||||
color.description = "Output color.";
|
|
||||||
color.type = ShaderParameterType::Color;
|
|
||||||
color.defaultNumbers = { 1.0, 0.25, 0.5, 1.0 };
|
|
||||||
color.minNumbers = { 0.0, 0.0, 0.0, 0.0 };
|
|
||||||
color.maxNumbers = { 1.0, 1.0, 1.0, 1.0 };
|
|
||||||
color.stepNumbers = { 0.01, 0.01, 0.01, 0.01 };
|
|
||||||
package.parameters.push_back(color);
|
|
||||||
return package;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,10 +45,41 @@ int main()
|
|||||||
telemetry.renderFps = 59.94;
|
telemetry.renderFps = 59.94;
|
||||||
telemetry.shaderBuildsCommitted = 1;
|
telemetry.shaderBuildsCommitted = 1;
|
||||||
|
|
||||||
std::vector<RenderCadenceCompositor::SupportedShaderSummary> shaders = {
|
const std::filesystem::path root = MakeTestRoot();
|
||||||
{ "solid-color", "Solid Color", "A single color shader.", "Generator" }
|
WriteFile(root / "solid-color" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 0.0, 1.0); }\n");
|
||||||
};
|
WriteFile(root / "solid-color" / "shader.json", R"({
|
||||||
const ShaderPackage package = MakePackage();
|
"id": "solid-color",
|
||||||
|
"name": "Solid Color",
|
||||||
|
"description": "A single color shader.",
|
||||||
|
"category": "Generator",
|
||||||
|
"entryPoint": "shadeVideo",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "color",
|
||||||
|
"label": "Color",
|
||||||
|
"description": "Output color.",
|
||||||
|
"type": "color",
|
||||||
|
"default": [1.0, 0.25, 0.5, 1.0],
|
||||||
|
"min": [0.0, 0.0, 0.0, 0.0],
|
||||||
|
"max": [1.0, 1.0, 1.0, 1.0],
|
||||||
|
"step": [0.01, 0.01, 0.01, 0.01]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})");
|
||||||
|
|
||||||
|
RenderCadenceCompositor::SupportedShaderCatalog shaderCatalog;
|
||||||
|
std::string error;
|
||||||
|
ExpectContains(shaderCatalog.Load(root, 4, error) ? "loaded" : error, "loaded", "test shader catalog should load");
|
||||||
|
|
||||||
|
RenderCadenceCompositor::RuntimeLayerModel layerModel;
|
||||||
|
layerModel.InitializeSingleLayer(shaderCatalog, "solid-color", error);
|
||||||
|
RuntimeShaderArtifact artifact;
|
||||||
|
artifact.shaderId = "solid-color";
|
||||||
|
artifact.displayName = "Solid Color";
|
||||||
|
artifact.fragmentShaderSource = "void main(){}";
|
||||||
|
artifact.message = "Runtime shader committed.";
|
||||||
|
layerModel.MarkBuildReady(artifact, error);
|
||||||
|
const RenderCadenceCompositor::RuntimeLayerModelSnapshot layerSnapshot = layerModel.Snapshot();
|
||||||
|
|
||||||
const std::string json = RenderCadenceCompositor::RuntimeStateToJson(RenderCadenceCompositor::RuntimeStateJsonInput{
|
const std::string json = RenderCadenceCompositor::RuntimeStateToJson(RenderCadenceCompositor::RuntimeStateJsonInput{
|
||||||
config,
|
config,
|
||||||
@@ -64,10 +87,8 @@ int main()
|
|||||||
8080,
|
8080,
|
||||||
true,
|
true,
|
||||||
"DeckLink scheduled output running.",
|
"DeckLink scheduled output running.",
|
||||||
shaders,
|
shaderCatalog,
|
||||||
true,
|
layerSnapshot
|
||||||
"Runtime shader committed.",
|
|
||||||
&package
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ExpectContains(json, "\"shaders\":[{\"id\":\"solid-color\"", "state JSON should include supported shaders");
|
ExpectContains(json, "\"shaders\":[{\"id\":\"solid-color\"", "state JSON should include supported shaders");
|
||||||
@@ -78,6 +99,8 @@ int main()
|
|||||||
ExpectContains(json, "\"width\":1920", "state JSON should expose output width");
|
ExpectContains(json, "\"width\":1920", "state JSON should expose output width");
|
||||||
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
|
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
|
||||||
|
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
|
||||||
if (gFailures != 0)
|
if (gFailures != 0)
|
||||||
{
|
{
|
||||||
std::cerr << gFailures << " RenderCadenceCompositorRuntimeStateJson test failure(s).\n";
|
std::cerr << gFailures << " RenderCadenceCompositorRuntimeStateJson test failure(s).\n";
|
||||||
|
|||||||
Reference in New Issue
Block a user