2 Commits

Author SHA1 Message Date
Aiden
f43b6f6519 shader control
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 3m3s
CI / Windows Release Package (push) Has been skipped
2026-05-12 17:52:55 +10:00
Aiden
dfd49fd0e3 Multipass shaders 2026-05-12 17:08:35 +10:00
26 changed files with 954 additions and 81 deletions

View File

@@ -290,6 +290,8 @@ set(RENDER_CADENCE_APP_SOURCES
"${APP_DIR}/gl/shader/Std140Buffer.h" "${APP_DIR}/gl/shader/Std140Buffer.h"
"${APP_DIR}/runtime/support/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.h" "${APP_DIR}/runtime/support/RuntimeJson.h"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.h"
"${APP_DIR}/shader/ShaderCompiler.cpp" "${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderCompiler.h" "${APP_DIR}/shader/ShaderCompiler.h"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
@@ -310,6 +312,8 @@ set(RENDER_CADENCE_APP_SOURCES
"${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.cpp" "${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.cpp"
"${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.h" "${RENDER_CADENCE_APP_DIR}/app/RuntimeLayerController.h"
"${RENDER_CADENCE_APP_DIR}/control/ControlActionResult.h" "${RENDER_CADENCE_APP_DIR}/control/ControlActionResult.h"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeControlCommand.cpp"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeControlCommand.h"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.cpp" "${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.h" "${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.h"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp" "${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp"
@@ -841,6 +845,7 @@ add_executable(RenderCadenceCompositorRuntimeLayerModelTests
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp" "${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"
"${APP_DIR}/runtime/support/RuntimeJson.cpp" "${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
) )
@@ -909,6 +914,7 @@ 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}/runtime/support/RuntimeParameterUtils.cpp"
"${APP_DIR}/shader/ShaderPackageRegistry.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"
@@ -940,6 +946,8 @@ endif()
add_test(NAME RenderCadenceCompositorRuntimeStateJsonTests COMMAND RenderCadenceCompositorRuntimeStateJsonTests) add_test(NAME RenderCadenceCompositorRuntimeStateJsonTests COMMAND RenderCadenceCompositorRuntimeStateJsonTests)
add_executable(RenderCadenceCompositorHttpControlServerTests add_executable(RenderCadenceCompositorHttpControlServerTests
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
"${RENDER_CADENCE_APP_DIR}/control/RuntimeControlCommand.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.cpp" "${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServer.cpp"
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp" "${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp"
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp" "${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
@@ -948,6 +956,8 @@ add_executable(RenderCadenceCompositorHttpControlServerTests
) )
target_include_directories(RenderCadenceCompositorHttpControlServerTests PRIVATE target_include_directories(RenderCadenceCompositorHttpControlServerTests PRIVATE
"${APP_DIR}"
"${APP_DIR}/runtime/support"
"${RENDER_CADENCE_APP_DIR}/control" "${RENDER_CADENCE_APP_DIR}/control"
"${RENDER_CADENCE_APP_DIR}/control/http" "${RENDER_CADENCE_APP_DIR}/control/http"
"${RENDER_CADENCE_APP_DIR}/json" "${RENDER_CADENCE_APP_DIR}/json"

View File

@@ -47,7 +47,9 @@ Included now:
- shared-context GL prepare worker for runtime shader program compile/link - shared-context GL prepare worker for runtime shader program compile/link
- render-thread-only GL program swap once a prepared program is ready - render-thread-only GL program swap once a prepared program is ready
- 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 - manifest-driven stateless named-pass shader packages
- atomic render-plan swap after every pass program is prepared
- HTTP shader list populated from supported stateless full-frame 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
@@ -60,7 +62,6 @@ Included now:
Intentionally not included yet: Intentionally not included yet:
- DeckLink input - DeckLink input
- multipass shader rendering
- temporal/history/feedback shader storage - temporal/history/feedback shader storage
- texture/LUT asset upload - texture/LUT asset upload
- text-parameter rasterization - text-parameter rasterization
@@ -86,6 +87,8 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
- [x] Shared-context GL shader/program preparation - [x] Shared-context GL shader/program preparation
- [x] Render-thread program swap at a frame boundary - [x] Render-thread program swap at a frame boundary
- [x] Stateless single-pass shader rendering - [x] Stateless single-pass shader rendering
- [x] Stateless named-pass shader rendering
- [x] Atomic multipass render-plan commit
- [x] Shader add/remove control path - [x] Shader add/remove control path
- [x] Previous-layer texture handoff for stacked shaders - [x] Previous-layer texture handoff for stacked shaders
- [x] Supported shader list in HTTP/UI state - [x] Supported shader list in HTTP/UI state
@@ -99,7 +102,6 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
- [ ] DeckLink input capture - [ ] DeckLink input capture
- [ ] Input frame upload into the render scene - [ ] Input frame upload into the render scene
- [ ] Live video input bound to `gVideoInput` - [ ] Live video input bound to `gVideoInput`
- [ ] Multipass shader rendering
- [ ] Temporal history buffers - [ ] Temporal history buffers
- [ ] Feedback buffers - [ ] Feedback buffers
- [ ] Texture asset loading and upload - [ ] Texture asset loading and upload
@@ -196,10 +198,10 @@ Current endpoints:
- `GET /ws`: upgrades to a WebSocket and streams state snapshots when they change - `GET /ws`: upgrades to a WebSocket and streams state snapshots when they change
- `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
- `POST /api/layers/add` and `POST /api/layers/remove` mutate the app-owned display layer model only - `POST /api/layers/add`, `/remove`, `/reorder`, `/set-bypass`, `/set-shader`, `/update-parameter`, and `/reset-parameters` use the shared runtime control-command path
- other OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }` - 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 translates POST bodies into runtime control commands. Command execution is app-owned, so future OSC ingress can create the same commands without depending on HTTP route code. Control commands may update the display layer model, start background shader builds, or publish an already-built render-layer snapshot, but they do not call render work or DeckLink scheduling directly.
## Optional DeckLink Output ## Optional DeckLink Output
@@ -244,9 +246,12 @@ On startup the app begins compiling the selected shader package on a background
The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. Once a completed shader artifact is published, the render-thread-owned runtime scene queues changed layers to a shared-context GL prepare worker. That worker compiles/links runtime shader programs off the cadence thread. The render thread only swaps in an already-prepared GL program at a frame boundary. If either the Slang build or GL preparation fails, the app keeps rendering the current renderer or simple motion fallback. The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. Once a completed shader artifact is published, the render-thread-owned runtime scene queues changed layers to a shared-context GL prepare worker. That worker compiles/links runtime shader programs off the cadence thread. The render thread only swaps in an already-prepared GL program at a frame boundary. If either the Slang build or GL preparation fails, the app keeps rendering the current renderer or simple motion fallback.
Current runtime shader support is deliberately limited to stateless single-pass packages: Current runtime shader support is deliberately limited to stateless full-frame packages:
- one pass only - one or more named passes
- one sampled source input per pass
- named intermediate outputs routed by the pass manifest
- final visible output must be named `layerOutput`
- no temporal history - no temporal history
- no feedback storage - no feedback storage
- no texture/LUT assets yet - no texture/LUT assets yet
@@ -255,11 +260,11 @@ Current runtime shader support is deliberately limited to stateless single-pass
- the first layer receives a small fallback source texture until DeckLink input is added - the first layer receives a small fallback source texture until DeckLink input is added
- stacked layers receive the previous ready layer output through both `gVideoInput` and `gLayerInput` - stacked layers receive the previous ready layer output through both `gVideoInput` and `gLayerInput`
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 temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now.
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. Add/remove POST controls mutate this app-owned model and may start background shader builds. 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. Add/remove POST controls mutate this app-owned model and may start background shader builds.
When a layer becomes render-ready, the app publishes the ready render-layer snapshot to the render thread. The render thread owns the GL-side `RuntimeRenderScene`, diffs that snapshot at a frame boundary, queues new or changed programs to the shared-context prepare worker, swaps in prepared programs when available, removes obsolete GL programs, and renders ready layers in order. Stacked stateless full-frame shaders render through internal ping-pong targets so each layer can sample the previous layer through `gLayerInput`; the final ready layer renders to the output target. When a layer becomes render-ready, the app publishes the ready render-layer snapshot to the render thread. The render thread owns the GL-side `RuntimeRenderScene`, diffs that snapshot at a frame boundary, queues new or changed pass programs to the shared-context prepare worker, swaps in a full prepared render plan only after every pass is ready, removes obsolete GL programs, and renders ready layers in order. Stacked stateless full-frame shaders render through internal ping-pong targets so each layer can sample the previous layer through `gLayerInput`; multipass shaders route named intermediate outputs through their manifest-declared pass inputs, and the final ready layer renders to the output target.
Successful handoff signs: Successful handoff signs:

View File

@@ -184,6 +184,13 @@ private:
callbacks.removeLayer = [this](const std::string& body) { callbacks.removeLayer = [this](const std::string& body) {
return mRuntimeLayers.HandleRemoveLayer(body); return mRuntimeLayers.HandleRemoveLayer(body);
}; };
callbacks.executePost = [this](const std::string& path, const std::string& body) {
RuntimeControlCommand command;
std::string error;
if (!ParseRuntimeControlCommand(path, body, command, error))
return ControlActionResult{ false, error };
return mRuntimeLayers.HandleControlCommand(command);
};
std::string error; std::string error;
if (!mHttpServer.Start( if (!mHttpServer.Start(

View File

@@ -90,6 +90,95 @@ ControlActionResult RuntimeLayerController::HandleRemoveLayer(const std::string&
return { true, std::string() }; return { true, std::string() };
} }
ControlActionResult RuntimeLayerController::HandleControlCommand(const RuntimeControlCommand& command)
{
CleanupRetiredShaderBuilds();
std::string error;
switch (command.type)
{
case RuntimeControlCommandType::AddLayer:
{
std::string layerId;
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.AddLayer(mShaderCatalog, command.shaderId, layerId, error))
return { false, error };
}
Log("runtime-shader", "Layer added: " + layerId + " shader=" + command.shaderId);
StartLayerShaderBuild(layerId, command.shaderId);
return { true, std::string() };
}
case RuntimeControlCommandType::RemoveLayer:
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.RemoveLayer(command.layerId, error))
return { false, error };
}
Log("runtime-shader", "Layer removed: " + command.layerId);
RetireLayerShaderBuild(command.layerId);
PublishRuntimeRenderLayers();
return { true, std::string() };
}
case RuntimeControlCommandType::ReorderLayer:
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.ReorderLayer(command.layerId, command.targetIndex, error))
return { false, error };
}
PublishRuntimeRenderLayers();
return { true, std::string() };
}
case RuntimeControlCommandType::SetLayerBypass:
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.SetLayerBypass(command.layerId, command.bypass, error))
return { false, error };
}
PublishRuntimeRenderLayers();
return { true, std::string() };
}
case RuntimeControlCommandType::SetLayerShader:
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.SetLayerShader(mShaderCatalog, command.layerId, command.shaderId, error))
return { false, error };
}
Log("runtime-shader", "Layer shader changed: " + command.layerId + " shader=" + command.shaderId);
StartLayerShaderBuild(command.layerId, command.shaderId);
return { true, std::string() };
}
case RuntimeControlCommandType::UpdateLayerParameter:
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.UpdateParameter(command.layerId, command.parameterId, command.value, error))
return { false, error };
}
PublishRuntimeRenderLayers();
return { true, std::string() };
}
case RuntimeControlCommandType::ResetLayerParameters:
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.ResetParameters(command.layerId, error))
return { false, error };
}
PublishRuntimeRenderLayers();
return { true, std::string() };
}
case RuntimeControlCommandType::Unsupported:
break;
}
return { false, "Unsupported runtime control command." };
}
RuntimeLayerModelSnapshot RuntimeLayerController::Snapshot(const CadenceTelemetrySnapshot& telemetry) const RuntimeLayerModelSnapshot RuntimeLayerController::Snapshot(const CadenceTelemetrySnapshot& telemetry) const
{ {
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex); std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "../control/ControlActionResult.h" #include "../control/ControlActionResult.h"
#include "../control/RuntimeControlCommand.h"
#include "../runtime/RuntimeLayerModel.h" #include "../runtime/RuntimeLayerModel.h"
#include "../runtime/RuntimeShaderBridge.h" #include "../runtime/RuntimeShaderBridge.h"
#include "../runtime/SupportedShaderCatalog.h" #include "../runtime/SupportedShaderCatalog.h"
@@ -32,6 +33,7 @@ public:
ControlActionResult HandleAddLayer(const std::string& body); ControlActionResult HandleAddLayer(const std::string& body);
ControlActionResult HandleRemoveLayer(const std::string& body); ControlActionResult HandleRemoveLayer(const std::string& body);
ControlActionResult HandleControlCommand(const RuntimeControlCommand& command);
RuntimeLayerModelSnapshot Snapshot(const CadenceTelemetrySnapshot& telemetry) const; RuntimeLayerModelSnapshot Snapshot(const CadenceTelemetrySnapshot& telemetry) const;
const SupportedShaderCatalog& ShaderCatalog() const { return mShaderCatalog; } const SupportedShaderCatalog& ShaderCatalog() const { return mShaderCatalog; }

View File

@@ -0,0 +1,127 @@
#include "RuntimeControlCommand.h"
namespace RenderCadenceCompositor
{
namespace
{
const JsonValue* RequireObjectField(const JsonValue& root, const char* fieldName, std::string& error)
{
const JsonValue* field = root.find(fieldName);
if (!field)
error = std::string("Request field '") + fieldName + "' is required.";
return field;
}
bool RequireStringField(const JsonValue& root, const char* fieldName, std::string& value, std::string& error)
{
const JsonValue* field = RequireObjectField(root, fieldName, error);
if (!field)
return false;
if (!field->isString() || field->asString().empty())
{
error = std::string("Request field '") + fieldName + "' must be a non-empty string.";
return false;
}
value = field->asString();
return true;
}
bool RequireBoolField(const JsonValue& root, const char* fieldName, bool& value, std::string& error)
{
const JsonValue* field = RequireObjectField(root, fieldName, error);
if (!field)
return false;
if (!field->isBoolean())
{
error = std::string("Request field '") + fieldName + "' must be a boolean.";
return false;
}
value = field->asBoolean();
return true;
}
bool RequireIntegerField(const JsonValue& root, const char* fieldName, int& value, std::string& error)
{
const JsonValue* field = RequireObjectField(root, fieldName, error);
if (!field)
return false;
if (!field->isNumber())
{
error = std::string("Request field '") + fieldName + "' must be a number.";
return false;
}
value = static_cast<int>(field->asNumber());
return true;
}
}
bool ParseRuntimeControlCommand(
const std::string& path,
const std::string& body,
RuntimeControlCommand& command,
std::string& error)
{
command = RuntimeControlCommand();
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;
}
if (path == "/api/layers/add")
{
command.type = RuntimeControlCommandType::AddLayer;
return RequireStringField(root, "shaderId", command.shaderId, error);
}
if (path == "/api/layers/remove")
{
command.type = RuntimeControlCommandType::RemoveLayer;
return RequireStringField(root, "layerId", command.layerId, error);
}
if (path == "/api/layers/reorder")
{
command.type = RuntimeControlCommandType::ReorderLayer;
return RequireStringField(root, "layerId", command.layerId, error)
&& RequireIntegerField(root, "targetIndex", command.targetIndex, error);
}
if (path == "/api/layers/set-bypass")
{
command.type = RuntimeControlCommandType::SetLayerBypass;
return RequireStringField(root, "layerId", command.layerId, error)
&& RequireBoolField(root, "bypass", command.bypass, error);
}
if (path == "/api/layers/set-shader")
{
command.type = RuntimeControlCommandType::SetLayerShader;
return RequireStringField(root, "layerId", command.layerId, error)
&& RequireStringField(root, "shaderId", command.shaderId, error);
}
if (path == "/api/layers/update-parameter")
{
command.type = RuntimeControlCommandType::UpdateLayerParameter;
const JsonValue* value = nullptr;
if (!RequireStringField(root, "layerId", command.layerId, error)
|| !RequireStringField(root, "parameterId", command.parameterId, error))
{
return false;
}
value = RequireObjectField(root, "value", error);
if (!value)
return false;
command.value = *value;
return true;
}
if (path == "/api/layers/reset-parameters")
{
command.type = RuntimeControlCommandType::ResetLayerParameters;
return RequireStringField(root, "layerId", command.layerId, error);
}
command.type = RuntimeControlCommandType::Unsupported;
error = "Endpoint is not implemented in RenderCadenceCompositor yet.";
return false;
}
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "RuntimeJson.h"
#include <string>
namespace RenderCadenceCompositor
{
enum class RuntimeControlCommandType
{
AddLayer,
RemoveLayer,
ReorderLayer,
SetLayerBypass,
SetLayerShader,
UpdateLayerParameter,
ResetLayerParameters,
Unsupported
};
struct RuntimeControlCommand
{
RuntimeControlCommandType type = RuntimeControlCommandType::Unsupported;
std::string layerId;
std::string shaderId;
std::string parameterId;
int targetIndex = 0;
bool bypass = false;
JsonValue value;
};
bool ParseRuntimeControlCommand(
const std::string& path,
const std::string& body,
RuntimeControlCommand& command,
std::string& error);
}

View File

@@ -93,6 +93,31 @@ inline void WriteDefaultParameterValue(JsonWriter& writer, const ShaderParameter
writer.Null(); writer.Null();
} }
inline void WriteParameterValue(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue& value)
{
switch (parameter.type)
{
case ShaderParameterType::Boolean:
writer.Bool(value.booleanValue);
return;
case ShaderParameterType::Enum:
writer.String(value.enumValue);
return;
case ShaderParameterType::Text:
writer.String(value.textValue);
return;
case ShaderParameterType::Trigger:
case ShaderParameterType::Float:
writer.Double(value.numberValues.empty() ? 0.0 : value.numberValues.front());
return;
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
WriteNumberArray(writer, value.numberValues);
return;
}
writer.Null();
}
inline void WriteTemporalJson(JsonWriter& writer, const TemporalSettings& temporal) inline void WriteTemporalJson(JsonWriter& writer, const TemporalSettings& temporal)
{ {
writer.BeginObject(); writer.BeginObject();
@@ -122,7 +147,7 @@ inline const char* RuntimeLayerBuildStateName(RuntimeLayerBuildState state)
return "unknown"; return "unknown";
} }
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter) inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue* value)
{ {
writer.BeginObject(); writer.BeginObject();
writer.KeyString("id", parameter.id); writer.KeyString("id", parameter.id);
@@ -132,6 +157,9 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
writer.Key("defaultValue"); writer.Key("defaultValue");
WriteDefaultParameterValue(writer, parameter); WriteDefaultParameterValue(writer, parameter);
writer.Key("value"); writer.Key("value");
if (value)
WriteParameterValue(writer, parameter, *value);
else
WriteDefaultParameterValue(writer, parameter); WriteDefaultParameterValue(writer, parameter);
if (!parameter.minNumbers.empty()) if (!parameter.minNumbers.empty())
@@ -197,10 +225,10 @@ inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& inp
WriteFeedbackJson(writer, FeedbackSettings()); WriteFeedbackJson(writer, FeedbackSettings());
writer.Key("parameters"); writer.Key("parameters");
writer.BeginArray(); writer.BeginArray();
if (shaderPackage) for (const ShaderParameterDefinition& parameter : layer.parameterDefinitions)
{ {
for (const ShaderParameterDefinition& parameter : shaderPackage->parameters) const auto valueIt = layer.parameterValues.find(parameter.id);
WriteParameterDefinitionJson(writer, parameter); WriteParameterDefinitionJson(writer, parameter, valueIt == layer.parameterValues.end() ? nullptr : &valueIt->second);
} }
writer.EndArray(); writer.EndArray();
writer.EndObject(); writer.EndObject();

View File

@@ -286,6 +286,12 @@ 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 (mCallbacks.executePost)
{
const ControlActionResult result = mCallbacks.executePost(request.path, request.body);
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
}
if (request.path == "/api/layers/add" && mCallbacks.addLayer) if (request.path == "/api/layers/add" && mCallbacks.addLayer)
{ {
const ControlActionResult result = mCallbacks.addLayer(request.body); const ControlActionResult result = mCallbacks.addLayer(request.body);

View File

@@ -28,6 +28,7 @@ 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&)> addLayer;
std::function<ControlActionResult(const std::string&)> removeLayer; std::function<ControlActionResult(const std::string&)> removeLayer;
std::function<ControlActionResult(const std::string&, const std::string&)> executePost;
}; };
class UniqueSocket class UniqueSocket

View File

@@ -28,7 +28,10 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layersToPrepare; std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layersToPrepare;
nextOrder.reserve(layers.size()); nextOrder.reserve(layers.size());
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers) for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
{
if (!layer.bypass)
nextOrder.push_back(layer.id); nextOrder.push_back(layer.id);
}
for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();) for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();)
{ {
@@ -39,14 +42,20 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
continue; continue;
} }
if (layerIt->renderer) for (LayerProgram::PassProgram& pass : layerIt->passes)
layerIt->renderer->ShutdownGl(); {
if (pass.renderer)
pass.renderer->ShutdownGl();
}
ReleasePendingPrograms(*layerIt);
layerIt = mLayers.erase(layerIt); layerIt = mLayers.erase(layerIt);
} }
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers) for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
{ {
if (layer.artifact.fragmentShaderSource.empty()) if (layer.bypass)
continue;
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
continue; continue;
const std::string fingerprint = Fingerprint(layer.artifact); const std::string fingerprint = Fingerprint(layer.artifact);
@@ -55,16 +64,32 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
{ {
LayerProgram next; LayerProgram next;
next.layerId = layer.id; next.layerId = layer.id;
next.renderer = std::make_unique<RuntimeShaderRenderer>();
mLayers.push_back(std::move(next)); mLayers.push_back(std::move(next));
program = &mLayers.back(); program = &mLayers.back();
} }
if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && program->renderer && program->renderer->HasProgram()) bool hasReadyPass = false;
for (const LayerProgram::PassProgram& pass : program->passes)
{
if (pass.renderer && pass.renderer->HasProgram())
{
hasReadyPass = true;
break;
}
}
if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && hasReadyPass)
{
for (LayerProgram::PassProgram& pass : program->passes)
{
if (pass.renderer)
pass.renderer->UpdateArtifactState(layer.artifact);
}
continue; continue;
}
if (program->pendingFingerprint == fingerprint) if (program->pendingFingerprint == fingerprint)
continue; continue;
ReleasePendingPrograms(*program);
program->shaderId = layer.shaderId; program->shaderId = layer.shaderId;
program->pendingFingerprint = fingerprint; program->pendingFingerprint = fingerprint;
layersToPrepare.push_back(layer); layersToPrepare.push_back(layer);
@@ -84,9 +109,14 @@ bool RuntimeRenderScene::HasLayers()
for (const std::string& layerId : mLayerOrder) for (const std::string& layerId : mLayerOrder)
{ {
const LayerProgram* layer = FindLayer(layerId); const LayerProgram* layer = FindLayer(layerId);
if (layer && layer->renderer && layer->renderer->HasProgram()) if (!layer)
continue;
for (const LayerProgram::PassProgram& pass : layer->passes)
{
if (pass.renderer && pass.renderer->HasProgram())
return true; return true;
} }
}
return false; return false;
} }
@@ -98,9 +128,16 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
for (const std::string& layerId : mLayerOrder) for (const std::string& layerId : mLayerOrder)
{ {
LayerProgram* layer = FindLayer(layerId); LayerProgram* layer = FindLayer(layerId);
if (!layer || !layer->renderer || !layer->renderer->HasProgram()) if (!layer)
continue; continue;
for (const LayerProgram::PassProgram& pass : layer->passes)
{
if (pass.renderer && pass.renderer->HasProgram())
{
readyLayers.push_back(layer); readyLayers.push_back(layer);
break;
}
}
} }
if (readyLayers.empty()) if (readyLayers.empty())
@@ -111,14 +148,14 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
if (readyLayers.size() == 1) if (readyLayers.size() == 1)
{ {
readyLayers.front()->renderer->RenderFrame(frameIndex, width, height); RenderLayer(*readyLayers.front(), frameIndex, width, height, 0, static_cast<GLuint>(outputFramebuffer), true);
return; return;
} }
if (!EnsureLayerTargets(width, height)) if (!EnsureLayerTargets(width, height))
{ {
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer)); glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
readyLayers.back()->renderer->RenderFrame(frameIndex, width, height); RenderLayer(*readyLayers.back(), frameIndex, width, height, 0, static_cast<GLuint>(outputFramebuffer), true);
return; return;
} }
@@ -130,12 +167,11 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
if (isFinalLayer) if (isFinalLayer)
{ {
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer)); glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
readyLayers[layerIndex]->renderer->RenderFrame(frameIndex, width, height, layerInputTexture, layerInputTexture); RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, layerInputTexture, static_cast<GLuint>(outputFramebuffer), true);
continue; continue;
} }
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[nextTargetIndex]); RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, layerInputTexture, mLayerFramebuffers[nextTargetIndex], true);
readyLayers[layerIndex]->renderer->RenderFrame(frameIndex, width, height, layerInputTexture, layerInputTexture);
layerInputTexture = mLayerTextures[nextTargetIndex]; layerInputTexture = mLayerTextures[nextTargetIndex];
nextTargetIndex = 1 - nextTargetIndex; nextTargetIndex = 1 - nextTargetIndex;
} }
@@ -146,8 +182,12 @@ void RuntimeRenderScene::ShutdownGl()
mPrepareWorker.Stop(); mPrepareWorker.Stop();
for (LayerProgram& layer : mLayers) for (LayerProgram& layer : mLayers)
{ {
if (layer.renderer) for (LayerProgram::PassProgram& pass : layer.passes)
layer.renderer->ShutdownGl(); {
if (pass.renderer)
pass.renderer->ShutdownGl();
}
ReleasePendingPrograms(layer);
} }
mLayers.clear(); mLayers.clear();
mLayerOrder.clear(); mLayerOrder.clear();
@@ -172,28 +212,165 @@ void RuntimeRenderScene::ConsumePreparedPrograms()
continue; continue;
} }
bool replacesExistingPendingPass = false;
for (RuntimePreparedShaderProgram& existing : layer->pendingPreparedPrograms)
{
if (existing.passId != preparedProgram.passId)
continue;
existing.ReleaseGl();
existing = std::move(preparedProgram);
replacesExistingPendingPass = true;
break;
}
if (!replacesExistingPendingPass)
layer->pendingPreparedPrograms.push_back(std::move(preparedProgram));
TryCommitPendingPrograms(*layer);
}
}
void RuntimeRenderScene::ReleasePendingPrograms(LayerProgram& layer)
{
for (RuntimePreparedShaderProgram& program : layer.pendingPreparedPrograms)
program.ReleaseGl();
layer.pendingPreparedPrograms.clear();
}
void RuntimeRenderScene::TryCommitPendingPrograms(LayerProgram& layer)
{
if (layer.pendingPreparedPrograms.empty())
return;
const RuntimeShaderArtifact& artifact = layer.pendingPreparedPrograms.front().artifact;
const std::size_t expectedPassCount = artifact.passes.empty() ? 1 : artifact.passes.size();
if (layer.pendingPreparedPrograms.size() < expectedPassCount)
return;
std::vector<LayerProgram::PassProgram> nextPasses;
nextPasses.reserve(expectedPassCount);
for (const RuntimeShaderPassArtifact& passArtifact : artifact.passes)
{
auto preparedIt = std::find_if(
layer.pendingPreparedPrograms.begin(),
layer.pendingPreparedPrograms.end(),
[&passArtifact](const RuntimePreparedShaderProgram& prepared) {
return prepared.passId == passArtifact.passId;
});
if (preparedIt == layer.pendingPreparedPrograms.end())
return;
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>(); std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
std::string error; std::string error;
if (!nextRenderer->CommitPreparedProgram(preparedProgram, error)) if (!nextRenderer->CommitPreparedProgram(*preparedIt, error))
{ {
preparedProgram.ReleaseGl(); ReleasePendingPrograms(layer);
return;
}
LayerProgram::PassProgram nextPass;
nextPass.passId = preparedIt->passId;
nextPass.inputNames = preparedIt->inputNames;
nextPass.outputName = preparedIt->outputName.empty() ? preparedIt->passId : preparedIt->outputName;
nextPass.renderer = std::move(nextRenderer);
nextPasses.push_back(std::move(nextPass));
}
if (artifact.passes.empty())
{
RuntimePreparedShaderProgram& prepared = layer.pendingPreparedPrograms.front();
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
std::string error;
if (!nextRenderer->CommitPreparedProgram(prepared, error))
{
ReleasePendingPrograms(layer);
return;
}
LayerProgram::PassProgram nextPass;
nextPass.passId = prepared.passId;
nextPass.inputNames = prepared.inputNames;
nextPass.outputName = prepared.outputName.empty() ? prepared.passId : prepared.outputName;
nextPass.renderer = std::move(nextRenderer);
nextPasses.push_back(std::move(nextPass));
}
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (pass.renderer)
pass.renderer->ShutdownGl();
}
layer.passes = std::move(nextPasses);
layer.shaderId = artifact.shaderId;
layer.sourceFingerprint = layer.pendingPreparedPrograms.front().sourceFingerprint;
layer.pendingFingerprint.clear();
layer.pendingPreparedPrograms.clear();
}
GLuint RuntimeRenderScene::RenderLayer(
LayerProgram& layer,
uint64_t frameIndex,
unsigned width,
unsigned height,
GLuint layerInputTexture,
GLuint outputFramebuffer,
bool renderToOutput)
{
GLuint namedOutputs[2] = {};
std::string namedOutputNames[2];
std::size_t nextTargetIndex = 2;
GLuint lastOutputTexture = layerInputTexture;
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (!pass.renderer || !pass.renderer->HasProgram())
continue;
GLuint sourceTexture = layerInputTexture;
if (!pass.inputNames.empty())
{
const std::string& inputName = pass.inputNames.front();
if (inputName != "layerInput" && inputName != "videoInput")
{
for (std::size_t index = 0; index < 2; ++index)
{
if (namedOutputNames[index] == inputName)
{
sourceTexture = namedOutputs[index];
break;
}
}
}
}
const bool writesLayerOutput = pass.outputName == "layerOutput";
if (writesLayerOutput && renderToOutput)
{
glBindFramebuffer(GL_FRAMEBUFFER, outputFramebuffer);
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, sourceTexture);
lastOutputTexture = 0;
continue; continue;
} }
if (layer->renderer) if (!EnsureLayerTargets(width, height))
layer->renderer->ShutdownGl(); continue;
layer->renderer = std::move(nextRenderer);
layer->shaderId = preparedProgram.shaderId; const std::size_t targetIndex = nextTargetIndex;
layer->sourceFingerprint = preparedProgram.sourceFingerprint; nextTargetIndex = nextTargetIndex == 2 ? 3 : 2;
layer->pendingFingerprint.clear(); glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[targetIndex]);
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, sourceTexture);
const std::size_t namedIndex = targetIndex - 2;
namedOutputs[namedIndex] = mLayerTextures[targetIndex];
namedOutputNames[namedIndex] = pass.outputName;
lastOutputTexture = mLayerTextures[targetIndex];
} }
return lastOutputTexture;
} }
bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height) bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
{ {
if (width == 0 || height == 0) if (width == 0 || height == 0)
return false; return false;
if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerTextures[0] != 0 && mLayerTextures[1] != 0 if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerFramebuffers[2] != 0 && mLayerFramebuffers[3] != 0
&& mLayerTextures[0] != 0 && mLayerTextures[1] != 0 && mLayerTextures[2] != 0 && mLayerTextures[3] != 0
&& mLayerTargetWidth == width && mLayerTargetHeight == height) && mLayerTargetWidth == width && mLayerTargetHeight == height)
return true; return true;
@@ -201,9 +378,9 @@ bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
mLayerTargetWidth = width; mLayerTargetWidth = width;
mLayerTargetHeight = height; mLayerTargetHeight = height;
glGenFramebuffers(2, mLayerFramebuffers); glGenFramebuffers(4, mLayerFramebuffers);
glGenTextures(2, mLayerTextures); glGenTextures(4, mLayerTextures);
for (int index = 0; index < 2; ++index) for (int index = 0; index < 4; ++index)
{ {
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]); glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -239,14 +416,15 @@ bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
void RuntimeRenderScene::DestroyLayerTargets() void RuntimeRenderScene::DestroyLayerTargets()
{ {
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0) if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0 || mLayerFramebuffers[2] != 0 || mLayerFramebuffers[3] != 0)
glDeleteFramebuffers(2, mLayerFramebuffers); glDeleteFramebuffers(4, mLayerFramebuffers);
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0) if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0 || mLayerTextures[2] != 0 || mLayerTextures[3] != 0)
glDeleteTextures(2, mLayerTextures); glDeleteTextures(4, mLayerTextures);
mLayerFramebuffers[0] = 0; for (int index = 0; index < 4; ++index)
mLayerFramebuffers[1] = 0; {
mLayerTextures[0] = 0; mLayerFramebuffers[index] = 0;
mLayerTextures[1] = 0; mLayerTextures[index] = 0;
}
mLayerTargetWidth = 0; mLayerTargetWidth = 0;
mLayerTargetHeight = 0; mLayerTargetHeight = 0;
} }
@@ -271,8 +449,23 @@ const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std:
return nullptr; return nullptr;
} }
RuntimeRenderScene::LayerProgram::PassProgram* RuntimeRenderScene::FindPass(LayerProgram& layer, const std::string& passId)
{
for (LayerProgram::PassProgram& pass : layer.passes)
{
if (pass.passId == passId)
return &pass;
}
return nullptr;
}
std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact) std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact)
{ {
const std::hash<std::string> hasher; const std::hash<std::string> hasher;
return artifact.shaderId + ":" + std::to_string(artifact.fragmentShaderSource.size()) + ":" + std::to_string(hasher(artifact.fragmentShaderSource)); std::string source;
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
if (source.empty())
source = artifact.fragmentShaderSource;
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
} }

