More http post end points filled
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 3m1s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
Aiden
2026-05-12 14:23:53 +10:00
parent 38d729b346
commit 1ddcf5d621
15 changed files with 854 additions and 97 deletions

View File

@@ -41,6 +41,7 @@ Included now:
- latest-N system-memory frame exchange
- rendered-frame warmup
- background Slang compile of `shaders/happy-accident`
- app-owned display/render layer model for shader build readiness
- app-owned submission of a completed shader artifact
- render-thread-only GL commit once the artifact is ready
- manifest-driven stateless single-pass shader packages
@@ -149,7 +150,8 @@ Current endpoints:
- `GET /api/state`: returns OpenAPI-shaped display data with cadence telemetry, supported shaders, output status, and a read-only current runtime layer
- `GET /docs/openapi.yaml` and `GET /openapi.yaml`: serves the OpenAPI document
- `GET /docs`: serves Swagger UI
- OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
- `POST /api/layers/add` and `POST /api/layers/remove` mutate the app-owned display layer model only
- other OpenAPI POST routes are present but return `{ "ok": false, "error": "Endpoint is not implemented in RenderCadenceCompositor yet." }`
The HTTP server runs on its own thread. It serves static UI/docs files, samples/copies telemetry through callbacks, and does not call render work or DeckLink scheduling.
@@ -208,7 +210,7 @@ Current runtime shader support is deliberately limited to stateless single-pass
The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as multipass, temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now.
The current runtime shader is also exposed as a read-only display layer with manifest parameter defaults. POST control endpoints still intentionally return "not implemented" responses until the control/state ownership model is ported.
Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Stage 1 add/remove POST controls mutate this app-owned model and may start background shader builds, but multi-layer render-scene handoff is not ported yet.
Successful handoff signs:
@@ -258,6 +260,7 @@ This app keeps the same core behavior but splits it into modules that can grow:
- `frames/`: system-memory handoff
- `platform/`: COM/Win32/hidden GL context support
- `render/`: cadence, simple rendering, PBO readback
- `runtime/`: app-owned shader layer readiness model, runtime Slang build bridge, and completed artifact handoff
- `control/`: local HTTP API edge and runtime-state JSON presentation
- `json/`: compact JSON serialization helpers
- `video/`: DeckLink output wrapper and scheduling thread

View File

