Pass 3
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m48s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
Aiden
2026-05-11 02:06:17 +10:00
parent b2369c418b
commit 5cbdbd6813
22 changed files with 2069 additions and 1504 deletions

View File

@@ -104,10 +104,20 @@ set(APP_SOURCES
"${APP_DIR}/resource.h" "${APP_DIR}/resource.h"
"${APP_DIR}/runtime/HealthTelemetry.cpp" "${APP_DIR}/runtime/HealthTelemetry.cpp"
"${APP_DIR}/runtime/HealthTelemetry.h" "${APP_DIR}/runtime/HealthTelemetry.h"
"${APP_DIR}/runtime/LayerStackStore.cpp"
"${APP_DIR}/runtime/LayerStackStore.h"
"${APP_DIR}/runtime/RenderSnapshotBuilder.cpp"
"${APP_DIR}/runtime/RenderSnapshotBuilder.h"
"${APP_DIR}/runtime/RuntimeConfigStore.cpp"
"${APP_DIR}/runtime/RuntimeConfigStore.h"
"${APP_DIR}/runtime/RuntimeCoordinator.cpp" "${APP_DIR}/runtime/RuntimeCoordinator.cpp"
"${APP_DIR}/runtime/RuntimeCoordinator.h" "${APP_DIR}/runtime/RuntimeCoordinator.h"
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp" "${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
"${APP_DIR}/runtime/RuntimeSnapshotProvider.h" "${APP_DIR}/runtime/RuntimeSnapshotProvider.h"
"${APP_DIR}/runtime/RuntimeStateJson.cpp"
"${APP_DIR}/runtime/RuntimeStateJson.h"
"${APP_DIR}/runtime/RuntimeStatePresenter.cpp"
"${APP_DIR}/runtime/RuntimeStatePresenter.h"
"${APP_DIR}/runtime/RuntimeClock.cpp" "${APP_DIR}/runtime/RuntimeClock.cpp"
"${APP_DIR}/runtime/RuntimeClock.h" "${APP_DIR}/runtime/RuntimeClock.h"
"${APP_DIR}/runtime/RuntimeJson.cpp" "${APP_DIR}/runtime/RuntimeJson.cpp"
@@ -116,6 +126,8 @@ set(APP_SOURCES
"${APP_DIR}/runtime/RuntimeParameterUtils.h" "${APP_DIR}/runtime/RuntimeParameterUtils.h"
"${APP_DIR}/runtime/RuntimeStore.cpp" "${APP_DIR}/runtime/RuntimeStore.cpp"
"${APP_DIR}/runtime/RuntimeStore.h" "${APP_DIR}/runtime/RuntimeStore.h"
"${APP_DIR}/runtime/ShaderPackageCatalog.cpp"
"${APP_DIR}/runtime/ShaderPackageCatalog.h"
"${APP_DIR}/shader/ShaderCompiler.cpp" "${APP_DIR}/shader/ShaderCompiler.cpp"
"${APP_DIR}/shader/ShaderCompiler.h" "${APP_DIR}/shader/ShaderCompiler.h"
"${APP_DIR}/shader/ShaderPackageRegistry.cpp" "${APP_DIR}/shader/ShaderPackageRegistry.cpp"

View File

@@ -36,7 +36,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
{ {
InitializeCriticalSection(&pMutex); InitializeCriticalSection(&pMutex);
mRuntimeStore = std::make_unique<RuntimeStore>(); mRuntimeStore = std::make_unique<RuntimeStore>();
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeStore); mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder());
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore); mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
mRenderEngine = std::make_unique<RenderEngine>( mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeSnapshotProvider, *mRuntimeSnapshotProvider,

View File

@@ -0,0 +1,738 @@
#include "LayerStackStore.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStateJson.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <set>
#include <sstream>
#include <utility>
namespace
{
std::string TrimCopy(const std::string& text)
{
std::size_t start = 0;
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
++start;
std::size_t end = text.size();
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
--end;
return text.substr(start, end - start);
}
std::string SimplifyControlKey(const std::string& text)
{
std::string simplified;
for (unsigned char ch : text)
{
if (std::isalnum(ch))
simplified.push_back(static_cast<char>(std::tolower(ch)));
}
return simplified;
}
bool MatchesControlKey(const std::string& candidate, const std::string& key)
{
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
}
bool TryParseLayerIdNumber(const std::string& layerId, uint64_t& number)
{
const std::string prefix = "layer-";
if (layerId.rfind(prefix, 0) != 0 || layerId.size() == prefix.size())
return false;
uint64_t parsed = 0;
for (std::size_t index = prefix.size(); index < layerId.size(); ++index)
{
const unsigned char ch = static_cast<unsigned char>(layerId[index]);
if (!std::isdigit(ch))
return false;
parsed = parsed * 10 + static_cast<uint64_t>(ch - '0');
}
number = parsed;
return true;
}
}
bool LayerStackStore::LoadPersistentStateValue(const JsonValue& root)
{
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())
mLayers.push_back(layer);
}
}
else
{
std::string activeShaderId;
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
activeShaderId = activeShaderValue->asString();
if (!activeShaderId.empty())
{
LayerPersistentState layer;
layer.id = GenerateLayerId(mLayers, mNextLayerId);
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;
}
}
}
mLayers.push_back(layer);
}
}
return true;
}
JsonValue LayerStackStore::BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const
{
JsonValue root = JsonValue::MakeObject();
JsonValue layers = JsonValue::MakeArray();
for (const LayerPersistentState& layer : mLayers)
{
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();
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
for (const auto& parameterItem : layer.parameterValues)
{
const ShaderParameterDefinition* definition = nullptr;
if (shaderPackage)
{
for (const ShaderParameterDefinition& candidate : shaderPackage->parameters)
{
if (candidate.id == parameterItem.first)
{
definition = &candidate;
break;
}
}
}
if (definition)
parameterValues.set(parameterItem.first, RuntimeStateJson::SerializeParameterValue(*definition, parameterItem.second));
}
layerValue.set("parameterValues", parameterValues);
layers.pushBack(layerValue);
}
root.set("layers", layers);
return root;
}
void LayerStackStore::NormalizeLayerIds()
{
std::set<std::string> usedIds;
uint64_t maxLayerNumber = mNextLayerId;
for (LayerPersistentState& layer : mLayers)
{
uint64_t layerNumber = 0;
const bool hasReusableId = !layer.id.empty() &&
usedIds.find(layer.id) == usedIds.end() &&
TryParseLayerIdNumber(layer.id, layerNumber);
if (hasReusableId)
{
usedIds.insert(layer.id);
maxLayerNumber = (std::max)(maxLayerNumber, layerNumber);
continue;
}
do
{
++maxLayerNumber;
layer.id = "layer-" + std::to_string(maxLayerNumber);
}
while (usedIds.find(layer.id) != usedIds.end());
usedIds.insert(layer.id);
}
mNextLayerId = maxLayerNumber;
}
void LayerStackStore::EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog)
{
for (LayerPersistentState& layer : mLayers)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
if (shaderPackage)
EnsureLayerDefaults(layer, *shaderPackage);
}
}
void LayerStackStore::EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog)
{
if (!mLayers.empty() || shaderCatalog.PackageOrder().empty())
return;
LayerPersistentState layer;
layer.id = GenerateLayerId(mLayers, mNextLayerId);
layer.shaderId = shaderCatalog.PackageOrder().front();
layer.bypass = false;
if (const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId))
EnsureLayerDefaults(layer, *shaderPackage);
mLayers.push_back(layer);
}
void LayerStackStore::RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog)
{
for (auto it = mLayers.begin(); it != mLayers.end();)
{
if (!shaderCatalog.HasPackage(it->shaderId))
it = mLayers.erase(it);
else
++it;
}
}
bool LayerStackStore::CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + shaderId;
return false;
}
LayerPersistentState layer;
layer.id = GenerateLayerId(mLayers, mNextLayerId);
layer.shaderId = shaderId;
layer.bypass = false;
EnsureLayerDefaults(layer, *shaderPackage);
mLayers.push_back(layer);
return true;
}
bool LayerStackStore::DeleteLayer(const std::string& layerId, std::string& error)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
mLayers.erase(it);
return true;
}
bool LayerStackStore::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
const std::ptrdiff_t newIndex = index + direction;
if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mLayers.size()))
return true;
std::swap(mLayers[index], mLayers[newIndex]);
return true;
}
bool LayerStackStore::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
if (mLayers.empty())
return true;
if (targetIndex >= mLayers.size())
targetIndex = mLayers.size() - 1;
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
if (sourceIndex == targetIndex)
return true;
LayerPersistentState movedLayer = *it;
mLayers.erase(mLayers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
mLayers.insert(mLayers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
return true;
}
bool LayerStackStore::SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
layer->bypass = bypassed;
return true;
}
bool LayerStackStore::SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + shaderId;
return false;
}
layer->shaderId = shaderId;
layer->parameterValues.clear();
EnsureLayerDefaults(*layer, *shaderPackage);
return true;
}
bool LayerStackStore::SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
layer->parameterValues[parameterId] = value;
return true;
}
bool LayerStackStore::ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error)
{
LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
layer->parameterValues.clear();
EnsureLayerDefaults(*layer, *shaderPackage);
return true;
}
bool LayerStackStore::HasLayer(const std::string& layerId) const
{
return FindLayerById(layerId) != nullptr;
}
bool LayerStackStore::TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const
{
const LayerPersistentState* layer = FindLayerById(layerId);
if (!layer)
{
error = "Unknown layer id: " + layerId;
return false;
}
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer->shaderId);
if (!shaderPackage)
{
error = "Unknown shader id: " + layer->shaderId;
return false;
}
auto parameterIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (parameterIt == shaderPackage->parameters.end())
{
error = "Unknown parameter id: " + parameterId;
return false;
}
snapshot = StoredParameterSnapshot();
snapshot.layerId = layer->id;
snapshot.definition = *parameterIt;
auto valueIt = layer->parameterValues.find(parameterIt->id);
if (valueIt != layer->parameterValues.end())
{
snapshot.currentValue = valueIt->second;
snapshot.hasCurrentValue = true;
}
return true;
}
bool LayerStackStore::TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const
{
const LayerPersistentState* matchedLayer = nullptr;
const ShaderPackage* matchedPackage = nullptr;
for (const LayerPersistentState& layer : mLayers)
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
if (!shaderPackage)
continue;
if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderPackage->id, layerKey) ||
MatchesControlKey(shaderPackage->displayName, layerKey))
{
matchedLayer = &layer;
matchedPackage = shaderPackage;
break;
}
}
if (!matchedLayer || !matchedPackage)
{
error = "Unknown OSC layer key: " + layerKey;
return false;
}
auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(),
[&parameterKey](const ShaderParameterDefinition& definition)
{
return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey);
});
if (parameterIt == matchedPackage->parameters.end())
{
error = "Unknown OSC parameter key: " + parameterKey;
return false;
}
snapshot = StoredParameterSnapshot();
snapshot.layerId = matchedLayer->id;
snapshot.definition = *parameterIt;
auto valueIt = matchedLayer->parameterValues.find(parameterIt->id);
if (valueIt != matchedLayer->parameterValues.end())
{
snapshot.currentValue = valueIt->second;
snapshot.hasCurrentValue = true;
}
return true;
}
bool LayerStackStore::ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
const std::ptrdiff_t index = std::distance(mLayers.begin(), it);
const std::ptrdiff_t newIndex = index + direction;
shouldMove = newIndex >= 0 && newIndex < static_cast<std::ptrdiff_t>(mLayers.size()) && newIndex != index;
return true;
}
bool LayerStackStore::ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
if (it == mLayers.end())
{
error = "Unknown layer id: " + layerId;
return false;
}
if (mLayers.empty())
{
shouldMove = false;
return true;
}
const std::size_t clampedTargetIndex = (std::min)(targetIndex, mLayers.size() - 1);
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mLayers.begin(), it));
shouldMove = sourceIndex != clampedTargetIndex;
return true;
}
JsonValue LayerStackStore::BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const
{
JsonValue root = JsonValue::MakeObject();
root.set("version", JsonValue(1.0));
root.set("name", JsonValue(TrimCopy(presetName)));
root.set("layers", RuntimeStateJson::SerializeLayerStack(*this, shaderCatalog));
return root;
}
bool LayerStackStore::LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error)
{
const JsonValue* layersValue = root.find("layers");
if (!layersValue || !layersValue->isArray())
{
error = "Preset file is missing a valid 'layers' array.";
return false;
}
std::vector<LayerPersistentState> nextLayers;
uint64_t nextLayerId = mNextLayerId;
if (!DeserializeLayerStack(shaderCatalog, *layersValue, nextLayers, nextLayerId, error))
return false;
if (nextLayers.empty())
{
error = "Preset does not contain any valid layers.";
return false;
}
mLayers = std::move(nextLayers);
mNextLayerId = nextLayerId;
return true;
}
std::string LayerStackStore::MakeSafePresetFileStem(const std::string& presetName)
{
return ::MakeSafePresetFileStem(presetName);
}
const std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers() const
{
return mLayers;
}
std::vector<LayerStackStore::LayerPersistentState>& LayerStackStore::Layers()
{
return mLayers;
}
std::size_t LayerStackStore::LayerCount() const
{
return mLayers.size();
}
const LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId) const
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
return it == mLayers.end() ? nullptr : &*it;
}
LayerStackStore::LayerPersistentState* LayerStackStore::FindLayerById(const std::string& layerId)
{
auto it = std::find_if(mLayers.begin(), mLayers.end(),
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
return it == mLayers.end() ? nullptr : &*it;
}
ShaderParameterValue LayerStackStore::DefaultValueForDefinition(const ShaderParameterDefinition& definition)
{
return ::DefaultValueForDefinition(definition);
}
void LayerStackStore::EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage)
{
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
{
auto valueIt = layerState.parameterValues.find(definition.id);
if (valueIt == layerState.parameterValues.end())
{
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
continue;
}
JsonValue valueJson;
bool shouldNormalize = true;
switch (definition.type)
{
case ShaderParameterType::Float:
if (valueIt->second.numberValues.empty())
shouldNormalize = false;
else
valueJson = JsonValue(valueIt->second.numberValues.front());
break;
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
valueJson = JsonValue::MakeArray();
for (double number : valueIt->second.numberValues)
valueJson.pushBack(JsonValue(number));
break;
case ShaderParameterType::Boolean:
valueJson = JsonValue(valueIt->second.booleanValue);
break;
case ShaderParameterType::Enum:
valueJson = JsonValue(valueIt->second.enumValue);
break;
case ShaderParameterType::Text:
{
const std::string textValue = !valueIt->second.textValue.empty()
? valueIt->second.textValue
: valueIt->second.enumValue;
if (textValue.empty())
{
valueIt->second = DefaultValueForDefinition(definition);
shouldNormalize = false;
}
else
{
valueJson = JsonValue(textValue);
}
break;
}
case ShaderParameterType::Trigger:
if (valueIt->second.numberValues.empty())
valueJson = JsonValue(0.0);
else
valueJson = JsonValue((std::max)(0.0, std::floor(valueIt->second.numberValues.front())));
break;
}
if (!shouldNormalize)
continue;
ShaderParameterValue normalizedValue;
std::string normalizeError;
if (NormalizeAndValidateParameterValue(definition, valueJson, normalizedValue, normalizeError))
valueIt->second = normalizedValue;
else
valueIt->second = DefaultValueForDefinition(definition);
}
}
bool LayerStackStore::DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error)
{
for (const JsonValue& layerValue : layersValue.asArray())
{
if (!layerValue.isObject())
continue;
const JsonValue* shaderIdValue = layerValue.find("shaderId");
if (!shaderIdValue)
continue;
const std::string shaderId = shaderIdValue->asString();
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderId);
if (!shaderPackage)
{
error = "Preset references unknown shader id: " + shaderId;
return false;
}
LayerPersistentState layer;
layer.id = GenerateLayerId(layers, nextLayerId);
layer.shaderId = shaderId;
if (const JsonValue* bypassValue = layerValue.find("bypass"))
layer.bypass = bypassValue->asBoolean(false);
if (const JsonValue* parametersValue = layerValue.find("parameters"))
{
for (const JsonValue& parameterValue : parametersValue->asArray())
{
if (!parameterValue.isObject())
continue;
const JsonValue* parameterIdValue = parameterValue.find("id");
const JsonValue* valueValue = parameterValue.find("value");
if (!parameterIdValue || !valueValue)
continue;
const std::string parameterId = parameterIdValue->asString();
auto definitionIt = std::find_if(shaderPackage->parameters.begin(), shaderPackage->parameters.end(),
[&parameterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
if (definitionIt == shaderPackage->parameters.end())
continue;
ShaderParameterValue normalizedValue;
if (!NormalizeAndValidateParameterValue(*definitionIt, *valueValue, normalizedValue, error))
return false;
layer.parameterValues[parameterId] = normalizedValue;
}
}
EnsureLayerDefaults(layer, *shaderPackage);
layers.push_back(layer);
}
return true;
}
std::string LayerStackStore::GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId)
{
while (true)
{
++nextLayerId;
const std::string candidate = "layer-" + std::to_string(nextLayerId);
auto it = std::find_if(layers.begin(), layers.end(),
[&candidate](const LayerPersistentState& layer) { return layer.id == candidate; });
if (it == layers.end())
return candidate;
}
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include "RuntimeJson.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h"
#include <cstddef>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
class LayerStackStore
{
public:
struct LayerPersistentState
{
std::string id;
std::string shaderId;
bool bypass = false;
std::map<std::string, ShaderParameterValue> parameterValues;
};
struct StoredParameterSnapshot
{
std::string layerId;
ShaderParameterDefinition definition;
ShaderParameterValue currentValue;
bool hasCurrentValue = false;
};
bool LoadPersistentStateValue(const JsonValue& root);
JsonValue BuildPersistentStateValue(const ShaderPackageCatalog& shaderCatalog) const;
void NormalizeLayerIds();
void EnsureDefaultsForAllLayers(const ShaderPackageCatalog& shaderCatalog);
void EnsureDefaultLayer(const ShaderPackageCatalog& shaderCatalog);
void RemoveLayersWithMissingPackages(const ShaderPackageCatalog& shaderCatalog);
bool CreateLayer(const ShaderPackageCatalog& shaderCatalog, const std::string& shaderId, std::string& error);
bool DeleteLayer(const std::string& layerId, std::string& error);
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetLayerBypassState(const std::string& layerId, bool bypassed, std::string& error);
bool SetLayerShaderSelection(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& shaderId, std::string& error);
bool SetParameterValue(const std::string& layerId, const std::string& parameterId, const ShaderParameterValue& value, std::string& error);
bool ResetLayerParameterValues(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, std::string& error);
bool HasLayer(const std::string& layerId) const;
bool TryGetParameterById(const ShaderPackageCatalog& shaderCatalog, const std::string& layerId, const std::string& parameterId, StoredParameterSnapshot& snapshot, std::string& error) const;
bool TryGetParameterByControlKey(const ShaderPackageCatalog& shaderCatalog, const std::string& layerKey, const std::string& parameterKey, StoredParameterSnapshot& snapshot, std::string& error) const;
bool ResolveLayerMove(const std::string& layerId, int direction, bool& shouldMove, std::string& error) const;
bool ResolveLayerMoveToIndex(const std::string& layerId, std::size_t targetIndex, bool& shouldMove, std::string& error) const;
JsonValue BuildStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const std::string& presetName) const;
bool LoadStackPresetValue(const ShaderPackageCatalog& shaderCatalog, const JsonValue& root, std::string& error);
static std::string MakeSafePresetFileStem(const std::string& presetName);
const std::vector<LayerPersistentState>& Layers() const;
std::vector<LayerPersistentState>& Layers();
std::size_t LayerCount() const;
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
LayerPersistentState* FindLayerById(const std::string& layerId);
private:
static ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition);
static void EnsureLayerDefaults(LayerPersistentState& layerState, const ShaderPackage& shaderPackage);
static bool DeserializeLayerStack(const ShaderPackageCatalog& shaderCatalog, const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId, std::string& error);
static std::string GenerateLayerId(std::vector<LayerPersistentState>& layers, uint64_t& nextLayerId);
std::vector<LayerPersistentState> mLayers;
uint64_t mNextLayerId = 0;
};