View File

@@ -32,21 +32,33 @@ private:
std::string shaderId; std::string shaderId;
std::string sourceFingerprint; std::string sourceFingerprint;
std::string pendingFingerprint; std::string pendingFingerprint;
std::vector<RuntimePreparedShaderProgram> pendingPreparedPrograms;
struct PassProgram
{
std::string passId;
std::vector<std::string> inputNames;
std::string outputName;
std::unique_ptr<RuntimeShaderRenderer> renderer; std::unique_ptr<RuntimeShaderRenderer> renderer;
}; };
std::vector<PassProgram> passes;
};
void ConsumePreparedPrograms(); void ConsumePreparedPrograms();
void ReleasePendingPrograms(LayerProgram& layer);
void TryCommitPendingPrograms(LayerProgram& layer);
bool EnsureLayerTargets(unsigned width, unsigned height); bool EnsureLayerTargets(unsigned width, unsigned height);
void DestroyLayerTargets(); void DestroyLayerTargets();
GLuint RenderLayer(LayerProgram& layer, uint64_t frameIndex, unsigned width, unsigned height, GLuint layerInputTexture, GLuint outputFramebuffer, bool renderToOutput);
LayerProgram* FindLayer(const std::string& layerId); LayerProgram* FindLayer(const std::string& layerId);
const LayerProgram* FindLayer(const std::string& layerId) const; const LayerProgram* FindLayer(const std::string& layerId) const;
LayerProgram::PassProgram* FindPass(LayerProgram& layer, const std::string& passId);
static std::string Fingerprint(const RuntimeShaderArtifact& artifact); static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
RuntimeShaderPrepareWorker mPrepareWorker; RuntimeShaderPrepareWorker mPrepareWorker;
std::vector<LayerProgram> mLayers; std::vector<LayerProgram> mLayers;
std::vector<std::string> mLayerOrder; std::vector<std::string> mLayerOrder;
GLuint mLayerFramebuffers[2] = {}; GLuint mLayerFramebuffers[4] = {};
GLuint mLayerTextures[2] = {}; GLuint mLayerTextures[4] = {};
unsigned mLayerTargetWidth = 0; unsigned mLayerTargetWidth = 0;
unsigned mLayerTargetHeight = 0; unsigned mLayerTargetHeight = 0;
}; };