@@ -3,15 +3,19 @@
#include "AppConfig.h"
#include "AppConfigProvider.h"
#include "../logging/Logger.h"
#include "../runtime/RuntimeLayerModel.h"
#include "../runtime/RuntimeShaderBridge.h"
#include "../runtime/SupportedShaderCatalog.h"
#include "../control/RuntimeStateJson.h"
#include "../telemetry/TelemetryHealthMonitor.h"
#include "../video/DeckLinkOutput.h"
#include "../video/DeckLinkOutputThread.h"
#include "RuntimeJson.h"
#include <chrono>
#include <filesystem>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
@@ -71,6 +75,7 @@ public:
bool Start(std::string& error)
{
LoadSupportedShaderCatalog();
InitializeRuntimeLayerModel();
Log("app", "Starting render thread.");
if (!detail::StartRenderThread(mRenderThread, error, 0))
@@ -174,6 +179,12 @@ private:
callbacks.getStateJson = [this]() {
return BuildStateJson();
};
callbacks.addLayer = [this](const std::string& body) {
return HandleAddLayer(body);
};
callbacks.removeLayer = [this](const std::string& body) {
return HandleRemoveLayer(body);
};
std::string error;
if (!mHttpServer.Start(
@@ -191,17 +202,15 @@ private:
std::string BuildStateJson()
{
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
RuntimeDisplayState runtimeState = CopyRuntimeDisplayState(telemetry);
RuntimeLayerModelSnapshot layerSnapshot = CopyRuntimeLayerSnapshot(telemetry);
return RuntimeStateToJson(RuntimeStateJsonInput{
mConfig,
telemetry,
mHttpServer.Port(),
mVideoOutputEnabled,
mVideoOutputStatus,
mShaderCatalog.Shaders(),
runtimeState.compileSucceeded,
runtimeState.compileMessage,
runtimeState.activeShaderPackage
mShaderCatalog,
layerSnapshot
});
}
@@ -226,17 +235,9 @@ private:
}
Log("runtime-shader", "Starting background Slang build for shader '" + mConfig.runtimeShaderId + "'.");
SetRuntimeDisplayState(true, "Runtime Slang build started for shader '" + mConfig.runtimeShaderId + "'.", mConfig.runtimeShaderId);
mShaderBridge.Start(
mConfig.runtimeShaderId,
[this](const RuntimeShaderArtifact& artifact) {
SetRuntimeDisplayState(true, artifact.message.empty() ? "Runtime shader artifact is ready." : artifact.message, artifact.shaderId);
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
},
[this](const std::string& message) {
SetRuntimeDisplayState(false, message);
LogError("runtime-shader", "Runtime Slang build failed: " + message);
});
const std::string layerId = FirstRuntimeLayerId();
if (!layerId.empty())
StartLayerShaderBuild(layerId, mConfig.runtimeShaderId, true);
}
void LoadSupportedShaderCatalog()
@@ -252,41 +253,185 @@ private:
Log("runtime-shader", "Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) + " shader(s).");
}
void InitializeRuntimeLayerModel()
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error;
if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, mConfig.runtimeShaderId, error))
{
LogWarning("runtime-shader", error + " Runtime shader build disabled.");
mConfig.runtimeShaderId.clear();
mRuntimeLayerModel.Clear();
}
}
void StopRuntimeShaderBuild()
{
mShaderBridge.Stop();
StopAllRuntimeShaderBuilds();
}
struct RuntimeDisplayState
void MarkRuntimeBuildStarted(const std::string& message)
{
bool compileSucceeded = true;
std::string compileMessage;
const ShaderPackage* activeShaderPackage = nullptr;
};
void SetRuntimeDisplayState(bool compileSucceeded, const std::string& compileMessage, const std::string& activeShaderId = std::string())
{
std::lock_guard<std::mutex> lock(mRuntimeDisplayMutex);
mRuntimeCompileSucceeded = compileSucceeded;
mRuntimeCompileMessage = compileMessage;
if (!activeShaderId.empty())
mActiveShaderId = activeShaderId;
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error;
const std::string layerId = mRuntimeLayerModel.FirstLayerId();
if (!layerId.empty())
mRuntimeLayerModel.MarkBuildStarted(layerId, message, error);
}
RuntimeDisplayState CopyRuntimeDisplayState(const CadenceTelemetrySnapshot& telemetry) const
void MarkRuntimeBuildReady(const RuntimeShaderArtifact& artifact)
{
std::lock_guard<std::mutex> lock(mRuntimeDisplayMutex);
RuntimeDisplayState state;
state.compileSucceeded = mRuntimeCompileSucceeded && telemetry.shaderBuildFailures == 0;
state.compileMessage = mRuntimeCompileMessage;
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error;
if (!mRuntimeLayerModel.MarkBuildReady(artifact, error))
LogWarning("runtime-shader", error);
}
void MarkRuntimeBuildFailed(const std::string& message)
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.MarkBuildFailedForShader(mConfig.runtimeShaderId, message))
LogWarning("runtime-shader", "Runtime shader failed without a matching display layer: " + message);
}
void MarkRuntimeBuildFailedForLayer(const std::string& layerId, const std::string& message)
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error;
if (!mRuntimeLayerModel.MarkBuildFailed(layerId, message, error))
LogWarning("runtime-shader", error);
}
std::string FirstRuntimeLayerId() const
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
return mRuntimeLayerModel.FirstLayerId();
}
void StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool submitToRender)
{
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
std::string error;
mRuntimeLayerModel.MarkBuildStarted(layerId, "Runtime Slang build started for shader '" + shaderId + "'.", error);
}
auto bridge = std::make_unique<RuntimeShaderBridge>();
RuntimeShaderBridge* bridgePtr = bridge.get();
{
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
auto existingIt = mShaderBuilds.find(layerId);
if (existingIt != mShaderBuilds.end())
existingIt->second->Stop();
mShaderBuilds[layerId] = std::move(bridge);
}
bridgePtr->Start(
layerId,
shaderId,
[this, submitToRender](const RuntimeShaderArtifact& artifact) {
MarkRuntimeBuildReady(artifact);
if (submitToRender)
mRenderThread.SubmitRuntimeShaderArtifact(artifact);
},
[this, layerId](const std::string& message) {
MarkRuntimeBuildFailedForLayer(layerId, message);
LogError("runtime-shader", "Runtime Slang build failed: " + message);
});
}
void StopLayerShaderBuild(const std::string& layerId)
{
std::unique_ptr<RuntimeShaderBridge> bridge;
{
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
auto bridgeIt = mShaderBuilds.find(layerId);
if (bridgeIt == mShaderBuilds.end())
return;
bridge = std::move(bridgeIt->second);
mShaderBuilds.erase(bridgeIt);
}
bridge->Stop();
}
void StopAllRuntimeShaderBuilds()
{
std::map<std::string, std::unique_ptr<RuntimeShaderBridge>> builds;
{
std::lock_guard<std::mutex> lock(mShaderBuildMutex);
builds.swap(mShaderBuilds);
}
for (auto& entry : builds)
entry.second->Stop();
}
ControlActionResult HandleAddLayer(const std::string& body)
{
std::string shaderId;
std::string error;
if (!ExtractStringField(body, "shaderId", shaderId, error))
return { false, error };
std::string layerId;
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.AddLayer(mShaderCatalog, shaderId, layerId, error))
return { false, error };
}
StartLayerShaderBuild(layerId, shaderId, false);
return { true, std::string() };
}
ControlActionResult HandleRemoveLayer(const std::string& body)
{
std::string layerId;
std::string error;
if (!ExtractStringField(body, "layerId", layerId, error))
return { false, error };
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
if (!mRuntimeLayerModel.RemoveLayer(layerId, error))
return { false, error };
}
StopLayerShaderBuild(layerId);
return { true, std::string() };
}
static bool ExtractStringField(const std::string& body, const char* fieldName, std::string& value, std::string& error)
{
JsonValue root;
std::string parseError;
if (!ParseJson(body.empty() ? "{}" : body, root, parseError) || !root.isObject())
{
error = parseError.empty() ? "Request body must be a JSON object." : parseError;
return false;
}
const JsonValue* field = root.find(fieldName);
if (!field || !field->isString() || field->asString().empty())
{
error = std::string("Request field '") + fieldName + "' must be a non-empty string.";
return false;
}
value = field->asString();
error.clear();
return true;
}
RuntimeLayerModelSnapshot CopyRuntimeLayerSnapshot(const CadenceTelemetrySnapshot& telemetry) const
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
RuntimeLayerModelSnapshot snapshot = mRuntimeLayerModel.Snapshot();
if (telemetry.shaderBuildFailures > 0)
state.compileMessage = "Runtime shader GL commit failed; see logs for details.";
if (state.compileMessage.empty())
state.compileMessage = mConfig.runtimeShaderId.empty()
? "Runtime shader build disabled."
: "Runtime shader build has not completed yet.";
state.activeShaderPackage = mShaderCatalog.FindPackage(mActiveShaderId);
return state;
{
snapshot.compileSucceeded = false;
snapshot.compileMessage = "Runtime shader GL commit failed; see logs for details.";
}
return snapshot;
}
RenderThread& mRenderThread;
@@ -297,12 +442,11 @@ private:
TelemetryHealthMonitor mTelemetryHealth;
CadenceTelemetry mHttpTelemetry;
HttpControlServer mHttpServer;
RuntimeShaderBridge mShaderBridge;
SupportedShaderCatalog mShaderCatalog;
mutable std::mutex mRuntimeDisplayMutex;
bool mRuntimeCompileSucceeded = true;
std::string mRuntimeCompileMessage;
std::string mActiveShaderId;
mutable std::mutex mRuntimeLayerMutex;
RuntimeLayerModel mRuntimeLayerModel;
std::mutex mShaderBuildMutex;
std::map<std::string, std::unique_ptr<RuntimeShaderBridge>> mShaderBuilds;
bool mStarted = false;
bool mVideoOutputEnabled = false;
std::string mVideoOutputStatus = "DeckLink output not started.";

