Compare commits
2 Commits
1429b2e660
...
f43b6f6519
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f43b6f6519 | ||
|
|
dfd49fd0e3 |
@@ -290,6 +290,8 @@ set(RENDER_CADENCE_APP_SOURCES
|
||||
"${APP_DIR}/gl/shader/Std140Buffer.h"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${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.h"
|
||||
"${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.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.h"
|
||||
"${RENDER_CADENCE_APP_DIR}/control/http/HttpControlServerWebSocket.cpp"
|
||||
@@ -841,6 +845,7 @@ add_executable(RenderCadenceCompositorRuntimeLayerModelTests
|
||||
"${RENDER_CADENCE_APP_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/tests/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
|
||||
)
|
||||
|
||||
@@ -909,6 +914,7 @@ add_test(NAME RenderCadenceCompositorJsonWriterTests COMMAND RenderCadenceCompos
|
||||
|
||||
add_executable(RenderCadenceCompositorRuntimeStateJsonTests
|
||||
"${APP_DIR}/runtime/support/RuntimeJson.cpp"
|
||||
"${APP_DIR}/runtime/support/RuntimeParameterUtils.cpp"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${RENDER_CADENCE_APP_DIR}/app/AppConfig.cpp"
|
||||
"${RENDER_CADENCE_APP_DIR}/app/AppConfigProvider.cpp"
|
||||
@@ -940,6 +946,8 @@ endif()
|
||||
add_test(NAME RenderCadenceCompositorRuntimeStateJsonTests COMMAND RenderCadenceCompositorRuntimeStateJsonTests)
|
||||
|
||||
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/HttpControlServerWebSocket.cpp"
|
||||
"${RENDER_CADENCE_APP_DIR}/json/JsonWriter.cpp"
|
||||
@@ -948,6 +956,8 @@ add_executable(RenderCadenceCompositorHttpControlServerTests
|
||||
)
|
||||
|
||||
target_include_directories(RenderCadenceCompositorHttpControlServerTests PRIVATE
|
||||
"${APP_DIR}"
|
||||
"${APP_DIR}/runtime/support"
|
||||
"${RENDER_CADENCE_APP_DIR}/control"
|
||||
"${RENDER_CADENCE_APP_DIR}/control/http"
|
||||
"${RENDER_CADENCE_APP_DIR}/json"
|
||||
|
||||
@@ -47,7 +47,9 @@ Included now:
|
||||
- shared-context GL prepare worker for runtime shader program compile/link
|
||||
- render-thread-only GL program swap once a prepared program is ready
|
||||
- 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
|
||||
- small JSON writer for future HTTP/WebSocket payloads
|
||||
- JSON serialization for cadence telemetry snapshots
|
||||
@@ -60,7 +62,6 @@ Included now:
|
||||
Intentionally not included yet:
|
||||
|
||||
- DeckLink input
|
||||
- multipass shader rendering
|
||||
- temporal/history/feedback shader storage
|
||||
- texture/LUT asset upload
|
||||
- text-parameter rasterization
|
||||
@@ -86,6 +87,8 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
|
||||
- [x] Shared-context GL shader/program preparation
|
||||
- [x] Render-thread program swap at a frame boundary
|
||||
- [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] Previous-layer texture handoff for stacked shaders
|
||||
- [x] Supported shader list in HTTP/UI state
|
||||
@@ -99,7 +102,6 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
|
||||
- [ ] DeckLink input capture
|
||||
- [ ] Input frame upload into the render scene
|
||||
- [ ] Live video input bound to `gVideoInput`
|
||||
- [ ] Multipass shader rendering
|
||||
- [ ] Temporal history buffers
|
||||
- [ ] Feedback buffers
|
||||
- [ ] 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 /docs/openapi.yaml` and `GET /openapi.yaml`: serves the OpenAPI document
|
||||
- `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." }`
|
||||
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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 feedback storage
|
||||
- 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
|
||||
- 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.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -184,6 +184,13 @@ private:
|
||||
callbacks.removeLayer = [this](const std::string& 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;
|
||||
if (!mHttpServer.Start(
|
||||
|
||||
@@ -90,6 +90,95 @@ ControlActionResult RuntimeLayerController::HandleRemoveLayer(const 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
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../control/ControlActionResult.h"
|
||||
#include "../control/RuntimeControlCommand.h"
|
||||
#include "../runtime/RuntimeLayerModel.h"
|
||||
#include "../runtime/RuntimeShaderBridge.h"
|
||||
#include "../runtime/SupportedShaderCatalog.h"
|
||||
@@ -32,6 +33,7 @@ public:
|
||||
|
||||
ControlActionResult HandleAddLayer(const std::string& body);
|
||||
ControlActionResult HandleRemoveLayer(const std::string& body);
|
||||
ControlActionResult HandleControlCommand(const RuntimeControlCommand& command);
|
||||
|
||||
RuntimeLayerModelSnapshot Snapshot(const CadenceTelemetrySnapshot& telemetry) const;
|
||||
const SupportedShaderCatalog& ShaderCatalog() const { return mShaderCatalog; }
|
||||
|
||||
127
apps/RenderCadenceCompositor/control/RuntimeControlCommand.cpp
Normal file
127
apps/RenderCadenceCompositor/control/RuntimeControlCommand.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
37
apps/RenderCadenceCompositor/control/RuntimeControlCommand.h
Normal file
37
apps/RenderCadenceCompositor/control/RuntimeControlCommand.h
Normal 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);
|
||||
}
|
||||
@@ -93,6 +93,31 @@ inline void WriteDefaultParameterValue(JsonWriter& writer, const ShaderParameter
|
||||
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)
|
||||
{
|
||||
writer.BeginObject();
|
||||
@@ -122,7 +147,7 @@ inline const char* RuntimeLayerBuildStateName(RuntimeLayerBuildState state)
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter)
|
||||
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter, const ShaderParameterValue* value)
|
||||
{
|
||||
writer.BeginObject();
|
||||
writer.KeyString("id", parameter.id);
|
||||
@@ -132,6 +157,9 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
|
||||
writer.Key("defaultValue");
|
||||
WriteDefaultParameterValue(writer, parameter);
|
||||
writer.Key("value");
|
||||
if (value)
|
||||
WriteParameterValue(writer, parameter, *value);
|
||||
else
|
||||
WriteDefaultParameterValue(writer, parameter);
|
||||
|
||||
if (!parameter.minNumbers.empty())
|
||||
@@ -197,10 +225,10 @@ inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& inp
|
||||
WriteFeedbackJson(writer, FeedbackSettings());
|
||||
writer.Key("parameters");
|
||||
writer.BeginArray();
|
||||
if (shaderPackage)
|
||||
for (const ShaderParameterDefinition& parameter : layer.parameterDefinitions)
|
||||
{
|
||||
for (const ShaderParameterDefinition& parameter : shaderPackage->parameters)
|
||||
WriteParameterDefinitionJson(writer, parameter);
|
||||
const auto valueIt = layer.parameterValues.find(parameter.id);
|
||||
WriteParameterDefinitionJson(writer, parameter, valueIt == layer.parameterValues.end() ? nullptr : &valueIt->second);
|
||||
}
|
||||
writer.EndArray();
|
||||
writer.EndObject();
|
||||
|
||||
@@ -286,6 +286,12 @@ HttpControlServer::HttpResponse HttpControlServer::ServePost(const HttpRequest&
|
||||
if (!IsKnownPostEndpoint(request.path))
|
||||
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)
|
||||
{
|
||||
const ControlActionResult result = mCallbacks.addLayer(request.body);
|
||||
|
||||
@@ -28,6 +28,7 @@ struct HttpControlServerCallbacks
|
||||
std::function<std::string()> getStateJson;
|
||||
std::function<ControlActionResult(const std::string&)> addLayer;
|
||||
std::function<ControlActionResult(const std::string&)> removeLayer;
|
||||
std::function<ControlActionResult(const std::string&, const std::string&)> executePost;
|
||||
};
|
||||
|
||||
class UniqueSocket
|
||||
|
||||
@@ -28,7 +28,10 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
|
||||
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layersToPrepare;
|
||||
nextOrder.reserve(layers.size());
|
||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||
{
|
||||
if (!layer.bypass)
|
||||
nextOrder.push_back(layer.id);
|
||||
}
|
||||
|
||||
for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();)
|
||||
{
|
||||
@@ -39,14 +42,20 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
|
||||
continue;
|
||||
}
|
||||
|
||||
if (layerIt->renderer)
|
||||
layerIt->renderer->ShutdownGl();
|
||||
for (LayerProgram::PassProgram& pass : layerIt->passes)
|
||||
{
|
||||
if (pass.renderer)
|
||||
pass.renderer->ShutdownGl();
|
||||
}
|
||||
ReleasePendingPrograms(*layerIt);
|
||||
layerIt = mLayers.erase(layerIt);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const std::string fingerprint = Fingerprint(layer.artifact);
|
||||
@@ -55,16 +64,32 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
|
||||
{
|
||||
LayerProgram next;
|
||||
next.layerId = layer.id;
|
||||
next.renderer = std::make_unique<RuntimeShaderRenderer>();
|
||||
mLayers.push_back(std::move(next));
|
||||
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;
|
||||
}
|
||||
if (program->pendingFingerprint == fingerprint)
|
||||
continue;
|
||||
|
||||
ReleasePendingPrograms(*program);
|
||||
program->shaderId = layer.shaderId;
|
||||
program->pendingFingerprint = fingerprint;
|
||||
layersToPrepare.push_back(layer);
|
||||
@@ -84,9 +109,14 @@ bool RuntimeRenderScene::HasLayers()
|
||||
for (const std::string& layerId : mLayerOrder)
|
||||
{
|
||||
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 false;
|
||||
}
|
||||
|
||||
@@ -98,9 +128,16 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
|
||||
for (const std::string& layerId : mLayerOrder)
|
||||
{
|
||||
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())
|
||||
{
|
||||
readyLayers.push_back(layer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readyLayers.empty())
|
||||
@@ -111,14 +148,14 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
|
||||
|
||||
if (readyLayers.size() == 1)
|
||||
{
|
||||
readyLayers.front()->renderer->RenderFrame(frameIndex, width, height);
|
||||
RenderLayer(*readyLayers.front(), frameIndex, width, height, 0, static_cast<GLuint>(outputFramebuffer), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureLayerTargets(width, height))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -130,12 +167,11 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
|
||||
if (isFinalLayer)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[nextTargetIndex]);
|
||||
readyLayers[layerIndex]->renderer->RenderFrame(frameIndex, width, height, layerInputTexture, layerInputTexture);
|
||||
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, layerInputTexture, mLayerFramebuffers[nextTargetIndex], true);
|
||||
layerInputTexture = mLayerTextures[nextTargetIndex];
|
||||
nextTargetIndex = 1 - nextTargetIndex;
|
||||
}
|
||||
@@ -146,8 +182,12 @@ void RuntimeRenderScene::ShutdownGl()
|
||||
mPrepareWorker.Stop();
|
||||
for (LayerProgram& layer : mLayers)
|
||||
{
|
||||
if (layer.renderer)
|
||||
layer.renderer->ShutdownGl();
|
||||
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||
{
|
||||
if (pass.renderer)
|
||||
pass.renderer->ShutdownGl();
|
||||
}
|
||||
ReleasePendingPrograms(layer);
|
||||
}
|
||||
mLayers.clear();
|
||||
mLayerOrder.clear();
|
||||
@@ -172,28 +212,165 @@ void RuntimeRenderScene::ConsumePreparedPrograms()
|
||||
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::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;
|
||||
}
|
||||
|
||||
if (layer->renderer)
|
||||
layer->renderer->ShutdownGl();
|
||||
layer->renderer = std::move(nextRenderer);
|
||||
layer->shaderId = preparedProgram.shaderId;
|
||||
layer->sourceFingerprint = preparedProgram.sourceFingerprint;
|
||||
layer->pendingFingerprint.clear();
|
||||
if (!EnsureLayerTargets(width, height))
|
||||
continue;
|
||||
|
||||
const std::size_t targetIndex = nextTargetIndex;
|
||||
nextTargetIndex = nextTargetIndex == 2 ? 3 : 2;
|
||||
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)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
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)
|
||||
return true;
|
||||
|
||||
@@ -201,9 +378,9 @@ bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
||||
mLayerTargetWidth = width;
|
||||
mLayerTargetHeight = height;
|
||||
|
||||
glGenFramebuffers(2, mLayerFramebuffers);
|
||||
glGenTextures(2, mLayerTextures);
|
||||
for (int index = 0; index < 2; ++index)
|
||||
glGenFramebuffers(4, mLayerFramebuffers);
|
||||
glGenTextures(4, mLayerTextures);
|
||||
for (int index = 0; index < 4; ++index)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
@@ -239,14 +416,15 @@ bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
||||
|
||||
void RuntimeRenderScene::DestroyLayerTargets()
|
||||
{
|
||||
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0)
|
||||
glDeleteFramebuffers(2, mLayerFramebuffers);
|
||||
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0)
|
||||
glDeleteTextures(2, mLayerTextures);
|
||||
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)
|
||||
glDeleteFramebuffers(4, mLayerFramebuffers);
|
||||
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0 || mLayerTextures[2] != 0 || mLayerTextures[3] != 0)
|
||||
glDeleteTextures(4, mLayerTextures);
|
||||
for (int index = 0; index < 4; ++index)
|
||||
{
|
||||
mLayerFramebuffers[index] = 0;
|
||||
mLayerTextures[index] = 0;
|
||||
}
|
||||
mLayerTargetWidth = 0;
|
||||
mLayerTargetHeight = 0;
|
||||
}
|
||||
@@ -271,8 +449,23 @@ const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std:
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -32,21 +32,33 @@ private:
|
||||
std::string shaderId;
|
||||
std::string sourceFingerprint;
|
||||
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::vector<PassProgram> passes;
|
||||
};
|
||||
|
||||
void ConsumePreparedPrograms();
|
||||
void ReleasePendingPrograms(LayerProgram& layer);
|
||||
void TryCommitPendingPrograms(LayerProgram& layer);
|
||||
bool EnsureLayerTargets(unsigned width, unsigned height);
|
||||
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);
|
||||
const LayerProgram* FindLayer(const std::string& layerId) const;
|
||||
LayerProgram::PassProgram* FindPass(LayerProgram& layer, const std::string& passId);
|
||||
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
|
||||
|
||||
RuntimeShaderPrepareWorker mPrepareWorker;
|
||||
std::vector<LayerProgram> mLayers;
|
||||
std::vector<std::string> mLayerOrder;
|
||||
GLuint mLayerFramebuffers[2] = {};
|
||||
GLuint mLayerTextures[2] = {};
|
||||
GLuint mLayerFramebuffers[4] = {};
|
||||
GLuint mLayerTextures[4] = {};
|
||||
unsigned mLayerTargetWidth = 0;
|
||||
unsigned mLayerTargetHeight = 0;
|
||||
};
|
||||
|
||||
@@ -82,7 +82,10 @@ std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
|
||||
|
||||
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)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
|
||||
@@ -77,21 +77,36 @@ void RuntimeShaderPrepareWorker::Submit(const std::vector<RenderCadenceComposito
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||
{
|
||||
if (layer.artifact.fragmentShaderSource.empty())
|
||||
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
|
||||
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;
|
||||
request.layerId = layer.id;
|
||||
request.shaderId = layer.shaderId;
|
||||
request.passId = pass.passId;
|
||||
request.sourceFingerprint = Fingerprint(layer.artifact);
|
||||
request.artifact = layer.artifact;
|
||||
|
||||
auto sameLayer = [&request](const PrepareRequest& existing) {
|
||||
return existing.layerId == request.layerId;
|
||||
};
|
||||
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
|
||||
request.passArtifact = pass;
|
||||
mRequests.push_back(std::move(request));
|
||||
}
|
||||
}
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
@@ -137,10 +152,11 @@ void RuntimeShaderPrepareWorker::ThreadMain()
|
||||
}
|
||||
|
||||
RuntimePreparedShaderProgram preparedProgram;
|
||||
RuntimeShaderRenderer::BuildPreparedProgram(
|
||||
RuntimeShaderRenderer::BuildPreparedPassProgram(
|
||||
request.layerId,
|
||||
request.sourceFingerprint,
|
||||
request.artifact,
|
||||
request.passArtifact,
|
||||
preparedProgram);
|
||||
glFlush();
|
||||
|
||||
@@ -154,5 +170,10 @@ void RuntimeShaderPrepareWorker::ThreadMain()
|
||||
std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -35,8 +35,10 @@ private:
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string passId;
|
||||
std::string sourceFingerprint;
|
||||
RuntimeShaderArtifact artifact;
|
||||
RuntimeShaderPassArtifact passArtifact;
|
||||
};
|
||||
|
||||
void ThreadMain();
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
#include "../../runtime/RuntimeShaderArtifact.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimePreparedShaderProgram
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string passId;
|
||||
std::string sourceFingerprint;
|
||||
RuntimeShaderArtifact artifact;
|
||||
RuntimeShaderPassArtifact passArtifact;
|
||||
std::vector<std::string> inputNames;
|
||||
std::string outputName;
|
||||
GLuint program = 0;
|
||||
GLuint vertexShader = 0;
|
||||
GLuint fragmentShader = 0;
|
||||
|
||||
@@ -94,26 +94,53 @@ bool RuntimeShaderRenderer::CommitPreparedProgram(RuntimePreparedShaderProgram&
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::UpdateArtifactState(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
mArtifact.parameterDefinitions = artifact.parameterDefinitions;
|
||||
mArtifact.parameterValues = artifact.parameterValues;
|
||||
mArtifact.message = artifact.message;
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::BuildPreparedProgram(
|
||||
const std::string& layerId,
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
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.layerId = layerId;
|
||||
preparedProgram.shaderId = artifact.shaderId;
|
||||
preparedProgram.passId = passArtifact.passId;
|
||||
preparedProgram.sourceFingerprint = sourceFingerprint;
|
||||
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.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!BuildProgram(
|
||||
artifact.fragmentShaderSource,
|
||||
passArtifact.fragmentShaderSource,
|
||||
preparedProgram.program,
|
||||
preparedProgram.vertexShader,
|
||||
preparedProgram.fragmentShader,
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
bool CommitShaderArtifact(const RuntimeShaderArtifact& artifact, std::string& error);
|
||||
bool CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error);
|
||||
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 ShutdownGl();
|
||||
|
||||
@@ -28,6 +29,12 @@ public:
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
RuntimePreparedShaderProgram& preparedProgram);
|
||||
static bool BuildPreparedPassProgram(
|
||||
const std::string& layerId,
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
const RuntimeShaderPassArtifact& passArtifact,
|
||||
RuntimePreparedShaderProgram& preparedProgram);
|
||||
|
||||
private:
|
||||
bool EnsureStaticGlResources(std::string& error);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "RuntimeLayerModel.h"
|
||||
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
@@ -26,6 +29,7 @@ bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shad
|
||||
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||
layer.buildState = RuntimeLayerBuildState::Pending;
|
||||
layer.message = "Runtime Slang build is waiting to start.";
|
||||
InitializeDefaultParameterValues(layer, *shaderPackage);
|
||||
mLayers.push_back(std::move(layer));
|
||||
error.clear();
|
||||
return true;
|
||||
@@ -46,6 +50,7 @@ bool RuntimeLayerModel::AddLayer(const SupportedShaderCatalog& shaderCatalog, co
|
||||
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||
layer.buildState = RuntimeLayerBuildState::Pending;
|
||||
layer.message = "Runtime Slang build is waiting to start.";
|
||||
InitializeDefaultParameterValues(layer, *shaderPackage);
|
||||
layerId = layer.id;
|
||||
mLayers.push_back(std::move(layer));
|
||||
error.clear();
|
||||
@@ -68,6 +73,117 @@ bool RuntimeLayerModel::RemoveLayer(const std::string& layerId, std::string& err
|
||||
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()
|
||||
{
|
||||
mLayers.clear();
|
||||
@@ -106,6 +222,7 @@ bool RuntimeLayerModel::MarkBuildReady(const RuntimeShaderArtifact& artifact, st
|
||||
layer->message = artifact.message;
|
||||
layer->renderReady = true;
|
||||
layer->artifact = artifact;
|
||||
layer->artifact.parameterValues = layer->parameterValues;
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
@@ -159,7 +276,9 @@ RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
|
||||
RuntimeRenderLayerModel renderLayer;
|
||||
renderLayer.id = layer.id;
|
||||
renderLayer.shaderId = layer.shaderId;
|
||||
renderLayer.bypass = layer.bypass;
|
||||
renderLayer.artifact = layer.artifact;
|
||||
renderLayer.artifact.parameterValues = layer.parameterValues;
|
||||
snapshot.renderLayers.push_back(std::move(renderLayer));
|
||||
}
|
||||
}
|
||||
@@ -204,6 +323,24 @@ RuntimeLayerModel::Layer* RuntimeLayerModel::FindFirstLayerForShader(const std::
|
||||
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()
|
||||
{
|
||||
return "runtime-layer-" + std::to_string(mNextLayerNumber++);
|
||||
@@ -219,6 +356,8 @@ RuntimeLayerReadModel RuntimeLayerModel::ToReadModel(const Layer& layer)
|
||||
readModel.buildState = layer.buildState;
|
||||
readModel.message = layer.message;
|
||||
readModel.renderReady = layer.renderReady;
|
||||
readModel.parameterDefinitions = layer.parameterDefinitions;
|
||||
readModel.parameterValues = layer.parameterValues;
|
||||
return readModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "RuntimeShaderArtifact.h"
|
||||
#include "SupportedShaderCatalog.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -25,12 +27,15 @@ struct RuntimeLayerReadModel
|
||||
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
|
||||
std::string message;
|
||||
bool renderReady = false;
|
||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
};
|
||||
|
||||
struct RuntimeRenderLayerModel
|
||||
{
|
||||
std::string id;
|
||||
std::string shaderId;
|
||||
bool bypass = false;
|
||||
RuntimeShaderArtifact artifact;
|
||||
};
|
||||
|
||||
@@ -50,6 +55,11 @@ public:
|
||||
|
||||
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 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 MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error);
|
||||
bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message);
|
||||
@@ -69,12 +79,16 @@ private:
|
||||
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
|
||||
std::string message;
|
||||
bool renderReady = false;
|
||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
RuntimeShaderArtifact artifact;
|
||||
};
|
||||
|
||||
Layer* FindLayer(const std::string& layerId);
|
||||
const Layer* FindLayer(const std::string& layerId) const;
|
||||
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();
|
||||
static RuntimeLayerReadModel ToReadModel(const Layer& layer);
|
||||
|
||||
|
||||
@@ -2,15 +2,26 @@
|
||||
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimeShaderPassArtifact
|
||||
{
|
||||
std::string passId;
|
||||
std::string fragmentShaderSource;
|
||||
std::vector<std::string> inputNames;
|
||||
std::string outputName;
|
||||
};
|
||||
|
||||
struct RuntimeShaderArtifact
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string displayName;
|
||||
std::string fragmentShaderSource;
|
||||
std::vector<RuntimeShaderPassArtifact> passes;
|
||||
std::string message;
|
||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
};
|
||||
|
||||
@@ -112,8 +112,6 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
|
||||
return build;
|
||||
}
|
||||
|
||||
const ShaderPassDefinition& pass = shaderPackage.passes.front();
|
||||
|
||||
ShaderCompiler compiler(
|
||||
repoRoot,
|
||||
runtimeBuildDir / (shaderId + ".wrapper.slang"),
|
||||
@@ -122,19 +120,32 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
|
||||
0);
|
||||
|
||||
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.message = error.empty() ? "Slang compile failed." : error;
|
||||
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 double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
|
||||
build.succeeded = true;
|
||||
build.artifact.shaderId = shaderPackage.id;
|
||||
build.artifact.displayName = shaderPackage.displayName;
|
||||
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.message = build.artifact.message;
|
||||
return build;
|
||||
|
||||
@@ -9,8 +9,8 @@ namespace RenderCadenceCompositor
|
||||
{
|
||||
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
|
||||
{
|
||||
if (shaderPackage.passes.size() != 1)
|
||||
return { false, "RenderCadenceCompositor currently supports only single-pass runtime shaders." };
|
||||
if (shaderPackage.passes.empty())
|
||||
return { false, "Shader package has no render passes." };
|
||||
|
||||
if (shaderPackage.temporal.enabled)
|
||||
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." };
|
||||
}
|
||||
|
||||
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() };
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,29 @@ void TestLayerPostEndpointsUseCallbacks()
|
||||
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()
|
||||
{
|
||||
using namespace RenderCadenceCompositor;
|
||||
@@ -169,6 +192,7 @@ int main()
|
||||
TestRootServesUiIndex();
|
||||
TestKnownPostEndpointReturnsActionError();
|
||||
TestLayerPostEndpointsUseCallbacks();
|
||||
TestGenericPostCallbackHandlesControlRoutes();
|
||||
TestUnknownEndpointReturns404();
|
||||
|
||||
if (gFailures != 0)
|
||||
|
||||
@@ -143,6 +143,49 @@ void TestAddAndRemoveLayers()
|
||||
|
||||
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()
|
||||
@@ -151,6 +194,7 @@ int main()
|
||||
TestRejectsUnsupportedStartupShader();
|
||||
TestBuildFailureStaysDisplaySide();
|
||||
TestAddAndRemoveLayers();
|
||||
TestLayerControlsUpdateDisplayAndRenderModels();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
|
||||
@@ -23,6 +23,8 @@ ShaderPackage MakeSinglePassPackage()
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = "main";
|
||||
pass.entryPoint = "mainImage";
|
||||
pass.sourcePath = "shader.slang";
|
||||
pass.outputName = "layerOutput";
|
||||
shaderPackage.passes.push_back(pass);
|
||||
return shaderPackage;
|
||||
}
|
||||
@@ -37,19 +39,35 @@ void SupportsSinglePassStatelessPackage()
|
||||
Expect(result.reason.empty(), "supported packages should not report a rejection reason");
|
||||
}
|
||||
|
||||
void RejectsMultipassPackage()
|
||||
void SupportsStatelessNamedPassPackage()
|
||||
{
|
||||
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||
shaderPackage.passes.front().outputName = "generatedMask";
|
||||
ShaderPassDefinition secondPass;
|
||||
secondPass.id = "second";
|
||||
secondPass.entryPoint = "mainImage";
|
||||
secondPass.sourcePath = "shader.slang";
|
||||
secondPass.inputNames.push_back("generatedMask");
|
||||
secondPass.outputName = "layerOutput";
|
||||
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");
|
||||
Expect(result.supported, "stateless named-pass packages should be supported");
|
||||
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()
|
||||
@@ -97,7 +115,8 @@ void RejectsTextParameters()
|
||||
int main()
|
||||
{
|
||||
SupportsSinglePassStatelessPackage();
|
||||
RejectsMultipassPackage();
|
||||
SupportsStatelessNamedPassPackage();
|
||||
RejectsUnknownPassInput();
|
||||
RejectsTemporalPackage();
|
||||
RejectsTextureAssets();
|
||||
RejectsTextParameters();
|
||||
|
||||
Reference in New Issue
Block a user