View File

@@ -82,7 +82,10 @@ std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
for (const ShaderParameterDefinition& definition : artifact.parameterDefinitions) for (const ShaderParameterDefinition& definition : artifact.parameterDefinitions)
{ {
const ShaderParameterValue value = DefaultValueForDefinition(definition); const auto valueIt = artifact.parameterValues.find(definition.id);
const ShaderParameterValue value = valueIt == artifact.parameterValues.end()
? DefaultValueForDefinition(definition)
: valueIt->second;
switch (definition.type) switch (definition.type)
{ {
case ShaderParameterType::Float: case ShaderParameterType::Float:

View File

@@ -77,21 +77,36 @@ void RuntimeShaderPrepareWorker::Submit(const std::vector<RenderCadenceComposito
std::lock_guard<std::mutex> lock(mMutex); std::lock_guard<std::mutex> lock(mMutex);
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers) for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
{ {
if (layer.artifact.fragmentShaderSource.empty()) if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
continue; continue;
std::vector<RuntimeShaderPassArtifact> passes = layer.artifact.passes;
if (passes.empty())
{
RuntimeShaderPassArtifact pass;
pass.passId = "main";
pass.fragmentShaderSource = layer.artifact.fragmentShaderSource;
pass.outputName = "layerOutput";
passes.push_back(std::move(pass));
}
auto sameLayer = [&layer](const PrepareRequest& existing) {
return existing.layerId == layer.id;
};
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
for (const RuntimeShaderPassArtifact& pass : passes)
{
PrepareRequest request; PrepareRequest request;
request.layerId = layer.id; request.layerId = layer.id;
request.shaderId = layer.shaderId; request.shaderId = layer.shaderId;
request.passId = pass.passId;
request.sourceFingerprint = Fingerprint(layer.artifact); request.sourceFingerprint = Fingerprint(layer.artifact);
request.artifact = layer.artifact; request.artifact = layer.artifact;
request.passArtifact = pass;
auto sameLayer = [&request](const PrepareRequest& existing) {
return existing.layerId == request.layerId;
};
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
mRequests.push_back(std::move(request)); mRequests.push_back(std::move(request));
} }
}
mCondition.notify_one(); mCondition.notify_one();
} }
@@ -137,10 +152,11 @@ void RuntimeShaderPrepareWorker::ThreadMain()
} }
RuntimePreparedShaderProgram preparedProgram; RuntimePreparedShaderProgram preparedProgram;
RuntimeShaderRenderer::BuildPreparedProgram( RuntimeShaderRenderer::BuildPreparedPassProgram(
request.layerId, request.layerId,
request.sourceFingerprint, request.sourceFingerprint,
request.artifact, request.artifact,
request.passArtifact,
preparedProgram); preparedProgram);
glFlush(); glFlush();
@@ -154,5 +170,10 @@ void RuntimeShaderPrepareWorker::ThreadMain()
std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact) std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact)
{ {
const std::hash<std::string> hasher; const std::hash<std::string> hasher;
return artifact.shaderId + ":" + std::to_string(artifact.fragmentShaderSource.size()) + ":" + std::to_string(hasher(artifact.fragmentShaderSource)); std::string source;
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
if (source.empty())
source = artifact.fragmentShaderSource;
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
} }