View File

@@ -266,6 +266,18 @@ HttpControlServer::HttpResponse HttpControlServer::ServePost(const HttpRequest&
if (!IsKnownPostEndpoint(request.path))
return TextResponse("404 Not Found", "Not Found");
if (request.path == "/api/layers/add" && mCallbacks.addLayer)
{
const ControlActionResult result = mCallbacks.addLayer(request.body);
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
}
if (request.path == "/api/layers/remove" && mCallbacks.removeLayer)
{
const ControlActionResult result = mCallbacks.removeLayer(request.body);
return JsonResponse(result.ok ? "200 OK" : "400 Bad Request", ActionResponse(result.ok, result.error));
}
return {
"400 Bad Request",
"application/json",

View File

@@ -19,9 +19,17 @@ struct HttpControlServerConfig
std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10);
};
struct ControlActionResult
{
bool ok = false;
std::string error;
};
struct HttpControlServerCallbacks
{
std::function<std::string()> getStateJson;
std::function<ControlActionResult(const std::string&)> addLayer;
std::function<ControlActionResult(const std::string&)> removeLayer;
};
class UniqueSocket

View File

@@ -3,6 +3,7 @@
#include "../app/AppConfig.h"
#include "../app/AppConfigProvider.h"
#include "../json/JsonWriter.h"
#include "../runtime/RuntimeLayerModel.h"
#include "../runtime/SupportedShaderCatalog.h"
#include "../telemetry/CadenceTelemetryJson.h"
@@ -19,10 +20,8 @@ struct RuntimeStateJsonInput
unsigned short serverPort = 0;
bool videoOutputEnabled = false;
std::string videoOutputStatus;
const std::vector<SupportedShaderSummary>& shaders;
bool runtimeCompileSucceeded = true;
std::string runtimeCompileMessage;
const ShaderPackage* activeShaderPackage = nullptr;
const SupportedShaderCatalog& shaderCatalog;
const RuntimeLayerModelSnapshot& runtimeLayers;
};
inline void WriteVideoIoStatusJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
@@ -112,6 +111,17 @@ inline void WriteFeedbackJson(JsonWriter& writer, const FeedbackSettings& feedba
writer.EndObject();
}
inline const char* RuntimeLayerBuildStateName(RuntimeLayerBuildState state)
{
switch (state)
{
case RuntimeLayerBuildState::Pending: return "pending";
case RuntimeLayerBuildState::Ready: return "ready";
case RuntimeLayerBuildState::Failed: return "failed";
}
return "unknown";
}
inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParameterDefinition& parameter)
{
writer.BeginObject();
@@ -164,22 +174,34 @@ inline void WriteParameterDefinitionJson(JsonWriter& writer, const ShaderParamet
inline void WriteLayersJson(JsonWriter& writer, const RuntimeStateJsonInput& input)
{
writer.BeginArray();
if (input.activeShaderPackage)
for (const RuntimeLayerReadModel& layer : input.runtimeLayers.displayLayers)
{
const ShaderPackage& shaderPackage = *input.activeShaderPackage;
const ShaderPackage* shaderPackage = input.shaderCatalog.FindPackage(layer.shaderId);
writer.BeginObject();
writer.KeyString("id", "runtime-layer-1");
writer.KeyString("shaderId", shaderPackage.id);
writer.KeyString("shaderName", shaderPackage.displayName.empty() ? shaderPackage.id : shaderPackage.displayName);
writer.KeyBool("bypass", false);
writer.KeyString("id", layer.id);
writer.KeyString("shaderId", layer.shaderId);
writer.KeyString("shaderName", layer.shaderName);
writer.KeyBool("bypass", layer.bypass);
writer.KeyString("buildState", RuntimeLayerBuildStateName(layer.buildState));
writer.KeyBool("renderReady", layer.renderReady);
writer.KeyString("message", layer.message);
writer.Key("temporal");
WriteTemporalJson(writer, shaderPackage.temporal);
if (shaderPackage)
WriteTemporalJson(writer, shaderPackage->temporal);
else
WriteTemporalJson(writer, TemporalSettings());
writer.Key("feedback");
WriteFeedbackJson(writer, shaderPackage.feedback);
if (shaderPackage)
WriteFeedbackJson(writer, shaderPackage->feedback);
else
WriteFeedbackJson(writer, FeedbackSettings());
writer.Key("parameters");
writer.BeginArray();
for (const ShaderParameterDefinition& parameter : shaderPackage.parameters)
WriteParameterDefinitionJson(writer, parameter);
if (shaderPackage)
{
for (const ShaderParameterDefinition& parameter : shaderPackage->parameters)
WriteParameterDefinitionJson(writer, parameter);
}
writer.EndArray();
writer.EndObject();
}
@@ -209,9 +231,9 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.Key("runtime");
writer.BeginObject();
writer.KeyUInt("layerCount", input.activeShaderPackage ? 1 : 0);
writer.KeyBool("compileSucceeded", input.runtimeCompileSucceeded);
writer.KeyString("compileMessage", input.runtimeCompileMessage);
writer.KeyUInt("layerCount", static_cast<uint64_t>(input.runtimeLayers.displayLayers.size()));
writer.KeyBool("compileSucceeded", input.runtimeLayers.compileSucceeded);
writer.KeyString("compileMessage", input.runtimeLayers.compileMessage);
writer.EndObject();
writer.Key("video");
@@ -250,7 +272,7 @@ inline std::string RuntimeStateToJson(const RuntimeStateJsonInput& input)
writer.KeyNull("runtimeEvents");
writer.Key("shaders");
writer.BeginArray();
for (const SupportedShaderSummary& shader : input.shaders)
for (const SupportedShaderSummary& shader : input.shaderCatalog.Shaders())
{
writer.BeginObject();
writer.KeyString("id", shader.id);

View File

@@ -0,0 +1,224 @@
#include "RuntimeLayerModel.h"
#include <utility>
namespace RenderCadenceCompositor
{
bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
{
Clear();
if (shaderId.empty())
{
error.clear();
return true;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Shader '" + shaderId + "' is not in the supported shader catalog.";
return false;
}
Layer layer;
layer.id = AllocateLayerId();
layer.shaderId = shaderPackage->id;
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
layer.buildState = RuntimeLayerBuildState::Pending;
layer.message = "Runtime Slang build is waiting to start.";
mLayers.push_back(std::move(layer));
error.clear();
return true;
}
bool RuntimeLayerModel::AddLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& layerId, std::string& error)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Shader '" + shaderId + "' is not in the supported shader catalog.";
return false;
}
Layer layer;
layer.id = AllocateLayerId();
layer.shaderId = shaderPackage->id;
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
layer.buildState = RuntimeLayerBuildState::Pending;
layer.message = "Runtime Slang build is waiting to start.";
layerId = layer.id;
mLayers.push_back(std::move(layer));
error.clear();
return true;
}
bool RuntimeLayerModel::RemoveLayer(const std::string& layerId, std::string& error)
{
for (auto layerIt = mLayers.begin(); layerIt != mLayers.end(); ++layerIt)
{
if (layerIt->id != layerId)
continue;
mLayers.erase(layerIt);
error.clear();
return true;
}
error = "Unknown runtime layer id: " + layerId;
return false;
}
void RuntimeLayerModel::Clear()
{
mLayers.clear();
}
bool RuntimeLayerModel::MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error)
{
Layer* layer = FindLayer(layerId);
if (!layer)
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
layer->buildState = RuntimeLayerBuildState::Pending;
layer->message = message;
layer->renderReady = false;
layer->artifact = RuntimeShaderArtifact();
error.clear();
return true;
}
bool RuntimeLayerModel::MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error)
{
Layer* layer = artifact.layerId.empty() ? FindFirstLayerForShader(artifact.shaderId) : FindLayer(artifact.layerId);
if (!layer)
{
error = artifact.layerId.empty()
? "No runtime layer is waiting for shader artifact: " + artifact.shaderId
: "No runtime layer is waiting for shader artifact on layer: " + artifact.layerId;
return false;
}
layer->shaderName = artifact.displayName.empty() ? artifact.shaderId : artifact.displayName;
layer->buildState = RuntimeLayerBuildState::Ready;
layer->message = artifact.message;
layer->renderReady = true;
layer->artifact = artifact;
error.clear();
return true;
}
bool RuntimeLayerModel::MarkBuildFailedForShader(const std::string& shaderId, const std::string& message)
{
Layer* layer = FindFirstLayerForShader(shaderId);
if (!layer)
return false;
std::string error;
return MarkBuildFailed(layer->id, message, error);
}
bool RuntimeLayerModel::MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error)
{
Layer* layer = FindLayer(layerId);
if (!layer)
{
error = "Unknown runtime layer id: " + layerId;
return false;
}
layer->buildState = RuntimeLayerBuildState::Failed;
layer->message = message;
layer->renderReady = false;
layer->artifact = RuntimeShaderArtifact();
error.clear();
return true;
}
bool RuntimeLayerModel::MarkRenderCommitFailed(const std::string& layerId, const std::string& message, std::string& error)
{
return MarkBuildFailed(layerId, message, error);
}
RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
{
RuntimeLayerModelSnapshot snapshot;
snapshot.compileSucceeded = true;
for (const Layer& layer : mLayers)
{
snapshot.displayLayers.push_back(ToReadModel(layer));
if (!layer.message.empty() && snapshot.compileMessage.empty())
snapshot.compileMessage = layer.message;
if (layer.buildState == RuntimeLayerBuildState::Failed)
snapshot.compileSucceeded = false;
if (layer.renderReady)
{
RuntimeRenderLayerModel renderLayer;
renderLayer.id = layer.id;
renderLayer.shaderId = layer.shaderId;
renderLayer.artifact = layer.artifact;
snapshot.renderLayers.push_back(std::move(renderLayer));
}
}
if (snapshot.compileMessage.empty())
snapshot.compileMessage = mLayers.empty() ? "Runtime shader build disabled." : "Runtime shader build has not completed yet.";
return snapshot;
}
std::string RuntimeLayerModel::FirstLayerId() const
{
return mLayers.empty() ? std::string() : mLayers.front().id;
}
RuntimeLayerModel::Layer* RuntimeLayerModel::FindLayer(const std::string& layerId)
{
for (Layer& layer : mLayers)
{
if (layer.id == layerId)
return &layer;
}
return nullptr;
}
const RuntimeLayerModel::Layer* RuntimeLayerModel::FindLayer(const std::string& layerId) const
{
for (const Layer& layer : mLayers)
{
if (layer.id == layerId)
return &layer;
}
return nullptr;
}
RuntimeLayerModel::Layer* RuntimeLayerModel::FindFirstLayerForShader(const std::string& shaderId)
{
for (Layer& layer : mLayers)
{
if (layer.shaderId == shaderId)
return &layer;
}
return nullptr;
}
std::string RuntimeLayerModel::AllocateLayerId()
{
return "runtime-layer-" + std::to_string(mNextLayerNumber++);
}
RuntimeLayerReadModel RuntimeLayerModel::ToReadModel(const Layer& layer)
{
RuntimeLayerReadModel readModel;
readModel.id = layer.id;
readModel.shaderId = layer.shaderId;
readModel.shaderName = layer.shaderName;
readModel.bypass = layer.bypass;
readModel.buildState = layer.buildState;
readModel.message = layer.message;
readModel.renderReady = layer.renderReady;
return readModel;
}
}

