Pass 3
This commit is contained in:
@@ -104,10 +104,20 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/resource.h"
|
||||
"${APP_DIR}/runtime/HealthTelemetry.cpp"
|
||||
"${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.h"
|
||||
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
|
||||
"${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.h"
|
||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||
@@ -116,6 +126,8 @@ set(APP_SOURCES
|
||||
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
|
||||
"${APP_DIR}/runtime/RuntimeStore.cpp"
|
||||
"${APP_DIR}/runtime/RuntimeStore.h"
|
||||
"${APP_DIR}/runtime/ShaderPackageCatalog.cpp"
|
||||
"${APP_DIR}/runtime/ShaderPackageCatalog.h"
|
||||
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||
"${APP_DIR}/shader/ShaderCompiler.h"
|
||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
|
||||
@@ -36,7 +36,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
||||
{
|
||||
InitializeCriticalSection(&pMutex);
|
||||
mRuntimeStore = std::make_unique<RuntimeStore>();
|
||||
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeStore);
|
||||
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder());
|
||||
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
|
||||
mRenderEngine = std::make_unique<RenderEngine>(
|
||||
*mRuntimeSnapshotProvider,
|
||||
|
||||
@@ -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(),
|
||||
[¶meterId](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(),
|
||||
[¶meterKey](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(),
|
||||
[¶meterId](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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -1,12 +1,9 @@
|
||||
#include "RuntimeSnapshotProvider.h"
|
||||
|
||||
#include "ShaderCompiler.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
|
||||
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeStore& runtimeStore) :
|
||||
mRuntimeStore(runtimeStore)
|
||||
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder) :
|
||||
mRenderSnapshotBuilder(renderSnapshotBuilder)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,36 +11,7 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
|
||||
{
|
||||
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;
|
||||
return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
@@ -59,20 +27,17 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
|
||||
|
||||
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
||||
{
|
||||
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
|
||||
return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
|
||||
}
|
||||
|
||||
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
||||
{
|
||||
RuntimeSnapshotVersions versions;
|
||||
versions.renderStateVersion = mRuntimeStore.GetRenderStateVersion();
|
||||
versions.parameterStateVersion = mRuntimeStore.GetParameterStateVersion();
|
||||
return versions;
|
||||
return mRenderSnapshotBuilder.GetVersions();
|
||||
}
|
||||
|
||||
void RuntimeSnapshotProvider::AdvanceFrame()
|
||||
{
|
||||
mRuntimeStore.AdvanceFrameCounter();
|
||||
mRenderSnapshotBuilder.AdvanceFrame();
|
||||
}
|
||||
|
||||
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
|
||||
@@ -87,7 +52,7 @@ RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(u
|
||||
RuntimeRenderStateSnapshot snapshot;
|
||||
snapshot.outputWidth = outputWidth;
|
||||
snapshot.outputHeight = outputHeight;
|
||||
mRuntimeStore.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
|
||||
mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
|
||||
|
||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
|
||||
@@ -107,7 +72,7 @@ bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth
|
||||
return true;
|
||||
|
||||
std::vector<RuntimeRenderState> states;
|
||||
if (!mRuntimeStore.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
|
||||
if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
|
||||
return false;
|
||||
|
||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||
@@ -128,7 +93,7 @@ bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth
|
||||
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
|
||||
{
|
||||
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
|
||||
if (!mRuntimeStore.TryRefreshLayerParameters(snapshot.states))
|
||||
if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
|
||||
return false;
|
||||
|
||||
const RuntimeSnapshotVersions versions = GetVersions();
|
||||
@@ -142,7 +107,7 @@ bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRende
|
||||
|
||||
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
mRuntimeStore.RefreshDynamicRenderStateFields(states);
|
||||
mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
|
||||
}
|
||||
|
||||
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeStore.h"
|
||||
#include "RenderSnapshotBuilder.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimeSnapshotVersions
|
||||
{
|
||||
uint64_t renderStateVersion = 0;
|
||||
uint64_t parameterStateVersion = 0;
|
||||
};
|
||||
|
||||
struct RuntimeRenderStateSnapshot
|
||||
{
|
||||
RuntimeSnapshotVersions versions;
|
||||
@@ -24,7 +17,7 @@ struct RuntimeRenderStateSnapshot
|
||||
class RuntimeSnapshotProvider
|
||||
{
|
||||
public:
|
||||
explicit RuntimeSnapshotProvider(RuntimeStore& runtimeStore);
|
||||
explicit RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder);
|
||||
|
||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
||||
unsigned GetMaxTemporalHistoryFrames() const;
|
||||
@@ -42,7 +35,7 @@ private:
|
||||
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
||||
const RuntimeSnapshotVersions& versions);
|
||||
|
||||
RuntimeStore& mRuntimeStore;
|
||||
RenderSnapshotBuilder& mRenderSnapshotBuilder;
|
||||
mutable std::mutex mPublishedSnapshotMutex;
|
||||
mutable bool mHasPublishedRenderStateSnapshot = false;
|
||||
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -1,64 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "HealthTelemetry.h"
|
||||
#include "LayerStackStore.h"
|
||||
#include "RenderSnapshotBuilder.h"
|
||||
#include "RuntimeConfigStore.h"
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderPackageCatalog.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeSnapshotProvider;
|
||||
class RuntimeStatePresenter;
|
||||
|
||||
class RuntimeStore
|
||||
{
|
||||
public:
|
||||
struct StoredParameterSnapshot
|
||||
{
|
||||
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;
|
||||
};
|
||||
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
|
||||
using LayerPersistentState = LayerStackStore::LayerPersistentState;
|
||||
|
||||
RuntimeStore();
|
||||
HealthTelemetry& GetHealthTelemetry();
|
||||
const HealthTelemetry& GetHealthTelemetry() const;
|
||||
RenderSnapshotBuilder& GetRenderSnapshotBuilder();
|
||||
const RenderSnapshotBuilder& GetRenderSnapshotBuilder() const;
|
||||
|
||||
bool InitializeStore(std::string& error);
|
||||
std::string BuildPersistentStateJson() const;
|
||||
@@ -104,66 +72,26 @@ public:
|
||||
void ClearReloadRequest();
|
||||
|
||||
private:
|
||||
friend class RuntimeSnapshotProvider;
|
||||
bool LoadConfig(std::string& error);
|
||||
friend class RenderSnapshotBuilder;
|
||||
friend class RuntimeStatePresenter;
|
||||
bool LoadPersistentState(std::string& error);
|
||||
bool SavePersistentState(std::string& error) const;
|
||||
bool ScanShaderPackages(std::string& error);
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||
bool ResolvePaths(std::string& error);
|
||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName) const;
|
||||
bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey,
|
||||
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;
|
||||
void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
|
||||
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 MarkParameterStateDirtyLocked();
|
||||
|
||||
RenderSnapshotBuilder mRenderSnapshotBuilder;
|
||||
RuntimeConfigStore mConfigStore;
|
||||
ShaderPackageCatalog mShaderCatalog;
|
||||
LayerStackStore mLayerStack;
|
||||
HealthTelemetry mHealthTelemetry;
|
||||
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 mCompileSucceeded;
|
||||
std::string mCompileMessage;
|
||||
@@ -172,8 +100,4 @@ private:
|
||||
bool mAutoReloadEnabled;
|
||||
std::chrono::steady_clock::time_point mStartTime;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -102,12 +102,21 @@ The codebase now has an initial Phase 1 compatibility split in place:
|
||||
- `RuntimeStore`
|
||||
- [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)
|
||||
- `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.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)
|
||||
- `RuntimeSnapshotProvider`
|
||||
- [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)
|
||||
- `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.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)
|
||||
@@ -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:
|
||||
|
||||
- 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/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost`
|
||||
- regular stored layer mutations and stack preset save/load now live in `RuntimeStore` instead of `RuntimeHost` public APIs
|
||||
- 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 routes through `RuntimeStatePresenter` and `RuntimeStateJson` instead of living in `RuntimeHost` or `RuntimeStore`
|
||||
- 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
|
||||
- mutation and reload policy now routes through `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`
|
||||
- `RuntimeSnapshotProvider` now depends on `RuntimeStore` rather than sharing `RuntimeHost` directly
|
||||
- render-state assembly, cached parameter refresh, and frame-context application now flow through `RuntimeSnapshotProvider` and store-owned snapshot helpers instead of `RuntimeHost` public APIs
|
||||
- `RuntimeSnapshotProvider` now depends on `RenderSnapshotBuilder` rather than on `RuntimeStore` friendship or shared `RuntimeHost` access
|
||||
- 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
|
||||
- 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
|
||||
@@ -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
|
||||
- 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
|
||||
- `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
|
||||
- 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`
|
||||
@@ -346,7 +359,8 @@ Allowed dependency directions:
|
||||
- `RuntimeCoordinator -> RuntimeStore`
|
||||
- `RuntimeCoordinator -> RuntimeSnapshotProvider`
|
||||
- `RuntimeCoordinator -> HealthTelemetry`
|
||||
- `RuntimeSnapshotProvider -> RuntimeStore`
|
||||
- `RuntimeSnapshotProvider -> RenderSnapshotBuilder`
|
||||
- `RenderSnapshotBuilder -> RuntimeStore`
|
||||
- `RenderEngine -> RuntimeSnapshotProvider`
|
||||
- `RenderEngine -> HealthTelemetry`
|
||||
- `VideoBackend -> RenderEngine`
|
||||
@@ -574,7 +588,8 @@ Likely examples:
|
||||
|
||||
- config loading/saving -> `RuntimeStore`
|
||||
- layer stack mutation validation -> `RuntimeCoordinator`
|
||||
- render state building/versioning -> `RuntimeSnapshotProvider`
|
||||
- render state building/versioning -> `RenderSnapshotBuilder`
|
||||
- render snapshot publication/cache -> `RuntimeSnapshotProvider`
|
||||
- timing/status setters -> `HealthTelemetry`
|
||||
|
||||
### Current `RuntimeServices`
|
||||
|
||||
@@ -42,11 +42,11 @@ That order mirrors the intended dependency story:
|
||||
## Subsystem Notes
|
||||
|
||||
- [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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -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 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:
|
||||
|
||||
@@ -29,13 +29,17 @@ Today the closest current behavior lives in:
|
||||
|
||||
`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
|
||||
- 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
|
||||
- translating runtime layer state into render-ready layer descriptors
|
||||
- attaching immutable or near-immutable shader/package-derived data needed by render
|
||||
- giving `RenderEngine` a cheap read path for the latest committed snapshot
|
||||
- making snapshot invalidation and publication rules explicit
|
||||
- maintaining render snapshot version counters and frame advancement
|
||||
|
||||
It is not responsible for:
|
||||
|
||||
@@ -75,7 +79,7 @@ The shape of render-facing layer state should remain consistent across phases ev
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -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 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
|
||||
- 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`
|
||||
|
||||
`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`
|
||||
|
||||
@@ -337,7 +342,7 @@ This is especially important while migrating away from the current lock/fallback
|
||||
|
||||
The current code suggests the following migration map.
|
||||
|
||||
### Move into `RuntimeSnapshotProvider`
|
||||
### Move into `RenderSnapshotBuilder`
|
||||
|
||||
From `RuntimeHost`:
|
||||
|
||||
@@ -346,6 +351,12 @@ From `RuntimeHost`:
|
||||
- explicit version composition for render-visible state
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
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 rebuilds full layer state
|
||||
- then reapplies render-local OSC overlay state
|
||||
@@ -367,8 +378,8 @@ The current `OpenGLComposite` cache path:
|
||||
During migration, that should become:
|
||||
|
||||
1. get latest published snapshot from provider
|
||||
2. compare snapshot versions against render-local cache
|
||||
3. rebuild only if needed
|
||||
2. compare snapshot versions produced by `RenderSnapshotBuilder`
|
||||
3. rebuild through `RenderSnapshotBuilder` only if needed
|
||||
4. apply render-local overlay state
|
||||
5. attach frame context
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ Per the Phase 1 subsystem design, `RuntimeStore` should sit low in the dependenc
|
||||
Allowed inbound dependencies:
|
||||
|
||||
- `RuntimeCoordinator -> RuntimeStore`
|
||||
- `RuntimeSnapshotProvider -> RuntimeStore`
|
||||
- `RenderSnapshotBuilder -> RuntimeStore`
|
||||
- temporary migration shims from `ControlServices` only where explicitly tolerated
|
||||
|
||||
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:
|
||||
|
||||
- `RuntimeConfigStore`
|
||||
- runtime host config load/save and resolved paths
|
||||
- `PersistentLayerStore`
|
||||
- runtime host config load and resolved paths
|
||||
|
||||
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
|
||||
- `StackPresetStore`
|
||||
- preset enumeration/load/save
|
||||
- `ShaderPackageCatalogStore`
|
||||
- layer CRUD/reorder and shader selection
|
||||
- stack preset value serialization/load
|
||||
- `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
|
||||
- shader package scanning, status/order/lookup, and asset/source change comparison
|
||||
- `PersistenceWriter` helper
|
||||
- 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.
|
||||
|
||||
## Persistence Model
|
||||
|
||||
Reference in New Issue
Block a user