more
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
#include "ControlServer.h"
|
||||
#include "OscServer.h"
|
||||
#include "RuntimeControlBridge.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeStore.h"
|
||||
#include <windows.h>
|
||||
|
||||
@@ -22,11 +21,11 @@ ControlServices::~ControlServices()
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool ControlServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
||||
bool ControlServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (!StartControlServicesBoundary(composite, runtimeHost, *this, *mControlServer, *mOscServer, error))
|
||||
if (!StartControlServicesBoundary(composite, runtimeStore, *this, *mControlServer, *mOscServer, error))
|
||||
{
|
||||
Stop();
|
||||
return false;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
class ControlServer;
|
||||
class OpenGLComposite;
|
||||
class OscServer;
|
||||
class RuntimeHost;
|
||||
class RuntimeStore;
|
||||
|
||||
struct RuntimePollEvents
|
||||
@@ -45,7 +44,7 @@ public:
|
||||
ControlServices();
|
||||
~ControlServices();
|
||||
|
||||
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
|
||||
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
||||
void BeginPolling(RuntimeStore& runtimeStore);
|
||||
void Stop();
|
||||
void BroadcastState();
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
#include "ControlServer.h"
|
||||
#include "OpenGLComposite.h"
|
||||
#include "OscServer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
bool StartControlServicesBoundary(
|
||||
OpenGLComposite& composite,
|
||||
RuntimeHost& runtimeHost,
|
||||
RuntimeStore& runtimeStore,
|
||||
ControlServices& controlServices,
|
||||
ControlServer& controlServer,
|
||||
OscServer& oscServer,
|
||||
@@ -38,15 +38,16 @@ bool StartControlServicesBoundary(
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!controlServer.Start(runtimeHost.GetUiRoot(), runtimeHost.GetDocsRoot(), runtimeHost.GetServerPort(), callbacks, error))
|
||||
if (!controlServer.Start(runtimeStore.GetRuntimeUiRoot(), runtimeStore.GetRuntimeDocsRoot(), runtimeStore.GetConfiguredControlServerPort(), callbacks, error))
|
||||
return false;
|
||||
runtimeHost.SetServerPort(controlServer.GetPort());
|
||||
runtimeStore.SetBoundControlServerPort(controlServer.GetPort());
|
||||
|
||||
OscServer::Callbacks oscCallbacks;
|
||||
oscCallbacks.updateParameter = [&controlServices](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
||||
return controlServices.QueueOscUpdate(layerKey, parameterKey, valueJson, actionError);
|
||||
};
|
||||
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscBindAddress(), runtimeHost.GetOscPort(), oscCallbacks, error))
|
||||
if (runtimeStore.GetConfiguredOscPort() > 0 &&
|
||||
!oscServer.Start(runtimeStore.GetConfiguredOscBindAddress(), runtimeStore.GetConfiguredOscPort(), oscCallbacks, error))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6,11 +6,11 @@ class ControlServer;
|
||||
class ControlServices;
|
||||
class OpenGLComposite;
|
||||
class OscServer;
|
||||
class RuntimeHost;
|
||||
class RuntimeStore;
|
||||
|
||||
bool StartControlServicesBoundary(
|
||||
OpenGLComposite& composite,
|
||||
RuntimeHost& runtimeHost,
|
||||
RuntimeStore& runtimeStore,
|
||||
ControlServices& controlServices,
|
||||
ControlServer& controlServer,
|
||||
OscServer& oscServer,
|
||||
|
||||
@@ -12,9 +12,9 @@ RuntimeServices::~RuntimeServices()
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error)
|
||||
bool RuntimeServices::Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error)
|
||||
{
|
||||
return mControlServices && mControlServices->Start(composite, runtimeHost, error);
|
||||
return mControlServices && mControlServices->Start(composite, runtimeStore, error);
|
||||
}
|
||||
|
||||
void RuntimeServices::BeginPolling(RuntimeStore& runtimeStore)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
class OpenGLComposite;
|
||||
class RuntimeHost;
|
||||
class RuntimeStore;
|
||||
|
||||
class RuntimeServices
|
||||
@@ -17,7 +16,7 @@ public:
|
||||
RuntimeServices();
|
||||
~RuntimeServices();
|
||||
|
||||
bool Start(OpenGLComposite& composite, RuntimeHost& runtimeHost, std::string& error);
|
||||
bool Start(OpenGLComposite& composite, RuntimeStore& runtimeStore, std::string& error);
|
||||
void BeginPolling(RuntimeStore& runtimeStore);
|
||||
void Stop();
|
||||
void BroadcastState();
|
||||
|
||||
@@ -203,7 +203,7 @@ bool OpenGLComposite::InitOpenGLState()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mRuntimeServices->Start(*this, *mRuntimeHost, runtimeError))
|
||||
if (!mRuntimeServices->Start(*this, *mRuntimeStore, runtimeError))
|
||||
{
|
||||
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
|
||||
return false;
|
||||
|
||||
@@ -675,39 +675,6 @@ RuntimeHost::RuntimeHost()
|
||||
{
|
||||
}
|
||||
|
||||
void RuntimeHost::SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||
{
|
||||
const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot();
|
||||
mHealthTelemetry.ReportSignalStatus(hasSignal, width, height, modeName);
|
||||
|
||||
if (previousStatus.hasSignal != hasSignal ||
|
||||
previousStatus.width != width ||
|
||||
previousStatus.height != height ||
|
||||
previousStatus.modeName != modeName)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
MarkRenderStateDirtyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeHost::TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName)
|
||||
{
|
||||
const HealthTelemetry::SignalStatusSnapshot previousStatus = mHealthTelemetry.GetSignalStatusSnapshot();
|
||||
if (!mHealthTelemetry.TryReportSignalStatus(hasSignal, width, height, modeName))
|
||||
return false;
|
||||
|
||||
if (previousStatus.hasSignal != hasSignal ||
|
||||
previousStatus.width != width ||
|
||||
previousStatus.height != height ||
|
||||
previousStatus.modeName != modeName)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
MarkRenderStateDirtyLocked();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeHost::MarkRenderStateDirtyLocked()
|
||||
{
|
||||
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -719,315 +686,6 @@ void RuntimeHost::MarkParameterStateDirtyLocked()
|
||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
SetVideoIOStatus("decklink", modelName, supportsInternalKeying, supportsExternalKeying, keyerInterfaceAvailable,
|
||||
externalKeyingRequested, externalKeyingActive, statusMessage);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage)
|
||||
{
|
||||
mHealthTelemetry.ReportVideoIOStatus(
|
||||
backendName,
|
||||
modelName,
|
||||
supportsInternalKeying,
|
||||
supportsExternalKeying,
|
||||
keyerInterfaceAvailable,
|
||||
externalKeyingRequested,
|
||||
externalKeyingActive,
|
||||
statusMessage);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||
{
|
||||
mHealthTelemetry.RecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||
}
|
||||
|
||||
bool RuntimeHost::TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds)
|
||||
{
|
||||
return mHealthTelemetry.TryRecordPerformanceStats(frameBudgetMilliseconds, renderMilliseconds);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||
{
|
||||
mHealthTelemetry.RecordFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||
}
|
||||
|
||||
bool RuntimeHost::TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount)
|
||||
{
|
||||
return mHealthTelemetry.TryRecordFramePacingStats(completionIntervalMilliseconds, smoothedCompletionIntervalMilliseconds,
|
||||
maxCompletionIntervalMilliseconds, lateFrameCount, droppedFrameCount, flushedFrameCount);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetServerPort(unsigned short port)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mServerPort = port;
|
||||
}
|
||||
|
||||
bool RuntimeHost::LoadConfig(std::string& error)
|
||||
{
|
||||
if (!std::filesystem::exists(mConfigPath))
|
||||
return true;
|
||||
|
||||
std::string configText = ReadTextFile(mConfigPath, error);
|
||||
if (configText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue configJson;
|
||||
if (!ParseJson(configText, configJson, error))
|
||||
return false;
|
||||
|
||||
if (const JsonValue* shaderLibraryValue = configJson.find("shaderLibrary"))
|
||||
mConfig.shaderLibrary = shaderLibraryValue->asString();
|
||||
if (const JsonValue* serverPortValue = configJson.find("serverPort"))
|
||||
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
||||
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
||||
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.oscPort));
|
||||
if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress"))
|
||||
mConfig.oscBindAddress = oscBindAddressValue->asString();
|
||||
if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing"))
|
||||
mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mConfig.oscSmoothing));
|
||||
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||
{
|
||||
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mConfig.maxTemporalHistoryFrames));
|
||||
mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* previewFpsValue = configJson.find("previewFps"))
|
||||
{
|
||||
const double configuredValue = previewFpsValue->asNumber(static_cast<double>(mConfig.previewFps));
|
||||
mConfig.previewFps = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying"))
|
||||
mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mConfig.enableExternalKeying);
|
||||
if (const JsonValue* videoFormatValue = configJson.find("videoFormat"))
|
||||
{
|
||||
if (videoFormatValue->isString() && !videoFormatValue->asString().empty())
|
||||
{
|
||||
mConfig.inputVideoFormat = videoFormatValue->asString();
|
||||
mConfig.outputVideoFormat = videoFormatValue->asString();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* frameRateValue = configJson.find("frameRate"))
|
||||
{
|
||||
if (frameRateValue->isString() && !frameRateValue->asString().empty())
|
||||
{
|
||||
mConfig.inputFrameRate = frameRateValue->asString();
|
||||
mConfig.outputFrameRate = frameRateValue->asString();
|
||||
}
|
||||
else if (frameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << frameRateValue->asNumber();
|
||||
mConfig.inputFrameRate = stream.str();
|
||||
mConfig.outputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat"))
|
||||
{
|
||||
if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty())
|
||||
mConfig.inputVideoFormat = inputVideoFormatValue->asString();
|
||||
}
|
||||
if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate"))
|
||||
{
|
||||
if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty())
|
||||
mConfig.inputFrameRate = inputFrameRateValue->asString();
|
||||
else if (inputFrameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << inputFrameRateValue->asNumber();
|
||||
mConfig.inputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat"))
|
||||
{
|
||||
if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty())
|
||||
mConfig.outputVideoFormat = outputVideoFormatValue->asString();
|
||||
}
|
||||
if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate"))
|
||||
{
|
||||
if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty())
|
||||
mConfig.outputFrameRate = outputFrameRateValue->asString();
|
||||
else if (outputFrameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << outputFrameRateValue->asNumber();
|
||||
mConfig.outputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
|
||||
mAutoReloadEnabled = mConfig.autoReload;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::LoadPersistentState(std::string& error)
|
||||
{
|
||||
if (!std::filesystem::exists(mRuntimeStatePath))
|
||||
return true;
|
||||
|
||||
std::string stateText = ReadTextFile(mRuntimeStatePath, error);
|
||||
if (stateText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue root;
|
||||
if (!ParseJson(stateText, root, error))
|
||||
return false;
|
||||
|
||||
if (const JsonValue* layersValue = root.find("layers"))
|
||||
{
|
||||
for (const JsonValue& layerValue : layersValue->asArray())
|
||||
{
|
||||
if (!layerValue.isObject())
|
||||
continue;
|
||||
LayerPersistentState layer;
|
||||
if (const JsonValue* idValue = layerValue.find("id"))
|
||||
layer.id = idValue->asString();
|
||||
if (const JsonValue* shaderIdValue = layerValue.find("shaderId"))
|
||||
layer.shaderId = shaderIdValue->asString();
|
||||
if (const JsonValue* bypassValue = layerValue.find("bypass"))
|
||||
layer.bypass = bypassValue->asBoolean(false);
|
||||
else if (const JsonValue* enabledValue = layerValue.find("enabled"))
|
||||
layer.bypass = !enabledValue->asBoolean(true);
|
||||
|
||||
if (const JsonValue* parameterValues = layerValue.find("parameterValues"))
|
||||
{
|
||||
for (const auto& parameterItem : parameterValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!layer.shaderId.empty())
|
||||
mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Migrate from the older single-shader state shape.
|
||||
std::string activeShaderId;
|
||||
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
|
||||
activeShaderId = activeShaderValue->asString();
|
||||
|
||||
if (!activeShaderId.empty())
|
||||
{
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId();
|
||||
layer.shaderId = activeShaderId;
|
||||
layer.bypass = false;
|
||||
|
||||
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
|
||||
{
|
||||
const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
|
||||
if (shaderValues)
|
||||
{
|
||||
for (const auto& parameterItem : shaderValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::SavePersistentState(std::string& error) const
|
||||
{
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
|
||||
JsonValue layers = JsonValue::MakeArray();
|
||||
for (const LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
JsonValue layerValue = JsonValue::MakeObject();
|
||||
layerValue.set("id", JsonValue(layer.id));
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
|
||||
JsonValue parameterValues = JsonValue::MakeObject();
|
||||
auto packageIt = mPackagesById.find(layer.shaderId);
|
||||
for (const auto& parameterItem : layer.parameterValues)
|
||||
{
|
||||
const ShaderParameterDefinition* definition = nullptr;
|
||||
if (packageIt != mPackagesById.end())
|
||||
{
|
||||
for (const ShaderParameterDefinition& candidate : packageIt->second.parameters)
|
||||
{
|
||||
if (candidate.id == parameterItem.first)
|
||||
{
|
||||
definition = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definition)
|
||||
parameterValues.set(parameterItem.first, SerializeParameterValue(*definition, parameterItem.second));
|
||||
}
|
||||
|
||||
layerValue.set("parameterValues", parameterValues);
|
||||
layers.pushBack(layerValue);
|
||||
}
|
||||
root.set("layers", layers);
|
||||
|
||||
return WriteTextFile(mRuntimeStatePath, SerializeJson(root, true), error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::ScanShaderPackages(std::string& error)
|
||||
{
|
||||
std::map<std::string, ShaderPackage> packagesById;
|
||||
std::vector<std::string> packageOrder;
|
||||
std::vector<ShaderPackageStatus> packageStatuses;
|
||||
ShaderPackageRegistry registry(mConfig.maxTemporalHistoryFrames);
|
||||
if (!registry.Scan(mShaderRoot, packagesById, packageOrder, packageStatuses, error))
|
||||
return false;
|
||||
|
||||
mPackagesById.swap(packagesById);
|
||||
mPackageOrder.swap(packageOrder);
|
||||
mPackageStatuses.swap(packageStatuses);
|
||||
|
||||
for (auto it = mPersistentState.layers.begin(); it != mPersistentState.layers.end();)
|
||||
{
|
||||
if (mPackagesById.find(it->shaderId) == mPackagesById.end())
|
||||
it = mPersistentState.layers.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
MarkRenderStateDirtyLocked();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const
|
||||
{
|
||||
return NormalizeAndValidateParameterValue(definition, value, normalizedValue, error);
|
||||
@@ -1107,79 +765,6 @@ void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, co
|
||||
}
|
||||
}
|
||||
|
||||
std::string RuntimeHost::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool RuntimeHost::WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const
|
||||
{
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(path.parent_path(), fsError);
|
||||
|
||||
const std::filesystem::path temporaryPath = path.string() + ".tmp";
|
||||
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output)
|
||||
{
|
||||
error = "Could not write file: " + temporaryPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
output << contents;
|
||||
output.close();
|
||||
if (!output.good())
|
||||
{
|
||||
error = "Could not finish writing file: " + temporaryPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MoveFileExA(temporaryPath.string().c_str(), path.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
|
||||
{
|
||||
const DWORD lastError = GetLastError();
|
||||
std::filesystem::remove(temporaryPath, fsError);
|
||||
error = "Could not replace file: " + path.string() + " (Win32 error " + std::to_string(lastError) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::ResolvePaths(std::string& error)
|
||||
{
|
||||
mRepoRoot = FindRepoRootCandidate();
|
||||
if (mRepoRoot.empty())
|
||||
{
|
||||
error = "Could not locate the repository root from the current runtime path.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
|
||||
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
|
||||
mDocsRoot = mRepoRoot / "docs";
|
||||
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
|
||||
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
|
||||
mRuntimeRoot = mRepoRoot / "runtime";
|
||||
mPresetRoot = mRuntimeRoot / "stack_presets";
|
||||
mRuntimeStatePath = mRuntimeRoot / "runtime_state.json";
|
||||
mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
|
||||
mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
|
||||
mPatchedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.frag";
|
||||
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError);
|
||||
std::filesystem::create_directories(mPresetRoot, fsError);
|
||||
return true;
|
||||
}
|
||||
|
||||
JsonValue RuntimeHost::SerializeLayerStackLocked() const
|
||||
{
|
||||
JsonValue layers = JsonValue::MakeArray();
|
||||
@@ -1366,31 +951,6 @@ void RuntimeHost::NormalizePersistentLayerIdsLocked()
|
||||
mNextLayerId = maxLayerNumber;
|
||||
}
|
||||
|
||||
std::vector<std::string> RuntimeHost::GetStackPresetNamesLocked() const
|
||||
{
|
||||
std::vector<std::string> presetNames;
|
||||
std::error_code fsError;
|
||||
if (!std::filesystem::exists(mPresetRoot, fsError))
|
||||
return presetNames;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(mPresetRoot, fsError))
|
||||
{
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
if (ToLowerCopy(entry.path().extension().string()) != ".json")
|
||||
continue;
|
||||
presetNames.push_back(entry.path().stem().string());
|
||||
}
|
||||
|
||||
std::sort(presetNames.begin(), presetNames.end());
|
||||
return presetNames;
|
||||
}
|
||||
|
||||
std::string RuntimeHost::MakeSafePresetFileStem(const std::string& presetName) const
|
||||
{
|
||||
return ::MakeSafePresetFileStem(presetName);
|
||||
}
|
||||
|
||||
JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const
|
||||
{
|
||||
switch (definition.type)
|
||||
|
||||
@@ -20,37 +20,8 @@ class RuntimeHost
|
||||
{
|
||||
public:
|
||||
RuntimeHost();
|
||||
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
bool TrySetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
void SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetVideoIOStatus(const std::string& backendName, const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
bool keyerInterfaceAvailable, bool externalKeyingRequested, bool externalKeyingActive, const std::string& statusMessage);
|
||||
void SetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
bool TrySetPerformanceStats(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
void SetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
bool TrySetFramePacingStats(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
HealthTelemetry& GetHealthTelemetry() { return mHealthTelemetry; }
|
||||
const HealthTelemetry& GetHealthTelemetry() const { return mHealthTelemetry; }
|
||||
|
||||
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
|
||||
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
||||
const std::filesystem::path& GetDocsRoot() const { return mDocsRoot; }
|
||||
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
||||
unsigned short GetServerPort() const { return mServerPort; }
|
||||
unsigned short GetOscPort() const { return mConfig.oscPort; }
|
||||
const std::string& GetOscBindAddress() const { return mConfig.oscBindAddress; }
|
||||
double GetOscSmoothing() const { return mConfig.oscSmoothing; }
|
||||
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||
unsigned GetPreviewFps() const { return mConfig.previewFps; }
|
||||
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
||||
const std::string& GetInputVideoFormat() const { return mConfig.inputVideoFormat; }
|
||||
const std::string& GetInputFrameRate() const { return mConfig.inputFrameRate; }
|
||||
const std::string& GetOutputVideoFormat() const { return mConfig.outputVideoFormat; }
|
||||
const std::string& GetOutputFrameRate() const { return mConfig.outputFrameRate; }
|
||||
void SetServerPort(unsigned short port);
|
||||
bool AutoReloadEnabled() const { return mAutoReloadEnabled; }
|
||||
|
||||
private:
|
||||
@@ -84,21 +55,12 @@ private:
|
||||
std::vector<LayerPersistentState> layers;
|
||||
};
|
||||
|
||||
bool LoadConfig(std::string& error);
|
||||
bool LoadPersistentState(std::string& error);
|
||||
bool SavePersistentState(std::string& error) const;
|
||||
bool ScanShaderPackages(std::string& error);
|
||||
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
||||
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||
bool ResolvePaths(std::string& error);
|
||||
JsonValue SerializeLayerStackLocked() const;
|
||||
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, std::string& error);
|
||||
void NormalizePersistentLayerIdsLocked();
|
||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName) const;
|
||||
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
|
||||
std::string TemporalHistorySourceToString(TemporalHistorySource source) const;
|
||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
||||
|
||||
@@ -69,7 +69,7 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
|
||||
|
||||
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
||||
{
|
||||
return mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
return mRuntimeHost.mConfig.maxTemporalHistoryFrames;
|
||||
}
|
||||
|
||||
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
#include "RuntimeStore.h"
|
||||
|
||||
#include "ShaderPackageRegistry.h"
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string TrimCopy(const std::string& text)
|
||||
@@ -31,6 +39,59 @@ bool MatchesControlKey(const std::string& candidate, const std::string& key)
|
||||
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
|
||||
}
|
||||
|
||||
double Clamp01(double value)
|
||||
{
|
||||
return (std::max)(0.0, (std::min)(1.0, value));
|
||||
}
|
||||
|
||||
std::string ToLowerCopy(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
return text;
|
||||
}
|
||||
|
||||
bool LooksLikePackagedRuntimeRoot(const std::filesystem::path& candidate)
|
||||
{
|
||||
return std::filesystem::exists(candidate / "config" / "runtime-host.json") &&
|
||||
std::filesystem::exists(candidate / "runtime" / "templates" / "shader_wrapper.slang.in") &&
|
||||
std::filesystem::exists(candidate / "shaders");
|
||||
}
|
||||
|
||||
bool LooksLikeRepoRoot(const std::filesystem::path& candidate)
|
||||
{
|
||||
return std::filesystem::exists(candidate / "CMakeLists.txt") &&
|
||||
std::filesystem::exists(candidate / "apps" / "LoopThroughWithOpenGLCompositing");
|
||||
}
|
||||
|
||||
std::filesystem::path FindRepoRootCandidate()
|
||||
{
|
||||
std::vector<std::filesystem::path> rootsToTry;
|
||||
|
||||
char currentDirectory[MAX_PATH] = {};
|
||||
if (GetCurrentDirectoryA(MAX_PATH, currentDirectory) > 0)
|
||||
rootsToTry.push_back(std::filesystem::path(currentDirectory));
|
||||
|
||||
char modulePath[MAX_PATH] = {};
|
||||
DWORD moduleLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH);
|
||||
if (moduleLength > 0 && moduleLength < MAX_PATH)
|
||||
rootsToTry.push_back(std::filesystem::path(modulePath).parent_path());
|
||||
|
||||
for (const std::filesystem::path& startPath : rootsToTry)
|
||||
{
|
||||
std::filesystem::path candidate = startPath;
|
||||
for (int depth = 0; depth < 10 && !candidate.empty(); ++depth)
|
||||
{
|
||||
if (LooksLikePackagedRuntimeRoot(candidate) || LooksLikeRepoRoot(candidate))
|
||||
return candidate;
|
||||
|
||||
candidate = candidate.parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
return std::filesystem::path();
|
||||
}
|
||||
|
||||
bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right)
|
||||
{
|
||||
if (left.size() != right.size())
|
||||
@@ -79,14 +140,14 @@ bool RuntimeStore::InitializeStore(std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
|
||||
|
||||
if (!mRuntimeHost.ResolvePaths(error))
|
||||
if (!ResolvePaths(error))
|
||||
return false;
|
||||
if (!mRuntimeHost.LoadConfig(error))
|
||||
if (!LoadConfig(error))
|
||||
return false;
|
||||
mRuntimeHost.mShaderRoot = mRuntimeHost.mRepoRoot / mRuntimeHost.mConfig.shaderLibrary;
|
||||
if (!mRuntimeHost.LoadPersistentState(error))
|
||||
if (!LoadPersistentState(error))
|
||||
return false;
|
||||
if (!mRuntimeHost.ScanShaderPackages(error))
|
||||
if (!ScanShaderPackages(error))
|
||||
return false;
|
||||
mRuntimeHost.NormalizePersistentLayerIdsLocked();
|
||||
|
||||
@@ -145,7 +206,7 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (mRuntimeHost.mLastScanTime != std::chrono::steady_clock::time_point::min() &&
|
||||
if (mRuntimeHost.mLastScanTime != (std::chrono::steady_clock::time_point::min)() &&
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - mRuntimeHost.mLastScanTime).count() < 250)
|
||||
{
|
||||
reloadRequested = mRuntimeHost.mReloadRequested;
|
||||
@@ -165,7 +226,7 @@ bool RuntimeStore::PollStoredFileChanges(bool& registryChanged, bool& reloadRequ
|
||||
previousLayerShaderTimes[layer.id] = std::make_pair(previous->second.shaderWriteTime, previous->second.manifestWriteTime);
|
||||
}
|
||||
|
||||
if (!mRuntimeHost.ScanShaderPackages(scanError))
|
||||
if (!ScanShaderPackages(scanError))
|
||||
{
|
||||
error = scanError;
|
||||
return false;
|
||||
@@ -249,7 +310,7 @@ bool RuntimeStore::CreateStoredLayer(const std::string& shaderId, std::string& e
|
||||
mRuntimeHost.mPersistentState.layers.push_back(layer);
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& error)
|
||||
@@ -266,7 +327,7 @@ bool RuntimeStore::DeleteStoredLayer(const std::string& layerId, std::string& er
|
||||
mRuntimeHost.mPersistentState.layers.erase(it);
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, std::string& error)
|
||||
@@ -288,7 +349,7 @@ bool RuntimeStore::MoveStoredLayer(const std::string& layerId, int direction, st
|
||||
std::swap(mRuntimeHost.mPersistentState.layers[index], mRuntimeHost.mPersistentState.layers[newIndex]);
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
@@ -317,7 +378,7 @@ bool RuntimeStore::MoveStoredLayerToIndex(const std::string& layerId, std::size_
|
||||
mRuntimeHost.mPersistentState.layers.insert(mRuntimeHost.mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
|
||||
@@ -333,7 +394,7 @@ bool RuntimeStore::SetStoredLayerBypassState(const std::string& layerId, bool by
|
||||
layer->bypass = bypassed;
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkParameterStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
@@ -358,7 +419,7 @@ bool RuntimeStore::SetStoredLayerShaderSelection(const std::string& layerId, con
|
||||
mRuntimeHost.EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
|
||||
@@ -404,7 +465,7 @@ bool RuntimeStore::SetStoredParameterValue(const std::string& layerId, const std
|
||||
|
||||
layer->parameterValues[parameterId] = normalized;
|
||||
mRuntimeHost.MarkParameterStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
|
||||
@@ -438,7 +499,7 @@ bool RuntimeStore::SetStoredParameterValueByControlKey(const std::string& layerK
|
||||
|
||||
matchedLayer->parameterValues[parameterIt->id] = normalized;
|
||||
mRuntimeHost.MarkParameterStateDirtyLocked();
|
||||
return !persistState || mRuntimeHost.SavePersistentState(error);
|
||||
return !persistState || SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, std::string& error)
|
||||
@@ -462,13 +523,13 @@ bool RuntimeStore::ResetStoredLayerParameterValues(const std::string& layerId, s
|
||||
layer->parameterValues.clear();
|
||||
mRuntimeHost.EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||
mRuntimeHost.MarkParameterStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
|
||||
const std::string safeStem = mRuntimeHost.MakeSafePresetFileStem(presetName);
|
||||
const std::string safeStem = MakeSafePresetFileStem(presetName);
|
||||
if (safeStem.empty())
|
||||
{
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
@@ -480,13 +541,13 @@ bool RuntimeStore::SaveStackPresetSnapshot(const std::string& presetName, std::s
|
||||
root.set("name", JsonValue(TrimCopy(presetName)));
|
||||
root.set("layers", mRuntimeHost.SerializeLayerStackLocked());
|
||||
|
||||
return mRuntimeHost.WriteTextFile(mRuntimeHost.mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error);
|
||||
return WriteTextFile(mRuntimeHost.mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
|
||||
const std::string safeStem = mRuntimeHost.MakeSafePresetFileStem(presetName);
|
||||
const std::string safeStem = MakeSafePresetFileStem(presetName);
|
||||
if (safeStem.empty())
|
||||
{
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
@@ -494,7 +555,7 @@ bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::s
|
||||
}
|
||||
|
||||
const std::filesystem::path presetPath = mRuntimeHost.mPresetRoot / (safeStem + ".json");
|
||||
std::string presetText = mRuntimeHost.ReadTextFile(presetPath, error);
|
||||
std::string presetText = ReadTextFile(presetPath, error);
|
||||
if (presetText.empty())
|
||||
return false;
|
||||
|
||||
@@ -522,82 +583,88 @@ bool RuntimeStore::LoadStackPresetSnapshot(const std::string& presetName, std::s
|
||||
mRuntimeHost.mPersistentState.layers = nextLayers;
|
||||
mRuntimeHost.mReloadRequested = true;
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return mRuntimeHost.SavePersistentState(error);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeRepositoryRoot() const
|
||||
{
|
||||
return mRuntimeHost.GetRepoRoot();
|
||||
return mRuntimeHost.mRepoRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeUiRoot() const
|
||||
{
|
||||
return mRuntimeHost.GetUiRoot();
|
||||
return mRuntimeHost.mUiRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeDocsRoot() const
|
||||
{
|
||||
return mRuntimeHost.GetDocsRoot();
|
||||
return mRuntimeHost.mDocsRoot;
|
||||
}
|
||||
|
||||
const std::filesystem::path& RuntimeStore::GetRuntimeDataRoot() const
|
||||
{
|
||||
return mRuntimeHost.GetRuntimeRoot();
|
||||
return mRuntimeHost.mRuntimeRoot;
|
||||
}
|
||||
|
||||
unsigned short RuntimeStore::GetConfiguredControlServerPort() const
|
||||
{
|
||||
return mRuntimeHost.GetServerPort();
|
||||
return mRuntimeHost.mServerPort;
|
||||
}
|
||||
|
||||
unsigned short RuntimeStore::GetConfiguredOscPort() const
|
||||
{
|
||||
return mRuntimeHost.GetOscPort();
|
||||
return mRuntimeHost.mConfig.oscPort;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredOscBindAddress() const
|
||||
{
|
||||
return mRuntimeHost.GetOscBindAddress();
|
||||
return mRuntimeHost.mConfig.oscBindAddress;
|
||||
}
|
||||
|
||||
double RuntimeStore::GetConfiguredOscSmoothing() const
|
||||
{
|
||||
return mRuntimeHost.GetOscSmoothing();
|
||||
return mRuntimeHost.mConfig.oscSmoothing;
|
||||
}
|
||||
|
||||
unsigned RuntimeStore::GetConfiguredMaxTemporalHistoryFrames() const
|
||||
{
|
||||
return mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
return mRuntimeHost.mConfig.maxTemporalHistoryFrames;
|
||||
}
|
||||
|
||||
unsigned RuntimeStore::GetConfiguredPreviewFps() const
|
||||
{
|
||||
return mRuntimeHost.GetPreviewFps();
|
||||
return mRuntimeHost.mConfig.previewFps;
|
||||
}
|
||||
|
||||
bool RuntimeStore::IsExternalKeyingConfigured() const
|
||||
{
|
||||
return mRuntimeHost.ExternalKeyingEnabled();
|
||||
return mRuntimeHost.mConfig.enableExternalKeying;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredInputVideoFormat() const
|
||||
{
|
||||
return mRuntimeHost.GetInputVideoFormat();
|
||||
return mRuntimeHost.mConfig.inputVideoFormat;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredInputFrameRate() const
|
||||
{
|
||||
return mRuntimeHost.GetInputFrameRate();
|
||||
return mRuntimeHost.mConfig.inputFrameRate;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredOutputVideoFormat() const
|
||||
{
|
||||
return mRuntimeHost.GetOutputVideoFormat();
|
||||
return mRuntimeHost.mConfig.outputVideoFormat;
|
||||
}
|
||||
|
||||
const std::string& RuntimeStore::GetConfiguredOutputFrameRate() const
|
||||
{
|
||||
return mRuntimeHost.GetOutputFrameRate();
|
||||
return mRuntimeHost.mConfig.outputFrameRate;
|
||||
}
|
||||
|
||||
void RuntimeStore::SetBoundControlServerPort(unsigned short port)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeHost.mMutex);
|
||||
mRuntimeHost.mServerPort = port;
|
||||
}
|
||||
|
||||
void RuntimeStore::SetCompileStatus(bool succeeded, const std::string& message)
|
||||
@@ -719,7 +786,7 @@ JsonValue RuntimeStore::BuildRuntimeStateValue() const
|
||||
root.set("shaders", shaderLibrary);
|
||||
|
||||
JsonValue stackPresets = JsonValue::MakeArray();
|
||||
for (const std::string& presetName : mRuntimeHost.GetStackPresetNamesLocked())
|
||||
for (const std::string& presetName : GetStackPresetNamesLocked())
|
||||
stackPresets.pushBack(JsonValue(presetName));
|
||||
root.set("stackPresets", stackPresets);
|
||||
|
||||
@@ -732,6 +799,360 @@ JsonValue RuntimeStore::SerializeLayerStack() const
|
||||
return mRuntimeHost.SerializeLayerStackLocked();
|
||||
}
|
||||
|
||||
bool RuntimeStore::LoadConfig(std::string& error)
|
||||
{
|
||||
if (!std::filesystem::exists(mRuntimeHost.mConfigPath))
|
||||
return true;
|
||||
|
||||
std::string configText = ReadTextFile(mRuntimeHost.mConfigPath, error);
|
||||
if (configText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue configJson;
|
||||
if (!ParseJson(configText, configJson, error))
|
||||
return false;
|
||||
|
||||
if (const JsonValue* shaderLibraryValue = configJson.find("shaderLibrary"))
|
||||
mRuntimeHost.mConfig.shaderLibrary = shaderLibraryValue->asString();
|
||||
if (const JsonValue* serverPortValue = configJson.find("serverPort"))
|
||||
mRuntimeHost.mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mRuntimeHost.mConfig.serverPort));
|
||||
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
||||
mRuntimeHost.mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mRuntimeHost.mConfig.oscPort));
|
||||
if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress"))
|
||||
mRuntimeHost.mConfig.oscBindAddress = oscBindAddressValue->asString();
|
||||
if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing"))
|
||||
mRuntimeHost.mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mRuntimeHost.mConfig.oscSmoothing));
|
||||
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||
mRuntimeHost.mConfig.autoReload = autoReloadValue->asBoolean(mRuntimeHost.mConfig.autoReload);
|
||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||
{
|
||||
const double configuredValue = maxTemporalHistoryFramesValue->asNumber(static_cast<double>(mRuntimeHost.mConfig.maxTemporalHistoryFrames));
|
||||
mRuntimeHost.mConfig.maxTemporalHistoryFrames = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* previewFpsValue = configJson.find("previewFps"))
|
||||
{
|
||||
const double configuredValue = previewFpsValue->asNumber(static_cast<double>(mRuntimeHost.mConfig.previewFps));
|
||||
mRuntimeHost.mConfig.previewFps = configuredValue <= 0.0 ? 0u : static_cast<unsigned>(configuredValue);
|
||||
}
|
||||
if (const JsonValue* enableExternalKeyingValue = configJson.find("enableExternalKeying"))
|
||||
mRuntimeHost.mConfig.enableExternalKeying = enableExternalKeyingValue->asBoolean(mRuntimeHost.mConfig.enableExternalKeying);
|
||||
if (const JsonValue* videoFormatValue = configJson.find("videoFormat"))
|
||||
{
|
||||
if (videoFormatValue->isString() && !videoFormatValue->asString().empty())
|
||||
{
|
||||
mRuntimeHost.mConfig.inputVideoFormat = videoFormatValue->asString();
|
||||
mRuntimeHost.mConfig.outputVideoFormat = videoFormatValue->asString();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* frameRateValue = configJson.find("frameRate"))
|
||||
{
|
||||
if (frameRateValue->isString() && !frameRateValue->asString().empty())
|
||||
{
|
||||
mRuntimeHost.mConfig.inputFrameRate = frameRateValue->asString();
|
||||
mRuntimeHost.mConfig.outputFrameRate = frameRateValue->asString();
|
||||
}
|
||||
else if (frameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << frameRateValue->asNumber();
|
||||
mRuntimeHost.mConfig.inputFrameRate = stream.str();
|
||||
mRuntimeHost.mConfig.outputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* inputVideoFormatValue = configJson.find("inputVideoFormat"))
|
||||
{
|
||||
if (inputVideoFormatValue->isString() && !inputVideoFormatValue->asString().empty())
|
||||
mRuntimeHost.mConfig.inputVideoFormat = inputVideoFormatValue->asString();
|
||||
}
|
||||
if (const JsonValue* inputFrameRateValue = configJson.find("inputFrameRate"))
|
||||
{
|
||||
if (inputFrameRateValue->isString() && !inputFrameRateValue->asString().empty())
|
||||
mRuntimeHost.mConfig.inputFrameRate = inputFrameRateValue->asString();
|
||||
else if (inputFrameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << inputFrameRateValue->asNumber();
|
||||
mRuntimeHost.mConfig.inputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
if (const JsonValue* outputVideoFormatValue = configJson.find("outputVideoFormat"))
|
||||
{
|
||||
if (outputVideoFormatValue->isString() && !outputVideoFormatValue->asString().empty())
|
||||
mRuntimeHost.mConfig.outputVideoFormat = outputVideoFormatValue->asString();
|
||||
}
|
||||
if (const JsonValue* outputFrameRateValue = configJson.find("outputFrameRate"))
|
||||
{
|
||||
if (outputFrameRateValue->isString() && !outputFrameRateValue->asString().empty())
|
||||
mRuntimeHost.mConfig.outputFrameRate = outputFrameRateValue->asString();
|
||||
else if (outputFrameRateValue->isNumber())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << outputFrameRateValue->asNumber();
|
||||
mRuntimeHost.mConfig.outputFrameRate = stream.str();
|
||||
}
|
||||
}
|
||||
|
||||
mRuntimeHost.mAutoReloadEnabled = mRuntimeHost.mConfig.autoReload;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::LoadPersistentState(std::string& error)
|
||||
{
|
||||
if (!std::filesystem::exists(mRuntimeHost.mRuntimeStatePath))
|
||||
return true;
|
||||
|
||||
std::string stateText = ReadTextFile(mRuntimeHost.mRuntimeStatePath, error);
|
||||
if (stateText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue root;
|
||||
if (!ParseJson(stateText, root, error))
|
||||
return false;
|
||||
|
||||
if (const JsonValue* layersValue = root.find("layers"))
|
||||
{
|
||||
for (const JsonValue& layerValue : layersValue->asArray())
|
||||
{
|
||||
if (!layerValue.isObject())
|
||||
continue;
|
||||
RuntimeHost::LayerPersistentState layer;
|
||||
if (const JsonValue* idValue = layerValue.find("id"))
|
||||
layer.id = idValue->asString();
|
||||
if (const JsonValue* shaderIdValue = layerValue.find("shaderId"))
|
||||
layer.shaderId = shaderIdValue->asString();
|
||||
if (const JsonValue* bypassValue = layerValue.find("bypass"))
|
||||
layer.bypass = bypassValue->asBoolean(false);
|
||||
else if (const JsonValue* enabledValue = layerValue.find("enabled"))
|
||||
layer.bypass = !enabledValue->asBoolean(true);
|
||||
|
||||
if (const JsonValue* parameterValues = layerValue.find("parameterValues"))
|
||||
{
|
||||
for (const auto& parameterItem : parameterValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!layer.shaderId.empty())
|
||||
mRuntimeHost.mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string activeShaderId;
|
||||
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
|
||||
activeShaderId = activeShaderValue->asString();
|
||||
|
||||
if (!activeShaderId.empty())
|
||||
{
|
||||
RuntimeHost::LayerPersistentState layer;
|
||||
layer.id = mRuntimeHost.GenerateLayerId();
|
||||
layer.shaderId = activeShaderId;
|
||||
layer.bypass = false;
|
||||
|
||||
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
|
||||
{
|
||||
const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
|
||||
if (shaderValues)
|
||||
{
|
||||
for (const auto& parameterItem : shaderValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mRuntimeHost.mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::SavePersistentState(std::string& error) const
|
||||
{
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
|
||||
JsonValue layers = JsonValue::MakeArray();
|
||||
for (const RuntimeHost::LayerPersistentState& layer : mRuntimeHost.mPersistentState.layers)
|
||||
{
|
||||
JsonValue layerValue = JsonValue::MakeObject();
|
||||
layerValue.set("id", JsonValue(layer.id));
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
|
||||
JsonValue parameterValues = JsonValue::MakeObject();
|
||||
auto packageIt = mRuntimeHost.mPackagesById.find(layer.shaderId);
|
||||
for (const auto& parameterItem : layer.parameterValues)
|
||||
{
|
||||
const ShaderParameterDefinition* definition = nullptr;
|
||||
if (packageIt != mRuntimeHost.mPackagesById.end())
|
||||
{
|
||||
for (const ShaderParameterDefinition& candidate : packageIt->second.parameters)
|
||||
{
|
||||
if (candidate.id == parameterItem.first)
|
||||
{
|
||||
definition = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definition)
|
||||
parameterValues.set(parameterItem.first, mRuntimeHost.SerializeParameterValue(*definition, parameterItem.second));
|
||||
}
|
||||
|
||||
layerValue.set("parameterValues", parameterValues);
|
||||
layers.pushBack(layerValue);
|
||||
}
|
||||
root.set("layers", layers);
|
||||
|
||||
return WriteTextFile(mRuntimeHost.mRuntimeStatePath, SerializeJson(root, true), error);
|
||||
}
|
||||
|
||||
bool RuntimeStore::ScanShaderPackages(std::string& error)
|
||||
{
|
||||
std::map<std::string, ShaderPackage> packagesById;
|
||||
std::vector<std::string> packageOrder;
|
||||
std::vector<ShaderPackageStatus> packageStatuses;
|
||||
ShaderPackageRegistry registry(mRuntimeHost.mConfig.maxTemporalHistoryFrames);
|
||||
if (!registry.Scan(mRuntimeHost.mShaderRoot, packagesById, packageOrder, packageStatuses, error))
|
||||
return false;
|
||||
|
||||
mRuntimeHost.mPackagesById.swap(packagesById);
|
||||
mRuntimeHost.mPackageOrder.swap(packageOrder);
|
||||
mRuntimeHost.mPackageStatuses.swap(packageStatuses);
|
||||
|
||||
for (auto it = mRuntimeHost.mPersistentState.layers.begin(); it != mRuntimeHost.mPersistentState.layers.end();)
|
||||
{
|
||||
if (mRuntimeHost.mPackagesById.find(it->shaderId) == mRuntimeHost.mPackagesById.end())
|
||||
it = mRuntimeHost.mPersistentState.layers.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
mRuntimeHost.MarkRenderStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RuntimeStore::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool RuntimeStore::WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const
|
||||
{
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(path.parent_path(), fsError);
|
||||
|
||||
const std::filesystem::path temporaryPath = path.string() + ".tmp";
|
||||
std::ofstream output(temporaryPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output)
|
||||
{
|
||||
error = "Could not write file: " + temporaryPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
output << contents;
|
||||
output.close();
|
||||
if (!output.good())
|
||||
{
|
||||
error = "Could not finish writing file: " + temporaryPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MoveFileExA(temporaryPath.string().c_str(), path.string().c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
|
||||
{
|
||||
const DWORD lastError = GetLastError();
|
||||
std::filesystem::remove(temporaryPath, fsError);
|
||||
error = "Could not replace file: " + path.string() + " (Win32 error " + std::to_string(lastError) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeStore::ResolvePaths(std::string& error)
|
||||
{
|
||||
mRuntimeHost.mRepoRoot = FindRepoRootCandidate();
|
||||
if (mRuntimeHost.mRepoRoot.empty())
|
||||
{
|
||||
error = "Could not locate the repository root from the current runtime path.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path builtUiRoot = mRuntimeHost.mRepoRoot / "ui" / "dist";
|
||||
mRuntimeHost.mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRuntimeHost.mRepoRoot / "ui");
|
||||
mRuntimeHost.mDocsRoot = mRuntimeHost.mRepoRoot / "docs";
|
||||
mRuntimeHost.mConfigPath = mRuntimeHost.mRepoRoot / "config" / "runtime-host.json";
|
||||
mRuntimeHost.mShaderRoot = mRuntimeHost.mRepoRoot / mRuntimeHost.mConfig.shaderLibrary;
|
||||
mRuntimeHost.mRuntimeRoot = mRuntimeHost.mRepoRoot / "runtime";
|
||||
mRuntimeHost.mPresetRoot = mRuntimeHost.mRuntimeRoot / "stack_presets";
|
||||
mRuntimeHost.mRuntimeStatePath = mRuntimeHost.mRuntimeRoot / "runtime_state.json";
|
||||
mRuntimeHost.mWrapperPath = mRuntimeHost.mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
|
||||
mRuntimeHost.mGeneratedGlslPath = mRuntimeHost.mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
|
||||
mRuntimeHost.mPatchedGlslPath = mRuntimeHost.mRuntimeRoot / "shader_cache" / "active_shader.frag";
|
||||
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(mRuntimeHost.mRuntimeRoot / "shader_cache", fsError);
|
||||
std::filesystem::create_directories(mRuntimeHost.mPresetRoot, fsError);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> RuntimeStore::GetStackPresetNamesLocked() const
|
||||
{
|
||||
std::vector<std::string> presetNames;
|
||||
std::error_code fsError;
|
||||
if (!std::filesystem::exists(mRuntimeHost.mPresetRoot, fsError))
|
||||
return presetNames;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(mRuntimeHost.mPresetRoot, fsError))
|
||||
{
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
if (ToLowerCopy(entry.path().extension().string()) != ".json")
|
||||
continue;
|
||||
presetNames.push_back(entry.path().stem().string());
|
||||
}
|
||||
|
||||
std::sort(presetNames.begin(), presetNames.end());
|
||||
return presetNames;
|
||||
}
|
||||
|
||||
std::string RuntimeStore::MakeSafePresetFileStem(const std::string& presetName) const
|
||||
{
|
||||
return ::MakeSafePresetFileStem(presetName);
|
||||
}
|
||||
|
||||
bool RuntimeStore::TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey,
|
||||
RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage,
|
||||
std::vector<ShaderParameterDefinition>::const_iterator& parameterIt, std::string& error) const
|
||||
|
||||
@@ -42,11 +42,21 @@ public:
|
||||
const std::string& GetConfiguredInputFrameRate() const;
|
||||
const std::string& GetConfiguredOutputVideoFormat() const;
|
||||
const std::string& GetConfiguredOutputFrameRate() const;
|
||||
void SetBoundControlServerPort(unsigned short port);
|
||||
|
||||
void SetCompileStatus(bool succeeded, const std::string& message);
|
||||
void ClearReloadRequest();
|
||||
|
||||
private:
|
||||
bool LoadConfig(std::string& error);
|
||||
bool LoadPersistentState(std::string& error);
|
||||
bool SavePersistentState(std::string& error) const;
|
||||
bool ScanShaderPackages(std::string& error);
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||
bool ResolvePaths(std::string& error);
|
||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName) const;
|
||||
bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey,
|
||||
RuntimeHost::LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage,
|
||||
std::vector<ShaderParameterDefinition>::const_iterator& parameterIt, std::string& error) const;
|
||||
|
||||
Reference in New Issue
Block a user