Layer stacking
This commit is contained in:
@@ -39,6 +39,13 @@ bool IsFiniteNumber(double value)
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
std::string ToLowerCopy(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
return text;
|
||||
}
|
||||
|
||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
||||
{
|
||||
std::vector<double> numbers;
|
||||
@@ -107,11 +114,6 @@ std::string SlangTypeForParameter(ShaderParameterType type)
|
||||
return "uniform float";
|
||||
}
|
||||
|
||||
std::string GlslTypeForUniformDeclaration(const std::string& declaration)
|
||||
{
|
||||
return Trim(declaration);
|
||||
}
|
||||
|
||||
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
||||
{
|
||||
if (typeName == "float")
|
||||
@@ -154,11 +156,10 @@ RuntimeHost::RuntimeHost()
|
||||
mSmoothedRenderMilliseconds(0.0),
|
||||
mServerPort(8080),
|
||||
mAutoReloadEnabled(true),
|
||||
mMixAmount(1.0),
|
||||
mBypass(false),
|
||||
mStartTime(std::chrono::steady_clock::now()),
|
||||
mLastScanTime(std::chrono::steady_clock::time_point::min()),
|
||||
mFrameCounter(0)
|
||||
mFrameCounter(0),
|
||||
mNextLayerId(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -178,8 +179,22 @@ bool RuntimeHost::Initialize(std::string& error)
|
||||
if (!ScanShaderPackages(error))
|
||||
return false;
|
||||
|
||||
if (mActiveShaderId.empty() && !mPackageOrder.empty())
|
||||
mActiveShaderId = mPackageOrder.front();
|
||||
for (LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
auto shaderIt = mPackagesById.find(layer.shaderId);
|
||||
if (shaderIt != mPackagesById.end())
|
||||
EnsureLayerDefaultsLocked(layer, shaderIt->second);
|
||||
}
|
||||
|
||||
if (mPersistentState.layers.empty() && !mPackageOrder.empty())
|
||||
{
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId();
|
||||
layer.shaderId = mPackageOrder.front();
|
||||
layer.bypass = false;
|
||||
EnsureLayerDefaultsLocked(layer, mPackagesById[layer.shaderId]);
|
||||
mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
|
||||
mServerPort = mConfig.serverPort;
|
||||
mAutoReloadEnabled = mConfig.autoReload;
|
||||
@@ -226,7 +241,13 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
|
||||
std::string scanError;
|
||||
std::map<std::string, ShaderPackage> previousPackages = mPackagesById;
|
||||
std::vector<std::string> previousOrder = mPackageOrder;
|
||||
const std::string previousActive = mActiveShaderId;
|
||||
std::map<std::string, std::pair<std::filesystem::file_time_type, std::filesystem::file_time_type>> previousLayerShaderTimes;
|
||||
for (const LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
auto previous = previousPackages.find(layer.shaderId);
|
||||
if (previous != previousPackages.end())
|
||||
previousLayerShaderTimes[layer.id] = std::make_pair(previous->second.shaderWriteTime, previous->second.manifestWriteTime);
|
||||
}
|
||||
|
||||
if (!ScanShaderPackages(scanError))
|
||||
{
|
||||
@@ -254,20 +275,23 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
|
||||
}
|
||||
}
|
||||
|
||||
auto previousActiveIt = previousPackages.find(previousActive);
|
||||
auto activeIt = mPackagesById.find(mActiveShaderId);
|
||||
if (previousActiveIt != previousPackages.end() && activeIt != mPackagesById.end())
|
||||
for (LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
if (previousActiveIt->second.shaderWriteTime != activeIt->second.shaderWriteTime ||
|
||||
previousActiveIt->second.manifestWriteTime != activeIt->second.manifestWriteTime)
|
||||
auto active = mPackagesById.find(layer.shaderId);
|
||||
auto previous = previousLayerShaderTimes.find(layer.id);
|
||||
if (active == mPackagesById.end())
|
||||
continue;
|
||||
EnsureLayerDefaultsLocked(layer, active->second);
|
||||
if (previous != previousLayerShaderTimes.end())
|
||||
{
|
||||
mReloadRequested = true;
|
||||
if (previous->second.first != active->second.shaderWriteTime ||
|
||||
previous->second.second != active->second.manifestWriteTime)
|
||||
{
|
||||
mReloadRequested = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (previousActive != mActiveShaderId)
|
||||
mReloadRequested = true;
|
||||
|
||||
reloadRequested = mReloadRequested;
|
||||
return true;
|
||||
}
|
||||
@@ -295,24 +319,115 @@ void RuntimeHost::ClearReloadRequest()
|
||||
mReloadRequested = false;
|
||||
}
|
||||
|
||||
bool RuntimeHost::SelectShader(const std::string& shaderId, std::string& error)
|
||||
bool RuntimeHost::AddLayer(const std::string& shaderId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mPackagesById.find(shaderId) == mPackagesById.end())
|
||||
auto shaderIt = mPackagesById.find(shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
{
|
||||
error = "Unknown shader id: " + shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
mActiveShaderId = shaderId;
|
||||
mPersistentState.activeShaderId = shaderId;
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId();
|
||||
layer.shaderId = shaderId;
|
||||
layer.bypass = false;
|
||||
EnsureLayerDefaultsLocked(layer, shaderIt->second);
|
||||
mPersistentState.layers.push_back(layer);
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
|
||||
bool RuntimeHost::RemoveLayer(const std::string& layerId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mPersistentState.layers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
mPersistentState.layers.erase(it);
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::MoveLayer(const std::string& layerId, int direction, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mPersistentState.layers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::ptrdiff_t index = std::distance(mPersistentState.layers.begin(), it);
|
||||
const std::ptrdiff_t newIndex = index + direction;
|
||||
if (newIndex < 0 || newIndex >= static_cast<std::ptrdiff_t>(mPersistentState.layers.size()))
|
||||
return true;
|
||||
|
||||
std::swap(mPersistentState.layers[index], mPersistentState.layers[newIndex]);
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
if (it == mPersistentState.layers.end())
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPersistentState.layers.empty())
|
||||
return true;
|
||||
|
||||
if (targetIndex >= mPersistentState.layers.size())
|
||||
targetIndex = mPersistentState.layers.size() - 1;
|
||||
|
||||
const std::size_t sourceIndex = static_cast<std::size_t>(std::distance(mPersistentState.layers.begin(), it));
|
||||
if (sourceIndex == targetIndex)
|
||||
return true;
|
||||
|
||||
LayerPersistentState movedLayer = *it;
|
||||
mPersistentState.layers.erase(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(sourceIndex));
|
||||
mPersistentState.layers.insert(mPersistentState.layers.begin() + static_cast<std::ptrdiff_t>(targetIndex), movedLayer);
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->bypass = bypassed;
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto shaderIt = mPackagesById.find(shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
@@ -321,6 +436,31 @@ bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string
|
||||
return false;
|
||||
}
|
||||
|
||||
layer->shaderId = shaderId;
|
||||
layer->parameterValues.clear();
|
||||
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto shaderIt = mPackagesById.find(layer->shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
{
|
||||
error = "Unknown shader id: " + layer->shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
||||
auto parameterIt = std::find_if(shaderPackage.parameters.begin(), shaderPackage.parameters.end(),
|
||||
[¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
|
||||
@@ -334,49 +474,89 @@ bool RuntimeHost::UpdateParameter(const std::string& shaderId, const std::string
|
||||
if (!NormalizeAndValidateValue(*parameterIt, newValue, normalized, error))
|
||||
return false;
|
||||
|
||||
mPersistentState.parameterValuesByShader[shaderId][parameterId] = normalized;
|
||||
|
||||
layer->parameterValues[parameterId] = normalized;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::ResetParameters(const std::string& shaderId, std::string& error)
|
||||
bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
auto shaderIt = mPackagesById.find(shaderId);
|
||||
LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto shaderIt = mPackagesById.find(layer->shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
{
|
||||
error = "Unknown shader id: " + shaderId;
|
||||
error = "Unknown shader id: " + layer->shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, ShaderParameterValue>& shaderValues = mPersistentState.parameterValuesByShader[shaderId];
|
||||
shaderValues.clear();
|
||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||
shaderValues[definition.id] = DefaultValueForDefinition(definition);
|
||||
|
||||
layer->parameterValues.clear();
|
||||
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::SetBypass(bool bypassEnabled, std::string& error)
|
||||
bool RuntimeHost::SaveStackPreset(const std::string& presetName, std::string& error) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mBypass = bypassEnabled;
|
||||
mPersistentState.bypass = bypassEnabled;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::SetMixAmount(double mixAmount, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!IsFiniteNumber(mixAmount))
|
||||
const std::string safeStem = MakeSafePresetFileStem(presetName);
|
||||
if (safeStem.empty())
|
||||
{
|
||||
error = "Mix amount must be a finite number.";
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mMixAmount = std::clamp(mixAmount, 0.0, 1.0);
|
||||
mPersistentState.mixAmount = mMixAmount;
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
root.set("version", JsonValue(1.0));
|
||||
root.set("name", JsonValue(Trim(presetName)));
|
||||
root.set("layers", SerializeLayerStackLocked());
|
||||
|
||||
return WriteTextFile(mPresetRoot / (safeStem + ".json"), SerializeJson(root, true), error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::LoadStackPreset(const std::string& presetName, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
const std::string safeStem = MakeSafePresetFileStem(presetName);
|
||||
if (safeStem.empty())
|
||||
{
|
||||
error = "Preset name must include at least one letter or number.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path presetPath = mPresetRoot / (safeStem + ".json");
|
||||
std::string presetText = ReadTextFile(presetPath, error);
|
||||
if (presetText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue root;
|
||||
if (!ParseJson(presetText, root, error))
|
||||
return false;
|
||||
|
||||
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;
|
||||
if (!DeserializeLayerStackLocked(*layersValue, nextLayers, error))
|
||||
return false;
|
||||
|
||||
if (nextLayers.empty())
|
||||
{
|
||||
error = "Preset does not contain any valid layers.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mPersistentState.layers = nextLayers;
|
||||
mReloadRequested = true;
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
@@ -413,17 +593,24 @@ void RuntimeHost::AdvanceFrame()
|
||||
++mFrameCounter;
|
||||
}
|
||||
|
||||
bool RuntimeHost::BuildActiveFragmentShaderSource(std::string& fragmentShaderSource, std::string& error)
|
||||
bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error)
|
||||
{
|
||||
try
|
||||
{
|
||||
ShaderPackage shaderPackage;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
auto it = mPackagesById.find(mActiveShaderId);
|
||||
const LayerPersistentState* layer = FindLayerById(layerId);
|
||||
if (!layer)
|
||||
{
|
||||
error = "Unknown layer id: " + layerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = mPackagesById.find(layer->shaderId);
|
||||
if (it == mPackagesById.end())
|
||||
{
|
||||
error = "No active shader is selected.";
|
||||
error = "Unknown shader id: " + layer->shaderId;
|
||||
return false;
|
||||
}
|
||||
shaderPackage = it->second;
|
||||
@@ -450,49 +637,53 @@ bool RuntimeHost::BuildActiveFragmentShaderSource(std::string& fragmentShaderSou
|
||||
}
|
||||
catch (const std::exception& exception)
|
||||
{
|
||||
error = std::string("RuntimeHost::BuildActiveFragmentShaderSource exception: ") + exception.what();
|
||||
error = std::string("RuntimeHost::BuildLayerFragmentShaderSource exception: ") + exception.what();
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "RuntimeHost::BuildActiveFragmentShaderSource threw a non-standard exception.";
|
||||
error = "RuntimeHost::BuildLayerFragmentShaderSource threw a non-standard exception.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeRenderState RuntimeHost::GetRenderState(unsigned outputWidth, unsigned outputHeight) const
|
||||
std::vector<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
RuntimeRenderState state;
|
||||
state.activeShaderId = mActiveShaderId;
|
||||
state.timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||
state.frameCount = static_cast<double>(mFrameCounter);
|
||||
state.mixAmount = mMixAmount;
|
||||
state.bypass = mBypass ? 1.0 : 0.0;
|
||||
state.inputWidth = mSignalWidth;
|
||||
state.inputHeight = mSignalHeight;
|
||||
state.outputWidth = outputWidth;
|
||||
state.outputHeight = outputHeight;
|
||||
std::vector<RuntimeRenderState> states;
|
||||
|
||||
auto shaderIt = mPackagesById.find(mActiveShaderId);
|
||||
if (shaderIt != mPackagesById.end())
|
||||
for (const LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
auto shaderIt = mPackagesById.find(layer.shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
continue;
|
||||
|
||||
RuntimeRenderState state;
|
||||
state.layerId = layer.id;
|
||||
state.shaderId = layer.shaderId;
|
||||
state.timeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||
state.frameCount = static_cast<double>(mFrameCounter);
|
||||
state.mixAmount = 1.0;
|
||||
state.bypass = layer.bypass ? 1.0 : 0.0;
|
||||
state.inputWidth = mSignalWidth;
|
||||
state.inputHeight = mSignalHeight;
|
||||
state.outputWidth = outputWidth;
|
||||
state.outputHeight = outputHeight;
|
||||
state.parameterDefinitions = shaderIt->second.parameters;
|
||||
auto persistedIt = mPersistentState.parameterValuesByShader.find(mActiveShaderId);
|
||||
|
||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||
{
|
||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
||||
if (persistedIt != mPersistentState.parameterValuesByShader.end())
|
||||
{
|
||||
auto valueIt = persistedIt->second.find(definition.id);
|
||||
if (valueIt != persistedIt->second.end())
|
||||
value = valueIt->second;
|
||||
}
|
||||
auto valueIt = layer.parameterValues.find(definition.id);
|
||||
if (valueIt != layer.parameterValues.end())
|
||||
value = valueIt->second;
|
||||
state.parameterValues[definition.id] = value;
|
||||
}
|
||||
|
||||
states.push_back(state);
|
||||
}
|
||||
|
||||
return state;
|
||||
return states;
|
||||
}
|
||||
|
||||
std::string RuntimeHost::BuildStateJson() const
|
||||
@@ -543,62 +734,102 @@ bool RuntimeHost::LoadPersistentState(std::string& error)
|
||||
if (!ParseJson(stateText, root, error))
|
||||
return false;
|
||||
|
||||
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
|
||||
mPersistentState.activeShaderId = activeShaderValue->asString();
|
||||
if (const JsonValue* mixAmountValue = root.find("mixAmount"))
|
||||
mPersistentState.mixAmount = mixAmountValue->asNumber(1.0);
|
||||
if (const JsonValue* bypassValue = root.find("bypass"))
|
||||
mPersistentState.bypass = bypassValue->asBoolean(false);
|
||||
|
||||
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
|
||||
if (const JsonValue* layersValue = root.find("layers"))
|
||||
{
|
||||
for (const auto& shaderItem : valuesByShader->asObject())
|
||||
for (const JsonValue& layerValue : layersValue->asArray())
|
||||
{
|
||||
std::map<std::string, ShaderParameterValue>& shaderValues = mPersistentState.parameterValuesByShader[shaderItem.first];
|
||||
for (const auto& parameterItem : shaderItem.second.asObject())
|
||||
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"))
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
for (const auto& parameterItem : parameterValues->asObject())
|
||||
{
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
shaderValues[parameterItem.first] = value;
|
||||
}
|
||||
|
||||
if (!layer.shaderId.empty())
|
||||
mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Migrate from the older single-shader state shape.
|
||||
std::string activeShaderId;
|
||||
if (const JsonValue* activeShaderValue = root.find("activeShaderId"))
|
||||
activeShaderId = activeShaderValue->asString();
|
||||
|
||||
if (!activeShaderId.empty())
|
||||
{
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId();
|
||||
layer.shaderId = activeShaderId;
|
||||
layer.bypass = false;
|
||||
|
||||
if (const JsonValue* valuesByShader = root.find("parameterValuesByShader"))
|
||||
{
|
||||
const JsonValue* shaderValues = valuesByShader->find(activeShaderId);
|
||||
if (shaderValues)
|
||||
{
|
||||
for (const auto& parameterItem : shaderValues->asObject())
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
const JsonValue& jsonValue = parameterItem.second;
|
||||
if (jsonValue.isBoolean())
|
||||
value.booleanValue = jsonValue.asBoolean();
|
||||
else if (jsonValue.isString())
|
||||
value.enumValue = jsonValue.asString();
|
||||
else if (jsonValue.isNumber())
|
||||
value.numberValues.push_back(jsonValue.asNumber());
|
||||
else if (jsonValue.isArray())
|
||||
value.numberValues = JsonArrayToNumbers(jsonValue);
|
||||
layer.parameterValues[parameterItem.first] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPersistentState.layers.push_back(layer);
|
||||
}
|
||||
}
|
||||
|
||||
mActiveShaderId = mPersistentState.activeShaderId;
|
||||
mMixAmount = std::clamp(mPersistentState.mixAmount, 0.0, 1.0);
|
||||
mBypass = mPersistentState.bypass;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::SavePersistentState(std::string& error) const
|
||||
{
|
||||
JsonValue root = JsonValue::MakeObject();
|
||||
root.set("activeShaderId", JsonValue(mActiveShaderId));
|
||||
root.set("mixAmount", JsonValue(mMixAmount));
|
||||
root.set("bypass", JsonValue(mBypass));
|
||||
|
||||
JsonValue valuesByShader = JsonValue::MakeObject();
|
||||
for (const auto& shaderItem : mPersistentState.parameterValuesByShader)
|
||||
JsonValue layers = JsonValue::MakeArray();
|
||||
for (const LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
JsonValue shaderValues = JsonValue::MakeObject();
|
||||
auto packageIt = mPackagesById.find(shaderItem.first);
|
||||
for (const auto& parameterItem : shaderItem.second)
|
||||
JsonValue layerValue = JsonValue::MakeObject();
|
||||
layerValue.set("id", JsonValue(layer.id));
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
|
||||
JsonValue parameterValues = JsonValue::MakeObject();
|
||||
auto packageIt = mPackagesById.find(layer.shaderId);
|
||||
for (const auto& parameterItem : layer.parameterValues)
|
||||
{
|
||||
const ShaderParameterDefinition* definition = nullptr;
|
||||
if (packageIt != mPackagesById.end())
|
||||
@@ -614,11 +845,13 @@ bool RuntimeHost::SavePersistentState(std::string& error) const
|
||||
}
|
||||
|
||||
if (definition)
|
||||
shaderValues.set(parameterItem.first, SerializeParameterValue(*definition, parameterItem.second));
|
||||
parameterValues.set(parameterItem.first, SerializeParameterValue(*definition, parameterItem.second));
|
||||
}
|
||||
valuesByShader.set(shaderItem.first, shaderValues);
|
||||
|
||||
layerValue.set("parameterValues", parameterValues);
|
||||
layers.pushBack(layerValue);
|
||||
}
|
||||
root.set("parameterValuesByShader", valuesByShader);
|
||||
root.set("layers", layers);
|
||||
|
||||
return WriteTextFile(mRuntimeStatePath, SerializeJson(root, true), error);
|
||||
}
|
||||
@@ -653,7 +886,6 @@ bool RuntimeHost::ScanShaderPackages(std::string& error)
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureParameterDefaultsLocked(shaderPackage);
|
||||
packageOrder.push_back(shaderPackage.id);
|
||||
packagesById[shaderPackage.id] = shaderPackage;
|
||||
}
|
||||
@@ -662,16 +894,12 @@ bool RuntimeHost::ScanShaderPackages(std::string& error)
|
||||
mPackagesById.swap(packagesById);
|
||||
mPackageOrder.swap(packageOrder);
|
||||
|
||||
if (!mActiveShaderId.empty() && mPackagesById.find(mActiveShaderId) == mPackagesById.end())
|
||||
for (auto it = mPersistentState.layers.begin(); it != mPersistentState.layers.end();)
|
||||
{
|
||||
mActiveShaderId.clear();
|
||||
mPersistentState.activeShaderId.clear();
|
||||
}
|
||||
|
||||
if (mActiveShaderId.empty() && !mPackageOrder.empty())
|
||||
{
|
||||
mActiveShaderId = mPackageOrder.front();
|
||||
mPersistentState.activeShaderId = mActiveShaderId;
|
||||
if (mPackagesById.find(it->shaderId) == mPackagesById.end())
|
||||
it = mPersistentState.layers.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -739,21 +967,13 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath,
|
||||
if (const JsonValue* defaultValue = parameterJson.find("default"))
|
||||
{
|
||||
if (definition.type == ShaderParameterType::Boolean)
|
||||
{
|
||||
definition.defaultBoolean = defaultValue->asBoolean(false);
|
||||
}
|
||||
else if (definition.type == ShaderParameterType::Enum)
|
||||
{
|
||||
definition.defaultEnumValue = defaultValue->asString();
|
||||
}
|
||||
else if (defaultValue->isNumber())
|
||||
{
|
||||
definition.defaultNumbers.push_back(defaultValue->asNumber());
|
||||
}
|
||||
else if (defaultValue->isArray())
|
||||
{
|
||||
definition.defaultNumbers = JsonArrayToNumbers(*defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (const JsonValue* minValue = parameterJson.find("min"))
|
||||
@@ -934,13 +1154,12 @@ ShaderParameterValue RuntimeHost::DefaultValueForDefinition(const ShaderParamete
|
||||
return value;
|
||||
}
|
||||
|
||||
void RuntimeHost::EnsureParameterDefaultsLocked(ShaderPackage& shaderPackage)
|
||||
void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const
|
||||
{
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
{
|
||||
auto& shaderValues = mPersistentState.parameterValuesByShader[shaderPackage.id];
|
||||
if (shaderValues.find(definition.id) == shaderValues.end())
|
||||
shaderValues[definition.id] = DefaultValueForDefinition(definition);
|
||||
if (layerState.parameterValues.find(definition.id) == layerState.parameterValues.end())
|
||||
layerState.parameterValues[definition.id] = DefaultValueForDefinition(definition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,10 +1346,12 @@ bool RuntimeHost::ResolvePaths(std::string& error)
|
||||
return false;
|
||||
}
|
||||
|
||||
mUiRoot = mRepoRoot / "ui";
|
||||
const std::filesystem::path builtUiRoot = mRepoRoot / "ui" / "dist";
|
||||
mUiRoot = std::filesystem::exists(builtUiRoot) ? builtUiRoot : (mRepoRoot / "ui");
|
||||
mConfigPath = mRepoRoot / "config" / "runtime-host.json";
|
||||
mShaderRoot = mRepoRoot / mConfig.shaderLibrary;
|
||||
mRuntimeRoot = mRepoRoot / "runtime";
|
||||
mPresetRoot = mRuntimeRoot / "stack_presets";
|
||||
mRuntimeStatePath = mRuntimeRoot / "runtime_state.json";
|
||||
mWrapperPath = mRuntimeRoot / "shader_cache" / "active_shader_wrapper.slang";
|
||||
mGeneratedGlslPath = mRuntimeRoot / "shader_cache" / "active_shader.raw.frag";
|
||||
@@ -1138,6 +1359,7 @@ bool RuntimeHost::ResolvePaths(std::string& error)
|
||||
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(mRuntimeRoot / "shader_cache", fsError);
|
||||
std::filesystem::create_directories(mPresetRoot, fsError);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1153,11 +1375,9 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
root.set("app", app);
|
||||
|
||||
JsonValue runtime = JsonValue::MakeObject();
|
||||
runtime.set("activeShaderId", JsonValue(mActiveShaderId));
|
||||
runtime.set("layerCount", JsonValue(static_cast<double>(mPersistentState.layers.size())));
|
||||
runtime.set("compileSucceeded", JsonValue(mCompileSucceeded));
|
||||
runtime.set("compileMessage", JsonValue(mCompileMessage));
|
||||
runtime.set("mixAmount", JsonValue(mMixAmount));
|
||||
runtime.set("bypass", JsonValue(mBypass));
|
||||
root.set("runtime", runtime);
|
||||
|
||||
JsonValue video = JsonValue::MakeObject();
|
||||
@@ -1174,23 +1394,49 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
performance.set("budgetUsedPercent", JsonValue(mFrameBudgetMilliseconds > 0.0 ? (mSmoothedRenderMilliseconds / mFrameBudgetMilliseconds) * 100.0 : 0.0));
|
||||
root.set("performance", performance);
|
||||
|
||||
JsonValue shaders = JsonValue::MakeArray();
|
||||
JsonValue shaderLibrary = JsonValue::MakeArray();
|
||||
for (const std::string& shaderId : mPackageOrder)
|
||||
{
|
||||
auto shaderIt = mPackagesById.find(shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
continue;
|
||||
|
||||
const ShaderPackage& shaderPackage = shaderIt->second;
|
||||
JsonValue shader = JsonValue::MakeObject();
|
||||
shader.set("id", JsonValue(shaderPackage.id));
|
||||
shader.set("name", JsonValue(shaderPackage.displayName));
|
||||
shader.set("description", JsonValue(shaderPackage.description));
|
||||
shader.set("category", JsonValue(shaderPackage.category));
|
||||
shader.set("id", JsonValue(shaderIt->second.id));
|
||||
shader.set("name", JsonValue(shaderIt->second.displayName));
|
||||
shader.set("description", JsonValue(shaderIt->second.description));
|
||||
shader.set("category", JsonValue(shaderIt->second.category));
|
||||
shaderLibrary.pushBack(shader);
|
||||
}
|
||||
root.set("shaders", shaderLibrary);
|
||||
|
||||
JsonValue stackPresets = JsonValue::MakeArray();
|
||||
for (const std::string& presetName : GetStackPresetNamesLocked())
|
||||
stackPresets.pushBack(JsonValue(presetName));
|
||||
root.set("stackPresets", stackPresets);
|
||||
|
||||
root.set("layers", SerializeLayerStackLocked());
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
JsonValue RuntimeHost::SerializeLayerStackLocked() const
|
||||
{
|
||||
JsonValue layers = JsonValue::MakeArray();
|
||||
for (const LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
auto shaderIt = mPackagesById.find(layer.shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
continue;
|
||||
|
||||
JsonValue layerValue = JsonValue::MakeObject();
|
||||
layerValue.set("id", JsonValue(layer.id));
|
||||
layerValue.set("shaderId", JsonValue(layer.shaderId));
|
||||
layerValue.set("shaderName", JsonValue(shaderIt->second.displayName));
|
||||
layerValue.set("bypass", JsonValue(layer.bypass));
|
||||
|
||||
JsonValue parameters = JsonValue::MakeArray();
|
||||
auto persistedIt = mPersistentState.parameterValuesByShader.find(shaderPackage.id);
|
||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||
for (const ShaderParameterDefinition& definition : shaderIt->second.parameters)
|
||||
{
|
||||
JsonValue parameter = JsonValue::MakeObject();
|
||||
parameter.set("id", JsonValue(definition.id));
|
||||
@@ -1218,7 +1464,6 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
stepValue.pushBack(JsonValue(number));
|
||||
parameter.set("step", stepValue);
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
{
|
||||
JsonValue options = JsonValue::MakeArray();
|
||||
@@ -1233,22 +1478,119 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
}
|
||||
|
||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
||||
if (persistedIt != mPersistentState.parameterValuesByShader.end())
|
||||
{
|
||||
auto valueIt = persistedIt->second.find(definition.id);
|
||||
if (valueIt != persistedIt->second.end())
|
||||
value = valueIt->second;
|
||||
}
|
||||
auto valueIt = layer.parameterValues.find(definition.id);
|
||||
if (valueIt != layer.parameterValues.end())
|
||||
value = valueIt->second;
|
||||
parameter.set("value", SerializeParameterValue(definition, value));
|
||||
parameters.pushBack(parameter);
|
||||
}
|
||||
|
||||
shader.set("parameters", parameters);
|
||||
shaders.pushBack(shader);
|
||||
layerValue.set("parameters", parameters);
|
||||
layers.pushBack(layerValue);
|
||||
}
|
||||
root.set("shaders", shaders);
|
||||
return layers;
|
||||
}
|
||||
|
||||
return root;
|
||||
bool RuntimeHost::DeserializeLayerStackLocked(const JsonValue& layersValue, std::vector<LayerPersistentState>& layers, 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();
|
||||
auto shaderIt = mPackagesById.find(shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
{
|
||||
error = "Preset references unknown shader id: " + shaderId;
|
||||
return false;
|
||||
}
|
||||
|
||||
LayerPersistentState layer;
|
||||
layer.id = GenerateLayerId();
|
||||
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(shaderIt->second.parameters.begin(), shaderIt->second.parameters.end(),
|
||||
[¶meterId](const ShaderParameterDefinition& definition) { return definition.id == parameterId; });
|
||||
if (definitionIt == shaderIt->second.parameters.end())
|
||||
continue;
|
||||
|
||||
ShaderParameterValue normalizedValue;
|
||||
if (!NormalizeAndValidateValue(*definitionIt, *valueValue, normalizedValue, error))
|
||||
return false;
|
||||
|
||||
layer.parameterValues[parameterId] = normalizedValue;
|
||||
}
|
||||
}
|
||||
|
||||
EnsureLayerDefaultsLocked(layer, shaderIt->second);
|
||||
layers.push_back(layer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> RuntimeHost::GetStackPresetNamesLocked() const
|
||||
{
|
||||
std::vector<std::string> presetNames;
|
||||
std::error_code fsError;
|
||||
if (!std::filesystem::exists(mPresetRoot, fsError))
|
||||
return presetNames;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(mPresetRoot, fsError))
|
||||
{
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
if (ToLowerCopy(entry.path().extension().string()) != ".json")
|
||||
continue;
|
||||
presetNames.push_back(entry.path().stem().string());
|
||||
}
|
||||
|
||||
std::sort(presetNames.begin(), presetNames.end());
|
||||
return presetNames;
|
||||
}
|
||||
|
||||
std::string RuntimeHost::MakeSafePresetFileStem(const std::string& presetName) const
|
||||
{
|
||||
std::string trimmed = Trim(presetName);
|
||||
std::string safe;
|
||||
safe.reserve(trimmed.size());
|
||||
|
||||
for (unsigned char ch : trimmed)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
safe.push_back(static_cast<char>(std::tolower(ch)));
|
||||
else if (ch == ' ' || ch == '-' || ch == '_')
|
||||
{
|
||||
if (safe.empty() || safe.back() == '-')
|
||||
continue;
|
||||
safe.push_back('-');
|
||||
}
|
||||
}
|
||||
|
||||
while (!safe.empty() && safe.back() == '-')
|
||||
safe.pop_back();
|
||||
|
||||
return safe;
|
||||
}
|
||||
|
||||
JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const
|
||||
@@ -1272,3 +1614,28 @@ JsonValue RuntimeHost::SerializeParameterValue(const ShaderParameterDefinition&
|
||||
}
|
||||
return JsonValue();
|
||||
}
|
||||
|
||||
RuntimeHost::LayerPersistentState* RuntimeHost::FindLayerById(const std::string& layerId)
|
||||
{
|
||||
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
return it == mPersistentState.layers.end() ? nullptr : &*it;
|
||||
}
|
||||
|
||||
const RuntimeHost::LayerPersistentState* RuntimeHost::FindLayerById(const std::string& layerId) const
|
||||
{
|
||||
auto it = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
[&layerId](const LayerPersistentState& layer) { return layer.id == layerId; });
|
||||
return it == mPersistentState.layers.end() ? nullptr : &*it;
|
||||
}
|
||||
|
||||
std::string RuntimeHost::GenerateLayerId()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
++mNextLayerId;
|
||||
const std::string candidate = "layer-" + std::to_string(mNextLayerId);
|
||||
if (!FindLayerById(candidate))
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user