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}/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"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
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();
|
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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user