View File

@@ -0,0 +1,208 @@
#include "RenderSnapshotBuilder.h"
#include "RuntimeClock.h"
#include "RuntimeParameterUtils.h"
#include "RuntimeStore.h"
#include "ShaderCompiler.h"
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <mutex>
#include <utility>
RenderSnapshotBuilder::RenderSnapshotBuilder(RuntimeStore& runtimeStore) :
mRuntimeStore(runtimeStore)
{
}
bool RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const
{
try
{
ShaderPackage shaderPackage;
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
return false;
std::filesystem::path repoRoot;
std::filesystem::path wrapperPath;
std::filesystem::path generatedGlslPath;
std::filesystem::path patchedGlslPath;
unsigned maxTemporalHistoryFrames = 0;
mRuntimeStore.GetShaderCompilerInputs(repoRoot, wrapperPath, generatedGlslPath, patchedGlslPath, maxTemporalHistoryFrames);
ShaderCompiler compiler(
repoRoot,
wrapperPath,
generatedGlslPath,
patchedGlslPath,
maxTemporalHistoryFrames);
passSources.clear();
passSources.reserve(shaderPackage.passes.size());
for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
ShaderPassBuildSource passSource;
passSource.passId = pass.id;
passSource.inputNames = pass.inputNames;
passSource.outputName = pass.outputName;
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error))
return false;
passSources.push_back(std::move(passSource));
}
return true;
}
catch (const std::exception& exception)
{
error = std::string("RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources exception: ") + exception.what();
return false;
}
catch (...)
{
error = "RenderSnapshotBuilder::BuildLayerPassFragmentShaderSources threw a non-standard exception.";
return false;
}
}
unsigned RenderSnapshotBuilder::GetMaxTemporalHistoryFrames() const
{
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
}
RuntimeSnapshotVersions RenderSnapshotBuilder::GetVersions() const
{
RuntimeSnapshotVersions versions;
versions.renderStateVersion = mRenderStateVersion.load(std::memory_order_relaxed);
versions.parameterStateVersion = mParameterStateVersion.load(std::memory_order_relaxed);
return versions;
}
void RenderSnapshotBuilder::AdvanceFrame()
{
++mFrameCounter;
}
void RenderSnapshotBuilder::BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mMutex);
BuildLayerRenderStatesLocked(outputWidth, outputHeight, states);
}
bool RenderSnapshotBuilder::TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
std::unique_lock<std::mutex> lock(mRuntimeStore.mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
BuildLayerRenderStatesLocked(outputWidth, outputHeight, states);
return true;
}
bool RenderSnapshotBuilder::TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const
{
std::unique_lock<std::mutex> lock(mRuntimeStore.mMutex, std::try_to_lock);
if (!lock.owns_lock())
return false;
RefreshLayerParametersLocked(states);
return true;
}
void RenderSnapshotBuilder::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{
std::lock_guard<std::mutex> lock(mRuntimeStore.mMutex);
RefreshDynamicRenderStateFieldsLocked(states);
}
void RenderSnapshotBuilder::MarkRenderStateDirty()
{
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
}
void RenderSnapshotBuilder::MarkParameterStateDirty()
{
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
}
void RenderSnapshotBuilder::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const
{
states.clear();
const HealthTelemetry::SignalStatusSnapshot signalStatus = mRuntimeStore.mHealthTelemetry.GetSignalStatusSnapshot();
for (const RuntimeStore::LayerPersistentState& layer : mRuntimeStore.mLayerStack.Layers())
{
const ShaderPackage* shaderPackage = mRuntimeStore.mShaderCatalog.FindPackage(layer.shaderId);
if (!shaderPackage)
continue;
RuntimeRenderState state;
state.layerId = layer.id;
state.shaderId = layer.shaderId;
state.shaderName = shaderPackage->displayName;
state.mixAmount = 1.0;
state.bypass = layer.bypass ? 1.0 : 0.0;
state.inputWidth = signalStatus.width;
state.inputHeight = signalStatus.height;
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
state.parameterDefinitions = shaderPackage->parameters;
state.textureAssets = shaderPackage->textureAssets;
state.fontAssets = shaderPackage->fontAssets;
state.isTemporal = shaderPackage->temporal.enabled;
state.temporalHistorySource = shaderPackage->temporal.historySource;
state.requestedTemporalHistoryLength = shaderPackage->temporal.requestedHistoryLength;
state.effectiveTemporalHistoryLength = shaderPackage->temporal.effectiveHistoryLength;
state.feedback = shaderPackage->feedback;
for (const ShaderParameterDefinition& definition : shaderPackage->parameters)
{
ShaderParameterValue value = DefaultValueForDefinition(definition);
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
states.push_back(state);
}
RefreshDynamicRenderStateFieldsLocked(states);
}
void RenderSnapshotBuilder::RefreshLayerParametersLocked(std::vector<RuntimeRenderState>& states) const
{
for (RuntimeRenderState& state : states)
{
const auto layerIt = std::find_if(mRuntimeStore.mLayerStack.Layers().begin(), mRuntimeStore.mLayerStack.Layers().end(),
[&state](const RuntimeStore::LayerPersistentState& layer) { return layer.id == state.layerId; });
if (layerIt == mRuntimeStore.mLayerStack.Layers().end())
continue;
state.bypass = layerIt->bypass ? 1.0 : 0.0;
state.parameterValues.clear();
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
ShaderParameterValue value = DefaultValueForDefinition(definition);
auto valueIt = layerIt->parameterValues.find(definition.id);
if (valueIt != layerIt->parameterValues.end())
value = valueIt->second;
state.parameterValues[definition.id] = value;
}
}
}
void RenderSnapshotBuilder::RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const
{
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
const double timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mRuntimeStore.mStartTime).count();
const double frameCount = static_cast<double>(mFrameCounter.load(std::memory_order_relaxed));
for (RuntimeRenderState& state : states)
{
state.timeSeconds = timeSeconds;
state.utcTimeSeconds = clock.utcTimeSeconds;
state.utcOffsetSeconds = clock.utcOffsetSeconds;
state.startupRandom = mRuntimeStore.mStartupRandom;
state.frameCount = frameCount;
}
}