View File

@@ -0,0 +1,84 @@
#pragma once
#include "RuntimeShaderArtifact.h"
#include "SupportedShaderCatalog.h"
#include <cstdint>
#include <string>
#include <vector>
namespace RenderCadenceCompositor
{
enum class RuntimeLayerBuildState
{
Pending,
Ready,
Failed
};
struct RuntimeLayerReadModel
{
std::string id;
std::string shaderId;
std::string shaderName;
bool bypass = false;
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
std::string message;
bool renderReady = false;
};
struct RuntimeRenderLayerModel
{
std::string id;
std::string shaderId;
RuntimeShaderArtifact artifact;
};
struct RuntimeLayerModelSnapshot
{
bool compileSucceeded = true;
std::string compileMessage;
std::vector<RuntimeLayerReadModel> displayLayers;
std::vector<RuntimeRenderLayerModel> renderLayers;
};
class RuntimeLayerModel
{
public:
bool InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
void Clear();
bool AddLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& layerId, std::string& error);
bool RemoveLayer(const std::string& layerId, std::string& error);
bool MarkBuildStarted(const std::string& layerId, const std::string& message, std::string& error);
bool MarkBuildReady(const RuntimeShaderArtifact& artifact, std::string& error);
bool MarkBuildFailedForShader(const std::string& shaderId, const std::string& message);
bool MarkBuildFailed(const std::string& layerId, const std::string& message, std::string& error);
bool MarkRenderCommitFailed(const std::string& layerId, const std::string& message, std::string& error);
RuntimeLayerModelSnapshot Snapshot() const;
std::string FirstLayerId() const;
private:
struct Layer
{
std::string id;
std::string shaderId;
std::string shaderName;
bool bypass = false;
RuntimeLayerBuildState buildState = RuntimeLayerBuildState::Pending;
std::string message;
bool renderReady = false;
RuntimeShaderArtifact artifact;
};
Layer* FindLayer(const std::string& layerId);
const Layer* FindLayer(const std::string& layerId) const;
Layer* FindFirstLayerForShader(const std::string& shaderId);
std::string AllocateLayerId();
static RuntimeLayerReadModel ToReadModel(const Layer& layer);
std::vector<Layer> mLayers;
uint64_t mNextLayerNumber = 1;
};
}