View File

@@ -35,8 +35,10 @@ private:
{ {
std::string layerId; std::string layerId;
std::string shaderId; std::string shaderId;
std::string passId;
std::string sourceFingerprint; std::string sourceFingerprint;
RuntimeShaderArtifact artifact; RuntimeShaderArtifact artifact;
RuntimeShaderPassArtifact passArtifact;
}; };
void ThreadMain(); void ThreadMain();

View File

@@ -4,13 +4,18 @@
#include "../../runtime/RuntimeShaderArtifact.h" #include "../../runtime/RuntimeShaderArtifact.h"
#include <string> #include <string>
#include <vector>
struct RuntimePreparedShaderProgram struct RuntimePreparedShaderProgram
{ {
std::string layerId; std::string layerId;
std::string shaderId; std::string shaderId;
std::string passId;
std::string sourceFingerprint; std::string sourceFingerprint;
RuntimeShaderArtifact artifact; RuntimeShaderArtifact artifact;
RuntimeShaderPassArtifact passArtifact;
std::vector<std::string> inputNames;
std::string outputName;
GLuint program = 0; GLuint program = 0;
GLuint vertexShader = 0; GLuint vertexShader = 0;
GLuint fragmentShader = 0; GLuint fragmentShader = 0;

View File

@@ -94,26 +94,53 @@ bool RuntimeShaderRenderer::CommitPreparedProgram(RuntimePreparedShaderProgram&
return true; return true;
} }
void RuntimeShaderRenderer::UpdateArtifactState(const RuntimeShaderArtifact& artifact)
{
mArtifact.parameterDefinitions = artifact.parameterDefinitions;
mArtifact.parameterValues = artifact.parameterValues;
mArtifact.message = artifact.message;
}
bool RuntimeShaderRenderer::BuildPreparedProgram( bool RuntimeShaderRenderer::BuildPreparedProgram(
const std::string& layerId, const std::string& layerId,
const std::string& sourceFingerprint, const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact, const RuntimeShaderArtifact& artifact,
RuntimePreparedShaderProgram& preparedProgram) RuntimePreparedShaderProgram& preparedProgram)
{
RuntimeShaderPassArtifact passArtifact;
passArtifact.passId = "main";
passArtifact.fragmentShaderSource = artifact.fragmentShaderSource;
passArtifact.outputName = "layerOutput";
if (!artifact.passes.empty())
passArtifact = artifact.passes.front();
return BuildPreparedPassProgram(layerId, sourceFingerprint, artifact, passArtifact, preparedProgram);
}
bool RuntimeShaderRenderer::BuildPreparedPassProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
const RuntimeShaderPassArtifact& passArtifact,
RuntimePreparedShaderProgram& preparedProgram)
{ {
preparedProgram = RuntimePreparedShaderProgram(); preparedProgram = RuntimePreparedShaderProgram();
preparedProgram.layerId = layerId; preparedProgram.layerId = layerId;
preparedProgram.shaderId = artifact.shaderId; preparedProgram.shaderId = artifact.shaderId;
preparedProgram.passId = passArtifact.passId;
preparedProgram.sourceFingerprint = sourceFingerprint; preparedProgram.sourceFingerprint = sourceFingerprint;
preparedProgram.artifact = artifact; preparedProgram.artifact = artifact;
preparedProgram.passArtifact = passArtifact;
preparedProgram.inputNames = passArtifact.inputNames;
preparedProgram.outputName = passArtifact.outputName.empty() ? passArtifact.passId : passArtifact.outputName;
if (artifact.fragmentShaderSource.empty()) if (passArtifact.fragmentShaderSource.empty())
{ {
preparedProgram.error = "Cannot prepare an empty fragment shader."; preparedProgram.error = "Cannot prepare an empty fragment shader.";
return false; return false;
} }
if (!BuildProgram( if (!BuildProgram(
artifact.fragmentShaderSource, passArtifact.fragmentShaderSource,
preparedProgram.program, preparedProgram.program,
preparedProgram.vertexShader, preparedProgram.vertexShader,
preparedProgram.fragmentShader, preparedProgram.fragmentShader,

View File

@@ -20,6 +20,7 @@ public:
bool CommitShaderArtifact(const RuntimeShaderArtifact& artifact, std::string& error); bool CommitShaderArtifact(const RuntimeShaderArtifact& artifact, std::string& error);
bool CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error); bool CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error);
bool HasProgram() const { return mProgram != 0; } bool HasProgram() const { return mProgram != 0; }
void UpdateArtifactState(const RuntimeShaderArtifact& artifact);
void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture = 0, GLuint layerInputTexture = 0); void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture = 0, GLuint layerInputTexture = 0);
void ShutdownGl(); void ShutdownGl();
@@ -28,6 +29,12 @@ public:
const std::string& sourceFingerprint, const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact, const RuntimeShaderArtifact& artifact,
RuntimePreparedShaderProgram& preparedProgram); RuntimePreparedShaderProgram& preparedProgram);
static bool BuildPreparedPassProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
const RuntimeShaderPassArtifact& passArtifact,
RuntimePreparedShaderProgram& preparedProgram);
private: private:
bool EnsureStaticGlResources(std::string& error); bool EnsureStaticGlResources(std::string& error);

