Compare commits
3 Commits
4096e9c26a
...
4ddb5b6428
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ddb5b6428 | |||
| 108edc096e | |||
| 269dbd0079 |
@@ -1,12 +1,7 @@
|
||||
#include "RuntimeLayerController.h"
|
||||
|
||||
#include "AppConfigProvider.h"
|
||||
#include "RuntimeJson.h"
|
||||
#include "../logging/Logger.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
@@ -16,103 +11,6 @@ namespace
|
||||
constexpr std::size_t kMaxConcurrentRuntimeShaderBuilds = 2;
|
||||
}
|
||||
|
||||
bool RuntimeLayerController::LoadSupportedShaderCatalog(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames)
|
||||
{
|
||||
const std::filesystem::path shaderRoot = FindRepoPath(shaderLibrary);
|
||||
std::string error;
|
||||
if (!mShaderCatalog.Load(shaderRoot, maxTemporalHistoryFrames, error))
|
||||
{
|
||||
LogWarning("runtime-shader", "Supported shader catalog is empty: " + error);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t preparedFontAtlases = 0;
|
||||
for (const auto& entry : mShaderCatalog.FontAtlases())
|
||||
preparedFontAtlases += entry.second.size();
|
||||
|
||||
Log(
|
||||
"runtime-shader",
|
||||
"Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) +
|
||||
" shader(s), prepared " + std::to_string(preparedFontAtlases) + " font atlas asset(s).");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeLayerController::InitializeLayerModel(std::string& runtimeShaderId)
|
||||
{
|
||||
if (InitializeLayerModelFromRuntimeState())
|
||||
return;
|
||||
|
||||
if (!runtimeShaderId.empty())
|
||||
Log("runtime-state", "Falling back to configured runtime shader '" + runtimeShaderId + "'.");
|
||||
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
std::string error;
|
||||
if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, runtimeShaderId, error))
|
||||
{
|
||||
LogWarning("runtime-shader", error + " Runtime shader build disabled.");
|
||||
runtimeShaderId.clear();
|
||||
mRuntimeLayerModel.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLayerController::InitializeLayerModelFromRuntimeState()
|
||||
{
|
||||
const std::filesystem::path runtimeStatePath = mRuntimeStatePath.empty() ? ResolveRuntimeStatePath() : mRuntimeStatePath;
|
||||
if (runtimeStatePath.empty())
|
||||
return false;
|
||||
if (!std::filesystem::exists(runtimeStatePath))
|
||||
return false;
|
||||
|
||||
std::ifstream input(runtimeStatePath, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
LogWarning("runtime-state", "Could not open runtime state file: " + runtimeStatePath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
|
||||
JsonValue runtimeState;
|
||||
std::string error;
|
||||
if (!ParseJson(buffer.str(), runtimeState, error))
|
||||
{
|
||||
LogWarning("runtime-state", "Could not parse runtime state file: " + error);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
if (!mRuntimeLayerModel.InitializeFromRuntimeState(mShaderCatalog, runtimeState, error))
|
||||
{
|
||||
LogWarning("runtime-state", "Could not restore runtime state: " + error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log("runtime-state", "Restored runtime layer stack from " + runtimeStatePath.string() + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path RuntimeLayerController::ResolveRuntimeStatePath() const
|
||||
{
|
||||
const std::filesystem::path runtimeDirectory = FindRepoPath("runtime");
|
||||
if (!runtimeDirectory.empty())
|
||||
return runtimeDirectory / "runtime_state.json";
|
||||
return std::filesystem::current_path() / "runtime" / "runtime_state.json";
|
||||
}
|
||||
|
||||
void RuntimeLayerController::RequestRuntimeStatePersistence()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
RequestRuntimeStatePersistenceLocked();
|
||||
}
|
||||
|
||||
void RuntimeLayerController::RequestRuntimeStatePersistenceLocked()
|
||||
{
|
||||
mPersistenceWriter.RequestSave(mRuntimeLayerModel.Snapshot());
|
||||
}
|
||||
|
||||
void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool preserveExistingRenderArtifact)
|
||||
{
|
||||
CleanupRetiredShaderBuilds();
|
||||
@@ -255,10 +153,4 @@ void RuntimeLayerController::StopAllRuntimeShaderBuilds()
|
||||
for (auto& bridge : retiredBuilds)
|
||||
bridge->Stop();
|
||||
}
|
||||
|
||||
std::string RuntimeLayerController::FirstRuntimeLayerId() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
return mRuntimeLayerModel.FirstLayerId();
|
||||
}
|
||||
}
|
||||
115
src/app/RuntimeLayerControllerStartup.cpp
Normal file
115
src/app/RuntimeLayerControllerStartup.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "RuntimeLayerController.h"
|
||||
|
||||
#include "AppConfigProvider.h"
|
||||
#include "RuntimeJson.h"
|
||||
#include "../logging/Logger.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
bool RuntimeLayerController::LoadSupportedShaderCatalog(const std::string& shaderLibrary, unsigned maxTemporalHistoryFrames)
|
||||
{
|
||||
const std::filesystem::path shaderRoot = FindRepoPath(shaderLibrary);
|
||||
std::string error;
|
||||
if (!mShaderCatalog.Load(shaderRoot, maxTemporalHistoryFrames, error))
|
||||
{
|
||||
LogWarning("runtime-shader", "Supported shader catalog is empty: " + error);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t preparedFontAtlases = 0;
|
||||
for (const auto& entry : mShaderCatalog.FontAtlases())
|
||||
preparedFontAtlases += entry.second.size();
|
||||
|
||||
Log(
|
||||
"runtime-shader",
|
||||
"Supported shader catalog loaded with " + std::to_string(mShaderCatalog.Shaders().size()) +
|
||||
" shader(s), prepared " + std::to_string(preparedFontAtlases) + " font atlas asset(s).");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeLayerController::InitializeLayerModel(std::string& runtimeShaderId)
|
||||
{
|
||||
if (InitializeLayerModelFromRuntimeState())
|
||||
return;
|
||||
|
||||
if (!runtimeShaderId.empty())
|
||||
Log("runtime-state", "Falling back to configured runtime shader '" + runtimeShaderId + "'.");
|
||||
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
std::string error;
|
||||
if (!mRuntimeLayerModel.InitializeSingleLayer(mShaderCatalog, runtimeShaderId, error))
|
||||
{
|
||||
LogWarning("runtime-shader", error + " Runtime shader build disabled.");
|
||||
runtimeShaderId.clear();
|
||||
mRuntimeLayerModel.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLayerController::InitializeLayerModelFromRuntimeState()
|
||||
{
|
||||
const std::filesystem::path runtimeStatePath = mRuntimeStatePath.empty() ? ResolveRuntimeStatePath() : mRuntimeStatePath;
|
||||
if (runtimeStatePath.empty())
|
||||
return false;
|
||||
if (!std::filesystem::exists(runtimeStatePath))
|
||||
return false;
|
||||
|
||||
std::ifstream input(runtimeStatePath, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
LogWarning("runtime-state", "Could not open runtime state file: " + runtimeStatePath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
|
||||
JsonValue runtimeState;
|
||||
std::string error;
|
||||
if (!ParseJson(buffer.str(), runtimeState, error))
|
||||
{
|
||||
LogWarning("runtime-state", "Could not parse runtime state file: " + error);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
if (!mRuntimeLayerModel.InitializeFromRuntimeState(mShaderCatalog, runtimeState, error))
|
||||
{
|
||||
LogWarning("runtime-state", "Could not restore runtime state: " + error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log("runtime-state", "Restored runtime layer stack from " + runtimeStatePath.string() + ".");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path RuntimeLayerController::ResolveRuntimeStatePath() const
|
||||
{
|
||||
const std::filesystem::path runtimeDirectory = FindRepoPath("runtime");
|
||||
if (!runtimeDirectory.empty())
|
||||
return runtimeDirectory / "runtime_state.json";
|
||||
return std::filesystem::current_path() / "runtime" / "runtime_state.json";
|
||||
}
|
||||
|
||||
void RuntimeLayerController::RequestRuntimeStatePersistence()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
RequestRuntimeStatePersistenceLocked();
|
||||
}
|
||||
|
||||
void RuntimeLayerController::RequestRuntimeStatePersistenceLocked()
|
||||
{
|
||||
mPersistenceWriter.RequestSave(mRuntimeLayerModel.Snapshot());
|
||||
}
|
||||
|
||||
std::string RuntimeLayerController::FirstRuntimeLayerId() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
|
||||
return mRuntimeLayerModel.FirstLayerId();
|
||||
}
|
||||
}
|
||||
@@ -5,73 +5,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
JsonValue ParameterValueToJson(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
||||
{
|
||||
switch (definition.type)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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());
|
||||
}
|
||||
return JsonValue();
|
||||
}
|
||||
|
||||
bool ParseRuntimeLayerNumber(const std::string& layerId, uint64_t& number)
|
||||
{
|
||||
const std::string prefix = "runtime-layer-";
|
||||
if (layerId.compare(0, prefix.size(), prefix) != 0)
|
||||
return false;
|
||||
|
||||
const std::string suffix = layerId.substr(prefix.size());
|
||||
if (suffix.empty())
|
||||
return false;
|
||||
|
||||
uint64_t parsed = 0;
|
||||
for (char character : suffix)
|
||||
{
|
||||
if (!std::isdigit(static_cast<unsigned char>(character)))
|
||||
return false;
|
||||
parsed = parsed * 10 + static_cast<uint64_t>(character - '0');
|
||||
}
|
||||
|
||||
number = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string AllocateRestoredLayerId(std::set<std::string>& usedLayerIds, uint64_t& nextLayerNumber)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::string candidate = "runtime-layer-" + std::to_string(nextLayerNumber++);
|
||||
if (usedLayerIds.insert(candidate).second)
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
|
||||
{
|
||||
Clear();
|
||||
@@ -270,145 +207,6 @@ bool RuntimeLayerModel::ResetParameters(const std::string& layerId, std::string&
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeLayerModel::InitializeFromRuntimeState(const SupportedShaderCatalog& shaderCatalog, const JsonValue& runtimeState, std::string& error)
|
||||
{
|
||||
if (!runtimeState.isObject())
|
||||
{
|
||||
error = "Runtime state root must be a JSON object.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const JsonValue* layersValue = runtimeState.find("layers");
|
||||
if (!layersValue || !layersValue->isArray())
|
||||
{
|
||||
error = "Runtime state must contain a layers array.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Layer> restoredLayers;
|
||||
std::set<std::string> usedLayerIds;
|
||||
uint64_t nextLayerNumber = 1;
|
||||
|
||||
for (const JsonValue& layerValue : layersValue->asArray())
|
||||
{
|
||||
if (!layerValue.isObject())
|
||||
continue;
|
||||
|
||||
const JsonValue* shaderIdValue = layerValue.find("shaderId");
|
||||
if (!shaderIdValue || !shaderIdValue->isString() || shaderIdValue->asString().empty())
|
||||
continue;
|
||||
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderIdValue->asString());
|
||||
if (!shaderPackage)
|
||||
continue;
|
||||
|
||||
Layer layer;
|
||||
const JsonValue* layerIdValue = layerValue.find("id");
|
||||
if (layerIdValue && layerIdValue->isString() && !layerIdValue->asString().empty() && usedLayerIds.insert(layerIdValue->asString()).second)
|
||||
layer.id = layerIdValue->asString();
|
||||
else
|
||||
layer.id = AllocateRestoredLayerId(usedLayerIds, nextLayerNumber);
|
||||
|
||||
uint64_t restoredLayerNumber = 0;
|
||||
if (ParseRuntimeLayerNumber(layer.id, restoredLayerNumber) && restoredLayerNumber >= nextLayerNumber)
|
||||
nextLayerNumber = restoredLayerNumber + 1;
|
||||
|
||||
layer.shaderId = shaderPackage->id;
|
||||
layer.packageFingerprint = ShaderPackageFingerprint(*shaderPackage);
|
||||
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||
const JsonValue* bypassValue = layerValue.find("bypass");
|
||||
layer.bypass = bypassValue && bypassValue->isBoolean() ? bypassValue->asBoolean() : false;
|
||||
layer.buildState = RuntimeLayerBuildState::Pending;
|
||||
layer.message = "Runtime Slang build is waiting to start.";
|
||||
InitializeDefaultParameterValues(layer, *shaderPackage);
|
||||
|
||||
const JsonValue* parameterValues = layerValue.find("parameterValues");
|
||||
if (parameterValues && parameterValues->isObject())
|
||||
{
|
||||
for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
|
||||
{
|
||||
const JsonValue* value = parameterValues->find(definition.id);
|
||||
if (!value)
|
||||
continue;
|
||||
|
||||
ShaderParameterValue normalizedValue;
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateParameterValue(definition, *value, normalizedValue, normalizeError))
|
||||
layer.parameterValues[definition.id] = normalizedValue;
|
||||
}
|
||||
}
|
||||
|
||||
restoredLayers.push_back(std::move(layer));
|
||||
}
|
||||
|
||||
if (restoredLayers.empty())
|
||||
{
|
||||
error = "Runtime state did not contain any supported layers.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mLayers = std::move(restoredLayers);
|
||||
mNextLayerNumber = nextLayerNumber;
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeLayerModel::ReloadFromCatalog(const SupportedShaderCatalog& shaderCatalog, std::vector<std::pair<std::string, std::string>>& buildsToStart, std::string& error)
|
||||
{
|
||||
buildsToStart.clear();
|
||||
for (Layer& layer : mLayers)
|
||||
{
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
layer.buildState = RuntimeLayerBuildState::Failed;
|
||||
layer.message = "Shader '" + layer.shaderId + "' is no longer available after reload.";
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string nextFingerprint = ShaderPackageFingerprint(*shaderPackage);
|
||||
if (layer.packageFingerprint == nextFingerprint)
|
||||
{
|
||||
buildsToStart.push_back({ layer.id, layer.shaderId });
|
||||
continue;
|
||||
}
|
||||
|
||||
std::map<std::string, ShaderParameterDefinition> previousDefinitions;
|
||||
for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
|
||||
previousDefinitions[definition.id] = definition;
|
||||
|
||||
std::map<std::string, ShaderParameterValue> nextValues;
|
||||
for (const ShaderParameterDefinition& nextDefinition : shaderPackage->parameters)
|
||||
{
|
||||
const auto previousDefinitionIt = previousDefinitions.find(nextDefinition.id);
|
||||
const auto previousValueIt = layer.parameterValues.find(nextDefinition.id);
|
||||
if (previousDefinitionIt != previousDefinitions.end()
|
||||
&& previousValueIt != layer.parameterValues.end()
|
||||
&& previousDefinitionIt->second.type == nextDefinition.type)
|
||||
{
|
||||
ShaderParameterValue preservedValue;
|
||||
JsonValue valueJson = ParameterValueToJson(previousDefinitionIt->second, previousValueIt->second);
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateParameterValue(nextDefinition, valueJson, preservedValue, normalizeError))
|
||||
{
|
||||
nextValues[nextDefinition.id] = preservedValue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
nextValues[nextDefinition.id] = DefaultValueForDefinition(nextDefinition);
|
||||
}
|
||||
|
||||
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||
layer.packageFingerprint = nextFingerprint;
|
||||
layer.parameterDefinitions = shaderPackage->parameters;
|
||||
layer.parameterValues = std::move(nextValues);
|
||||
buildsToStart.push_back({ layer.id, layer.shaderId });
|
||||
}
|
||||
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeLayerModel::Clear()
|
||||
{
|
||||
mLayers.clear();
|
||||
@@ -508,37 +306,6 @@ bool RuntimeLayerModel::MarkRenderCommitFailed(const std::string& layerId, const
|
||||
return MarkBuildFailed(layerId, message, error);
|
||||
}
|
||||
|
||||
RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
|
||||
{
|
||||
RuntimeLayerModelSnapshot snapshot;
|
||||
snapshot.compileSucceeded = true;
|
||||
|
||||
for (const Layer& layer : mLayers)
|
||||
{
|
||||
snapshot.displayLayers.push_back(ToReadModel(layer));
|
||||
if (!layer.message.empty() && snapshot.compileMessage.empty())
|
||||
snapshot.compileMessage = layer.message;
|
||||
if (layer.buildState == RuntimeLayerBuildState::Failed)
|
||||
snapshot.compileSucceeded = false;
|
||||
if (layer.renderReady)
|
||||
{
|
||||
RuntimeRenderLayerModel renderLayer;
|
||||
renderLayer.id = layer.id;
|
||||
renderLayer.bypass = layer.bypass;
|
||||
renderLayer.artifact = layer.artifact;
|
||||
renderLayer.shaderId = renderLayer.artifact.shaderId.empty() ? layer.shaderId : renderLayer.artifact.shaderId;
|
||||
if (layer.buildState == RuntimeLayerBuildState::Ready)
|
||||
renderLayer.artifact.parameterValues = layer.parameterValues;
|
||||
renderLayer.artifact.fontAtlases.clear();
|
||||
snapshot.renderLayers.push_back(std::move(renderLayer));
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshot.compileMessage.empty())
|
||||
snapshot.compileMessage = mLayers.empty() ? "Runtime shader build disabled." : "Runtime shader build has not completed yet.";
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::string RuntimeLayerModel::FirstLayerId() const
|
||||
{
|
||||
return mLayers.empty() ? std::string() : mLayers.front().id;
|
||||
@@ -608,21 +375,6 @@ std::string RuntimeLayerModel::AllocateLayerId()
|
||||
return "runtime-layer-" + std::to_string(mNextLayerNumber++);
|
||||
}
|
||||
|
||||
RuntimeLayerReadModel RuntimeLayerModel::ToReadModel(const Layer& layer)
|
||||
{
|
||||
RuntimeLayerReadModel readModel;
|
||||
readModel.id = layer.id;
|
||||
readModel.shaderId = layer.shaderId;
|
||||
readModel.shaderName = layer.shaderName;
|
||||
readModel.bypass = layer.bypass;
|
||||
readModel.buildState = layer.buildState;
|
||||
readModel.message = layer.message;
|
||||
readModel.renderReady = layer.renderReady;
|
||||
readModel.parameterDefinitions = layer.parameterDefinitions;
|
||||
readModel.parameterValues = layer.parameterValues;
|
||||
return readModel;
|
||||
}
|
||||
|
||||
double RuntimeLayerModel::RuntimeElapsedSeconds() const
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||
|
||||
94
src/runtime/RuntimeLayerReload.cpp
Normal file
94
src/runtime/RuntimeLayerReload.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "RuntimeLayerModel.h"
|
||||
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
JsonValue ParameterValueToJson(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
||||
{
|
||||
switch (definition.type)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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());
|
||||
}
|
||||
return JsonValue();
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLayerModel::ReloadFromCatalog(const SupportedShaderCatalog& shaderCatalog, std::vector<std::pair<std::string, std::string>>& buildsToStart, std::string& error)
|
||||
{
|
||||
buildsToStart.clear();
|
||||
for (Layer& layer : mLayers)
|
||||
{
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(layer.shaderId);
|
||||
if (!shaderPackage)
|
||||
{
|
||||
layer.buildState = RuntimeLayerBuildState::Failed;
|
||||
layer.message = "Shader '" + layer.shaderId + "' is no longer available after reload.";
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string nextFingerprint = ShaderPackageFingerprint(*shaderPackage);
|
||||
if (layer.packageFingerprint == nextFingerprint)
|
||||
{
|
||||
buildsToStart.push_back({ layer.id, layer.shaderId });
|
||||
continue;
|
||||
}
|
||||
|
||||
std::map<std::string, ShaderParameterDefinition> previousDefinitions;
|
||||
for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
|
||||
previousDefinitions[definition.id] = definition;
|
||||
|
||||
std::map<std::string, ShaderParameterValue> nextValues;
|
||||
for (const ShaderParameterDefinition& nextDefinition : shaderPackage->parameters)
|
||||
{
|
||||
const auto previousDefinitionIt = previousDefinitions.find(nextDefinition.id);
|
||||
const auto previousValueIt = layer.parameterValues.find(nextDefinition.id);
|
||||
if (previousDefinitionIt != previousDefinitions.end()
|
||||
&& previousValueIt != layer.parameterValues.end()
|
||||
&& previousDefinitionIt->second.type == nextDefinition.type)
|
||||
{
|
||||
ShaderParameterValue preservedValue;
|
||||
JsonValue valueJson = ParameterValueToJson(previousDefinitionIt->second, previousValueIt->second);
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateParameterValue(nextDefinition, valueJson, preservedValue, normalizeError))
|
||||
{
|
||||
nextValues[nextDefinition.id] = preservedValue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
nextValues[nextDefinition.id] = DefaultValueForDefinition(nextDefinition);
|
||||
}
|
||||
|
||||
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||
layer.packageFingerprint = nextFingerprint;
|
||||
layer.parameterDefinitions = shaderPackage->parameters;
|
||||
layer.parameterValues = std::move(nextValues);
|
||||
buildsToStart.push_back({ layer.id, layer.shaderId });
|
||||
}
|
||||
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
52
src/runtime/RuntimeLayerSnapshot.cpp
Normal file
52
src/runtime/RuntimeLayerSnapshot.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "RuntimeLayerModel.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
RuntimeLayerModelSnapshot RuntimeLayerModel::Snapshot() const
|
||||
{
|
||||
RuntimeLayerModelSnapshot snapshot;
|
||||
snapshot.compileSucceeded = true;
|
||||
|
||||
for (const Layer& layer : mLayers)
|
||||
{
|
||||
snapshot.displayLayers.push_back(ToReadModel(layer));
|
||||
if (!layer.message.empty() && snapshot.compileMessage.empty())
|
||||
snapshot.compileMessage = layer.message;
|
||||
if (layer.buildState == RuntimeLayerBuildState::Failed)
|
||||
snapshot.compileSucceeded = false;
|
||||
if (layer.renderReady)
|
||||
{
|
||||
RuntimeRenderLayerModel renderLayer;
|
||||
renderLayer.id = layer.id;
|
||||
renderLayer.bypass = layer.bypass;
|
||||
renderLayer.artifact = layer.artifact;
|
||||
renderLayer.shaderId = renderLayer.artifact.shaderId.empty() ? layer.shaderId : renderLayer.artifact.shaderId;
|
||||
if (layer.buildState == RuntimeLayerBuildState::Ready)
|
||||
renderLayer.artifact.parameterValues = layer.parameterValues;
|
||||
renderLayer.artifact.fontAtlases.clear();
|
||||
snapshot.renderLayers.push_back(std::move(renderLayer));
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshot.compileMessage.empty())
|
||||
snapshot.compileMessage = mLayers.empty() ? "Runtime shader build disabled." : "Runtime shader build has not completed yet.";
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
RuntimeLayerReadModel RuntimeLayerModel::ToReadModel(const Layer& layer)
|
||||
{
|
||||
RuntimeLayerReadModel readModel;
|
||||
readModel.id = layer.id;
|
||||
readModel.shaderId = layer.shaderId;
|
||||
readModel.shaderName = layer.shaderName;
|
||||
readModel.bypass = layer.bypass;
|
||||
readModel.buildState = layer.buildState;
|
||||
readModel.message = layer.message;
|
||||
readModel.renderReady = layer.renderReady;
|
||||
readModel.parameterDefinitions = layer.parameterDefinitions;
|
||||
readModel.parameterValues = layer.parameterValues;
|
||||
return readModel;
|
||||
}
|
||||
}
|
||||
129
src/runtime/RuntimeLayerStateRestore.cpp
Normal file
129
src/runtime/RuntimeLayerStateRestore.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "RuntimeLayerModel.h"
|
||||
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool ParseRuntimeLayerNumber(const std::string& layerId, uint64_t& number)
|
||||
{
|
||||
const std::string prefix = "runtime-layer-";
|
||||
if (layerId.compare(0, prefix.size(), prefix) != 0)
|
||||
return false;
|
||||
|
||||
const std::string suffix = layerId.substr(prefix.size());
|
||||
if (suffix.empty())
|
||||
return false;
|
||||
|
||||
uint64_t parsed = 0;
|
||||
for (char character : suffix)
|
||||
{
|
||||
if (!std::isdigit(static_cast<unsigned char>(character)))
|
||||
return false;
|
||||
parsed = parsed * 10 + static_cast<uint64_t>(character - '0');
|
||||
}
|
||||
|
||||
number = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string AllocateRestoredLayerId(std::set<std::string>& usedLayerIds, uint64_t& nextLayerNumber)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::string candidate = "runtime-layer-" + std::to_string(nextLayerNumber++);
|
||||
if (usedLayerIds.insert(candidate).second)
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeLayerModel::InitializeFromRuntimeState(const SupportedShaderCatalog& shaderCatalog, const JsonValue& runtimeState, std::string& error)
|
||||
{
|
||||
if (!runtimeState.isObject())
|
||||
{
|
||||
error = "Runtime state root must be a JSON object.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const JsonValue* layersValue = runtimeState.find("layers");
|
||||
if (!layersValue || !layersValue->isArray())
|
||||
{
|
||||
error = "Runtime state must contain a layers array.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<Layer> restoredLayers;
|
||||
std::set<std::string> usedLayerIds;
|
||||
uint64_t nextLayerNumber = 1;
|
||||
|
||||
for (const JsonValue& layerValue : layersValue->asArray())
|
||||
{
|
||||
if (!layerValue.isObject())
|
||||
continue;
|
||||
|
||||
const JsonValue* shaderIdValue = layerValue.find("shaderId");
|
||||
if (!shaderIdValue || !shaderIdValue->isString() || shaderIdValue->asString().empty())
|
||||
continue;
|
||||
|
||||
const ShaderPackage* shaderPackage = shaderCatalog.FindPackage(shaderIdValue->asString());
|
||||
if (!shaderPackage)
|
||||
continue;
|
||||
|
||||
Layer layer;
|
||||
const JsonValue* layerIdValue = layerValue.find("id");
|
||||
if (layerIdValue && layerIdValue->isString() && !layerIdValue->asString().empty() && usedLayerIds.insert(layerIdValue->asString()).second)
|
||||
layer.id = layerIdValue->asString();
|
||||
else
|
||||
layer.id = AllocateRestoredLayerId(usedLayerIds, nextLayerNumber);
|
||||
|
||||
uint64_t restoredLayerNumber = 0;
|
||||
if (ParseRuntimeLayerNumber(layer.id, restoredLayerNumber) && restoredLayerNumber >= nextLayerNumber)
|
||||
nextLayerNumber = restoredLayerNumber + 1;
|
||||
|
||||
layer.shaderId = shaderPackage->id;
|
||||
layer.packageFingerprint = ShaderPackageFingerprint(*shaderPackage);
|
||||
layer.shaderName = shaderPackage->displayName.empty() ? shaderPackage->id : shaderPackage->displayName;
|
||||
const JsonValue* bypassValue = layerValue.find("bypass");
|
||||
layer.bypass = bypassValue && bypassValue->isBoolean() ? bypassValue->asBoolean() : false;
|
||||
layer.buildState = RuntimeLayerBuildState::Pending;
|
||||
layer.message = "Runtime Slang build is waiting to start.";
|
||||
InitializeDefaultParameterValues(layer, *shaderPackage);
|
||||
|
||||
const JsonValue* parameterValues = layerValue.find("parameterValues");
|
||||
if (parameterValues && parameterValues->isObject())
|
||||
{
|
||||
for (const ShaderParameterDefinition& definition : layer.parameterDefinitions)
|
||||
{
|
||||
const JsonValue* value = parameterValues->find(definition.id);
|
||||
if (!value)
|
||||
continue;
|
||||
|
||||
ShaderParameterValue normalizedValue;
|
||||
std::string normalizeError;
|
||||
if (NormalizeAndValidateParameterValue(definition, *value, normalizedValue, normalizeError))
|
||||
layer.parameterValues[definition.id] = normalizedValue;
|
||||
}
|
||||
}
|
||||
|
||||
restoredLayers.push_back(std::move(layer));
|
||||
}
|
||||
|
||||
if (restoredLayers.empty())
|
||||
{
|
||||
error = "Runtime state did not contain any supported layers.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mLayers = std::move(restoredLayers);
|
||||
mNextLayerNumber = nextLayerNumber;
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
91
src/shader/ShaderManifestAssets.cpp
Normal file
91
src/shader/ShaderManifestAssets.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "stdafx.h"
|
||||
#include "ShaderManifestParser.h"
|
||||
|
||||
namespace ShaderManifestParsing
|
||||
{
|
||||
bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* texturesValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "textures", texturesValue, manifestPath, error))
|
||||
return false;
|
||||
if (!texturesValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& textureJson : texturesValue->asArray())
|
||||
{
|
||||
if (!textureJson.isObject())
|
||||
{
|
||||
error = "Shader texture entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string textureId;
|
||||
std::string texturePath;
|
||||
if (!RequireNonEmptyStringField(textureJson, "id", textureId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(textureJson, "path", texturePath, manifestPath, error))
|
||||
{
|
||||
error = "Shader texture is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(textureId, "textures[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
ShaderTextureAsset textureAsset;
|
||||
textureAsset.id = textureId;
|
||||
textureAsset.path = shaderPackage.directoryPath / texturePath;
|
||||
if (!std::filesystem::exists(textureAsset.path))
|
||||
{
|
||||
error = "Shader texture asset not found for package " + shaderPackage.id + ": " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
textureAsset.writeTime = std::filesystem::last_write_time(textureAsset.path);
|
||||
shaderPackage.textureAssets.push_back(textureAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fontsValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "fonts", fontsValue, manifestPath, error))
|
||||
return false;
|
||||
if (!fontsValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& fontJson : fontsValue->asArray())
|
||||
{
|
||||
if (!fontJson.isObject())
|
||||
{
|
||||
error = "Shader font entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fontId;
|
||||
std::string fontPath;
|
||||
if (!RequireNonEmptyStringField(fontJson, "id", fontId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(fontJson, "path", fontPath, manifestPath, error))
|
||||
{
|
||||
error = "Shader font is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(fontId, "fonts[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
ShaderFontAsset fontAsset;
|
||||
fontAsset.id = fontId;
|
||||
fontAsset.path = shaderPackage.directoryPath / fontPath;
|
||||
if (!std::filesystem::exists(fontAsset.path))
|
||||
{
|
||||
error = "Shader font asset not found for package " + shaderPackage.id + ": " + fontAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
fontAsset.writeTime = std::filesystem::last_write_time(fontAsset.path);
|
||||
shaderPackage.fontAssets.push_back(fontAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
228
src/shader/ShaderManifestParameters.cpp
Normal file
228
src/shader/ShaderManifestParameters.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "stdafx.h"
|
||||
#include "ShaderManifestParser.h"
|
||||
|
||||
namespace ShaderManifestParsing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
||||
{
|
||||
if (typeName == "float")
|
||||
{
|
||||
type = ShaderParameterType::Float;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "vec2")
|
||||
{
|
||||
type = ShaderParameterType::Vec2;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "color")
|
||||
{
|
||||
type = ShaderParameterType::Color;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "bool")
|
||||
{
|
||||
type = ShaderParameterType::Boolean;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "enum")
|
||||
{
|
||||
type = ShaderParameterType::Enum;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "text")
|
||||
{
|
||||
type = ShaderParameterType::Text;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "trigger")
|
||||
{
|
||||
type = ShaderParameterType::Trigger;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (const JsonValue* fieldValue = parameterJson.find(fieldName))
|
||||
return NumberListFromJsonValue(*fieldValue, values, fieldName, manifestPath, error);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* defaultValue = parameterJson.find("default");
|
||||
if (!defaultValue)
|
||||
return true;
|
||||
|
||||
if (definition.type == ShaderParameterType::Boolean)
|
||||
{
|
||||
if (!defaultValue->isBoolean())
|
||||
{
|
||||
error = "Boolean parameter default must be a boolean for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultBoolean = defaultValue->asBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
{
|
||||
if (!defaultValue->isString())
|
||||
{
|
||||
error = "Enum parameter default must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultEnumValue = defaultValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
if (!defaultValue->isString())
|
||||
{
|
||||
error = "Text parameter default must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultTextValue = defaultValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error);
|
||||
}
|
||||
|
||||
bool ParseParameterOptions(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* optionsValue = nullptr;
|
||||
if (!OptionalArrayField(parameterJson, "options", optionsValue, manifestPath, error) || !optionsValue)
|
||||
{
|
||||
error = "Enum parameter is missing 'options' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const JsonValue& optionJson : optionsValue->asArray())
|
||||
{
|
||||
if (!optionJson.isObject())
|
||||
{
|
||||
error = "Enum parameter option must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderParameterOption option;
|
||||
if (!RequireStringField(optionJson, "value", option.value, manifestPath, error) ||
|
||||
!RequireStringField(optionJson, "label", option.label, manifestPath, error))
|
||||
{
|
||||
error = "Enum parameter option is missing 'value' or 'label' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
definition.enumOptions.push_back(option);
|
||||
}
|
||||
|
||||
bool defaultFound = definition.defaultEnumValue.empty();
|
||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
||||
{
|
||||
if (option.value == definition.defaultEnumValue)
|
||||
{
|
||||
defaultFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultFound)
|
||||
{
|
||||
error = "Enum parameter default is not present in its option list for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!parameterJson.isObject())
|
||||
{
|
||||
error = "Shader parameter entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string typeName;
|
||||
if (!RequireStringField(parameterJson, "id", definition.id, manifestPath, error) ||
|
||||
!RequireStringField(parameterJson, "label", definition.label, manifestPath, error) ||
|
||||
!RequireStringField(parameterJson, "type", typeName, manifestPath, error))
|
||||
{
|
||||
error = "Shader parameter is missing required fields in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseShaderParameterType(typeName, definition.type))
|
||||
{
|
||||
error = "Unsupported parameter type '" + typeName + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "step", definition.stepNumbers, manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
if (const JsonValue* fontValue = parameterJson.find("font"))
|
||||
{
|
||||
if (!fontValue->isString())
|
||||
{
|
||||
error = "Text parameter 'font' must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.fontId = fontValue->asString();
|
||||
if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
|
||||
return false;
|
||||
}
|
||||
if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
|
||||
{
|
||||
if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
|
||||
{
|
||||
error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.maxLength = static_cast<unsigned>(maxLengthValue->asNumber());
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
return ParseParameterOptions(parameterJson, definition, manifestPath, error);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* parametersValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "parameters", parametersValue, manifestPath, error))
|
||||
return false;
|
||||
if (!parametersValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& parameterJson : parametersValue->asArray())
|
||||
{
|
||||
ShaderParameterDefinition definition;
|
||||
if (!ParseParameterDefinition(parameterJson, definition, manifestPath, error))
|
||||
return false;
|
||||
shaderPackage.parameters.push_back(definition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
370
src/shader/ShaderManifestParser.cpp
Normal file
370
src/shader/ShaderManifestParser.cpp
Normal file
@@ -0,0 +1,370 @@
|
||||
#include "stdafx.h"
|
||||
#include "ShaderManifestParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
|
||||
namespace ShaderManifestParsing
|
||||
{
|
||||
std::string Trim(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);
|
||||
}
|
||||
|
||||
bool IsFiniteNumber(double value)
|
||||
{
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
|
||||
{
|
||||
if (sourceName == "source")
|
||||
{
|
||||
source = TemporalHistorySource::Source;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "preLayerInput")
|
||||
{
|
||||
source = TemporalHistorySource::PreLayerInput;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "none")
|
||||
{
|
||||
source = TemporalHistorySource::None;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ManifestPathMessage(const std::filesystem::path& manifestPath)
|
||||
{
|
||||
return manifestPath.string();
|
||||
}
|
||||
|
||||
bool RequireStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fieldValue = object.find(fieldName);
|
||||
if (!fieldValue || !fieldValue->isString())
|
||||
{
|
||||
error = "Shader manifest is missing required string '" + std::string(fieldName) + "' field: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = fieldValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequireNonEmptyStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!RequireStringField(object, fieldName, value, manifestPath, error))
|
||||
return false;
|
||||
if (Trim(value).empty())
|
||||
{
|
||||
error = "Shader manifest string '" + std::string(fieldName) + "' must not be empty: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::string& fallback, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fieldValue = object.find(fieldName);
|
||||
if (!fieldValue)
|
||||
{
|
||||
value = fallback;
|
||||
return true;
|
||||
}
|
||||
if (!fieldValue->isString())
|
||||
{
|
||||
error = "Shader manifest field '" + std::string(fieldName) + "' must be a string in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
value = fieldValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalArrayField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
value = object.find(fieldName);
|
||||
if (!value)
|
||||
return true;
|
||||
if (!value->isArray())
|
||||
{
|
||||
error = "Shader manifest '" + std::string(fieldName) + "' field must be an array in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalObjectField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
value = object.find(fieldName);
|
||||
if (!value)
|
||||
return true;
|
||||
if (!value->isObject())
|
||||
{
|
||||
error = "Shader manifest '" + std::string(fieldName) + "' field must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NumberListFromJsonValue(const JsonValue& value, std::vector<double>& numbers, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (value.isNumber())
|
||||
{
|
||||
numbers.push_back(value.asNumber());
|
||||
return true;
|
||||
}
|
||||
if (value.isArray())
|
||||
{
|
||||
numbers = JsonArrayToNumbers(value);
|
||||
if (numbers.size() != value.asArray().size())
|
||||
{
|
||||
error = "Shader parameter field '" + fieldName + "' must contain only numbers in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
error = "Shader parameter field '" + fieldName + "' must be a number or array of numbers in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateShaderIdentifier(const std::string& identifier, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (identifier.empty() || !(std::isalpha(static_cast<unsigned char>(identifier.front())) || identifier.front() == '_'))
|
||||
{
|
||||
error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (char ch : identifier)
|
||||
{
|
||||
const unsigned char unsignedCh = static_cast<unsigned char>(ch);
|
||||
if (!(std::isalnum(unsignedCh) || ch == '_'))
|
||||
{
|
||||
error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseShaderMetadata(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!RequireStringField(manifestJson, "id", shaderPackage.id, manifestPath, error) ||
|
||||
!RequireStringField(manifestJson, "name", shaderPackage.displayName, manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "description", shaderPackage.description, "", manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "category", shaderPackage.category, "", manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "entryPoint", shaderPackage.entryPoint, "shadeVideo", manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateShaderIdentifier(shaderPackage.entryPoint, "entryPoint", manifestPath, error))
|
||||
return false;
|
||||
|
||||
shaderPackage.directoryPath = manifestPath.parent_path();
|
||||
shaderPackage.shaderPath = shaderPackage.directoryPath / "shader.slang";
|
||||
shaderPackage.manifestPath = manifestPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* passesValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "passes", passesValue, manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!passesValue)
|
||||
{
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = "main";
|
||||
pass.entryPoint = shaderPackage.entryPoint;
|
||||
pass.sourcePath = shaderPackage.shaderPath;
|
||||
pass.outputName = "layerOutput";
|
||||
if (!std::filesystem::exists(pass.sourcePath))
|
||||
{
|
||||
error = "Shader source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||
return false;
|
||||
}
|
||||
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||
shaderPackage.passes.push_back(pass);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (passesValue->asArray().empty())
|
||||
{
|
||||
error = "Shader manifest 'passes' field must not be empty in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const JsonValue& passJson : passesValue->asArray())
|
||||
{
|
||||
if (!passJson.isObject())
|
||||
{
|
||||
error = "Shader pass entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string passId;
|
||||
std::string sourcePath;
|
||||
if (!RequireNonEmptyStringField(passJson, "id", passId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(passJson, "source", sourcePath, manifestPath, error))
|
||||
{
|
||||
error = "Shader pass is missing required 'id' or 'source' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(passId, "passes[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
for (const ShaderPassDefinition& existingPass : shaderPackage.passes)
|
||||
{
|
||||
if (existingPass.id == passId)
|
||||
{
|
||||
error = "Duplicate shader pass id '" + passId + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = passId;
|
||||
pass.sourcePath = shaderPackage.directoryPath / sourcePath;
|
||||
if (!OptionalStringField(passJson, "entryPoint", pass.entryPoint, shaderPackage.entryPoint, manifestPath, error) ||
|
||||
!OptionalStringField(passJson, "output", pass.outputName, passId, manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(pass.entryPoint, "passes[].entryPoint", manifestPath, error))
|
||||
return false;
|
||||
|
||||
const JsonValue* inputsValue = nullptr;
|
||||
if (!OptionalArrayField(passJson, "inputs", inputsValue, manifestPath, error))
|
||||
return false;
|
||||
if (inputsValue)
|
||||
{
|
||||
for (const JsonValue& inputValue : inputsValue->asArray())
|
||||
{
|
||||
if (!inputValue.isString())
|
||||
{
|
||||
error = "Shader pass inputs must be strings in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
pass.inputNames.push_back(inputValue.asString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(pass.sourcePath))
|
||||
{
|
||||
error = "Shader pass source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||
return false;
|
||||
}
|
||||
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||
shaderPackage.passes.push_back(pass);
|
||||
}
|
||||
|
||||
shaderPackage.shaderPath = shaderPackage.passes.front().sourcePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* temporalValue = nullptr;
|
||||
if (!OptionalObjectField(manifestJson, "temporal", temporalValue, manifestPath, error))
|
||||
return false;
|
||||
if (!temporalValue)
|
||||
return true;
|
||||
|
||||
const JsonValue* enabledValue = temporalValue->find("enabled");
|
||||
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||
return true;
|
||||
|
||||
std::string historySourceName;
|
||||
if (!RequireNonEmptyStringField(*temporalValue, "historySource", historySourceName, manifestPath, error))
|
||||
{
|
||||
error = "Temporal shader is missing required 'historySource' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const JsonValue* historyLengthValue = temporalValue->find("historyLength");
|
||||
if (!historyLengthValue || !historyLengthValue->isNumber())
|
||||
{
|
||||
error = "Temporal shader is missing required numeric 'historyLength' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
if (!ParseTemporalHistorySource(historySourceName, historySource))
|
||||
{
|
||||
error = "Unsupported temporal historySource '" + historySourceName + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double requestedHistoryLength = historyLengthValue->asNumber();
|
||||
if (!IsFiniteNumber(requestedHistoryLength) || requestedHistoryLength <= 0.0 || std::floor(requestedHistoryLength) != requestedHistoryLength)
|
||||
{
|
||||
error = "Temporal shader 'historyLength' must be a positive integer in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
shaderPackage.temporal.enabled = true;
|
||||
shaderPackage.temporal.historySource = historySource;
|
||||
shaderPackage.temporal.requestedHistoryLength = static_cast<unsigned>(requestedHistoryLength);
|
||||
shaderPackage.temporal.effectiveHistoryLength = std::min(shaderPackage.temporal.requestedHistoryLength, maxTemporalHistoryFrames);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFeedbackSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* feedbackValue = nullptr;
|
||||
if (!OptionalObjectField(manifestJson, "feedback", feedbackValue, manifestPath, error))
|
||||
return false;
|
||||
if (!feedbackValue)
|
||||
return true;
|
||||
|
||||
const JsonValue* enabledValue = feedbackValue->find("enabled");
|
||||
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||
return true;
|
||||
|
||||
shaderPackage.feedback.enabled = true;
|
||||
if (!OptionalStringField(*feedbackValue, "writePass", shaderPackage.feedback.writePassId, "", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (shaderPackage.feedback.writePassId.empty())
|
||||
{
|
||||
if (shaderPackage.passes.empty())
|
||||
{
|
||||
error = "Feedback-enabled shader has no passes to target in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
shaderPackage.feedback.writePassId = shaderPackage.passes.back().id;
|
||||
}
|
||||
|
||||
if (!ValidateShaderIdentifier(shaderPackage.feedback.writePassId, "feedback.writePass", manifestPath, error))
|
||||
return false;
|
||||
|
||||
const auto passIt = std::find_if(shaderPackage.passes.begin(), shaderPackage.passes.end(),
|
||||
[&shaderPackage](const ShaderPassDefinition& pass) { return pass.id == shaderPackage.feedback.writePassId; });
|
||||
if (passIt == shaderPackage.passes.end())
|
||||
{
|
||||
error = "Feedback writePass '" + shaderPackage.feedback.writePassId + "' does not match any declared pass in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
31
src/shader/ShaderManifestParser.h
Normal file
31
src/shader/ShaderManifestParser.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ShaderManifestParsing
|
||||
{
|
||||
std::string Trim(const std::string& text);
|
||||
bool IsFiniteNumber(double value);
|
||||
std::string ManifestPathMessage(const std::filesystem::path& manifestPath);
|
||||
|
||||
bool RequireStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool RequireNonEmptyStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool OptionalStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::string& fallback, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool OptionalArrayField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool OptionalObjectField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool NumberListFromJsonValue(const JsonValue& value, std::vector<double>& numbers, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ValidateShaderIdentifier(const std::string& identifier, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error);
|
||||
|
||||
bool ParseShaderMetadata(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ParseFeedbackSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error);
|
||||
bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error);
|
||||
}
|
||||
@@ -2,93 +2,14 @@
|
||||
#include "ShaderPackageRegistry.h"
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderManifestParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string Trim(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);
|
||||
}
|
||||
|
||||
bool IsFiniteNumber(double value)
|
||||
{
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
||||
{
|
||||
if (typeName == "float")
|
||||
{
|
||||
type = ShaderParameterType::Float;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "vec2")
|
||||
{
|
||||
type = ShaderParameterType::Vec2;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "color")
|
||||
{
|
||||
type = ShaderParameterType::Color;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "bool")
|
||||
{
|
||||
type = ShaderParameterType::Boolean;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "enum")
|
||||
{
|
||||
type = ShaderParameterType::Enum;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "text")
|
||||
{
|
||||
type = ShaderParameterType::Text;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "trigger")
|
||||
{
|
||||
type = ShaderParameterType::Trigger;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
|
||||
{
|
||||
if (sourceName == "source")
|
||||
{
|
||||
source = TemporalHistorySource::Source;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "preLayerInput")
|
||||
{
|
||||
source = TemporalHistorySource::PreLayerInput;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "none")
|
||||
{
|
||||
source = TemporalHistorySource::None;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
@@ -103,596 +24,6 @@ std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
std::string ManifestPathMessage(const std::filesystem::path& manifestPath)
|
||||
{
|
||||
return manifestPath.string();
|
||||
}
|
||||
|
||||
bool RequireStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fieldValue = object.find(fieldName);
|
||||
if (!fieldValue || !fieldValue->isString())
|
||||
{
|
||||
error = "Shader manifest is missing required string '" + std::string(fieldName) + "' field: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = fieldValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequireNonEmptyStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!RequireStringField(object, fieldName, value, manifestPath, error))
|
||||
return false;
|
||||
if (Trim(value).empty())
|
||||
{
|
||||
error = "Shader manifest string '" + std::string(fieldName) + "' must not be empty: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::string& fallback, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fieldValue = object.find(fieldName);
|
||||
if (!fieldValue)
|
||||
{
|
||||
value = fallback;
|
||||
return true;
|
||||
}
|
||||
if (!fieldValue->isString())
|
||||
{
|
||||
error = "Shader manifest field '" + std::string(fieldName) + "' must be a string in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
value = fieldValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalArrayField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
value = object.find(fieldName);
|
||||
if (!value)
|
||||
return true;
|
||||
if (!value->isArray())
|
||||
{
|
||||
error = "Shader manifest '" + std::string(fieldName) + "' field must be an array in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalObjectField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
value = object.find(fieldName);
|
||||
if (!value)
|
||||
return true;
|
||||
if (!value->isObject())
|
||||
{
|
||||
error = "Shader manifest '" + std::string(fieldName) + "' field must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NumberListFromJsonValue(const JsonValue& value, std::vector<double>& numbers, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (value.isNumber())
|
||||
{
|
||||
numbers.push_back(value.asNumber());
|
||||
return true;
|
||||
}
|
||||
if (value.isArray())
|
||||
{
|
||||
numbers = JsonArrayToNumbers(value);
|
||||
if (numbers.size() != value.asArray().size())
|
||||
{
|
||||
error = "Shader parameter field '" + fieldName + "' must contain only numbers in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
error = "Shader parameter field '" + fieldName + "' must be a number or array of numbers in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateShaderIdentifier(const std::string& identifier, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (identifier.empty() || !(std::isalpha(static_cast<unsigned char>(identifier.front())) || identifier.front() == '_'))
|
||||
{
|
||||
error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (char ch : identifier)
|
||||
{
|
||||
const unsigned char unsignedCh = static_cast<unsigned char>(ch);
|
||||
if (!(std::isalnum(unsignedCh) || ch == '_'))
|
||||
{
|
||||
error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseShaderMetadata(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!RequireStringField(manifestJson, "id", shaderPackage.id, manifestPath, error) ||
|
||||
!RequireStringField(manifestJson, "name", shaderPackage.displayName, manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "description", shaderPackage.description, "", manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "category", shaderPackage.category, "", manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "entryPoint", shaderPackage.entryPoint, "shadeVideo", manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateShaderIdentifier(shaderPackage.entryPoint, "entryPoint", manifestPath, error))
|
||||
return false;
|
||||
|
||||
shaderPackage.directoryPath = manifestPath.parent_path();
|
||||
shaderPackage.shaderPath = shaderPackage.directoryPath / "shader.slang";
|
||||
shaderPackage.manifestPath = manifestPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* passesValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "passes", passesValue, manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!passesValue)
|
||||
{
|
||||
// Existing shader packages are treated as a single implicit pass, so
|
||||
// multipass support does not require manifest churn.
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = "main";
|
||||
pass.entryPoint = shaderPackage.entryPoint;
|
||||
pass.sourcePath = shaderPackage.shaderPath;
|
||||
pass.outputName = "layerOutput";
|
||||
if (!std::filesystem::exists(pass.sourcePath))
|
||||
{
|
||||
error = "Shader source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||
return false;
|
||||
}
|
||||
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||
shaderPackage.passes.push_back(pass);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (passesValue->asArray().empty())
|
||||
{
|
||||
error = "Shader manifest 'passes' field must not be empty in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const JsonValue& passJson : passesValue->asArray())
|
||||
{
|
||||
if (!passJson.isObject())
|
||||
{
|
||||
error = "Shader pass entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string passId;
|
||||
std::string sourcePath;
|
||||
if (!RequireNonEmptyStringField(passJson, "id", passId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(passJson, "source", sourcePath, manifestPath, error))
|
||||
{
|
||||
error = "Shader pass is missing required 'id' or 'source' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(passId, "passes[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
for (const ShaderPassDefinition& existingPass : shaderPackage.passes)
|
||||
{
|
||||
if (existingPass.id == passId)
|
||||
{
|
||||
error = "Duplicate shader pass id '" + passId + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = passId;
|
||||
pass.sourcePath = shaderPackage.directoryPath / sourcePath;
|
||||
if (!OptionalStringField(passJson, "entryPoint", pass.entryPoint, shaderPackage.entryPoint, manifestPath, error) ||
|
||||
!OptionalStringField(passJson, "output", pass.outputName, passId, manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(pass.entryPoint, "passes[].entryPoint", manifestPath, error))
|
||||
return false;
|
||||
|
||||
const JsonValue* inputsValue = nullptr;
|
||||
if (!OptionalArrayField(passJson, "inputs", inputsValue, manifestPath, error))
|
||||
return false;
|
||||
if (inputsValue)
|
||||
{
|
||||
for (const JsonValue& inputValue : inputsValue->asArray())
|
||||
{
|
||||
if (!inputValue.isString())
|
||||
{
|
||||
error = "Shader pass inputs must be strings in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
pass.inputNames.push_back(inputValue.asString());
|
||||
}
|
||||
}
|
||||
|
||||
// Keep source validation in the registry. Bad pass declarations then
|
||||
// appear as unavailable shaders instead of failing at render time.
|
||||
if (!std::filesystem::exists(pass.sourcePath))
|
||||
{
|
||||
error = "Shader pass source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||
return false;
|
||||
}
|
||||
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||
shaderPackage.passes.push_back(pass);
|
||||
}
|
||||
|
||||
shaderPackage.shaderPath = shaderPackage.passes.front().sourcePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* texturesValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "textures", texturesValue, manifestPath, error))
|
||||
return false;
|
||||
if (!texturesValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& textureJson : texturesValue->asArray())
|
||||
{
|
||||
if (!textureJson.isObject())
|
||||
{
|
||||
error = "Shader texture entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string textureId;
|
||||
std::string texturePath;
|
||||
if (!RequireNonEmptyStringField(textureJson, "id", textureId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(textureJson, "path", texturePath, manifestPath, error))
|
||||
{
|
||||
error = "Shader texture is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(textureId, "textures[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
ShaderTextureAsset textureAsset;
|
||||
textureAsset.id = textureId;
|
||||
textureAsset.path = shaderPackage.directoryPath / texturePath;
|
||||
if (!std::filesystem::exists(textureAsset.path))
|
||||
{
|
||||
error = "Shader texture asset not found for package " + shaderPackage.id + ": " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
textureAsset.writeTime = std::filesystem::last_write_time(textureAsset.path);
|
||||
shaderPackage.textureAssets.push_back(textureAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fontsValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "fonts", fontsValue, manifestPath, error))
|
||||
return false;
|
||||
if (!fontsValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& fontJson : fontsValue->asArray())
|
||||
{
|
||||
if (!fontJson.isObject())
|
||||
{
|
||||
error = "Shader font entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fontId;
|
||||
std::string fontPath;
|
||||
if (!RequireNonEmptyStringField(fontJson, "id", fontId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(fontJson, "path", fontPath, manifestPath, error))
|
||||
{
|
||||
error = "Shader font is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(fontId, "fonts[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
ShaderFontAsset fontAsset;
|
||||
fontAsset.id = fontId;
|
||||
fontAsset.path = shaderPackage.directoryPath / fontPath;
|
||||
if (!std::filesystem::exists(fontAsset.path))
|
||||
{
|
||||
error = "Shader font asset not found for package " + shaderPackage.id + ": " + fontAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
fontAsset.writeTime = std::filesystem::last_write_time(fontAsset.path);
|
||||
shaderPackage.fontAssets.push_back(fontAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* temporalValue = nullptr;
|
||||
if (!OptionalObjectField(manifestJson, "temporal", temporalValue, manifestPath, error))
|
||||
return false;
|
||||
if (!temporalValue)
|
||||
return true;
|
||||
|
||||
const JsonValue* enabledValue = temporalValue->find("enabled");
|
||||
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||
return true;
|
||||
|
||||
std::string historySourceName;
|
||||
if (!RequireNonEmptyStringField(*temporalValue, "historySource", historySourceName, manifestPath, error))
|
||||
{
|
||||
error = "Temporal shader is missing required 'historySource' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const JsonValue* historyLengthValue = temporalValue->find("historyLength");
|
||||
if (!historyLengthValue || !historyLengthValue->isNumber())
|
||||
{
|
||||
error = "Temporal shader is missing required numeric 'historyLength' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
if (!ParseTemporalHistorySource(historySourceName, historySource))
|
||||
{
|
||||
error = "Unsupported temporal historySource '" + historySourceName + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double requestedHistoryLength = historyLengthValue->asNumber();
|
||||
if (!IsFiniteNumber(requestedHistoryLength) || requestedHistoryLength <= 0.0 || std::floor(requestedHistoryLength) != requestedHistoryLength)
|
||||
{
|
||||
error = "Temporal shader 'historyLength' must be a positive integer in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
shaderPackage.temporal.enabled = true;
|
||||
shaderPackage.temporal.historySource = historySource;
|
||||
shaderPackage.temporal.requestedHistoryLength = static_cast<unsigned>(requestedHistoryLength);
|
||||
shaderPackage.temporal.effectiveHistoryLength = std::min(shaderPackage.temporal.requestedHistoryLength, maxTemporalHistoryFrames);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFeedbackSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* feedbackValue = nullptr;
|
||||
if (!OptionalObjectField(manifestJson, "feedback", feedbackValue, manifestPath, error))
|
||||
return false;
|
||||
if (!feedbackValue)
|
||||
return true;
|
||||
|
||||
const JsonValue* enabledValue = feedbackValue->find("enabled");
|
||||
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||
return true;
|
||||
|
||||
shaderPackage.feedback.enabled = true;
|
||||
if (!OptionalStringField(*feedbackValue, "writePass", shaderPackage.feedback.writePassId, "", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (shaderPackage.feedback.writePassId.empty())
|
||||
{
|
||||
if (shaderPackage.passes.empty())
|
||||
{
|
||||
error = "Feedback-enabled shader has no passes to target in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
shaderPackage.feedback.writePassId = shaderPackage.passes.back().id;
|
||||
}
|
||||
|
||||
if (!ValidateShaderIdentifier(shaderPackage.feedback.writePassId, "feedback.writePass", manifestPath, error))
|
||||
return false;
|
||||
|
||||
const auto passIt = std::find_if(shaderPackage.passes.begin(), shaderPackage.passes.end(),
|
||||
[&shaderPackage](const ShaderPassDefinition& pass) { return pass.id == shaderPackage.feedback.writePassId; });
|
||||
if (passIt == shaderPackage.passes.end())
|
||||
{
|
||||
error = "Feedback writePass '" + shaderPackage.feedback.writePassId + "' does not match any declared pass in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (const JsonValue* fieldValue = parameterJson.find(fieldName))
|
||||
return NumberListFromJsonValue(*fieldValue, values, fieldName, manifestPath, error);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* defaultValue = parameterJson.find("default");
|
||||
if (!defaultValue)
|
||||
return true;
|
||||
|
||||
if (definition.type == ShaderParameterType::Boolean)
|
||||
{
|
||||
if (!defaultValue->isBoolean())
|
||||
{
|
||||
error = "Boolean parameter default must be a boolean for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultBoolean = defaultValue->asBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
{
|
||||
if (!defaultValue->isString())
|
||||
{
|
||||
error = "Enum parameter default must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultEnumValue = defaultValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
if (!defaultValue->isString())
|
||||
{
|
||||
error = "Text parameter default must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultTextValue = defaultValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error);
|
||||
}
|
||||
|
||||
bool ParseParameterOptions(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* optionsValue = nullptr;
|
||||
if (!OptionalArrayField(parameterJson, "options", optionsValue, manifestPath, error) || !optionsValue)
|
||||
{
|
||||
error = "Enum parameter is missing 'options' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const JsonValue& optionJson : optionsValue->asArray())
|
||||
{
|
||||
if (!optionJson.isObject())
|
||||
{
|
||||
error = "Enum parameter option must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderParameterOption option;
|
||||
if (!RequireStringField(optionJson, "value", option.value, manifestPath, error) ||
|
||||
!RequireStringField(optionJson, "label", option.label, manifestPath, error))
|
||||
{
|
||||
error = "Enum parameter option is missing 'value' or 'label' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
definition.enumOptions.push_back(option);
|
||||
}
|
||||
|
||||
bool defaultFound = definition.defaultEnumValue.empty();
|
||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
||||
{
|
||||
if (option.value == definition.defaultEnumValue)
|
||||
{
|
||||
defaultFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultFound)
|
||||
{
|
||||
error = "Enum parameter default is not present in its option list for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!parameterJson.isObject())
|
||||
{
|
||||
error = "Shader parameter entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string typeName;
|
||||
if (!RequireStringField(parameterJson, "id", definition.id, manifestPath, error) ||
|
||||
!RequireStringField(parameterJson, "label", definition.label, manifestPath, error) ||
|
||||
!RequireStringField(parameterJson, "type", typeName, manifestPath, error))
|
||||
{
|
||||
error = "Shader parameter is missing required fields in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseShaderParameterType(typeName, definition.type))
|
||||
{
|
||||
error = "Unsupported parameter type '" + typeName + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "step", definition.stepNumbers, manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
if (const JsonValue* fontValue = parameterJson.find("font"))
|
||||
{
|
||||
if (!fontValue->isString())
|
||||
{
|
||||
error = "Text parameter 'font' must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.fontId = fontValue->asString();
|
||||
if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
|
||||
return false;
|
||||
}
|
||||
if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
|
||||
{
|
||||
if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
|
||||
{
|
||||
error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.maxLength = static_cast<unsigned>(maxLengthValue->asNumber());
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
return ParseParameterOptions(parameterJson, definition, manifestPath, error);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* parametersValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "parameters", parametersValue, manifestPath, error))
|
||||
return false;
|
||||
if (!parametersValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& parameterJson : parametersValue->asArray())
|
||||
{
|
||||
ShaderParameterDefinition definition;
|
||||
if (!ParseParameterDefinition(parameterJson, definition, manifestPath, error))
|
||||
return false;
|
||||
shaderPackage.parameters.push_back(definition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string UniqueUnavailableShaderId(const std::filesystem::path& manifestPath, const std::string& parsedId)
|
||||
{
|
||||
const std::string fallbackId = manifestPath.parent_path().filename().string();
|
||||
@@ -796,6 +127,7 @@ bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestP
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace ShaderManifestParsing;
|
||||
if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -42,10 +42,16 @@ add_video_shader_test(RenderCadenceCompositorRuntimeShaderParamsTests
|
||||
add_video_shader_test(RenderCadenceCompositorRuntimeLayerModelTests
|
||||
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerModel.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerReload.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerSnapshot.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerStateRestore.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeParameterUtils.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeTextTextureComposer.cpp"
|
||||
"${SRC_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestAssets.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParameters.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParser.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
|
||||
)
|
||||
@@ -60,6 +66,9 @@ add_video_shader_test(RuntimeStatePersistenceTests
|
||||
add_video_shader_test(FontAtlasBuilderTests
|
||||
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestAssets.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParameters.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParser.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/FontAtlasBuilderTests.cpp"
|
||||
)
|
||||
@@ -68,6 +77,9 @@ add_video_shader_test(RenderCadenceCompositorSupportedShaderCatalogTests
|
||||
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestAssets.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParameters.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParser.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorSupportedShaderCatalogTests.cpp"
|
||||
)
|
||||
@@ -79,9 +91,15 @@ add_video_shader_test(RenderCadenceCompositorRuntimeStateJsonTests
|
||||
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerModel.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerReload.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerSnapshot.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeLayerStateRestore.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeParameterUtils.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeTextTextureComposer.cpp"
|
||||
"${SRC_DIR}/runtime/SupportedShaderCatalog.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestAssets.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParameters.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParser.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorRuntimeStateJsonTests.cpp"
|
||||
)
|
||||
@@ -118,6 +136,9 @@ add_video_shader_test(RuntimeParameterUtilsTests
|
||||
|
||||
add_video_shader_test(ShaderPackageRegistryTests
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestAssets.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParameters.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParser.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/ShaderPackageRegistryTests.cpp"
|
||||
)
|
||||
@@ -125,6 +146,9 @@ add_video_shader_test(ShaderPackageRegistryTests
|
||||
add_video_shader_test(ShaderSlangValidationTests
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/shader/ShaderCompiler.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestAssets.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParameters.cpp"
|
||||
"${SRC_DIR}/shader/ShaderManifestParser.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/ShaderSlangValidationTests.cpp"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user