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

@@ -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.";