View File

@@ -1,5 +1,8 @@
#include "RuntimeLayerModel.h" #include "RuntimeLayerModel.h"
#include "RuntimeParameterUtils.h"
#include <algorithm>
#include <utility> #include <utility>
namespace RenderCadenceCompositor namespace RenderCadenceCompositor
@@ -26,6 +29,7 @@ bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shad
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName; layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
layer.buildState = RuntimeLayerBuildState::Pending; layer.buildState = RuntimeLayerBuildState::Pending;
layer.message = "Runtime Slang build is waiting to start."; layer.message = "Runtime Slang build is waiting to start.";
InitializeDefaultParameterValues(layer, *shaderPackage);
mLayers.push_back(std::move(layer)); mLayers.push_back(std::move(layer));
error.clear(); error.clear();
return true; return true;
@@ -46,6 +50,7 @@ bool RuntimeLayerModel::AddLayer(const SupportedShaderCatalog& shaderCatalog, co
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName; layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
layer.buildState = RuntimeLayerBuildState::Pending; layer.buildState = RuntimeLayerBuildState::Pending;
layer.message = "Runtime Slang build is waiting to start."; layer.message = "Runtime Slang build is waiting to start.";
InitializeDefaultParameterValues(layer, *shaderPackage);
layerId = layer.id; layerId = layer.id;
mLayers.push_back(std::move(layer)); mLayers.push_back(std::move(layer));
error.clear(); error.clear();
@@ -68,6 +73,117 @@ bool RuntimeLayerModel::RemoveLayer(const std::string& layerId, std::string& err
return false; return false;
} }
bool RuntimeLayerModel::ReorderLayer(const std::string& layerId, int targetIndex, std::string& error)
{
auto layerIt = std::find_if(mLayers.begin(), mLayers.end(), [&layerId](const Layer& layer) {
return layer.id == layerId;
});
if (layerIt == mLayers.end())
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
if (targetIndex < 0)
targetIndex = 0;
if (targetIndex >= static_cast<int>(mLayers.size()))
targetIndex = static_cast<int>(mLayers.size()) - 1;
Layer layer = std::move(*layerIt);
mLayers.erase(layerIt);
std::size_t destinationIndex = static_cast<std::size_t>(targetIndex);
if (destinationIndex > mLayers.size())
destinationIndex = mLayers.size();
mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(destinationIndex), std::move(layer));
error.clear();
return true;
}
bool RuntimeLayerModel::SetLayerBypass(const std::string& layerId, bool bypass, std::string& error)
{
Layer* layer = FindLayer(layerId);
if (!layer)
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
layer->bypass = bypass;
error.clear();
return true;
}
bool RuntimeLayerModel::SetLayerShader(const SupportedShaderCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
{
Layer* layer = FindLayer(layerId);
if (!layer)
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Shader '" + shaderId + "' is not in the supported shader catalog.";
return false;
}
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.";
layer->renderReady = false;
layer->artifact = RuntimeShaderArtifact();
InitializeDefaultParameterValues(*layer, *shaderPackage);
error.clear();
return true;
}
bool RuntimeLayerModel::UpdateParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& value, std::string& error)
{
Layer* layer = FindLayer(layerId);
if (!layer)
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
const ShaderParameterDefinition* definition = FindParameterDefinition(*layer, parameterId);
if (!definition)
{
error = "Unknown parameter id '" + parameterId + "' for layer " + layerId + ".";
return false;
}
ShaderParameterValue normalizedValue;
if (!NormalizeAndValidateParameterValue(*definition, value, normalizedValue, error))
return false;
layer->parameterValues[parameterId] = normalizedValue;
if (layer->renderReady)
layer->artifact.parameterValues = layer->parameterValues;
error.clear();
return true;
}
bool RuntimeLayerModel::ResetParameters(const std::string& layerId, std::string& error)
{
Layer* layer = FindLayer(layerId);
if (!layer)
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
layer->parameterValues.clear();
for (const ShaderParameterDefinition& definition : layer->parameterDefinitions)
layer->parameterValues[definition.id] = DefaultValueForDefinition(definition);
if (layer->renderReady)
layer->artifact.parameterValues = layer->parameterValues;
error.clear();
return true;
}
void RuntimeLayerModel::Clear() void RuntimeLayerModel::Clear()
{ {
mLayers.clear(); mLayers.clear();
@@ -106,6 +222,7 @@ bool RuntimeLayerModel::MarkBuildReady(const RuntimeShaderArtifact& artifact, st
layer->message = artifact.message; layer->message = artifact.message;
layer->renderReady = true; layer->renderReady = true;
layer->artifact = artifact; layer->artifact = artifact;
layer->artifact.parameterValues = layer->parameterValues;
error.clear(); error.clear();
return true; return true;
} }
@@ -159,7 +276,9 @@ RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
RuntimeRenderLayerModel renderLayer; RuntimeRenderLayerModel renderLayer;
renderLayer.id = layer.id; renderLayer.id = layer.id;
renderLayer.shaderId = layer.shaderId; renderLayer.shaderId = layer.shaderId;
renderLayer.bypass = layer.bypass;
renderLayer.artifact = layer.artifact; renderLayer.artifact = layer.artifact;
renderLayer.artifact.parameterValues = layer.parameterValues;
snapshot.renderLayers.push_back(std::move(renderLayer)); snapshot.renderLayers.push_back(std::move(renderLayer));
} }
} }
@@ -204,6 +323,24 @@ RuntimeLayerModel::Layer* RuntimeLayerModel::FindFirstLayerForShader(const std::
return nullptr; return nullptr;
} }
void RuntimeLayerModel::InitializeDefaultParameterValues(Layer& layer, const ShaderPackage& shaderPackage)
{
layer.parameterDefinitions = shaderPackage.parameters;
layer.parameterValues.clear();
for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
layer.parameterValues[definition.id] = DefaultValueForDefinition(definition);
}
const ShaderParameterDefinition* RuntimeLayerModel::FindParameterDefinition(const Layer& layer, const std::string& parameterId)
{
for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
{
if (definition.id == parameterId)
return &definition;
}
return nullptr;
}
std::string RuntimeLayerModel::AllocateLayerId() std::string RuntimeLayerModel::AllocateLayerId()
{ {
return "runtime-layer-" + std::to_string(mNextLayerNumber++); return "runtime-layer-" + std::to_string(mNextLayerNumber++);
@@ -219,6 +356,8 @@ RuntimeLayerReadModel RuntimeLayerModel::ToReadModel(const Layer& layer)
readModel.buildState = layer.buildState; readModel.buildState = layer.buildState;
readModel.message = layer.message; readModel.message = layer.message;
readModel.renderReady = layer.renderReady; readModel.renderReady = layer.renderReady;
readModel.parameterDefinitions = layer.parameterDefinitions;
readModel.parameterValues = layer.parameterValues;
return readModel; return readModel;
} }
} }