View File

@@ -0,0 +1,43 @@
#pragma once
#include "ShaderTypes.h"
#include <atomic>
#include <cstdint>
#include <string>
#include <vector>
class RuntimeStore;
struct RuntimeSnapshotVersions
{
uint64_t renderStateVersion = 0;
uint64_t parameterStateVersion = 0;
};
class RenderSnapshotBuilder
{
public:
explicit RenderSnapshotBuilder(RuntimeStore& runtimeStore);
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
unsigned GetMaxTemporalHistoryFrames() const;
RuntimeSnapshotVersions GetVersions() const;
void AdvanceFrame();
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
void MarkRenderStateDirty();
void MarkParameterStateDirty();
private:
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
void RefreshLayerParametersLocked(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const;
RuntimeStore& mRuntimeStore;
std::atomic<uint64_t> mFrameCounter{ 0 };
std::atomic<uint64_t> mRenderStateVersion{ 0 };
std::atomic<uint64_t> mParameterStateVersion{ 0 };
};

View File

@@ -0,0 +1,270 @@
#include "RuntimeConfigStore.h"
#include "RuntimeJson.h"
#include <algorithm>
#include <fstream>
#include <sstream>
#include <vector>
#include <windows.h>
namespace
{
double Clamp01(double value)
{
return (std::max)(0.0, (std::min)(1.0, value));
}
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 RuntimeConfigStore::Initialize(std::string& error)
{
if (!ResolvePaths(error))
return false;
if (!LoadConfig(error))
return false;
RefreshConfigDependentPaths();
return true;
}
const RuntimeConfigStore::AppConfig& RuntimeConfigStore::GetConfig() const
{
return mConfig;
}
const std::filesystem::path& RuntimeConfigStore::GetRepoRoot() const
{
return mRepoRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetUiRoot() const
{
return mUiRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetDocsRoot() const
{
return mDocsRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetShaderRoot() const
{
return mShaderRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetRuntimeRoot() const
{
return mRuntimeRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetPresetRoot() const
{
return mPresetRoot;
}
const std::filesystem::path& RuntimeConfigStore::GetRuntimeStatePath() const
{
return mRuntimeStatePath;
}
const std::filesystem::path& RuntimeConfigStore::GetWrapperPath() const
{
return mWrapperPath;
}
const std::filesystem::path& RuntimeConfigStore::GetGeneratedGlslPath() const
{
return mGeneratedGlslPath;
}
const std::filesystem::path& RuntimeConfigStore::GetPatchedGlslPath() const
{
return mPatchedGlslPath;
}
void RuntimeConfigStore::SetBoundControlServerPort(unsigned short port)
{
mConfig.serverPort = port;
}
bool RuntimeConfigStore::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";
mRuntimeRoot = mRepoRoot / "runtime";
mPresetRoot = mRuntimeRoot / "stack_presets";
mRuntimeStatePath = mRuntimeRoot / "runtime_state.json";
RefreshConfigDependentPaths();
std::error_code fsError;
std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError);
std::filesystem::create_directories(mPresetRoot, fsError);
return true;
}
bool RuntimeConfigStore::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();
}
}
return true;
}
std::string RuntimeConfigStore::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();
}
void RuntimeConfigStore::RefreshConfigDependentPaths()
{
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
mPatchedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.frag";
}

View File

@@ -0,0 +1,59 @@
#pragma once
#include <filesystem>
#include <string>
class RuntimeConfigStore
{
public:
struct AppConfig
{
std::string shaderLibrary = "shaders";
unsigned short serverPort = 8080;
unsigned short oscPort = 9000;
std::string oscBindAddress = "127.0.0.1";
double oscSmoothing = 0.18;
bool autoReload = true;
unsigned maxTemporalHistoryFrames = 4;
unsigned previewFps = 30;
bool enableExternalKeying = false;
std::string inputVideoFormat = "1080p";
std::string inputFrameRate = "59.94";
std::string outputVideoFormat = "1080p";
std::string outputFrameRate = "59.94";
};
bool Initialize(std::string& error);
const AppConfig& GetConfig() const;
const std::filesystem::path& GetRepoRoot() const;
const std::filesystem::path& GetUiRoot() const;
const std::filesystem::path& GetDocsRoot() const;
const std::filesystem::path& GetShaderRoot() const;
const std::filesystem::path& GetRuntimeRoot() const;
const std::filesystem::path& GetPresetRoot() const;
const std::filesystem::path& GetRuntimeStatePath() const;
const std::filesystem::path& GetWrapperPath() const;
const std::filesystem::path& GetGeneratedGlslPath() const;
const std::filesystem::path& GetPatchedGlslPath() const;
void SetBoundControlServerPort(unsigned short port);
private:
bool ResolvePaths(std::string& error);
bool LoadConfig(std::string& error);
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
void RefreshConfigDependentPaths();
AppConfig mConfig;
std::filesystem::path mRepoRoot;
std::filesystem::path mUiRoot;
std::filesystem::path mDocsRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mPresetRoot;
std::filesystem::path mRuntimeStatePath;
std::filesystem::path mConfigPath;
std::filesystem::path mWrapperPath;
std::filesystem::path mGeneratedGlslPath;
std::filesystem::path mPatchedGlslPath;
};

View File

@@ -1,12 +1,9 @@
#include "RuntimeSnapshotProvider.h" #include "RuntimeSnapshotProvider.h"
#include "ShaderCompiler.h"
#include <filesystem>
#include <utility> #include <utility>
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeStore& runtimeStore) : RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder) :
mRuntimeStore(runtimeStore) mRenderSnapshotBuilder(renderSnapshotBuilder)
{ {
} }
@@ -14,36 +11,7 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
{ {
try try
{ {
ShaderPackage shaderPackage; return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
return false;
std::filesystem::path repoRoot;
std::filesystem::path wrapperPath;
std::filesystem::path generatedGlslPath;
std::filesystem::path patchedGlslPath;
unsigned maxTemporalHistoryFrames = 0;
mRuntimeStore.GetShaderCompilerInputs(repoRoot, wrapperPath, generatedGlslPath, patchedGlslPath, maxTemporalHistoryFrames);
ShaderCompiler compiler(
repoRoot,
wrapperPath,
generatedGlslPath,
patchedGlslPath,
maxTemporalHistoryFrames);
passSources.clear();
passSources.reserve(shaderPackage.passes.size());
for (const ShaderPassDefinition& pass : shaderPackage.passes)
{
ShaderPassBuildSource passSource;
passSource.passId = pass.id;
passSource.inputNames = pass.inputNames;
passSource.outputName = pass.outputName;
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error))
return false;
passSources.push_back(std::move(passSource));
}
return true;
} }
catch (const std::exception& exception) catch (const std::exception& exception)
{ {
@@ -59,20 +27,17 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
{ {
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames(); return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
} }
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
{ {
RuntimeSnapshotVersions versions; return mRenderSnapshotBuilder.GetVersions();
versions.renderStateVersion = mRuntimeStore.GetRenderStateVersion();
versions.parameterStateVersion = mRuntimeStore.GetParameterStateVersion();
return versions;
} }
void RuntimeSnapshotProvider::AdvanceFrame() void RuntimeSnapshotProvider::AdvanceFrame()
{ {
mRuntimeStore.AdvanceFrameCounter(); mRenderSnapshotBuilder.AdvanceFrame();
} }
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
@@ -87,7 +52,7 @@ RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(u
RuntimeRenderStateSnapshot snapshot; RuntimeRenderStateSnapshot snapshot;
snapshot.outputWidth = outputWidth; snapshot.outputWidth = outputWidth;
snapshot.outputHeight = outputHeight; snapshot.outputHeight = outputHeight;
mRuntimeStore.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states); mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
const RuntimeSnapshotVersions versionsAfter = GetVersions(); const RuntimeSnapshotVersions versionsAfter = GetVersions();
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion && if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
@@ -107,7 +72,7 @@ bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth
return true; return true;
std::vector<RuntimeRenderState> states; std::vector<RuntimeRenderState> states;
if (!mRuntimeStore.TryBuildLayerRenderStates(outputWidth, outputHeight, states)) if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
return false; return false;
const RuntimeSnapshotVersions versionsAfter = GetVersions(); const RuntimeSnapshotVersions versionsAfter = GetVersions();
@@ -128,7 +93,7 @@ bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
{ {
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion; const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
if (!mRuntimeStore.TryRefreshLayerParameters(snapshot.states)) if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
return false; return false;
const RuntimeSnapshotVersions versions = GetVersions(); const RuntimeSnapshotVersions versions = GetVersions();
@@ -142,7 +107,7 @@ bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRende
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
{ {
mRuntimeStore.RefreshDynamicRenderStateFields(states); mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
} }
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight, bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,

View File

@@ -1,18 +1,11 @@
#pragma once #pragma once
#include "RuntimeStore.h" #include "RenderSnapshotBuilder.h"
#include <cstdint>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <vector> #include <vector>
struct RuntimeSnapshotVersions
{
uint64_t renderStateVersion = 0;
uint64_t parameterStateVersion = 0;
};
struct RuntimeRenderStateSnapshot struct RuntimeRenderStateSnapshot
{ {
RuntimeSnapshotVersions versions; RuntimeSnapshotVersions versions;
@@ -24,7 +17,7 @@ struct RuntimeRenderStateSnapshot
class RuntimeSnapshotProvider class RuntimeSnapshotProvider
{ {
public: public:
explicit RuntimeSnapshotProvider(RuntimeStore& runtimeStore); explicit RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder);
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const; bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
unsigned GetMaxTemporalHistoryFrames() const; unsigned GetMaxTemporalHistoryFrames() const;
@@ -42,7 +35,7 @@ private:
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight, static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
const RuntimeSnapshotVersions& versions); const RuntimeSnapshotVersions& versions);
RuntimeStore& mRuntimeStore; RenderSnapshotBuilder& mRenderSnapshotBuilder;
mutable std::mutex mPublishedSnapshotMutex; mutable std::mutex mPublishedSnapshotMutex;
mutable bool mHasPublishedRenderStateSnapshot = false; mutable bool mHasPublishedRenderStateSnapshot = false;
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot; mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;

View File

@@ -0,0 +1,157 @@
#include "RuntimeStateJson.h"
#include "RuntimeParameterUtils.h"
namespace
{
std::string ShaderParameterTypeToString(ShaderParameterType type)
{
switch (type)
{
case ShaderParameterType::Float: return "float";
case ShaderParameterType::Vec2: return "vec2";
case ShaderParameterType::Color: return "color";
case ShaderParameterType::Boolean: return "bool";
case ShaderParameterType::Enum: return "enum";
case ShaderParameterType::Text: return "text";
case ShaderParameterType::Trigger: return "trigger";
}
return "unknown";
}
}
JsonValue RuntimeStateJson::SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog)
{
JsonValue layers = JsonValue::MakeArray();
for (const LayerStackStore::LayerPersistentState& layer : layerStack.Layers())
{
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
if (!shaderPackage)
continue;
JsonValue layerValue = JsonValue::MakeObject();
layerValue.set("id", JsonValue(layer.id));
layerValue.set("shaderId", JsonValue(layer.shaderId));
layerValue.set("shaderName", JsonValue(shaderPackage->displayName));
layerValue.set("bypass", JsonValue(layer.bypass));
if (shaderPackage->temporal.enabled)
{
JsonValue temporal = JsonValue::MakeObject();
temporal.set("enabled", JsonValue(true));
temporal.set("historySource", JsonValue(TemporalHistorySourceToString(shaderPackage->temporal.historySource)));
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage->temporal.requestedHistoryLength)));
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage->temporal.effectiveHistoryLength)));
layerValue.set("temporal", temporal);
}
if (shaderPackage->feedback.enabled)
{
JsonValue feedback = JsonValue::MakeObject();
feedback.set("enabled", JsonValue(true));
feedback.set("writePass", JsonValue(shaderPackage->feedback.writePassId));
layerValue.set("feedback", feedback);
}
JsonValue parameters = JsonValue::MakeArray();
for (const ShaderParameterDefinition& definition : shaderPackage->parameters)
{
JsonValue parameter = JsonValue::MakeObject();
parameter.set("id", JsonValue(definition.id));
parameter.set("label", JsonValue(definition.label));
if (!definition.description.empty())
parameter.set("description", JsonValue(definition.description));
parameter.set("type", JsonValue(ShaderParameterTypeToString(definition.type)));
parameter.set("defaultValue", SerializeParameterValue(definition, DefaultValueForDefinition(definition)));
if (!definition.minNumbers.empty())
{
JsonValue minValue = JsonValue::MakeArray();
for (double number : definition.minNumbers)
minValue.pushBack(JsonValue(number));
parameter.set("min", minValue);
}
if (!definition.maxNumbers.empty())
{
JsonValue maxValue = JsonValue::MakeArray();
for (double number : definition.maxNumbers)
maxValue.pushBack(JsonValue(number));
parameter.set("max", maxValue);
}
if (!definition.stepNumbers.empty())
{
JsonValue stepValue = JsonValue::MakeArray();
for (double number : definition.stepNumbers)
stepValue.pushBack(JsonValue(number));
parameter.set("step", stepValue);
}
if (definition.type == ShaderParameterType::Enum)
{
JsonValue options = JsonValue::MakeArray();
for (const ShaderParameterOption& option : definition.enumOptions)
{
JsonValue optionValue = JsonValue::MakeObject();
optionValue.set("value", JsonValue(option.value));
optionValue.set("label", JsonValue(option.label));
options.pushBack(optionValue);
}
parameter.set("options", options);
}
if (definition.type == ShaderParameterType::Text)
{
parameter.set("maxLength", JsonValue(static_cast<double>(definition.maxLength)));
if (!definition.fontId.empty())
parameter.set("font", JsonValue(definition.fontId));
}
ShaderParameterValue value = DefaultValueForDefinition(definition);
auto valueIt = layer.parameterValues.find(definition.id);
if (valueIt != layer.parameterValues.end())
value = valueIt->second;
parameter.set("value", SerializeParameterValue(definition, value));
parameters.pushBack(parameter);
}
layerValue.set("parameters", parameters);
layers.pushBack(layerValue);
}
return layers;
}
JsonValue RuntimeStateJson::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
switch (definition.type)
{
case ShaderParameterType::Boolean:
return JsonValue(value.booleanValue);
case ShaderParameterType::Enum:
return JsonValue(value.enumValue);
case ShaderParameterType::Text:
return JsonValue(value.textValue);
case ShaderParameterType::Trigger:
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
case ShaderParameterType::Float:
return JsonValue(value.numberValues.empty() ? 0.0 : value.numberValues.front());
case ShaderParameterType::Vec2:
case ShaderParameterType::Color:
{
JsonValue array = JsonValue::MakeArray();
for (double number : value.numberValues)
array.pushBack(JsonValue(number));
return array;
}
}
return JsonValue();
}
std::string RuntimeStateJson::TemporalHistorySourceToString(TemporalHistorySource source)
{
switch (source)
{
case TemporalHistorySource::Source:
return "source";
case TemporalHistorySource::PreLayerInput:
return "preLayerInput";
case TemporalHistorySource::None:
default:
return "none";
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "LayerStackStore.h"
#include "RuntimeJson.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h"
#include <string>
class RuntimeStateJson
{
public:
static JsonValue SerializeLayerStack(const LayerStackStore& layerStack, const ShaderPackageCatalog& shaderCatalog);
static JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value);
static std::string TemporalHistorySourceToString(TemporalHistorySource source);
};

View File

@@ -0,0 +1,125 @@
#include "RuntimeStatePresenter.h"
#include "RuntimeStateJson.h"
#include "RuntimeStore.h"
#include <mutex>
std::string RuntimeStatePresenter::BuildRuntimeStateJson(const RuntimeStore& runtimeStore)
{
return SerializeJson(BuildRuntimeStateValue(runtimeStore), true);
}
JsonValue RuntimeStatePresenter::BuildRuntimeStateValue(const RuntimeStore& runtimeStore)
{
const HealthTelemetry::Snapshot telemetrySnapshot = runtimeStore.mHealthTelemetry.GetSnapshot();
std::lock_guard<std::mutex> lock(runtimeStore.mMutex);
JsonValue root = JsonValue::MakeObject();
JsonValue app = JsonValue::MakeObject();
app.set("serverPort", JsonValue(static_cast<double>(runtimeStore.mServerPort)));
app.set("oscPort", JsonValue(static_cast<double>(runtimeStore.mConfigStore.GetConfig().oscPort)));
app.set("oscBindAddress", JsonValue(runtimeStore.mConfigStore.GetConfig().oscBindAddress));
app.set("oscSmoothing", JsonValue(runtimeStore.mConfigStore.GetConfig().oscSmoothing));
app.set("autoReload", JsonValue(runtimeStore.mAutoReloadEnabled));
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(runtimeStore.mConfigStore.GetConfig().maxTemporalHistoryFrames)));
app.set("previewFps", JsonValue(static_cast<double>(runtimeStore.mConfigStore.GetConfig().previewFps)));
app.set("enableExternalKeying", JsonValue(runtimeStore.mConfigStore.GetConfig().enableExternalKeying));
app.set("inputVideoFormat", JsonValue(runtimeStore.mConfigStore.GetConfig().inputVideoFormat));
app.set("inputFrameRate", JsonValue(runtimeStore.mConfigStore.GetConfig().inputFrameRate));
app.set("outputVideoFormat", JsonValue(runtimeStore.mConfigStore.GetConfig().outputVideoFormat));
app.set("outputFrameRate", JsonValue(runtimeStore.mConfigStore.GetConfig().outputFrameRate));
root.set("app", app);
JsonValue runtime = JsonValue::MakeObject();
runtime.set("layerCount", JsonValue(static_cast<double>(runtimeStore.mLayerStack.LayerCount())));
runtime.set("compileSucceeded", JsonValue(runtimeStore.mCompileSucceeded));
runtime.set("compileMessage", JsonValue(runtimeStore.mCompileMessage));
root.set("runtime", runtime);
JsonValue video = JsonValue::MakeObject();
video.set("hasSignal", JsonValue(telemetrySnapshot.signal.hasSignal));
video.set("width", JsonValue(static_cast<double>(telemetrySnapshot.signal.width)));
video.set("height", JsonValue(static_cast<double>(telemetrySnapshot.signal.height)));
video.set("modeName", JsonValue(telemetrySnapshot.signal.modeName));
root.set("video", video);
JsonValue deckLink = JsonValue::MakeObject();
deckLink.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
deckLink.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
deckLink.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
deckLink.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
deckLink.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
deckLink.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
deckLink.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
root.set("decklink", deckLink);
JsonValue videoIO = JsonValue::MakeObject();
videoIO.set("backend", JsonValue(telemetrySnapshot.videoIO.backendName));
videoIO.set("modelName", JsonValue(telemetrySnapshot.videoIO.modelName));
videoIO.set("supportsInternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsInternalKeying));
videoIO.set("supportsExternalKeying", JsonValue(telemetrySnapshot.videoIO.supportsExternalKeying));
videoIO.set("keyerInterfaceAvailable", JsonValue(telemetrySnapshot.videoIO.keyerInterfaceAvailable));
videoIO.set("externalKeyingRequested", JsonValue(telemetrySnapshot.videoIO.externalKeyingRequested));
videoIO.set("externalKeyingActive", JsonValue(telemetrySnapshot.videoIO.externalKeyingActive));
videoIO.set("statusMessage", JsonValue(telemetrySnapshot.videoIO.statusMessage));
root.set("videoIO", videoIO);
JsonValue performance = JsonValue::MakeObject();
performance.set("frameBudgetMs", JsonValue(telemetrySnapshot.performance.frameBudgetMilliseconds));
performance.set("renderMs", JsonValue(telemetrySnapshot.performance.renderMilliseconds));
performance.set("smoothedRenderMs", JsonValue(telemetrySnapshot.performance.smoothedRenderMilliseconds));
performance.set("budgetUsedPercent", JsonValue(
telemetrySnapshot.performance.frameBudgetMilliseconds > 0.0
? (telemetrySnapshot.performance.smoothedRenderMilliseconds / telemetrySnapshot.performance.frameBudgetMilliseconds) * 100.0
: 0.0));
performance.set("completionIntervalMs", JsonValue(telemetrySnapshot.performance.completionIntervalMilliseconds));
performance.set("smoothedCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.smoothedCompletionIntervalMilliseconds));
performance.set("maxCompletionIntervalMs", JsonValue(telemetrySnapshot.performance.maxCompletionIntervalMilliseconds));
performance.set("lateFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.lateFrameCount)));
performance.set("droppedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.droppedFrameCount)));
performance.set("flushedFrameCount", JsonValue(static_cast<double>(telemetrySnapshot.performance.flushedFrameCount)));
root.set("performance", performance);
JsonValue shaderLibrary = JsonValue::MakeArray();
for (const ShaderPackageStatus& status : runtimeStore.mShaderCatalog.PackageStatuses())
{
JsonValue shader = JsonValue::MakeObject();
shader.set("id", JsonValue(status.id));
shader.set("name", JsonValue(status.displayName));
shader.set("description", JsonValue(status.description));
shader.set("category", JsonValue(status.category));
shader.set("available", JsonValue(status.available));
if (!status.available)
shader.set("error", JsonValue(status.error));
const ShaderPackage* shaderPackage = runtimeStore.mShaderCatalog.FindPackage(status.id);
if (status.available && shaderPackage && shaderPackage->temporal.enabled)
{
JsonValue temporal = JsonValue::MakeObject();
temporal.set("enabled", JsonValue(true));
temporal.set("historySource", JsonValue(RuntimeStateJson::TemporalHistorySourceToString(shaderPackage->temporal.historySource)));
temporal.set("requestedHistoryLength", JsonValue(static_cast<double>(shaderPackage->temporal.requestedHistoryLength)));
temporal.set("effectiveHistoryLength", JsonValue(static_cast<double>(shaderPackage->temporal.effectiveHistoryLength)));
shader.set("temporal", temporal);
}
if (status.available && shaderPackage && shaderPackage->feedback.enabled)
{
JsonValue feedback = JsonValue::MakeObject();
feedback.set("enabled", JsonValue(true));
feedback.set("writePass", JsonValue(shaderPackage->feedback.writePassId));
shader.set("feedback", feedback);
}
shaderLibrary.pushBack(shader);
}
root.set("shaders", shaderLibrary);
JsonValue stackPresets = JsonValue::MakeArray();
for (const std::string& presetName : runtimeStore.GetStackPresetNamesLocked())
stackPresets.pushBack(JsonValue(presetName));
root.set("stackPresets", stackPresets);
root.set("layers", RuntimeStateJson::SerializeLayerStack(runtimeStore.mLayerStack, runtimeStore.mShaderCatalog));
return root;
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "RuntimeJson.h"
#include <string>
class RuntimeStore;
class RuntimeStatePresenter
{
public:
static std::string BuildRuntimeStateJson(const RuntimeStore& runtimeStore);
static JsonValue BuildRuntimeStateValue(const RuntimeStore& runtimeStore);
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +1,32 @@
#pragma once #pragma once
#include "HealthTelemetry.h" #include "HealthTelemetry.h"
#include "LayerStackStore.h"
#include "RenderSnapshotBuilder.h"
#include "RuntimeConfigStore.h"
#include "RuntimeJson.h" #include "RuntimeJson.h"
#include "ShaderPackageCatalog.h"
#include "ShaderTypes.h" #include "ShaderTypes.h"
#include <atomic>
#include <chrono> #include <chrono>
#include <cstdint>
#include <filesystem> #include <filesystem>
#include <map>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <vector> #include <vector>
class RuntimeSnapshotProvider; class RuntimeStatePresenter;
class RuntimeStore class RuntimeStore
{ {
public: public:
struct StoredParameterSnapshot using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
{ using LayerPersistentState = LayerStackStore::LayerPersistentState;
std::string layerId;
ShaderParameterDefinition definition;
ShaderParameterValue currentValue;
bool hasCurrentValue = false;
};
struct AppConfig
{
std::string shaderLibrary = "shaders";
unsigned short serverPort = 8080;
unsigned short oscPort = 9000;
std::string oscBindAddress = "127.0.0.1";
double oscSmoothing = 0.18;
bool autoReload = true;
unsigned maxTemporalHistoryFrames = 4;
unsigned previewFps = 30;
bool enableExternalKeying = false;
std::string inputVideoFormat = "1080p";
std::string inputFrameRate = "59.94";
std::string outputVideoFormat = "1080p";
std::string outputFrameRate = "59.94";
};
struct LayerPersistentState
{
std::string id;
std::string shaderId;
bool bypass = false;
std::map<std::string, ShaderParameterValue> parameterValues;
};
struct PersistentState
{
std::vector<LayerPersistentState> layers;
};
RuntimeStore(); RuntimeStore();
HealthTelemetry& GetHealthTelemetry(); HealthTelemetry& GetHealthTelemetry();
const HealthTelemetry& GetHealthTelemetry() const; const HealthTelemetry& GetHealthTelemetry() const;
RenderSnapshotBuilder& GetRenderSnapshotBuilder();
const RenderSnapshotBuilder& GetRenderSnapshotBuilder() const;
bool InitializeStore(std::string& error); bool InitializeStore(std::string& error);
std::string BuildPersistentStateJson() const; std::string BuildPersistentStateJson() const;
@@ -104,66 +72,26 @@ public:
void ClearReloadRequest(); void ClearReloadRequest();
private: private:
friend class RuntimeSnapshotProvider; friend class RenderSnapshotBuilder;
bool LoadConfig(std::string& error); friend class RuntimeStatePresenter;
bool LoadPersistentState(std::string& error); bool LoadPersistentState(std::string& error);
bool SavePersistentState(std::string& error) const; bool SavePersistentState(std::string& error) const;
bool ScanShaderPackages(std::string& error); bool ScanShaderPackages(std::string& error);
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) 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 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::vector<std::string> GetStackPresetNamesLocked() const;
std::string MakeSafePresetFileStem(const std::string& presetName) const;
bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey,
const LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage,
std::vector<ShaderParameterDefinition>::const_iterator& parameterIt, std::string& error) const;
bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const; bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const;
void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath, void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const; std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const;
uint64_t GetRenderStateVersion() const;
uint64_t GetParameterStateVersion() const;
void AdvanceFrameCounter();
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
void RefreshLayerParametersLocked(std::vector<RuntimeRenderState>& states) const;
void RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const;
JsonValue BuildRuntimeStateValue() const;
JsonValue SerializeLayerStack() const;
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;
JsonValue SerializeLayerStackLocked() const;
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, std::string& error);
void NormalizePersistentLayerIdsLocked();
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
std::string TemporalHistorySourceToString(TemporalHistorySource source) const;
LayerPersistentState* FindLayerById(const std::string& layerId);
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
std::string GenerateLayerId();
void MarkRenderStateDirtyLocked(); void MarkRenderStateDirtyLocked();
void MarkParameterStateDirtyLocked(); void MarkParameterStateDirtyLocked();
RenderSnapshotBuilder mRenderSnapshotBuilder;
RuntimeConfigStore mConfigStore;
ShaderPackageCatalog mShaderCatalog;
LayerStackStore mLayerStack;
HealthTelemetry mHealthTelemetry; HealthTelemetry mHealthTelemetry;
mutable std::mutex mMutex; mutable std::mutex mMutex;
AppConfig mConfig;
PersistentState mPersistentState;
std::filesystem::path mRepoRoot;
std::filesystem::path mUiRoot;
std::filesystem::path mDocsRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mPresetRoot;
std::filesystem::path mRuntimeStatePath;
std::filesystem::path mConfigPath;
std::filesystem::path mWrapperPath;
std::filesystem::path mGeneratedGlslPath;
std::filesystem::path mPatchedGlslPath;
std::map<std::string, ShaderPackage> mPackagesById;
std::vector<std::string> mPackageOrder;
std::vector<ShaderPackageStatus> mPackageStatuses;
bool mReloadRequested; bool mReloadRequested;
bool mCompileSucceeded; bool mCompileSucceeded;
std::string mCompileMessage; std::string mCompileMessage;
@@ -172,8 +100,4 @@ private:
bool mAutoReloadEnabled; bool mAutoReloadEnabled;
std::chrono::steady_clock::time_point mStartTime; std::chrono::steady_clock::time_point mStartTime;
std::chrono::steady_clock::time_point mLastScanTime; std::chrono::steady_clock::time_point mLastScanTime;
std::atomic<uint64_t> mFrameCounter{ 0 };
std::atomic<uint64_t> mRenderStateVersion{ 0 };
std::atomic<uint64_t> mParameterStateVersion{ 0 };
uint64_t mNextLayerId;
}; };

View File

@@ -0,0 +1,127 @@
#include "ShaderPackageCatalog.h"
#include "ShaderPackageRegistry.h"
bool ShaderPackageCatalog::Scan(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error)
{
std::map<std::string, ShaderPackage> packagesById;
std::vector<std::string> packageOrder;
std::vector<ShaderPackageStatus> packageStatuses;
ShaderPackageRegistry registry(maxTemporalHistoryFrames);
if (!registry.Scan(shaderRoot, packagesById, packageOrder, packageStatuses, error))
return false;
mPackagesById.swap(packagesById);
mPackageOrder.swap(packageOrder);
mPackageStatuses.swap(packageStatuses);
return true;
}
ShaderPackageCatalog::Snapshot ShaderPackageCatalog::CaptureSnapshot() const
{
Snapshot snapshot;
snapshot.packagesById = mPackagesById;
snapshot.packageOrder = mPackageOrder;
return snapshot;
}
bool ShaderPackageCatalog::HasCatalogChangedSince(const Snapshot& snapshot) const
{
if (snapshot.packageOrder != mPackageOrder || snapshot.packagesById.size() != mPackagesById.size())
return true;
for (const auto& item : mPackagesById)
{
auto previous = snapshot.packagesById.find(item.first);
if (previous == snapshot.packagesById.end() || !PackagesEquivalent(previous->second, item.second))
return true;
}
return false;
}
bool ShaderPackageCatalog::HasPackageChangedSince(const Snapshot& snapshot, const std::string& shaderId) const
{
auto previous = snapshot.packagesById.find(shaderId);
auto current = mPackagesById.find(shaderId);
if (previous == snapshot.packagesById.end() || current == mPackagesById.end())
return previous != snapshot.packagesById.end() || current != mPackagesById.end();
return !PackagesEquivalent(previous->second, current->second);
}
bool ShaderPackageCatalog::HasPackage(const std::string& shaderId) const
{
return mPackagesById.find(shaderId) != mPackagesById.end();
}
const ShaderPackage* ShaderPackageCatalog::FindPackage(const std::string& shaderId) const
{
auto it = mPackagesById.find(shaderId);
return it == mPackagesById.end() ? nullptr : &it->second;
}
bool ShaderPackageCatalog::CopyPackage(const std::string& shaderId, ShaderPackage& shaderPackage) const
{
const ShaderPackage* package = FindPackage(shaderId);
if (!package)
return false;
shaderPackage = *package;
return true;
}
const std::vector<std::string>& ShaderPackageCatalog::PackageOrder() const
{
return mPackageOrder;
}
const std::vector<ShaderPackageStatus>& ShaderPackageCatalog::PackageStatuses() const
{
return mPackageStatuses;
}
bool ShaderPackageCatalog::PackagesEquivalent(const ShaderPackage& left, const ShaderPackage& right)
{
return left.shaderWriteTime == right.shaderWriteTime &&
left.manifestWriteTime == right.manifestWriteTime &&
TextureAssetsEqual(left.textureAssets, right.textureAssets) &&
FontAssetsEqual(left.fontAssets, right.fontAssets);
}
bool ShaderPackageCatalog::TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right)
{
if (left.size() != right.size())
return false;
for (std::size_t index = 0; index < left.size(); ++index)
{
if (left[index].id != right[index].id ||
left[index].path != right[index].path ||
left[index].writeTime != right[index].writeTime)
{
return false;
}
}
return true;
}
bool ShaderPackageCatalog::FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& right)
{
if (left.size() != right.size())
return false;
for (std::size_t index = 0; index < left.size(); ++index)
{
if (left[index].id != right[index].id ||
left[index].path != right[index].path ||
left[index].writeTime != right[index].writeTime)
{
return false;
}
}
return true;
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "ShaderTypes.h"
#include <filesystem>
#include <map>
#include <string>
#include <vector>
class ShaderPackageCatalog
{
public:
struct Snapshot
{
std::map<std::string, ShaderPackage> packagesById;
std::vector<std::string> packageOrder;
};
bool Scan(const std::filesystem::path& shaderRoot, unsigned maxTemporalHistoryFrames, std::string& error);
Snapshot CaptureSnapshot() const;
bool HasCatalogChangedSince(const Snapshot& snapshot) const;
bool HasPackageChangedSince(const Snapshot& snapshot, const std::string& shaderId) const;
bool HasPackage(const std::string& shaderId) const;
const ShaderPackage* FindPackage(const std::string& shaderId) const;
bool CopyPackage(const std::string& shaderId, ShaderPackage& shaderPackage) const;
const std::vector<std::string>& PackageOrder() const;
const std::vector<ShaderPackageStatus>& PackageStatuses() const;
private:
static bool PackagesEquivalent(const ShaderPackage& left, const ShaderPackage& right);
static bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right);
static bool FontAssetsEqual(const std::vector<ShaderFontAsset>& left, const std::vector<ShaderFontAsset>& right);
std::map<std::string, ShaderPackage> mPackagesById;
std::vector<std::string> mPackageOrder;
std::vector<ShaderPackageStatus> mPackageStatuses;
};

