end point adjsutments
This commit is contained in:
@@ -337,6 +337,8 @@ set(RENDER_CADENCE_APP_SOURCES
|
|||||||
"${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"
|
||||||
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.h"
|
"${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.h"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetryJson.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetryJson.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetry.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetry.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryHealthMonitor.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryHealthMonitor.h"
|
||||||
@@ -819,6 +821,26 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
|
add_test(NAME RenderCadenceCompositorRuntimeShaderParamsTests COMMAND RenderCadenceCompositorRuntimeShaderParamsTests)
|
||||||
|
|
||||||
|
add_executable(RenderCadenceCompositorSupportedShaderCatalogTests
|
||||||
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorSupportedShaderCatalogTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RenderCadenceCompositorSupportedShaderCatalogTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
|
"${APP_DIR}/shader"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RenderCadenceCompositorSupportedShaderCatalogTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RenderCadenceCompositorSupportedShaderCatalogTests COMMAND RenderCadenceCompositorSupportedShaderCatalogTests)
|
||||||
|
|
||||||
add_executable(RenderCadenceCompositorLoggerTests
|
add_executable(RenderCadenceCompositorLoggerTests
|
||||||
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
|
"${RENDER_CADENCE_APP_DIR}/logging/Logger.cpp"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorLoggerTests.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorLoggerTests.cpp"
|
||||||
@@ -849,6 +871,35 @@ endif()
|
|||||||
|
|
||||||
add_test(NAME RenderCadenceCompositorJsonWriterTests COMMAND RenderCadenceCompositorJsonWriterTests)
|
add_test(NAME RenderCadenceCompositorJsonWriterTests COMMAND RenderCadenceCompositorJsonWriterTests)
|
||||||
|
|
||||||
|
add_executable(RenderCadenceCompositorRuntimeStateJsonTests
|
||||||
|
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(RenderCadenceCompositorRuntimeStateJsonTests PRIVATE
|
||||||
|
"${APP_DIR}"
|
||||||
|
"${APP_DIR}/runtime/support"
|
||||||
|
"${APP_DIR}/shader"
|
||||||
|
"${APP_DIR}/videoio"
|
||||||
|
"${APP_DIR}/videoio/decklink"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/app"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/control"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/json"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/logging"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/telemetry"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/video"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
target_compile_options(RenderCadenceCompositorRuntimeStateJsonTests PRIVATE /W3)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME RenderCadenceCompositorRuntimeStateJsonTests COMMAND RenderCadenceCompositorRuntimeStateJsonTests)
|
||||||
|
|
||||||
add_executable(RenderCadenceCompositorHttpControlServerTests
|
add_executable(RenderCadenceCompositorHttpControlServerTests
|
||||||
"${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.cpp"
|
"${RENDER_CADENCE_APP_DIR}/control/HttpControlServer.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
|
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Included now:
|
|||||||
- 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
|
||||||
|
- HTTP shader list populated from supported stateless single-pass shader packages
|
||||||
- default float, vec2, color, boolean, enum, and trigger parameters
|
- default float, vec2, color, boolean, enum, and trigger parameters
|
||||||
- small JSON writer for future HTTP/WebSocket payloads
|
- small JSON writer for future HTTP/WebSocket payloads
|
||||||
- JSON serialization for cadence telemetry snapshots
|
- JSON serialization for cadence telemetry snapshots
|
||||||
@@ -145,7 +146,7 @@ The app starts a local HTTP control server on `http://127.0.0.1:8080` by default
|
|||||||
Current endpoints:
|
Current endpoints:
|
||||||
|
|
||||||
- `GET /` and UI asset paths: serve the bundled control UI from `ui/dist`
|
- `GET /` and UI asset paths: serve the bundled control UI from `ui/dist`
|
||||||
- `GET /api/state`: returns an OpenAPI-shaped state scaffold with cadence telemetry under `performance.cadence`
|
- `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." }`
|
- OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
|
||||||
@@ -205,6 +206,10 @@ Current runtime shader support is deliberately limited to stateless single-pass
|
|||||||
- manifest defaults are used for parameters
|
- manifest defaults are used for parameters
|
||||||
- `gVideoInput` and `gLayerInput` are bound to a small fallback source texture until DeckLink input is added
|
- `gVideoInput` and `gLayerInput` are bound to a small fallback source texture until DeckLink input is added
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
Successful handoff signs:
|
Successful handoff signs:
|
||||||
|
|
||||||
- telemetry shows `shaderCommitted=1`
|
- telemetry shows `shaderCommitted=1`
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "AppConfigProvider.h"
|
#include "AppConfigProvider.h"
|
||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
#include "../runtime/RuntimeShaderBridge.h"
|
#include "../runtime/RuntimeShaderBridge.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"
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -68,6 +70,8 @@ public:
|
|||||||
|
|
||||||
bool Start(std::string& error)
|
bool Start(std::string& error)
|
||||||
{
|
{
|
||||||
|
LoadSupportedShaderCatalog();
|
||||||
|
|
||||||
Log("app", "Starting render thread.");
|
Log("app", "Starting render thread.");
|
||||||
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
if (!detail::StartRenderThread(mRenderThread, error, 0))
|
||||||
{
|
{
|
||||||
@@ -187,12 +191,17 @@ 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);
|
||||||
return RuntimeStateToJson(RuntimeStateJsonInput{
|
return RuntimeStateToJson(RuntimeStateJsonInput{
|
||||||
mConfig,
|
mConfig,
|
||||||
telemetry,
|
telemetry,
|
||||||
mHttpServer.Port(),
|
mHttpServer.Port(),
|
||||||
mVideoOutputEnabled,
|
mVideoOutputEnabled,
|
||||||
mVideoOutputStatus
|
mVideoOutputStatus,
|
||||||
|
mShaderCatalog.Shaders(),
|
||||||
|
runtimeState.compileSucceeded,
|
||||||
|
runtimeState.compileMessage,
|
||||||
|
runtimeState.activeShaderPackage
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,21 +226,69 @@ 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);
|
||||||
mShaderBridge.Start(
|
mShaderBridge.Start(
|
||||||
mConfig.runtimeShaderId,
|
mConfig.runtimeShaderId,
|
||||||
[this](const RuntimeShaderArtifact& artifact) {
|
[this](const RuntimeShaderArtifact& artifact) {
|
||||||
|
SetRuntimeDisplayState(true, artifact.message.empty() ? "Runtime shader artifact is ready." : artifact.message, artifact.shaderId);
|
||||||
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
|
||||||
},
|
},
|
||||||
[](const std::string& message) {
|
[this](const std::string& message) {
|
||||||
|
SetRuntimeDisplayState(false, message);
|
||||||
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
LogError("runtime-shader", "Runtime Slang build failed: " + message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoadSupportedShaderCatalog()
|
||||||
|
{
|
||||||
|
const std::filesystem::path shaderRoot = FindRepoPath(mConfig.shaderLibrary);
|
||||||
|
std::string error;
|
||||||
|
if (!mShaderCatalog.Load(shaderRoot, static_cast<unsigned>(mConfig.maxTemporalHistoryFrames), error))
|
||||||
|
{
|
||||||
|
LogWarning("runtime-shader", "Supported shader catalog is empty: " + error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("runtime-shader", "Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) + " shader(s).");
|
||||||
|
}
|
||||||
|
|
||||||
void StopRuntimeShaderBuild()
|
void StopRuntimeShaderBuild()
|
||||||
{
|
{
|
||||||
mShaderBridge.Stop();
|
mShaderBridge.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RuntimeDisplayState
|
||||||
|
{
|
||||||
|
bool compileSucceeded = true;
|
||||||
|
std::string compileMessage;
|
||||||
|
const ShaderPackage* activeShaderPackage = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mRuntimeDisplayMutex);
|
||||||
|
RuntimeDisplayState state;
|
||||||
|
state.compileSucceeded = mRuntimeCompileSucceeded && telemetry.shaderBuildFailures == 0;
|
||||||
|
state.compileMessage = mRuntimeCompileMessage;
|
||||||
|
if (telemetry.shaderBuildFailures > 0)
|
||||||
|
state.compileMessage = "Runtime shader GL commit failed; see logs for details.";
|
||||||
|
if (state.compileMessage.empty())
|
||||||
|
state.compileMessage = mConfig.runtimeShaderId.empty()
|
||||||
|
? "Runtime shader build disabled."
|
||||||
|
: "Runtime shader build has not completed yet.";
|
||||||
|
state.activeShaderPackage = mShaderCatalog.FindPackage(mActiveShaderId);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
RenderThread& mRenderThread;
|
RenderThread& mRenderThread;
|
||||||
SystemFrameExchange& mFrameExchange;
|
SystemFrameExchange& mFrameExchange;
|
||||||
AppConfig mConfig;
|
AppConfig mConfig;
|
||||||
@@ -241,6 +298,11 @@ private:
|
|||||||
CadenceTelemetry mHttpTelemetry;
|
CadenceTelemetry mHttpTelemetry;
|
||||||
HttpControlServer mHttpServer;
|
HttpControlServer mHttpServer;
|
||||||
RuntimeShaderBridge mShaderBridge;
|
RuntimeShaderBridge mShaderBridge;
|
||||||
|
SupportedShaderCatalog mShaderCatalog;
|
||||||
|
mutable std::mutex mRuntimeDisplayMutex;
|
||||||
|
bool mRuntimeCompileSucceeded = true;
|
||||||
|
std::string mRuntimeCompileMessage;
|
||||||
|
std::string mActiveShaderId;
|
||||||
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.";
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
#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/SupportedShaderCatalog.h"
|
||||||
#include "../telemetry/CadenceTelemetryJson.h"
|
#include "../telemetry/CadenceTelemetryJson.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace RenderCadenceCompositor
|
namespace RenderCadenceCompositor
|
||||||
{
|
{
|
||||||
@@ -17,6 +19,10 @@ 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;
|
||||||
|
bool runtimeCompileSucceeded = true;
|
||||||
|
std::string runtimeCompileMessage;
|
||||||
|
const ShaderPackage* activeShaderPackage = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
@@ -33,6 +39,153 @@ inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInp
|
|||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void OutputDimensions(const RuntimeStateJsonInput& input, unsigned& width, unsigned& height)
|
||||||
|
{
|
||||||
|
VideoFormatDimensions(input.config.outputVideoFormat, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char* ShaderParameterTypeName(ShaderParameterType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ShaderParameterType::Float: return "float";
|
||||||
|
case ShaderParameterType::Vec2: return "vec2";
|
||||||
|
case ShaderParameterType::Color: return "color";
|
||||||
|
case ShaderParameterType::Boolean: return "bool";
|
||||||
|
case ShaderParameterType::Enum: return "enum";
|
||||||
|
case ShaderParameterType::Text: return "text";
|
||||||
|
case ShaderParameterType::Trigger: return "trigger";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteNumberArray(JsonWriter& writer, const std::vector<double>& values)
|
||||||
|
{
|
||||||
|
writer.BeginArray();
|
||||||
|
for (double value : values)
|
||||||
|
writer.Double(value);
|
||||||
|
writer.EndArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteDefaultParameterValue(JsonWriter& writer, const ShaderParameterDefinition& parameter)
|
||||||
|
{
|
||||||
|
switch (parameter.type)
|
||||||
|
{
|
||||||
|
case ShaderParameterType::Boolean:
|
||||||
|
writer.Bool(parameter.defaultBoolean);
|
||||||
|
return;
|
||||||
|
case ShaderParameterType::Enum:
|
||||||
|
writer.String(parameter.defaultEnumValue);
|
||||||
|
return;
|
||||||
|
case ShaderParameterType::Text:
|
||||||
|
writer.String(parameter.defaultTextValue);
|
||||||
|
return;
|
||||||
|
case ShaderParameterType::Trigger:
|
||||||
|
writer.Double(0.0);
|
||||||
|
return;
|
||||||
|
case ShaderParameterType::Float:
|
||||||
|
writer.Double(parameter.defaultNumbers.empty() ? 0.0 : parameter.defaultNumbers.front());
|
||||||
|
return;
|
||||||
|
case ShaderParameterType::Vec2:
|
||||||
|
case ShaderParameterType::Color:
|
||||||
|
WriteNumberArray(writer, parameter.defaultNumbers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writer.Null();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteTemporalJson(JsonWriter& writer, const TemporalSettings& temporal)
|
||||||
|
{
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyBool("enabled", temporal.enabled);
|
||||||
|
writer.KeyString("historySource", "none");
|
||||||
|
writer.KeyUInt("requestedHistoryLength", temporal.requestedHistoryLength);
|
||||||
|
writer.KeyUInt("effectiveHistoryLength", temporal.effectiveHistoryLength);
|
||||||
|
writer.EndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteFeedbackJson(JsonWriter& writer, const FeedbackSettings& feedback)
|
||||||
|
{
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyBool("enabled", feedback.enabled);
|
||||||
|
writer.KeyString("writePass", feedback.writePassId);
|
||||||
|
writer.EndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter)
|
||||||
|
{
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyString("id", parameter.id);
|
||||||
|
writer.KeyString("label", parameter.label.empty() ? parameter.id : parameter.label);
|
||||||
|
writer.KeyString("description", parameter.description);
|
||||||
|
writer.KeyString("type", ShaderParameterTypeName(parameter.type));
|
||||||
|
writer.Key("defaultValue");
|
||||||
|
WriteDefaultParameterValue(writer, parameter);
|
||||||
|
writer.Key("value");
|
||||||
|
WriteDefaultParameterValue(writer, parameter);
|
||||||
|
|
||||||
|
if (!parameter.minNumbers.empty())
|
||||||
|
{
|
||||||
|
writer.Key("min");
|
||||||
|
WriteNumberArray(writer, parameter.minNumbers);
|
||||||
|
}
|
||||||
|
if (!parameter.maxNumbers.empty())
|
||||||
|
{
|
||||||
|
writer.Key("max");
|
||||||
|
WriteNumberArray(writer, parameter.maxNumbers);
|
||||||
|
}
|
||||||
|
if (!parameter.stepNumbers.empty())
|
||||||
|
{
|
||||||
|
writer.Key("step");
|
||||||
|
WriteNumberArray(writer, parameter.stepNumbers);
|
||||||
|
}
|
||||||
|
if (parameter.type == ShaderParameterType::Enum)
|
||||||
|
{
|
||||||
|
writer.Key("options");
|
||||||
|
writer.BeginArray();
|
||||||
|
for (const ShaderParameterOption& option : parameter.enumOptions)
|
||||||
|
{
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyString("value", option.value);
|
||||||
|
writer.KeyString("label", option.label.empty() ? option.value : option.label);
|
||||||
|
writer.EndObject();
|
||||||
|
}
|
||||||
|
writer.EndArray();
|
||||||
|
}
|
||||||
|
if (parameter.type == ShaderParameterType::Text)
|
||||||
|
{
|
||||||
|
writer.KeyUInt("maxLength", parameter.maxLength);
|
||||||
|
if (!parameter.fontId.empty())
|
||||||
|
writer.KeyString("font", parameter.fontId);
|
||||||
|
}
|
||||||
|
writer.EndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
|
||||||
|
{
|
||||||
|
writer.BeginArray();
|
||||||
|
if (input.activeShaderPackage)
|
||||||
|
{
|
||||||
|
const ShaderPackage& shaderPackage = *input.activeShaderPackage;
|
||||||
|
writer.BeginObject();
|
||||||
|
writer.KeyString("id", "runtime-layer-1");
|
||||||
|
writer.KeyString("shaderId", shaderPackage.id);
|
||||||
|
writer.KeyString("shaderName", shaderPackage.displayName.empty() ? shaderPackage.id : shaderPackage.displayName);
|
||||||
|
writer.KeyBool("bypass", false);
|
||||||
|
writer.Key("temporal");
|
||||||
|
WriteTemporalJson(writer, shaderPackage.temporal);
|
||||||
|
writer.Key("feedback");
|
||||||
|
WriteFeedbackJson(writer, shaderPackage.feedback);
|
||||||
|
writer.Key("parameters");
|
||||||
|
writer.BeginArray();
|
||||||
|
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
|
||||||
|
WriteParameterDefinitionJson(writer, parameter);
|
||||||
|
writer.EndArray();
|
||||||
|
writer.EndObject();
|
||||||
|
}
|
||||||
|
writer.EndArray();
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
||||||
{
|
{
|
||||||
JsonWriter writer;
|
JsonWriter writer;
|
||||||
@@ -56,17 +209,20 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
|
|||||||
|
|
||||||
writer.Key("runtime");
|
writer.Key("runtime");
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
writer.KeyUInt("layerCount", 0);
|
writer.KeyUInt("layerCount", input.activeShaderPackage ? 1 : 0);
|
||||||
writer.KeyBool("compileSucceeded", true);
|
writer.KeyBool("compileSucceeded", input.runtimeCompileSucceeded);
|
||||||
writer.KeyString("compileMessage", "Runtime state is not ported into RenderCadenceCompositor yet.");
|
writer.KeyString("compileMessage", input.runtimeCompileMessage);
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
|
|
||||||
writer.Key("video");
|
writer.Key("video");
|
||||||
writer.BeginObject();
|
writer.BeginObject();
|
||||||
writer.KeyBool("hasSignal", false);
|
unsigned outputWidth = 0;
|
||||||
writer.KeyNull("width");
|
unsigned outputHeight = 0;
|
||||||
writer.KeyNull("height");
|
OutputDimensions(input, outputWidth, outputHeight);
|
||||||
writer.KeyString("modeName", "output-only");
|
writer.KeyBool("hasSignal", input.videoOutputEnabled);
|
||||||
|
writer.KeyUInt("width", outputWidth);
|
||||||
|
writer.KeyUInt("height", outputHeight);
|
||||||
|
writer.KeyString("modeName", input.config.outputVideoFormat + " output-only");
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
|
|
||||||
writer.Key("decklink");
|
writer.Key("decklink");
|
||||||
@@ -94,13 +250,23 @@ 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)
|
||||||
|
{
|
||||||
|
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();
|
writer.EndArray();
|
||||||
writer.Key("stackPresets");
|
writer.Key("stackPresets");
|
||||||
writer.BeginArray();
|
writer.BeginArray();
|
||||||
writer.EndArray();
|
writer.EndArray();
|
||||||
writer.Key("layers");
|
writer.Key("layers");
|
||||||
writer.BeginArray();
|
WriteLayersJson(writer, input);
|
||||||
writer.EndArray();
|
|
||||||
|
|
||||||
writer.EndObject();
|
writer.EndObject();
|
||||||
return writer.StringValue();
|
return writer.StringValue();
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
#include "ShaderCompiler.h"
|
#include "ShaderCompiler.h"
|
||||||
#include "ShaderPackageRegistry.h"
|
#include "ShaderPackageRegistry.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
|
#include "SupportedShaderCatalog.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -28,43 +28,6 @@ std::filesystem::path FindRepoRoot()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsStatelessSinglePassPackage(const ShaderPackage& shaderPackage, std::string& error)
|
|
||||||
{
|
|
||||||
if (shaderPackage.passes.size() != 1)
|
|
||||||
{
|
|
||||||
error = "RenderCadenceCompositor currently supports only single-pass runtime shaders.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (shaderPackage.temporal.enabled)
|
|
||||||
{
|
|
||||||
error = "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (shaderPackage.feedback.enabled)
|
|
||||||
{
|
|
||||||
error = "RenderCadenceCompositor currently supports only stateless shaders; feedback storage is not enabled in this app.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!shaderPackage.textureAssets.empty())
|
|
||||||
{
|
|
||||||
error = "RenderCadenceCompositor does not load shader texture assets on the render thread; texture-backed shaders need a CPU-prepared asset handoff first.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!shaderPackage.fontAssets.empty())
|
|
||||||
{
|
|
||||||
error = "RenderCadenceCompositor does not load shader font assets on the render thread; text shaders need a CPU-prepared asset handoff first.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
|
|
||||||
{
|
|
||||||
if (parameter.type == ShaderParameterType::Text)
|
|
||||||
{
|
|
||||||
error = "RenderCadenceCompositor currently skips text parameters because they require per-shader text texture storage.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeSlangShaderCompiler::~RuntimeSlangShaderCompiler()
|
RuntimeSlangShaderCompiler::~RuntimeSlangShaderCompiler()
|
||||||
@@ -140,10 +103,12 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
|
|||||||
build.message = error.empty() ? "Shader manifest parse failed." : error;
|
build.message = error.empty() ? "Shader manifest parse failed." : error;
|
||||||
return build;
|
return build;
|
||||||
}
|
}
|
||||||
if (!IsStatelessSinglePassPackage(shaderPackage, error))
|
const RenderCadenceCompositor::ShaderSupportResult support =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
if (!support.supported)
|
||||||
{
|
{
|
||||||
build.succeeded = false;
|
build.succeeded = false;
|
||||||
build.message = error;
|
build.message = support.reason;
|
||||||
return build;
|
return build;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#include "SupportedShaderCatalog.h"
|
||||||
|
|
||||||
|
#include "ShaderPackageRegistry.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
|
||||||
|
{
|
||||||
|
if (shaderPackage.passes.size() != 1)
|
||||||
|
return { false, "RenderCadenceCompositor currently supports only single-pass runtime shaders." };
|
||||||
|
|
||||||
|
if (shaderPackage.temporal.enabled)
|
||||||
|
return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." };
|
||||||
|
|
||||||
|
if (shaderPackage.feedback.enabled)
|
||||||
|
return { false, "RenderCadenceCompositor currently supports only stateless shaders; feedback storage is not enabled in this app." };
|
||||||
|
|
||||||
|
if (!shaderPackage.textureAssets.empty())
|
||||||
|
return { false, "RenderCadenceCompositor does not load shader texture assets yet; texture-backed shaders need a CPU-prepared asset handoff first." };
|
||||||
|
|
||||||
|
if (!shaderPackage.fontAssets.empty())
|
||||||
|
return { false, "RenderCadenceCompositor does not load shader font assets yet; text shaders need a CPU-prepared asset handoff first." };
|
||||||
|
|
||||||
|
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
|
||||||
|
{
|
||||||
|
if (parameter.type == ShaderParameterType::Text)
|
||||||
|
return { false, "RenderCadenceCompositor currently skips text parameters because they require per-shader text texture storage." };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { true, std::string() };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SupportedShaderCatalog::Load(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error)
|
||||||
|
{
|
||||||
|
mShaders.clear();
|
||||||
|
mPackagesById.clear();
|
||||||
|
|
||||||
|
if (shaderRoot.empty())
|
||||||
|
{
|
||||||
|
error = "Shader library path is empty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderPackageRegistry registry(maxTemporalHistoryFrames);
|
||||||
|
std::map<std::string, ShaderPackage> packagesById;
|
||||||
|
std::vector<std::string> packageOrder;
|
||||||
|
std::vector<ShaderPackageStatus> packageStatuses;
|
||||||
|
if (!registry.Scan(shaderRoot, packagesById, packageOrder, packageStatuses, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const std::string& packageId : packageOrder)
|
||||||
|
{
|
||||||
|
const auto packageIt = packagesById.find(packageId);
|
||||||
|
if (packageIt == packagesById.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const ShaderPackage& shaderPackage = packageIt->second;
|
||||||
|
const ShaderSupportResult support = CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
if (!support.supported)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SupportedShaderSummary summary;
|
||||||
|
summary.id = shaderPackage.id;
|
||||||
|
summary.name = shaderPackage.displayName.empty() ? shaderPackage.id : shaderPackage.displayName;
|
||||||
|
summary.description = shaderPackage.description;
|
||||||
|
summary.category = shaderPackage.category;
|
||||||
|
mShaders.push_back(std::move(summary));
|
||||||
|
mPackagesById[shaderPackage.id] = shaderPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
error.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ShaderPackage* SupportedShaderCatalog::FindPackage(const std::string& shaderId) const
|
||||||
|
{
|
||||||
|
const auto packageIt = mPackagesById.find(shaderId);
|
||||||
|
return packageIt == mPackagesById.end() ? nullptr : &packageIt->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace RenderCadenceCompositor
|
||||||
|
{
|
||||||
|
struct SupportedShaderSummary
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string name;
|
||||||
|
std::string description;
|
||||||
|
std::string category;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderSupportResult
|
||||||
|
{
|
||||||
|
bool supported = false;
|
||||||
|
std::string reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage);
|
||||||
|
|
||||||
|
class SupportedShaderCatalog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool Load(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error);
|
||||||
|
const std::vector<SupportedShaderSummary>& Shaders() const { return mShaders; }
|
||||||
|
const ShaderPackage* FindPackage(const std::string& shaderId) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<SupportedShaderSummary> mShaders;
|
||||||
|
std::map<std::string, ShaderPackage> mPackagesById;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,17 +6,20 @@ info:
|
|||||||
REST API exposed by the local Video Shader Toys control server.
|
REST API exposed by the local Video Shader Toys control server.
|
||||||
|
|
||||||
The API is intended for local control tools and the bundled React UI. All mutating
|
The API is intended for local control tools and the bundled React UI. All mutating
|
||||||
endpoints return a small action result object. Successful mutating requests also
|
endpoints return a small action result object.
|
||||||
broadcast the latest runtime state over the `/ws` WebSocket.
|
|
||||||
|
|
||||||
WebSocket state streaming is not described by OpenAPI; connect to `ws://127.0.0.1:{port}/ws`
|
WebSocket state streaming is planned for the control UI but is not currently served
|
||||||
to receive full runtime state JSON messages whenever state changes.
|
by RenderCadenceCompositor. Clients should poll `/api/state` until `/ws` is implemented.
|
||||||
servers:
|
servers:
|
||||||
- url: http://127.0.0.1:8080
|
- url: http://127.0.0.1:8080
|
||||||
description: Default local control server
|
description: Default local control server
|
||||||
tags:
|
tags:
|
||||||
- name: State
|
- name: State
|
||||||
description: Runtime state and status.
|
description: Runtime state and status.
|
||||||
|
- name: Static
|
||||||
|
description: Bundled control UI and static assets served by the local host.
|
||||||
|
- name: Docs
|
||||||
|
description: OpenAPI and Swagger UI documentation served by the local host.
|
||||||
- name: Layers
|
- name: Layers
|
||||||
description: Layer stack control.
|
description: Layer stack control.
|
||||||
- name: Stack Presets
|
- name: Stack Presets
|
||||||
@@ -24,6 +27,146 @@ tags:
|
|||||||
- name: Runtime
|
- name: Runtime
|
||||||
description: Runtime actions.
|
description: Runtime actions.
|
||||||
paths:
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
tags: [Static]
|
||||||
|
summary: Serve the bundled control UI
|
||||||
|
description: Returns the built React control UI `index.html` from `ui/dist`.
|
||||||
|
operationId: getControlUiRoot
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Control UI HTML.
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: UI bundle was not found.
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/index.html:
|
||||||
|
get:
|
||||||
|
tags: [Static]
|
||||||
|
summary: Serve the bundled control UI index file
|
||||||
|
description: Returns the built React control UI `index.html` from `ui/dist`.
|
||||||
|
operationId: getControlUiIndex
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Control UI HTML.
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: UI bundle was not found.
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/assets/{assetPath}:
|
||||||
|
get:
|
||||||
|
tags: [Static]
|
||||||
|
summary: Serve a bundled control UI asset
|
||||||
|
description: Serves files from `ui/dist/assets`. The server rejects unsafe relative paths and guesses the content type from the file extension.
|
||||||
|
operationId: getControlUiAsset
|
||||||
|
parameters:
|
||||||
|
- name: assetPath
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Relative asset path below `ui/dist/assets`.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Static asset.
|
||||||
|
content:
|
||||||
|
text/javascript:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
text/css:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
image/svg+xml:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
image/png:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Asset was not found or the path was unsafe.
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/docs:
|
||||||
|
get:
|
||||||
|
tags: [Docs]
|
||||||
|
summary: Serve Swagger UI
|
||||||
|
description: Returns a small Swagger UI page pointed at `/docs/openapi.yaml`.
|
||||||
|
operationId: getSwaggerUi
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Swagger UI HTML.
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/docs/:
|
||||||
|
get:
|
||||||
|
tags: [Docs]
|
||||||
|
summary: Serve Swagger UI
|
||||||
|
description: Alias for `/docs`.
|
||||||
|
operationId: getSwaggerUiWithTrailingSlash
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Swagger UI HTML.
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/docs/openapi.yaml:
|
||||||
|
get:
|
||||||
|
tags: [Docs]
|
||||||
|
summary: Serve the OpenAPI document
|
||||||
|
operationId: getOpenApiDocumentFromDocs
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OpenAPI YAML document.
|
||||||
|
content:
|
||||||
|
application/yaml:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: OpenAPI document was not found.
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
/openapi.yaml:
|
||||||
|
get:
|
||||||
|
tags: [Docs]
|
||||||
|
summary: Serve the OpenAPI document
|
||||||
|
description: Alias for `/docs/openapi.yaml`.
|
||||||
|
operationId: getOpenApiDocument
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OpenAPI YAML document.
|
||||||
|
content:
|
||||||
|
application/yaml:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: OpenAPI document was not found.
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
/api/state:
|
/api/state:
|
||||||
get:
|
get:
|
||||||
tags: [State]
|
tags: [State]
|
||||||
|
|||||||
89
tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp
Normal file
89
tests/RenderCadenceCompositorRuntimeStateJsonTests.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "RuntimeStateJson.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int gFailures = 0;
|
||||||
|
|
||||||
|
void ExpectContains(const std::string& text, const std::string& fragment, const std::string& message)
|
||||||
|
{
|
||||||
|
if (text.find(fragment) != std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
++gFailures;
|
||||||
|
std::cerr << "FAIL: " << message << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderPackage MakePackage()
|
||||||
|
{
|
||||||
|
ShaderPackage package;
|
||||||
|
package.id = "solid-color";
|
||||||
|
package.displayName = "Solid Color";
|
||||||
|
package.description = "A single color shader.";
|
||||||
|
package.category = "Generator";
|
||||||
|
|
||||||
|
ShaderPassDefinition pass;
|
||||||
|
pass.id = "main";
|
||||||
|
package.passes.push_back(pass);
|
||||||
|
|
||||||
|
ShaderParameterDefinition color;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
RenderCadenceCompositor::AppConfig config = RenderCadenceCompositor::DefaultAppConfig();
|
||||||
|
config.outputVideoFormat = "1080p";
|
||||||
|
config.outputFrameRate = "59.94";
|
||||||
|
|
||||||
|
RenderCadenceCompositor::CadenceTelemetrySnapshot telemetry;
|
||||||
|
telemetry.renderFps = 59.94;
|
||||||
|
telemetry.shaderBuildsCommitted = 1;
|
||||||
|
|
||||||
|
std::vector<RenderCadenceCompositor::SupportedShaderSummary> shaders = {
|
||||||
|
{ "solid-color", "Solid Color", "A single color shader.", "Generator" }
|
||||||
|
};
|
||||||
|
const ShaderPackage package = MakePackage();
|
||||||
|
|
||||||
|
const std::string json = RenderCadenceCompositor::RuntimeStateToJson(RenderCadenceCompositor::RuntimeStateJsonInput{
|
||||||
|
config,
|
||||||
|
telemetry,
|
||||||
|
8080,
|
||||||
|
true,
|
||||||
|
"DeckLink scheduled output running.",
|
||||||
|
shaders,
|
||||||
|
true,
|
||||||
|
"Runtime shader committed.",
|
||||||
|
&package
|
||||||
|
});
|
||||||
|
|
||||||
|
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, "\"layers\":[{\"id\":\"runtime-layer-1\"", "state JSON should expose the active display layer");
|
||||||
|
ExpectContains(json, "\"parameters\":[{\"id\":\"color\"", "state JSON should expose active shader parameters");
|
||||||
|
ExpectContains(json, "\"type\":\"color\"", "state JSON should serialize parameter types for the UI");
|
||||||
|
ExpectContains(json, "\"width\":1920", "state JSON should expose output width");
|
||||||
|
ExpectContains(json, "\"height\":1080", "state JSON should expose output height");
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " RenderCadenceCompositorRuntimeStateJson test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RenderCadenceCompositorRuntimeStateJson tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
113
tests/RenderCadenceCompositorSupportedShaderCatalogTests.cpp
Normal file
113
tests/RenderCadenceCompositorSupportedShaderCatalogTests.cpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#include "SupportedShaderCatalog.h"
|
||||||
|
|
||||||
|
#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";
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderPackage MakeSinglePassPackage()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage;
|
||||||
|
shaderPackage.id = "supported";
|
||||||
|
ShaderPassDefinition pass;
|
||||||
|
pass.id = "main";
|
||||||
|
pass.entryPoint = "mainImage";
|
||||||
|
shaderPackage.passes.push_back(pass);
|
||||||
|
return shaderPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SupportsSinglePassStatelessPackage()
|
||||||
|
{
|
||||||
|
const ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(result.supported, "single-pass stateless packages should be supported");
|
||||||
|
Expect(result.reason.empty(), "supported packages should not report a rejection reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RejectsMultipassPackage()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
ShaderPassDefinition secondPass;
|
||||||
|
secondPass.id = "second";
|
||||||
|
secondPass.entryPoint = "mainImage";
|
||||||
|
shaderPackage.passes.push_back(secondPass);
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(!result.supported, "multipass packages should be rejected");
|
||||||
|
Expect(result.reason.find("single-pass") != std::string::npos, "multipass rejection should explain the single-pass limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RejectsTemporalPackage()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
shaderPackage.temporal.enabled = true;
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(!result.supported, "temporal packages should be rejected");
|
||||||
|
Expect(result.reason.find("temporal") != std::string::npos, "temporal rejection should mention temporal storage");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RejectsTextureAssets()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
ShaderTextureAsset asset;
|
||||||
|
asset.id = "lut";
|
||||||
|
shaderPackage.textureAssets.push_back(asset);
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(!result.supported, "texture-backed packages should be rejected for now");
|
||||||
|
Expect(result.reason.find("texture") != std::string::npos, "texture rejection should mention texture assets");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RejectsTextParameters()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
ShaderParameterDefinition parameter;
|
||||||
|
parameter.id = "caption";
|
||||||
|
parameter.type = ShaderParameterType::Text;
|
||||||
|
shaderPackage.parameters.push_back(parameter);
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(!result.supported, "text-parameter packages should be rejected for now");
|
||||||
|
Expect(result.reason.find("text") != std::string::npos, "text rejection should mention text parameters");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
SupportsSinglePassStatelessPackage();
|
||||||
|
RejectsMultipassPackage();
|
||||||
|
RejectsTemporalPackage();
|
||||||
|
RejectsTextureAssets();
|
||||||
|
RejectsTextParameters();
|
||||||
|
|
||||||
|
if (gFailures != 0)
|
||||||
|
{
|
||||||
|
std::cerr << gFailures << " RenderCadenceCompositorSupportedShaderCatalog test failure(s).\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "RenderCadenceCompositorSupportedShaderCatalog tests passed.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ function App() {
|
|||||||
const [dropTargetLayerId, setDropTargetLayerId] = useState(null);
|
const [dropTargetLayerId, setDropTargetLayerId] = useState(null);
|
||||||
|
|
||||||
const layers = appState?.layers ?? [];
|
const layers = appState?.layers ?? [];
|
||||||
const shaders = appState?.shaders ?? [];
|
const shaders = (appState?.shaders ?? []).filter((shader) => shader.available !== false);
|
||||||
const performance = appState?.performance ?? {};
|
const performance = appState?.performance ?? {};
|
||||||
const runtime = appState?.runtime ?? {};
|
const runtime = appState?.runtime ?? {};
|
||||||
const video = appState?.video ?? {};
|
const video = appState?.video ?? {};
|
||||||
|
|||||||
Reference in New Issue
Block a user