View File

@@ -1,9 +1,11 @@
#pragma once #pragma once
#include "RuntimeJson.h"
#include "RuntimeShaderArtifact.h" #include "RuntimeShaderArtifact.h"
#include "SupportedShaderCatalog.h" #include "SupportedShaderCatalog.h"
#include <cstdint> #include <cstdint>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -25,12 +27,15 @@ struct RuntimeLayerReadModel
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending; RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
std::string message; std::string message;
bool renderReady = false; bool renderReady = false;
std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues;
}; };
struct RuntimeRenderLayerModel struct RuntimeRenderLayerModel
{ {
std::string id; std::string id;
std::string shaderId; std::string shaderId;
bool bypass = false;
RuntimeShaderArtifact artifact; RuntimeShaderArtifact artifact;
}; };
@@ -50,6 +55,11 @@ public:
bool AddLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& layerId, std::string& error); 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 RemoveLayer(const std::string& layerId, std::string& error);
bool ReorderLayer(const std::string& layerId, int targetIndex, std::string& error);
bool SetLayerBypass(const std::string& layerId, bool bypass, std::string& error);
bool SetLayerShader(const SupportedShaderCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
bool UpdateParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& value, std::string& error);
bool ResetParameters(const std::string& layerId, std::string& error);
bool MarkBuildStarted(const std::string& layerId, const std::string& message, 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 MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error);
bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message); bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message);
@@ -69,12 +79,16 @@ private:
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending; RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
std::string message; std::string message;
bool renderReady = false; bool renderReady = false;
std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues;
RuntimeShaderArtifact artifact; RuntimeShaderArtifact artifact;
}; };
Layer* FindLayer(const std::string& layerId); Layer* FindLayer(const std::string& layerId);
const Layer* FindLayer(const std::string& layerId) const; const Layer* FindLayer(const std::string& layerId) const;
Layer* FindFirstLayerForShader(const std::string& shaderId); Layer* FindFirstLayerForShader(const std::string& shaderId);
static void InitializeDefaultParameterValues(Layer& layer, const ShaderPackage& shaderPackage);
static const ShaderParameterDefinition* FindParameterDefinition(const Layer& layer, const std::string& parameterId);
std::string AllocateLayerId(); std::string AllocateLayerId();
static RuntimeLayerReadModel ToReadModel(const Layer& layer); static RuntimeLayerReadModel ToReadModel(const Layer& layer);