View File

@@ -102,12 +102,21 @@ The codebase now has an initial Phase 1 compatibility split in place:
- `RuntimeStore` - `RuntimeStore`
- [RuntimeStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h) - [RuntimeStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h)
- [RuntimeStore.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp) - [RuntimeStore.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp)
- `RuntimeConfigStore`
- [RuntimeConfigStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeConfigStore.h)
- [RuntimeConfigStore.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeConfigStore.cpp)
- `ShaderPackageCatalog`
- [ShaderPackageCatalog.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/ShaderPackageCatalog.h)
- [ShaderPackageCatalog.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/ShaderPackageCatalog.cpp)
- `RuntimeCoordinator` - `RuntimeCoordinator`
- [RuntimeCoordinator.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h) - [RuntimeCoordinator.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h)
- [RuntimeCoordinator.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp) - [RuntimeCoordinator.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp)
- `RuntimeSnapshotProvider` - `RuntimeSnapshotProvider`
- [RuntimeSnapshotProvider.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h) - [RuntimeSnapshotProvider.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h)
- [RuntimeSnapshotProvider.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp) - [RuntimeSnapshotProvider.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp)
- `RenderSnapshotBuilder`
- [RenderSnapshotBuilder.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RenderSnapshotBuilder.h)
- [RenderSnapshotBuilder.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RenderSnapshotBuilder.cpp)
- `ControlServices` - `ControlServices`
- [ControlServices.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h) - [ControlServices.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h)
- [ControlServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp) - [ControlServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp)
@@ -124,15 +133,15 @@ The codebase now has an initial Phase 1 compatibility split in place:
These are still compatibility seams, not a completed subsystem extraction. Some responsibilities have moved behind the new boundaries, while other paths still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later work real code boundaries that can be expanded without first inventing the names: These are still compatibility seams, not a completed subsystem extraction. Some responsibilities have moved behind the new boundaries, while other paths still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later work real code boundaries that can be expanded without first inventing the names:
- UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator` - UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator`
- runtime startup for path resolution, config load, persistent state load, and shader package scan now initializes through `RuntimeStore` - runtime startup now initializes path resolution and config loading through `RuntimeConfigStore`, with shader package scan and lookup delegated to `ShaderPackageCatalog`
- runtime/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost` - runtime/UI state JSON composition now routes through `RuntimeStatePresenter` and `RuntimeStateJson` instead of living in `RuntimeHost` or `RuntimeStore`
- regular stored layer mutations and stack preset save/load now live in `RuntimeStore` instead of `RuntimeHost` public APIs - regular stored layer mutations and stack preset save/load now route through `RuntimeStore` into `LayerStackStore` instead of `RuntimeHost` public APIs
- persisted OSC-by-control-key commits now route through `RuntimeCoordinator` before applying store changes - persisted OSC-by-control-key commits now route through `RuntimeCoordinator` before applying store changes
- mutation and reload policy now routes through `RuntimeCoordinator` - mutation and reload policy now routes through `RuntimeCoordinator`
- parameter target resolution, value normalization, trigger classification, and move no-op classification now live under `RuntimeCoordinator` - parameter target resolution, value normalization, trigger classification, and move no-op classification now live under `RuntimeCoordinator`
- render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider` - render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider`
- `RuntimeSnapshotProvider` now depends on `RuntimeStore` rather than sharing `RuntimeHost` directly - `RuntimeSnapshotProvider` now depends on `RenderSnapshotBuilder` rather than on `RuntimeStore` friendship or shared `RuntimeHost` access
- render-state assembly, cached parameter refresh, and frame-context application now flow through `RuntimeSnapshotProvider` and store-owned snapshot helpers instead of `RuntimeHost` public APIs - render-state assembly, cached parameter refresh, dynamic frame-field application, and render snapshot versions now live in `RenderSnapshotBuilder` instead of `RuntimeStore`
- `RuntimeSnapshotProvider` now publishes versioned render snapshot objects and serves matching consumers from the last published snapshot - `RuntimeSnapshotProvider` now publishes versioned render snapshot objects and serves matching consumers from the last published snapshot
- service ingress and polling coordination now route through `ControlServices` - service ingress and polling coordination now route through `ControlServices`
- `ControlServices` now queues coordinator results for OSC commit and file-poll outcomes instead of directly deciding runtime/store policy - `ControlServices` now queues coordinator results for OSC commit and file-poll outcomes instead of directly deciding runtime/store policy
@@ -140,6 +149,10 @@ These are still compatibility seams, not a completed subsystem extraction. Some
- `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields - `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields
- render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost` - render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost`
- `RuntimeStore` now owns its durable/session backing fields directly instead of wrapping a compatibility `RuntimeHost` object - `RuntimeStore` now owns its durable/session backing fields directly instead of wrapping a compatibility `RuntimeHost` object
- `RuntimeConfigStore` now owns runtime config parsing, path resolution, configured ports/formats, runtime roots, and shader compiler paths instead of leaving those responsibilities inside `RuntimeStore`
- `ShaderPackageCatalog` now owns shader package scanning, package status/order/lookup, and package asset/source change comparison instead of leaving those responsibilities inside `RuntimeStore`
- `LayerStackStore` now owns durable layer state, layer CRUD/reorder, parameter persistence, and stack preset value serialization/load instead of leaving those responsibilities inside `RuntimeStore`
- `RuntimeStatePresenter` and `RuntimeStateJson` now own runtime-state JSON assembly and layer-stack presentation serialization instead of leaving those responsibilities inside storage classes
- `RuntimeCoordinator` now uses explicit `RuntimeStore` query APIs/read models instead of friendship or direct store-internal access - `RuntimeCoordinator` now uses explicit `RuntimeStore` query APIs/read models instead of friendship or direct store-internal access
- live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite` - live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite`
- coordinator result application, shader-build requests, ready-build application, and runtime-state broadcasts now route through `RuntimeUpdateController` instead of being interpreted directly by `OpenGLComposite` - coordinator result application, shader-build requests, ready-build application, and runtime-state broadcasts now route through `RuntimeUpdateController` instead of being interpreted directly by `OpenGLComposite`
@@ -346,7 +359,8 @@ Allowed dependency directions:
- `RuntimeCoordinator -> RuntimeStore` - `RuntimeCoordinator -> RuntimeStore`
- `RuntimeCoordinator -> RuntimeSnapshotProvider` - `RuntimeCoordinator -> RuntimeSnapshotProvider`
- `RuntimeCoordinator -> HealthTelemetry` - `RuntimeCoordinator -> HealthTelemetry`
- `RuntimeSnapshotProvider -> RuntimeStore` - `RuntimeSnapshotProvider -> RenderSnapshotBuilder`
- `RenderSnapshotBuilder -> RuntimeStore`
- `RenderEngine -> RuntimeSnapshotProvider` - `RenderEngine -> RuntimeSnapshotProvider`
- `RenderEngine -> HealthTelemetry` - `RenderEngine -> HealthTelemetry`
- `VideoBackend -> RenderEngine` - `VideoBackend -> RenderEngine`
@@ -574,7 +588,8 @@ Likely examples:
- config loading/saving -> `RuntimeStore` - config loading/saving -> `RuntimeStore`
- layer stack mutation validation -> `RuntimeCoordinator` - layer stack mutation validation -> `RuntimeCoordinator`
- render state building/versioning -> `RuntimeSnapshotProvider` - render state building/versioning -> `RenderSnapshotBuilder`
- render snapshot publication/cache -> `RuntimeSnapshotProvider`
- timing/status setters -> `HealthTelemetry` - timing/status setters -> `HealthTelemetry`
### Current `RuntimeServices` ### Current `RuntimeServices`

View File

@@ -42,11 +42,11 @@ That order mirrors the intended dependency story:
## Subsystem Notes ## Subsystem Notes
- [RuntimeStore.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeStore.md) - [RuntimeStore.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeStore.md)
Durable runtime config, persisted layer state, presets, and package metadata ownership. Durable runtime-state facade over layer-stack, config, package-catalog, presentation, and persistence boundaries.
- [RuntimeCoordinator.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeCoordinator.md) - [RuntimeCoordinator.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeCoordinator.md)
Mutation validation, state classification, reset/reload policy, and publication/persistence requests. Mutation validation, state classification, reset/reload policy, and publication/persistence requests.
- [RuntimeSnapshotProvider.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeSnapshotProvider.md) - [RuntimeSnapshotProvider.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeSnapshotProvider.md)
Render-facing snapshot build, publication, and versioning boundaries. Render-facing snapshot publication boundary backed by explicit render snapshot building/versioning.
- [ControlServices.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/ControlServices.md) - [ControlServices.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/ControlServices.md)
OSC, HTTP/WebSocket, and file-watch ingress plus normalization and service-local buffering. OSC, HTTP/WebSocket, and file-watch ingress plus normalization and service-local buffering.
- [RenderEngine.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RenderEngine.md) - [RenderEngine.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RenderEngine.md)

View File

@@ -12,7 +12,7 @@ It exists to solve three current problems:
- render state is still built directly out of `RuntimeHost` under a shared mutex - render state is still built directly out of `RuntimeHost` under a shared mutex
- render reads and refreshes partially mutable cached layer state in more than one place - render reads and refreshes partially mutable cached layer state in more than one place
- state publication, state versioning, and dynamic frame-field refresh are not yet explicit subsystems - state publication, state versioning, and dynamic frame-field refresh need explicit ownership
Today the closest current behavior lives in: Today the closest current behavior lives in:
@@ -29,13 +29,17 @@ Today the closest current behavior lives in:
`RuntimeSnapshotProvider` is responsible for: `RuntimeSnapshotProvider` is responsible for:
- building render-facing snapshots from durable store state plus whatever committed-live state view the Phase 3 split ultimately exposes
- publishing stable, versioned snapshots that can be consumed without large shared mutable locks - publishing stable, versioned snapshots that can be consumed without large shared mutable locks
- giving `RenderEngine` a cheap read path for the latest committed snapshot
- making snapshot invalidation and publication rules explicit
`RenderSnapshotBuilder` is responsible for:
- building render-facing snapshots from durable store state plus whatever committed-live state view the Phase 3 split ultimately exposes
- separating structural snapshot changes from dynamic frame fields - separating structural snapshot changes from dynamic frame fields
- translating runtime layer state into render-ready layer descriptors - translating runtime layer state into render-ready layer descriptors
- attaching immutable or near-immutable shader/package-derived data needed by render - attaching immutable or near-immutable shader/package-derived data needed by render
- giving `RenderEngine` a cheap read path for the latest committed snapshot - maintaining render snapshot version counters and frame advancement
- making snapshot invalidation and publication rules explicit
It is not responsible for: It is not responsible for:
@@ -75,7 +79,7 @@ The shape of render-facing layer state should remain consistent across phases ev
## Snapshot Inputs ## Snapshot Inputs
`RuntimeSnapshotProvider` should build from a read-oriented runtime view, not from direct mutation calls. `RenderSnapshotBuilder` should build from a read-oriented runtime view, not from direct mutation calls. `RuntimeSnapshotProvider` should consume the builder's output and own publication/cache behavior.
That view will likely include: That view will likely include:
@@ -87,7 +91,8 @@ That view will likely include:
The important Phase 1 rule is not "the provider always reads one specific object." It is: The important Phase 1 rule is not "the provider always reads one specific object." It is:
- the provider consumes read-oriented committed runtime state - the builder consumes read-oriented committed runtime state
- the provider consumes builder-published render snapshot data
- the provider does not own mutation policy - the provider does not own mutation policy
- render consumes the provider's published output instead of reaching back into whichever runtime object currently stores the truth - render consumes the provider's published output instead of reaching back into whichever runtime object currently stores the truth
@@ -292,11 +297,11 @@ Notes:
### `RuntimeStore` ### `RuntimeStore`
`RuntimeSnapshotProvider` depends on store-owned durable data and package metadata through a read-oriented interface or view. `RenderSnapshotBuilder` depends on store-owned durable data and package metadata through a read-oriented interface or view. `RuntimeSnapshotProvider` depends on the builder rather than reaching into store internals directly.
If committed live state remains physically co-located with the store during early migration, the provider may read it through the same view. If committed live state moves behind a coordinator-owned live-session model later, the provider should consume that through a similarly read-oriented view. If committed live state remains physically co-located with the store during early migration, the builder may read it through the same view. If committed live state moves behind a coordinator-owned live-session model later, the builder should consume that through a similarly read-oriented view.
It should not mutate the store directly. Neither the builder nor provider should mutate the store directly.
### `RuntimeCoordinator` ### `RuntimeCoordinator`
@@ -337,7 +342,7 @@ This is especially important while migrating away from the current lock/fallback
The current code suggests the following migration map. The current code suggests the following migration map.
### Move into `RuntimeSnapshotProvider` ### Move into `RenderSnapshotBuilder`
From `RuntimeHost`: From `RuntimeHost`:
@@ -346,6 +351,12 @@ From `RuntimeHost`:
- explicit version composition for render-visible state - explicit version composition for render-visible state
- dynamic frame-context construction currently done in `RefreshDynamicRenderStateFields(...)` - dynamic frame-context construction currently done in `RefreshDynamicRenderStateFields(...)`
### Move into `RuntimeSnapshotProvider`
- published snapshot cache ownership
- version matching for already-published snapshots
- render-facing compatibility API while render callers migrate
### Stop exposing directly from the host/store boundary ### Stop exposing directly from the host/store boundary
Current methods that should become compatibility shims and later disappear: Current methods that should become compatibility shims and later disappear:
@@ -357,9 +368,9 @@ Current methods that should become compatibility shims and later disappear:
### Render-side compatibility during migration ### Render-side compatibility during migration
The current `OpenGLComposite` cache path: The previous `OpenGLComposite` cache path:
- reads versions from `RuntimeHost` - reads versions from `RuntimeHost`/store-owned counters
- conditionally calls `TryRefreshCachedLayerStates(...)` - conditionally calls `TryRefreshCachedLayerStates(...)`
- conditionally rebuilds full layer state - conditionally rebuilds full layer state
- then reapplies render-local OSC overlay state - then reapplies render-local OSC overlay state
@@ -367,8 +378,8 @@ The current `OpenGLComposite` cache path:
During migration, that should become: During migration, that should become:
1. get latest published snapshot from provider 1. get latest published snapshot from provider
2. compare snapshot versions against render-local cache 2. compare snapshot versions produced by `RenderSnapshotBuilder`
3. rebuild only if needed 3. rebuild through `RenderSnapshotBuilder` only if needed
4. apply render-local overlay state 4. apply render-local overlay state
5. attach frame context 5. attach frame context

View File

@@ -276,7 +276,7 @@ Per the Phase 1 subsystem design, `RuntimeStore` should sit low in the dependenc
Allowed inbound dependencies: Allowed inbound dependencies:
- `RuntimeCoordinator -> RuntimeStore` - `RuntimeCoordinator -> RuntimeStore`
- `RuntimeSnapshotProvider -> RuntimeStore` - `RenderSnapshotBuilder -> RuntimeStore`
- temporary migration shims from `ControlServices` only where explicitly tolerated - temporary migration shims from `ControlServices` only where explicitly tolerated
Allowed outbound dependencies: Allowed outbound dependencies:
@@ -359,16 +359,33 @@ Those belong under other target subsystems.
`RuntimeStore` does not need to be one monolithic class forever. A practical internal shape would be: `RuntimeStore` does not need to be one monolithic class forever. A practical internal shape would be:
- `RuntimeConfigStore` - `RuntimeConfigStore`
- runtime host config load/save and resolved paths - runtime host config load and resolved paths
- `PersistentLayerStore`
The current codebase has begun this split: `RuntimeConfigStore` owns config parsing, path resolution, configured ports/formats, runtime roots, and shader compiler paths, while `RuntimeStore` keeps compatibility delegates for existing callers.
- `LayerStackStore`
- durable layer stack and parameter values - durable layer stack and parameter values
- `StackPresetStore` - layer CRUD/reorder and shader selection
- preset enumeration/load/save - stack preset value serialization/load
- `ShaderPackageCatalogStore` - `RuntimeStatePresenter` / `RuntimeStateJson`
- runtime-state JSON assembly
- layer-stack presentation serialization
- `RenderSnapshotBuilder`
- render-state assembly and parameter refresh
- dynamic frame-field refresh and render snapshot version counters
- `ShaderPackageCatalog`
- durable manifest/package metadata - durable manifest/package metadata
- shader package scanning, status/order/lookup, and asset/source change comparison
- `PersistenceWriter` helper - `PersistenceWriter` helper
- synchronous at first, async/debounced later - synchronous at first, async/debounced later
The current codebase has begun the layer split: `LayerStackStore` owns durable layer state, layer CRUD/reorder, parameter persistence, and stack preset value serialization/load. `RuntimeStore` still owns locking, file IO, and compatibility delegates for existing callers.
The current codebase has begun the render snapshot split: `RenderSnapshotBuilder` owns render-state assembly, cached parameter refresh, dynamic frame-field refresh, and render snapshot versions. `RuntimeSnapshotProvider` depends on this builder rather than on `RuntimeStore` friendship.
The current codebase has also begun the presentation split: `RuntimeStatePresenter` owns top-level runtime-state JSON assembly, while `RuntimeStateJson` owns the layer-stack and parameter presentation shape used by runtime state clients.
The current codebase has also begun the package split: `ShaderPackageCatalog` owns package scanning and registry comparison, while `RuntimeStore` uses it to keep layer state valid and to build compatibility read models.
These can still be presented through one subsystem façade during migration. These can still be presented through one subsystem façade during migration.
## Persistence Model ## Persistence Model