View File

@@ -7,6 +7,7 @@
struct RuntimeShaderArtifact
{
std::string layerId;
std::string shaderId;
std::string displayName;
std::string fragmentShaderSource;

View File

@@ -8,11 +8,17 @@ RuntimeShaderBridge::~RuntimeShaderBridge()
}
void RuntimeShaderBridge::Start(const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError)
{
Start(std::string(), shaderId, std::move(onArtifactReady), std::move(onError));
}
void RuntimeShaderBridge::Start(const std::string& layerId, const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError)
{
Stop();
if (shaderId.empty())
return;
mLayerId = layerId;
mOnArtifactReady = std::move(onArtifactReady);
mOnError = std::move(onError);
mStopping.store(false, std::memory_order_release);
@@ -26,6 +32,7 @@ void RuntimeShaderBridge::Stop()
if (mThread.joinable())
mThread.join();
mCompiler.Stop();
mLayerId.clear();
mOnArtifactReady = ArtifactCallback();
mOnError = ErrorCallback();
}
@@ -39,6 +46,7 @@ void RuntimeShaderBridge::ThreadMain()
{
if (build.succeeded)
{
build.artifact.layerId = mLayerId;
if (mOnArtifactReady)
mOnArtifactReady(build.artifact);
}

View File

@@ -20,6 +20,7 @@ public:
~RuntimeShaderBridge();
void Start(const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError);
void Start(const std::string& layerId, const std::string& shaderId, ArtifactCallback onArtifactReady, ErrorCallback onError);
void Stop();
private:
@@ -28,6 +29,7 @@ private:
RuntimeSlangShaderCompiler mCompiler;
std::thread mThread;
std::atomic<bool> mStopping{ false };
std::string mLayerId;
ArtifactCallback mOnArtifactReady;
ErrorCallback mOnError;
};