Pass 3
This commit is contained in:
@@ -104,10 +104,20 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/resource.h"
|
"${APP_DIR}/resource.h"
|
||||||
"${APP_DIR}/runtime/HealthTelemetry.cpp"
|
"${APP_DIR}/runtime/HealthTelemetry.cpp"
|
||||||
"${APP_DIR}/runtime/HealthTelemetry.h"
|
"${APP_DIR}/runtime/HealthTelemetry.h"
|
||||||
|
"${APP_DIR}/runtime/LayerStackStore.cpp"
|
||||||
|
"${APP_DIR}/runtime/LayerStackStore.h"
|
||||||
|
"${APP_DIR}/runtime/RenderSnapshotBuilder.cpp"
|
||||||
|
"${APP_DIR}/runtime/RenderSnapshotBuilder.h"
|
||||||
|
"${APP_DIR}/runtime/RuntimeConfigStore.cpp"
|
||||||
|
"${APP_DIR}/runtime/RuntimeConfigStore.h"
|
||||||
"${APP_DIR}/runtime/RuntimeCoordinator.cpp"
|
"${APP_DIR}/runtime/RuntimeCoordinator.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeCoordinator.h"
|
"${APP_DIR}/runtime/RuntimeCoordinator.h"
|
||||||
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
|
"${APP_DIR}/runtime/RuntimeSnapshotProvider.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeSnapshotProvider.h"
|
"${APP_DIR}/runtime/RuntimeSnapshotProvider.h"
|
||||||
|
"${APP_DIR}/runtime/RuntimeStateJson.cpp"
|
||||||
|
"${APP_DIR}/runtime/RuntimeStateJson.h"
|
||||||
|
"${APP_DIR}/runtime/RuntimeStatePresenter.cpp"
|
||||||
|
"${APP_DIR}/runtime/RuntimeStatePresenter.h"
|
||||||
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
"${APP_DIR}/runtime/RuntimeClock.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeClock.h"
|
"${APP_DIR}/runtime/RuntimeClock.h"
|
||||||
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
"${APP_DIR}/runtime/RuntimeJson.cpp"
|
||||||
@@ -116,6 +126,8 @@ set(APP_SOURCES
|
|||||||
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
|
"${APP_DIR}/runtime/RuntimeParameterUtils.h"
|
||||||
"${APP_DIR}/runtime/RuntimeStore.cpp"
|
"${APP_DIR}/runtime/RuntimeStore.cpp"
|
||||||
"${APP_DIR}/runtime/RuntimeStore.h"
|
"${APP_DIR}/runtime/RuntimeStore.h"
|
||||||
|
"${APP_DIR}/runtime/ShaderPackageCatalog.cpp"
|
||||||
|
"${APP_DIR}/runtime/ShaderPackageCatalog.h"
|
||||||
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
"${APP_DIR}/shader/ShaderCompiler.cpp"
|
||||||
"${APP_DIR}/shader/ShaderCompiler.h"
|
"${APP_DIR}/shader/ShaderCompiler.h"
|
||||||
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
"${APP_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
|
|||||||
{
|
{
|
||||||
InitializeCriticalSection(&pMutex);
|
InitializeCriticalSection(&pMutex);
|
||||||
mRuntimeStore = std::make_unique<RuntimeStore>();
|
mRuntimeStore = std::make_unique<RuntimeStore>();
|
||||||
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(*mRuntimeStore);
|
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder());
|
||||||
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
|
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore);
|
||||||
mRenderEngine = std::make_unique<RenderEngine>(
|
mRenderEngine = std::make_unique<RenderEngine>(
|
||||||
*mRuntimeSnapshotProvider,
|
*mRuntimeSnapshotProvider,
|
||||||
|
|||||||
@@ -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 "RuntimeSnapshotProvider.h"
|
||||||
|
|
||||||
#include "ShaderCompiler.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RuntimeStore& runtimeStore) :
|
RuntimeSnapshotProvider::RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder) :
|
||||||
mRuntimeStore(runtimeStore)
|
mRenderSnapshotBuilder(renderSnapshotBuilder)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,36 +11,7 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ShaderPackage shaderPackage;
|
return mRenderSnapshotBuilder.BuildLayerPassFragmentShaderSources(layerId, passSources, error);
|
||||||
if (!mRuntimeStore.CopyShaderPackageForStoredLayer(layerId, shaderPackage, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::filesystem::path repoRoot;
|
|
||||||
std::filesystem::path wrapperPath;
|
|
||||||
std::filesystem::path generatedGlslPath;
|
|
||||||
std::filesystem::path patchedGlslPath;
|
|
||||||
unsigned maxTemporalHistoryFrames = 0;
|
|
||||||
mRuntimeStore.GetShaderCompilerInputs(repoRoot, wrapperPath, generatedGlslPath, patchedGlslPath, maxTemporalHistoryFrames);
|
|
||||||
|
|
||||||
ShaderCompiler compiler(
|
|
||||||
repoRoot,
|
|
||||||
wrapperPath,
|
|
||||||
generatedGlslPath,
|
|
||||||
patchedGlslPath,
|
|
||||||
maxTemporalHistoryFrames);
|
|
||||||
passSources.clear();
|
|
||||||
passSources.reserve(shaderPackage.passes.size());
|
|
||||||
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
|
||||||
{
|
|
||||||
ShaderPassBuildSource passSource;
|
|
||||||
passSource.passId = pass.id;
|
|
||||||
passSource.inputNames = pass.inputNames;
|
|
||||||
passSource.outputName = pass.outputName;
|
|
||||||
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, passSource.fragmentShaderSource, error))
|
|
||||||
return false;
|
|
||||||
passSources.push_back(std::move(passSource));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& exception)
|
catch (const std::exception& exception)
|
||||||
{
|
{
|
||||||
@@ -59,20 +27,17 @@ bool RuntimeSnapshotProvider::BuildLayerPassFragmentShaderSources(const std::str
|
|||||||
|
|
||||||
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
unsigned RuntimeSnapshotProvider::GetMaxTemporalHistoryFrames() const
|
||||||
{
|
{
|
||||||
return mRuntimeStore.GetConfiguredMaxTemporalHistoryFrames();
|
return mRenderSnapshotBuilder.GetMaxTemporalHistoryFrames();
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
RuntimeSnapshotVersions RuntimeSnapshotProvider::GetVersions() const
|
||||||
{
|
{
|
||||||
RuntimeSnapshotVersions versions;
|
return mRenderSnapshotBuilder.GetVersions();
|
||||||
versions.renderStateVersion = mRuntimeStore.GetRenderStateVersion();
|
|
||||||
versions.parameterStateVersion = mRuntimeStore.GetParameterStateVersion();
|
|
||||||
return versions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeSnapshotProvider::AdvanceFrame()
|
void RuntimeSnapshotProvider::AdvanceFrame()
|
||||||
{
|
{
|
||||||
mRuntimeStore.AdvanceFrameCounter();
|
mRenderSnapshotBuilder.AdvanceFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
|
RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight) const
|
||||||
@@ -87,7 +52,7 @@ RuntimeRenderStateSnapshot RuntimeSnapshotProvider::PublishRenderStateSnapshot(u
|
|||||||
RuntimeRenderStateSnapshot snapshot;
|
RuntimeRenderStateSnapshot snapshot;
|
||||||
snapshot.outputWidth = outputWidth;
|
snapshot.outputWidth = outputWidth;
|
||||||
snapshot.outputHeight = outputHeight;
|
snapshot.outputHeight = outputHeight;
|
||||||
mRuntimeStore.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
|
mRenderSnapshotBuilder.BuildLayerRenderStates(outputWidth, outputHeight, snapshot.states);
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||||
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
|
if (versionsBefore.renderStateVersion == versionsAfter.renderStateVersion &&
|
||||||
@@ -107,7 +72,7 @@ bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
std::vector<RuntimeRenderState> states;
|
std::vector<RuntimeRenderState> states;
|
||||||
if (!mRuntimeStore.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
|
if (!mRenderSnapshotBuilder.TryBuildLayerRenderStates(outputWidth, outputHeight, states))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
const RuntimeSnapshotVersions versionsAfter = GetVersions();
|
||||||
@@ -128,7 +93,7 @@ bool RuntimeSnapshotProvider::TryPublishRenderStateSnapshot(unsigned outputWidth
|
|||||||
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
|
bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRenderStateSnapshot& snapshot) const
|
||||||
{
|
{
|
||||||
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
|
const uint64_t expectedRenderStateVersion = snapshot.versions.renderStateVersion;
|
||||||
if (!mRuntimeStore.TryRefreshLayerParameters(snapshot.states))
|
if (!mRenderSnapshotBuilder.TryRefreshLayerParameters(snapshot.states))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const RuntimeSnapshotVersions versions = GetVersions();
|
const RuntimeSnapshotVersions versions = GetVersions();
|
||||||
@@ -142,7 +107,7 @@ bool RuntimeSnapshotProvider::TryRefreshPublishedSnapshotParameters(RuntimeRende
|
|||||||
|
|
||||||
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
void RuntimeSnapshotProvider::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||||
{
|
{
|
||||||
mRuntimeStore.RefreshDynamicRenderStateFields(states);
|
mRenderSnapshotBuilder.RefreshDynamicRenderStateFields(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
bool RuntimeSnapshotProvider::TryGetPublishedRenderStateSnapshot(unsigned outputWidth, unsigned outputHeight,
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RuntimeStore.h"
|
#include "RenderSnapshotBuilder.h"
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct RuntimeSnapshotVersions
|
|
||||||
{
|
|
||||||
uint64_t renderStateVersion = 0;
|
|
||||||
uint64_t parameterStateVersion = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeRenderStateSnapshot
|
struct RuntimeRenderStateSnapshot
|
||||||
{
|
{
|
||||||
RuntimeSnapshotVersions versions;
|
RuntimeSnapshotVersions versions;
|
||||||
@@ -24,7 +17,7 @@ struct RuntimeRenderStateSnapshot
|
|||||||
class RuntimeSnapshotProvider
|
class RuntimeSnapshotProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit RuntimeSnapshotProvider(RuntimeStore& runtimeStore);
|
explicit RuntimeSnapshotProvider(RenderSnapshotBuilder& renderSnapshotBuilder);
|
||||||
|
|
||||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error) const;
|
||||||
unsigned GetMaxTemporalHistoryFrames() const;
|
unsigned GetMaxTemporalHistoryFrames() const;
|
||||||
@@ -42,7 +35,7 @@ private:
|
|||||||
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
static bool SnapshotMatches(const RuntimeRenderStateSnapshot& snapshot, unsigned outputWidth, unsigned outputHeight,
|
||||||
const RuntimeSnapshotVersions& versions);
|
const RuntimeSnapshotVersions& versions);
|
||||||
|
|
||||||
RuntimeStore& mRuntimeStore;
|
RenderSnapshotBuilder& mRenderSnapshotBuilder;
|
||||||
mutable std::mutex mPublishedSnapshotMutex;
|
mutable std::mutex mPublishedSnapshotMutex;
|
||||||
mutable bool mHasPublishedRenderStateSnapshot = false;
|
mutable bool mHasPublishedRenderStateSnapshot = false;
|
||||||
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;
|
mutable RuntimeRenderStateSnapshot mPublishedRenderStateSnapshot;
|
||||||
|
|||||||
@@ -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
|
#pragma once
|
||||||
|
|
||||||
#include "HealthTelemetry.h"
|
#include "HealthTelemetry.h"
|
||||||
|
#include "LayerStackStore.h"
|
||||||
|
#include "RenderSnapshotBuilder.h"
|
||||||
|
#include "RuntimeConfigStore.h"
|
||||||
#include "RuntimeJson.h"
|
#include "RuntimeJson.h"
|
||||||
|
#include "ShaderPackageCatalog.h"
|
||||||
#include "ShaderTypes.h"
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <map>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class RuntimeSnapshotProvider;
|
class RuntimeStatePresenter;
|
||||||
|
|
||||||
class RuntimeStore
|
class RuntimeStore
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct StoredParameterSnapshot
|
using StoredParameterSnapshot = LayerStackStore::StoredParameterSnapshot;
|
||||||
{
|
using LayerPersistentState = LayerStackStore::LayerPersistentState;
|
||||||
std::string layerId;
|
|
||||||
ShaderParameterDefinition definition;
|
|
||||||
ShaderParameterValue currentValue;
|
|
||||||
bool hasCurrentValue = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AppConfig
|
|
||||||
{
|
|
||||||
std::string shaderLibrary = "shaders";
|
|
||||||
unsigned short serverPort = 8080;
|
|
||||||
unsigned short oscPort = 9000;
|
|
||||||
std::string oscBindAddress = "127.0.0.1";
|
|
||||||
double oscSmoothing = 0.18;
|
|
||||||
bool autoReload = true;
|
|
||||||
unsigned maxTemporalHistoryFrames = 4;
|
|
||||||
unsigned previewFps = 30;
|
|
||||||
bool enableExternalKeying = false;
|
|
||||||
std::string inputVideoFormat = "1080p";
|
|
||||||
std::string inputFrameRate = "59.94";
|
|
||||||
std::string outputVideoFormat = "1080p";
|
|
||||||
std::string outputFrameRate = "59.94";
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LayerPersistentState
|
|
||||||
{
|
|
||||||
std::string id;
|
|
||||||
std::string shaderId;
|
|
||||||
bool bypass = false;
|
|
||||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PersistentState
|
|
||||||
{
|
|
||||||
std::vector<LayerPersistentState> layers;
|
|
||||||
};
|
|
||||||
|
|
||||||
RuntimeStore();
|
RuntimeStore();
|
||||||
HealthTelemetry& GetHealthTelemetry();
|
HealthTelemetry& GetHealthTelemetry();
|
||||||
const HealthTelemetry& GetHealthTelemetry() const;
|
const HealthTelemetry& GetHealthTelemetry() const;
|
||||||
|
RenderSnapshotBuilder& GetRenderSnapshotBuilder();
|
||||||
|
const RenderSnapshotBuilder& GetRenderSnapshotBuilder() const;
|
||||||
|
|
||||||
bool InitializeStore(std::string& error);
|
bool InitializeStore(std::string& error);
|
||||||
std::string BuildPersistentStateJson() const;
|
std::string BuildPersistentStateJson() const;
|
||||||
@@ -104,66 +72,26 @@ public:
|
|||||||
void ClearReloadRequest();
|
void ClearReloadRequest();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class RuntimeSnapshotProvider;
|
friend class RenderSnapshotBuilder;
|
||||||
bool LoadConfig(std::string& error);
|
friend class RuntimeStatePresenter;
|
||||||
bool LoadPersistentState(std::string& error);
|
bool LoadPersistentState(std::string& error);
|
||||||
bool SavePersistentState(std::string& error) const;
|
bool SavePersistentState(std::string& error) const;
|
||||||
bool ScanShaderPackages(std::string& error);
|
bool ScanShaderPackages(std::string& error);
|
||||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||||
bool ResolvePaths(std::string& error);
|
|
||||||
std::vector<std::string> GetStackPresetNamesLocked() const;
|
std::vector<std::string> GetStackPresetNamesLocked() const;
|
||||||
std::string MakeSafePresetFileStem(const std::string& presetName) const;
|
|
||||||
bool TryResolveStoredLayerAndParameterByControlKeyLocked(const std::string& layerKey, const std::string& parameterKey,
|
|
||||||
const LayerPersistentState*& matchedLayer, const ShaderPackage*& matchedPackage,
|
|
||||||
std::vector<ShaderParameterDefinition>::const_iterator& parameterIt, std::string& error) const;
|
|
||||||
bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const;
|
bool CopyShaderPackageForStoredLayer(const std::string& layerId, ShaderPackage& shaderPackage, std::string& error) const;
|
||||||
void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
|
void GetShaderCompilerInputs(std::filesystem::path& repoRoot, std::filesystem::path& wrapperPath,
|
||||||
std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const;
|
std::filesystem::path& generatedGlslPath, std::filesystem::path& patchedGlslPath, unsigned& maxTemporalHistoryFrames) const;
|
||||||
uint64_t GetRenderStateVersion() const;
|
|
||||||
uint64_t GetParameterStateVersion() const;
|
|
||||||
void AdvanceFrameCounter();
|
|
||||||
void BuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
bool TryBuildLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
bool TryRefreshLayerParameters(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void RefreshLayerParametersLocked(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
void RefreshDynamicRenderStateFieldsLocked(std::vector<RuntimeRenderState>& states) const;
|
|
||||||
JsonValue BuildRuntimeStateValue() const;
|
|
||||||
JsonValue SerializeLayerStack() const;
|
|
||||||
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
|
||||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
|
||||||
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
|
||||||
JsonValue SerializeLayerStackLocked() const;
|
|
||||||
bool DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, std::string& error);
|
|
||||||
void NormalizePersistentLayerIdsLocked();
|
|
||||||
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
|
|
||||||
std::string TemporalHistorySourceToString(TemporalHistorySource source) const;
|
|
||||||
LayerPersistentState* FindLayerById(const std::string& layerId);
|
|
||||||
const LayerPersistentState* FindLayerById(const std::string& layerId) const;
|
|
||||||
std::string GenerateLayerId();
|
|
||||||
void MarkRenderStateDirtyLocked();
|
void MarkRenderStateDirtyLocked();
|
||||||
void MarkParameterStateDirtyLocked();
|
void MarkParameterStateDirtyLocked();
|
||||||
|
|
||||||
|
RenderSnapshotBuilder mRenderSnapshotBuilder;
|
||||||
|
RuntimeConfigStore mConfigStore;
|
||||||
|
ShaderPackageCatalog mShaderCatalog;
|
||||||
|
LayerStackStore mLayerStack;
|
||||||
HealthTelemetry mHealthTelemetry;
|
HealthTelemetry mHealthTelemetry;
|
||||||
mutable std::mutex mMutex;
|
mutable std::mutex mMutex;
|
||||||
AppConfig mConfig;
|
|
||||||
PersistentState mPersistentState;
|
|
||||||
std::filesystem::path mRepoRoot;
|
|
||||||
std::filesystem::path mUiRoot;
|
|
||||||
std::filesystem::path mDocsRoot;
|
|
||||||
std::filesystem::path mShaderRoot;
|
|
||||||
std::filesystem::path mRuntimeRoot;
|
|
||||||
std::filesystem::path mPresetRoot;
|
|
||||||
std::filesystem::path mRuntimeStatePath;
|
|
||||||
std::filesystem::path mConfigPath;
|
|
||||||
std::filesystem::path mWrapperPath;
|
|
||||||
std::filesystem::path mGeneratedGlslPath;
|
|
||||||
std::filesystem::path mPatchedGlslPath;
|
|
||||||
std::map<std::string, ShaderPackage> mPackagesById;
|
|
||||||
std::vector<std::string> mPackageOrder;
|
|
||||||
std::vector<ShaderPackageStatus> mPackageStatuses;
|
|
||||||
bool mReloadRequested;
|
bool mReloadRequested;
|
||||||
bool mCompileSucceeded;
|
bool mCompileSucceeded;
|
||||||
std::string mCompileMessage;
|
std::string mCompileMessage;
|
||||||
@@ -172,8 +100,4 @@ private:
|
|||||||
bool mAutoReloadEnabled;
|
bool mAutoReloadEnabled;
|
||||||
std::chrono::steady_clock::time_point mStartTime;
|
std::chrono::steady_clock::time_point mStartTime;
|
||||||
std::chrono::steady_clock::time_point mLastScanTime;
|
std::chrono::steady_clock::time_point mLastScanTime;
|
||||||
std::atomic<uint64_t> mFrameCounter{ 0 };
|
|
||||||
std::atomic<uint64_t> mRenderStateVersion{ 0 };
|
|
||||||
std::atomic<uint64_t> mParameterStateVersion{ 0 };
|
|
||||||
uint64_t mNextLayerId;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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`
|
||||||
- [RuntimeStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h)
|
- [RuntimeStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.h)
|
||||||
- [RuntimeStore.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp)
|
- [RuntimeStore.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeStore.cpp)
|
||||||
|
- `RuntimeConfigStore`
|
||||||
|
- [RuntimeConfigStore.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeConfigStore.h)
|
||||||
|
- [RuntimeConfigStore.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeConfigStore.cpp)
|
||||||
|
- `ShaderPackageCatalog`
|
||||||
|
- [ShaderPackageCatalog.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/ShaderPackageCatalog.h)
|
||||||
|
- [ShaderPackageCatalog.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/ShaderPackageCatalog.cpp)
|
||||||
- `RuntimeCoordinator`
|
- `RuntimeCoordinator`
|
||||||
- [RuntimeCoordinator.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h)
|
- [RuntimeCoordinator.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.h)
|
||||||
- [RuntimeCoordinator.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp)
|
- [RuntimeCoordinator.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeCoordinator.cpp)
|
||||||
- `RuntimeSnapshotProvider`
|
- `RuntimeSnapshotProvider`
|
||||||
- [RuntimeSnapshotProvider.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h)
|
- [RuntimeSnapshotProvider.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.h)
|
||||||
- [RuntimeSnapshotProvider.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp)
|
- [RuntimeSnapshotProvider.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeSnapshotProvider.cpp)
|
||||||
|
- `RenderSnapshotBuilder`
|
||||||
|
- [RenderSnapshotBuilder.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RenderSnapshotBuilder.h)
|
||||||
|
- [RenderSnapshotBuilder.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/runtime/RenderSnapshotBuilder.cpp)
|
||||||
- `ControlServices`
|
- `ControlServices`
|
||||||
- [ControlServices.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h)
|
- [ControlServices.h](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.h)
|
||||||
- [ControlServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp)
|
- [ControlServices.cpp](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/apps/LoopThroughWithOpenGLCompositing/control/ControlServices.cpp)
|
||||||
@@ -124,15 +133,15 @@ The codebase now has an initial Phase 1 compatibility split in place:
|
|||||||
These are still compatibility seams, not a completed subsystem extraction. Some responsibilities have moved behind the new boundaries, while other paths still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later work real code boundaries that can be expanded without first inventing the names:
|
These are still compatibility seams, not a completed subsystem extraction. Some responsibilities have moved behind the new boundaries, while other paths still delegate through compatibility helpers, `OpenGLComposite`, `DeckLinkSession`, and the existing bridge/pipeline classes. Their purpose is to give later work real code boundaries that can be expanded without first inventing the names:
|
||||||
|
|
||||||
- UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator`
|
- UI/runtime control calls in `OpenGLCompositeRuntimeControls.cpp` now route through `RuntimeCoordinator`
|
||||||
- runtime startup for path resolution, config load, persistent state load, and shader package scan now initializes through `RuntimeStore`
|
- runtime startup now initializes path resolution and config loading through `RuntimeConfigStore`, with shader package scan and lookup delegated to `ShaderPackageCatalog`
|
||||||
- runtime/UI state JSON composition now lives in `RuntimeStore` instead of `RuntimeHost`
|
- runtime/UI state JSON composition now routes through `RuntimeStatePresenter` and `RuntimeStateJson` instead of living in `RuntimeHost` or `RuntimeStore`
|
||||||
- regular stored layer mutations and stack preset save/load now live in `RuntimeStore` instead of `RuntimeHost` public APIs
|
- regular stored layer mutations and stack preset save/load now route through `RuntimeStore` into `LayerStackStore` instead of `RuntimeHost` public APIs
|
||||||
- persisted OSC-by-control-key commits now route through `RuntimeCoordinator` before applying store changes
|
- persisted OSC-by-control-key commits now route through `RuntimeCoordinator` before applying store changes
|
||||||
- mutation and reload policy now routes through `RuntimeCoordinator`
|
- mutation and reload policy now routes through `RuntimeCoordinator`
|
||||||
- parameter target resolution, value normalization, trigger classification, and move no-op classification now live under `RuntimeCoordinator`
|
- parameter target resolution, value normalization, trigger classification, and move no-op classification now live under `RuntimeCoordinator`
|
||||||
- render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider`
|
- render-state and shader-build reads in `OpenGLComposite.cpp`, `OpenGLShaderPrograms.cpp`, and `ShaderBuildQueue.cpp` now route through `RuntimeSnapshotProvider`
|
||||||
- `RuntimeSnapshotProvider` now depends on `RuntimeStore` rather than sharing `RuntimeHost` directly
|
- `RuntimeSnapshotProvider` now depends on `RenderSnapshotBuilder` rather than on `RuntimeStore` friendship or shared `RuntimeHost` access
|
||||||
- render-state assembly, cached parameter refresh, and frame-context application now flow through `RuntimeSnapshotProvider` and store-owned snapshot helpers instead of `RuntimeHost` public APIs
|
- render-state assembly, cached parameter refresh, dynamic frame-field application, and render snapshot versions now live in `RenderSnapshotBuilder` instead of `RuntimeStore`
|
||||||
- `RuntimeSnapshotProvider` now publishes versioned render snapshot objects and serves matching consumers from the last published snapshot
|
- `RuntimeSnapshotProvider` now publishes versioned render snapshot objects and serves matching consumers from the last published snapshot
|
||||||
- service ingress and polling coordination now route through `ControlServices`
|
- service ingress and polling coordination now route through `ControlServices`
|
||||||
- `ControlServices` now queues coordinator results for OSC commit and file-poll outcomes instead of directly deciding runtime/store policy
|
- `ControlServices` now queues coordinator results for OSC commit and file-poll outcomes instead of directly deciding runtime/store policy
|
||||||
@@ -140,6 +149,10 @@ These are still compatibility seams, not a completed subsystem extraction. Some
|
|||||||
- `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields
|
- `HealthTelemetry` now owns the live signal, video-I/O, and performance snapshots directly instead of `RuntimeHost` keeping those backing fields
|
||||||
- render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost`
|
- render-side frame advancement and render-performance reporting now flow through `RuntimeSnapshotProvider` and `HealthTelemetry` instead of directly through `RuntimeHost`
|
||||||
- `RuntimeStore` now owns its durable/session backing fields directly instead of wrapping a compatibility `RuntimeHost` object
|
- `RuntimeStore` now owns its durable/session backing fields directly instead of wrapping a compatibility `RuntimeHost` object
|
||||||
|
- `RuntimeConfigStore` now owns runtime config parsing, path resolution, configured ports/formats, runtime roots, and shader compiler paths instead of leaving those responsibilities inside `RuntimeStore`
|
||||||
|
- `ShaderPackageCatalog` now owns shader package scanning, package status/order/lookup, and package asset/source change comparison instead of leaving those responsibilities inside `RuntimeStore`
|
||||||
|
- `LayerStackStore` now owns durable layer state, layer CRUD/reorder, parameter persistence, and stack preset value serialization/load instead of leaving those responsibilities inside `RuntimeStore`
|
||||||
|
- `RuntimeStatePresenter` and `RuntimeStateJson` now own runtime-state JSON assembly and layer-stack presentation serialization instead of leaving those responsibilities inside storage classes
|
||||||
- `RuntimeCoordinator` now uses explicit `RuntimeStore` query APIs/read models instead of friendship or direct store-internal access
|
- `RuntimeCoordinator` now uses explicit `RuntimeStore` query APIs/read models instead of friendship or direct store-internal access
|
||||||
- live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite`
|
- live OSC overlay state and smoothing/commit decisions now live under `RenderEngine` instead of `OpenGLComposite`
|
||||||
- coordinator result application, shader-build requests, ready-build application, and runtime-state broadcasts now route through `RuntimeUpdateController` instead of being interpreted directly by `OpenGLComposite`
|
- coordinator result application, shader-build requests, ready-build application, and runtime-state broadcasts now route through `RuntimeUpdateController` instead of being interpreted directly by `OpenGLComposite`
|
||||||
@@ -346,7 +359,8 @@ Allowed dependency directions:
|
|||||||
- `RuntimeCoordinator -> RuntimeStore`
|
- `RuntimeCoordinator -> RuntimeStore`
|
||||||
- `RuntimeCoordinator -> RuntimeSnapshotProvider`
|
- `RuntimeCoordinator -> RuntimeSnapshotProvider`
|
||||||
- `RuntimeCoordinator -> HealthTelemetry`
|
- `RuntimeCoordinator -> HealthTelemetry`
|
||||||
- `RuntimeSnapshotProvider -> RuntimeStore`
|
- `RuntimeSnapshotProvider -> RenderSnapshotBuilder`
|
||||||
|
- `RenderSnapshotBuilder -> RuntimeStore`
|
||||||
- `RenderEngine -> RuntimeSnapshotProvider`
|
- `RenderEngine -> RuntimeSnapshotProvider`
|
||||||
- `RenderEngine -> HealthTelemetry`
|
- `RenderEngine -> HealthTelemetry`
|
||||||
- `VideoBackend -> RenderEngine`
|
- `VideoBackend -> RenderEngine`
|
||||||
@@ -574,7 +588,8 @@ Likely examples:
|
|||||||
|
|
||||||
- config loading/saving -> `RuntimeStore`
|
- config loading/saving -> `RuntimeStore`
|
||||||
- layer stack mutation validation -> `RuntimeCoordinator`
|
- layer stack mutation validation -> `RuntimeCoordinator`
|
||||||
- render state building/versioning -> `RuntimeSnapshotProvider`
|
- render state building/versioning -> `RenderSnapshotBuilder`
|
||||||
|
- render snapshot publication/cache -> `RuntimeSnapshotProvider`
|
||||||
- timing/status setters -> `HealthTelemetry`
|
- timing/status setters -> `HealthTelemetry`
|
||||||
|
|
||||||
### Current `RuntimeServices`
|
### Current `RuntimeServices`
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ That order mirrors the intended dependency story:
|
|||||||
## Subsystem Notes
|
## Subsystem Notes
|
||||||
|
|
||||||
- [RuntimeStore.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeStore.md)
|
- [RuntimeStore.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeStore.md)
|
||||||
Durable runtime config, persisted layer state, presets, and package metadata ownership.
|
Durable runtime-state facade over layer-stack, config, package-catalog, presentation, and persistence boundaries.
|
||||||
- [RuntimeCoordinator.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeCoordinator.md)
|
- [RuntimeCoordinator.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeCoordinator.md)
|
||||||
Mutation validation, state classification, reset/reload policy, and publication/persistence requests.
|
Mutation validation, state classification, reset/reload policy, and publication/persistence requests.
|
||||||
- [RuntimeSnapshotProvider.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeSnapshotProvider.md)
|
- [RuntimeSnapshotProvider.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RuntimeSnapshotProvider.md)
|
||||||
Render-facing snapshot build, publication, and versioning boundaries.
|
Render-facing snapshot publication boundary backed by explicit render snapshot building/versioning.
|
||||||
- [ControlServices.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/ControlServices.md)
|
- [ControlServices.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/ControlServices.md)
|
||||||
OSC, HTTP/WebSocket, and file-watch ingress plus normalization and service-local buffering.
|
OSC, HTTP/WebSocket, and file-watch ingress plus normalization and service-local buffering.
|
||||||
- [RenderEngine.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RenderEngine.md)
|
- [RenderEngine.md](/c:/Users/Aiden/Documents/GitHub/video-shader-toys/docs/subsystems/RenderEngine.md)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ It exists to solve three current problems:
|
|||||||
|
|
||||||
- render state is still built directly out of `RuntimeHost` under a shared mutex
|
- render state is still built directly out of `RuntimeHost` under a shared mutex
|
||||||
- render reads and refreshes partially mutable cached layer state in more than one place
|
- render reads and refreshes partially mutable cached layer state in more than one place
|
||||||
- state publication, state versioning, and dynamic frame-field refresh are not yet explicit subsystems
|
- state publication, state versioning, and dynamic frame-field refresh need explicit ownership
|
||||||
|
|
||||||
Today the closest current behavior lives in:
|
Today the closest current behavior lives in:
|
||||||
|
|
||||||
@@ -29,13 +29,17 @@ Today the closest current behavior lives in:
|
|||||||
|
|
||||||
`RuntimeSnapshotProvider` is responsible for:
|
`RuntimeSnapshotProvider` is responsible for:
|
||||||
|
|
||||||
- building render-facing snapshots from durable store state plus whatever committed-live state view the Phase 3 split ultimately exposes
|
|
||||||
- publishing stable, versioned snapshots that can be consumed without large shared mutable locks
|
- publishing stable, versioned snapshots that can be consumed without large shared mutable locks
|
||||||
|
- giving `RenderEngine` a cheap read path for the latest committed snapshot
|
||||||
|
- making snapshot invalidation and publication rules explicit
|
||||||
|
|
||||||
|
`RenderSnapshotBuilder` is responsible for:
|
||||||
|
|
||||||
|
- building render-facing snapshots from durable store state plus whatever committed-live state view the Phase 3 split ultimately exposes
|
||||||
- separating structural snapshot changes from dynamic frame fields
|
- separating structural snapshot changes from dynamic frame fields
|
||||||
- translating runtime layer state into render-ready layer descriptors
|
- translating runtime layer state into render-ready layer descriptors
|
||||||
- attaching immutable or near-immutable shader/package-derived data needed by render
|
- attaching immutable or near-immutable shader/package-derived data needed by render
|
||||||
- giving `RenderEngine` a cheap read path for the latest committed snapshot
|
- maintaining render snapshot version counters and frame advancement
|
||||||
- making snapshot invalidation and publication rules explicit
|
|
||||||
|
|
||||||
It is not responsible for:
|
It is not responsible for:
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ The shape of render-facing layer state should remain consistent across phases ev
|
|||||||
|
|
||||||
## Snapshot Inputs
|
## Snapshot Inputs
|
||||||
|
|
||||||
`RuntimeSnapshotProvider` should build from a read-oriented runtime view, not from direct mutation calls.
|
`RenderSnapshotBuilder` should build from a read-oriented runtime view, not from direct mutation calls. `RuntimeSnapshotProvider` should consume the builder's output and own publication/cache behavior.
|
||||||
|
|
||||||
That view will likely include:
|
That view will likely include:
|
||||||
|
|
||||||
@@ -87,7 +91,8 @@ That view will likely include:
|
|||||||
|
|
||||||
The important Phase 1 rule is not "the provider always reads one specific object." It is:
|
The important Phase 1 rule is not "the provider always reads one specific object." It is:
|
||||||
|
|
||||||
- the provider consumes read-oriented committed runtime state
|
- the builder consumes read-oriented committed runtime state
|
||||||
|
- the provider consumes builder-published render snapshot data
|
||||||
- the provider does not own mutation policy
|
- the provider does not own mutation policy
|
||||||
- render consumes the provider's published output instead of reaching back into whichever runtime object currently stores the truth
|
- render consumes the provider's published output instead of reaching back into whichever runtime object currently stores the truth
|
||||||
|
|
||||||
@@ -292,11 +297,11 @@ Notes:
|
|||||||
|
|
||||||
### `RuntimeStore`
|
### `RuntimeStore`
|
||||||
|
|
||||||
`RuntimeSnapshotProvider` depends on store-owned durable data and package metadata through a read-oriented interface or view.
|
`RenderSnapshotBuilder` depends on store-owned durable data and package metadata through a read-oriented interface or view. `RuntimeSnapshotProvider` depends on the builder rather than reaching into store internals directly.
|
||||||
|
|
||||||
If committed live state remains physically co-located with the store during early migration, the provider may read it through the same view. If committed live state moves behind a coordinator-owned live-session model later, the provider should consume that through a similarly read-oriented view.
|
If committed live state remains physically co-located with the store during early migration, the builder may read it through the same view. If committed live state moves behind a coordinator-owned live-session model later, the builder should consume that through a similarly read-oriented view.
|
||||||
|
|
||||||
It should not mutate the store directly.
|
Neither the builder nor provider should mutate the store directly.
|
||||||
|
|
||||||
### `RuntimeCoordinator`
|
### `RuntimeCoordinator`
|
||||||
|
|
||||||
@@ -337,7 +342,7 @@ This is especially important while migrating away from the current lock/fallback
|
|||||||
|
|
||||||
The current code suggests the following migration map.
|
The current code suggests the following migration map.
|
||||||
|
|
||||||
### Move into `RuntimeSnapshotProvider`
|
### Move into `RenderSnapshotBuilder`
|
||||||
|
|
||||||
From `RuntimeHost`:
|
From `RuntimeHost`:
|
||||||
|
|
||||||
@@ -346,6 +351,12 @@ From `RuntimeHost`:
|
|||||||
- explicit version composition for render-visible state
|
- explicit version composition for render-visible state
|
||||||
- dynamic frame-context construction currently done in `RefreshDynamicRenderStateFields(...)`
|
- dynamic frame-context construction currently done in `RefreshDynamicRenderStateFields(...)`
|
||||||
|
|
||||||
|
### Move into `RuntimeSnapshotProvider`
|
||||||
|
|
||||||
|
- published snapshot cache ownership
|
||||||
|
- version matching for already-published snapshots
|
||||||
|
- render-facing compatibility API while render callers migrate
|
||||||
|
|
||||||
### Stop exposing directly from the host/store boundary
|
### Stop exposing directly from the host/store boundary
|
||||||
|
|
||||||
Current methods that should become compatibility shims and later disappear:
|
Current methods that should become compatibility shims and later disappear:
|
||||||
@@ -357,9 +368,9 @@ Current methods that should become compatibility shims and later disappear:
|
|||||||
|
|
||||||
### Render-side compatibility during migration
|
### Render-side compatibility during migration
|
||||||
|
|
||||||
The current `OpenGLComposite` cache path:
|
The previous `OpenGLComposite` cache path:
|
||||||
|
|
||||||
- reads versions from `RuntimeHost`
|
- reads versions from `RuntimeHost`/store-owned counters
|
||||||
- conditionally calls `TryRefreshCachedLayerStates(...)`
|
- conditionally calls `TryRefreshCachedLayerStates(...)`
|
||||||
- conditionally rebuilds full layer state
|
- conditionally rebuilds full layer state
|
||||||
- then reapplies render-local OSC overlay state
|
- then reapplies render-local OSC overlay state
|
||||||
@@ -367,8 +378,8 @@ The current `OpenGLComposite` cache path:
|
|||||||
During migration, that should become:
|
During migration, that should become:
|
||||||
|
|
||||||
1. get latest published snapshot from provider
|
1. get latest published snapshot from provider
|
||||||
2. compare snapshot versions against render-local cache
|
2. compare snapshot versions produced by `RenderSnapshotBuilder`
|
||||||
3. rebuild only if needed
|
3. rebuild through `RenderSnapshotBuilder` only if needed
|
||||||
4. apply render-local overlay state
|
4. apply render-local overlay state
|
||||||
5. attach frame context
|
5. attach frame context
|
||||||
|
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ Per the Phase 1 subsystem design, `RuntimeStore` should sit low in the dependenc
|
|||||||
Allowed inbound dependencies:
|
Allowed inbound dependencies:
|
||||||
|
|
||||||
- `RuntimeCoordinator -> RuntimeStore`
|
- `RuntimeCoordinator -> RuntimeStore`
|
||||||
- `RuntimeSnapshotProvider -> RuntimeStore`
|
- `RenderSnapshotBuilder -> RuntimeStore`
|
||||||
- temporary migration shims from `ControlServices` only where explicitly tolerated
|
- temporary migration shims from `ControlServices` only where explicitly tolerated
|
||||||
|
|
||||||
Allowed outbound dependencies:
|
Allowed outbound dependencies:
|
||||||
@@ -359,16 +359,33 @@ Those belong under other target subsystems.
|
|||||||
`RuntimeStore` does not need to be one monolithic class forever. A practical internal shape would be:
|
`RuntimeStore` does not need to be one monolithic class forever. A practical internal shape would be:
|
||||||
|
|
||||||
- `RuntimeConfigStore`
|
- `RuntimeConfigStore`
|
||||||
- runtime host config load/save and resolved paths
|
- runtime host config load and resolved paths
|
||||||
- `PersistentLayerStore`
|
|
||||||
|
The current codebase has begun this split: `RuntimeConfigStore` owns config parsing, path resolution, configured ports/formats, runtime roots, and shader compiler paths, while `RuntimeStore` keeps compatibility delegates for existing callers.
|
||||||
|
- `LayerStackStore`
|
||||||
- durable layer stack and parameter values
|
- durable layer stack and parameter values
|
||||||
- `StackPresetStore`
|
- layer CRUD/reorder and shader selection
|
||||||
- preset enumeration/load/save
|
- stack preset value serialization/load
|
||||||
- `ShaderPackageCatalogStore`
|
- `RuntimeStatePresenter` / `RuntimeStateJson`
|
||||||
|
- runtime-state JSON assembly
|
||||||
|
- layer-stack presentation serialization
|
||||||
|
- `RenderSnapshotBuilder`
|
||||||
|
- render-state assembly and parameter refresh
|
||||||
|
- dynamic frame-field refresh and render snapshot version counters
|
||||||
|
- `ShaderPackageCatalog`
|
||||||
- durable manifest/package metadata
|
- durable manifest/package metadata
|
||||||
|
- shader package scanning, status/order/lookup, and asset/source change comparison
|
||||||
- `PersistenceWriter` helper
|
- `PersistenceWriter` helper
|
||||||
- synchronous at first, async/debounced later
|
- synchronous at first, async/debounced later
|
||||||
|
|
||||||
|
The current codebase has begun the layer split: `LayerStackStore` owns durable layer state, layer CRUD/reorder, parameter persistence, and stack preset value serialization/load. `RuntimeStore` still owns locking, file IO, and compatibility delegates for existing callers.
|
||||||
|
|
||||||
|
The current codebase has begun the render snapshot split: `RenderSnapshotBuilder` owns render-state assembly, cached parameter refresh, dynamic frame-field refresh, and render snapshot versions. `RuntimeSnapshotProvider` depends on this builder rather than on `RuntimeStore` friendship.
|
||||||
|
|
||||||
|
The current codebase has also begun the presentation split: `RuntimeStatePresenter` owns top-level runtime-state JSON assembly, while `RuntimeStateJson` owns the layer-stack and parameter presentation shape used by runtime state clients.
|
||||||
|
|
||||||
|
The current codebase has also begun the package split: `ShaderPackageCatalog` owns package scanning and registry comparison, while `RuntimeStore` uses it to keep layer state valid and to build compatibility read models.
|
||||||
|
|
||||||
These can still be presented through one subsystem façade during migration.
|
These can still be presented through one subsystem façade during migration.
|
||||||
|
|
||||||
## Persistence Model
|
## Persistence Model
|
||||||
|
|||||||
Reference in New Issue
Block a user