View File

@@ -2,15 +2,26 @@
#include "ShaderTypes.h" #include "ShaderTypes.h"
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
struct RuntimeShaderPassArtifact
{
std::string passId;
std::string fragmentShaderSource;
std::vector<std::string> inputNames;
std::string outputName;
};
struct RuntimeShaderArtifact struct RuntimeShaderArtifact
{ {
std::string layerId; std::string layerId;
std::string shaderId; std::string shaderId;
std::string displayName; std::string displayName;
std::string fragmentShaderSource; std::string fragmentShaderSource;
std::vector<RuntimeShaderPassArtifact> passes;
std::string message; std::string message;
std::vector<ShaderParameterDefinition> parameterDefinitions; std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues;
}; };

View File

@@ -112,8 +112,6 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
return build; return build;
} }
const ShaderPassDefinition& pass = shaderPackage.passes.front();
ShaderCompiler compiler( ShaderCompiler compiler(
repoRoot, repoRoot,
runtimeBuildDir / (shaderId + ".wrapper.slang"), runtimeBuildDir / (shaderId + ".wrapper.slang"),
@@ -122,19 +120,32 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
0); 0);
const auto start = std::chrono::steady_clock::now(); const auto start = std::chrono::steady_clock::now();
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, build.artifact.fragmentShaderSource, error)) for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
std::string fragmentShaderSource;
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, fragmentShaderSource, error))
{ {
build.succeeded = false; build.succeeded = false;
build.message = error.empty() ? "Slang compile failed." : error; build.message = error.empty() ? "Slang compile failed." : error;
return build; return build;
} }
RuntimeShaderPassArtifact passArtifact;
passArtifact.passId = pass.id;
passArtifact.fragmentShaderSource = std::move(fragmentShaderSource);
passArtifact.inputNames = pass.inputNames;
passArtifact.outputName = pass.outputName;
build.artifact.passes.push_back(std::move(passArtifact));
}
const auto end = std::chrono::steady_clock::now(); const auto end = std::chrono::steady_clock::now();
const double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count(); const double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
build.succeeded = true; build.succeeded = true;
build.artifact.shaderId = shaderPackage.id; build.artifact.shaderId = shaderPackage.id;
build.artifact.displayName = shaderPackage.displayName; build.artifact.displayName = shaderPackage.displayName;
build.artifact.parameterDefinitions = shaderPackage.parameters; build.artifact.parameterDefinitions = shaderPackage.parameters;
if (!build.artifact.passes.empty())
build.artifact.fragmentShaderSource = build.artifact.passes.front().fragmentShaderSource;
build.artifact.message = shaderPackage.displayName + " Slang compile completed in " + std::to_string(milliseconds) + " ms."; build.artifact.message = shaderPackage.displayName + " Slang compile completed in " + std::to_string(milliseconds) + " ms.";
build.message = build.artifact.message; build.message = build.artifact.message;
return build; return build;

View File

@@ -9,8 +9,8 @@ namespace RenderCadenceCompositor
{ {
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage) ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
{ {
if (shaderPackage.passes.size() != 1) if (shaderPackage.passes.empty())
return { false, "RenderCadenceCompositor currently supports only single-pass runtime shaders." }; return { false, "Shader package has no render passes." };
if (shaderPackage.temporal.enabled) if (shaderPackage.temporal.enabled)
return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." }; return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." };
@@ -30,6 +30,35 @@ ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& s
return { false, "RenderCadenceCompositor currently skips text parameters because they require per-shader text texture storage." }; return { false, "RenderCadenceCompositor currently skips text parameters because they require per-shader text texture storage." };
} }
bool writesLayerOutput = false;
for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
if (pass.sourcePath.empty())
{
return { false, "Shader pass '" + pass.id + "' has no source." };
}
if (pass.outputName == "layerOutput")
writesLayerOutput = true;
for (const std::string& inputName : pass.inputNames)
{
if (inputName == "videoInput" || inputName == "layerInput")
continue;
bool matchesNamedOutput = false;
for (const ShaderPassDefinition& outputPass : shaderPackage.passes)
{
if (outputPass.outputName == inputName)
{
matchesNamedOutput = true;
break;
}
}
if (!matchesNamedOutput)
return { false, "Shader pass '" + pass.id + "' references unknown input '" + inputName + "'." };
}
}
if (!writesLayerOutput)
return { false, "Shader package must write a pass output named 'layerOutput'." };
return { true, std::string() }; return { true, std::string() };
} }

View File

@@ -147,6 +147,29 @@ void TestLayerPostEndpointsUseCallbacks()
Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic"); Expect(removeResponse.body.find("Unknown layer id.") != std::string::npos, "remove layer callback returns diagnostic");
} }
void TestGenericPostCallbackHandlesControlRoutes()
{
using namespace RenderCadenceCompositor;
HttpControlServer server;
HttpControlServerCallbacks callbacks;
callbacks.executePost = [](const std::string& path, const std::string& body) {
ExpectEquals(path, "/api/layers/set-bypass", "generic callback receives route path");
Expect(body.find("runtime-layer-1") != std::string::npos, "generic callback receives request body");
return ControlActionResult{ true, std::string() };
};
server.SetCallbacksForTest(callbacks);
HttpControlServer::HttpRequest request;
request.method = "POST";
request.path = "/api/layers/set-bypass";
request.body = "{\"layerId\":\"runtime-layer-1\",\"bypass\":true}";
const HttpControlServer::HttpResponse response = server.RouteRequestForTest(request);
ExpectEquals(response.status, "200 OK", "generic control callback success returns 200");
Expect(response.body.find("\"ok\":true") != std::string::npos, "generic control callback returns action success");
}
void TestUnknownEndpointReturns404() void TestUnknownEndpointReturns404()
{ {
using namespace RenderCadenceCompositor; using namespace RenderCadenceCompositor;
@@ -169,6 +192,7 @@ int main()
TestRootServesUiIndex(); TestRootServesUiIndex();
TestKnownPostEndpointReturnsActionError(); TestKnownPostEndpointReturnsActionError();
TestLayerPostEndpointsUseCallbacks(); TestLayerPostEndpointsUseCallbacks();
TestGenericPostCallbackHandlesControlRoutes();
TestUnknownEndpointReturns404(); TestUnknownEndpointReturns404();
if (gFailures != 0) if (gFailures != 0)

View File

@@ -143,6 +143,49 @@ void TestAddAndRemoveLayers()
std::filesystem::remove_all(root); std::filesystem::remove_all(root);
} }
void TestLayerControlsUpdateDisplayAndRenderModels()
{
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 control layer can be added");
Expect(model.AddLayer(catalog, "solid", secondLayerId, error), "second control layer can be added");
Expect(model.SetLayerBypass(firstLayerId, true, error), "bypass can be set");
Expect(model.ReorderLayer(firstLayerId, 1, error), "layer can be reordered");
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
Expect(snapshot.displayLayers[1].id == firstLayerId, "reordered layer moves to requested index");
Expect(snapshot.displayLayers[1].bypass, "bypass state is visible in read model");
JsonValue gainValue(0.75);
Expect(model.UpdateParameter(firstLayerId, "gain", gainValue, error), "parameter value can be updated");
snapshot = model.Snapshot();
Expect(snapshot.displayLayers[1].parameterValues.at("gain").numberValues.front() == 0.75, "updated parameter value is visible");
RuntimeShaderArtifact artifact;
artifact.layerId = firstLayerId;
artifact.shaderId = "solid";
artifact.displayName = "Solid";
artifact.fragmentShaderSource = "void main(){}";
artifact.parameterDefinitions = snapshot.displayLayers[1].parameterDefinitions;
artifact.message = "build ready";
Expect(model.MarkBuildReady(artifact, error), "ready artifact keeps layer parameter state");
snapshot = model.Snapshot();
Expect(snapshot.renderLayers.size() == 1, "ready layer produces render model");
Expect(snapshot.renderLayers[0].bypass, "render model carries bypass state");
Expect(snapshot.renderLayers[0].artifact.parameterValues.at("gain").numberValues.front() == 0.75, "render artifact carries updated parameter value");
Expect(model.ResetParameters(firstLayerId, error), "parameters can reset to defaults");
snapshot = model.Snapshot();
Expect(snapshot.displayLayers[1].parameterValues.at("gain").numberValues.front() == 0.5, "reset restores default value");
std::filesystem::remove_all(root);
}
} }
int main() int main()
@@ -151,6 +194,7 @@ int main()
TestRejectsUnsupportedStartupShader(); TestRejectsUnsupportedStartupShader();
TestBuildFailureStaysDisplaySide(); TestBuildFailureStaysDisplaySide();
TestAddAndRemoveLayers(); TestAddAndRemoveLayers();
TestLayerControlsUpdateDisplayAndRenderModels();
if (gFailures != 0) if (gFailures != 0)
{ {

View File

@@ -23,6 +23,8 @@ ShaderPackage MakeSinglePassPackage()
ShaderPassDefinition pass; ShaderPassDefinition pass;
pass.id = "main"; pass.id = "main";
pass.entryPoint = "mainImage"; pass.entryPoint = "mainImage";
pass.sourcePath = "shader.slang";
pass.outputName = "layerOutput";
shaderPackage.passes.push_back(pass); shaderPackage.passes.push_back(pass);
return shaderPackage; return shaderPackage;
} }
@@ -37,19 +39,35 @@ void SupportsSinglePassStatelessPackage()
Expect(result.reason.empty(), "supported packages should not report a rejection reason"); Expect(result.reason.empty(), "supported packages should not report a rejection reason");
} }
void RejectsMultipassPackage() void SupportsStatelessNamedPassPackage()
{ {
ShaderPackage shaderPackage = MakeSinglePassPackage(); ShaderPackage shaderPackage = MakeSinglePassPackage();
shaderPackage.passes.front().outputName = "generatedMask";
ShaderPassDefinition secondPass; ShaderPassDefinition secondPass;
secondPass.id = "second"; secondPass.id = "second";
secondPass.entryPoint = "mainImage"; secondPass.entryPoint = "mainImage";
secondPass.sourcePath = "shader.slang";
secondPass.inputNames.push_back("generatedMask");
secondPass.outputName = "layerOutput";
shaderPackage.passes.push_back(secondPass); shaderPackage.passes.push_back(secondPass);
const RenderCadenceCompositor::ShaderSupportResult result = const RenderCadenceCompositor::ShaderSupportResult result =
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage); RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
Expect(!result.supported, "multipass packages should be rejected"); Expect(result.supported, "stateless named-pass packages should be supported");
Expect(result.reason.find("single-pass") != std::string::npos, "multipass rejection should explain the single-pass limit"); Expect(result.reason.empty(), "supported named-pass packages should not report a rejection reason");
}
void RejectsUnknownPassInput()
{
ShaderPackage shaderPackage = MakeSinglePassPackage();
shaderPackage.passes.front().inputNames.push_back("missingIntermediate");
const RenderCadenceCompositor::ShaderSupportResult result =
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
Expect(!result.supported, "packages with unknown pass inputs should be rejected");
Expect(result.reason.find("unknown input") != std::string::npos, "unknown input rejection should explain the missing named output");
} }
void RejectsTemporalPackage() void RejectsTemporalPackage()
@@ -97,7 +115,8 @@ void RejectsTextParameters()
int main() int main()
{ {
SupportsSinglePassStatelessPackage(); SupportsSinglePassStatelessPackage();
RejectsMultipassPackage(); SupportsStatelessNamedPassPackage();
RejectsUnknownPassInput();
RejectsTemporalPackage(); RejectsTemporalPackage();
RejectsTextureAssets(); RejectsTextureAssets();
RejectsTextParameters(); RejectsTextParameters();