3 Commits

Author SHA1 Message Date
4ddb5b6428 split shaders
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Failing after 2m18s
CI / Windows Release Package (push) Has been skipped
2026-05-21 17:48:27 +10:00
108edc096e Separating build queue 2026-05-21 17:43:17 +10:00
269dbd0079 Runtime layer split 2026-05-21 17:40:31 +10:00
12 changed files with 1136 additions and 1026 deletions

View File

@@ -1,12 +1,7 @@
#include "RuntimeLayerController.h" #include "RuntimeLayerController.h"
#include "AppConfigProvider.h"
#include "RuntimeJson.h"
#include "../logging/Logger.h" #include "../logging/Logger.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <algorithm> #include <algorithm>
namespace RenderCadenceCompositor namespace RenderCadenceCompositor
@@ -16,103 +11,6 @@ namespace
constexpr std::size_t kMaxConcurrentRuntimeShaderBuilds = 2; 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) void RuntimeLayerController::StartLayerShaderBuild(const std::string& layerId, const std::string& shaderId, bool preserveExistingRenderArtifact)
{ {
CleanupRetiredShaderBuilds(); CleanupRetiredShaderBuilds();
@@ -255,10 +153,4 @@ void RuntimeLayerController::StopAllRuntimeShaderBuilds()
for (auto& bridge : retiredBuilds) for (auto& bridge : retiredBuilds)
bridge->Stop(); bridge->Stop();
} }
std::string RuntimeLayerController::FirstRuntimeLayerId() const
{
std::lock_guard<std::mutex> lock(mRuntimeLayerMutex);
return mRuntimeLayerModel.FirstLayerId();
}
} }

View 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();
}
}

View File

@@ -5,73 +5,10 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <cctype>
#include <set>
#include <utility> #include <utility>
namespace RenderCadenceCompositor 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) bool RuntimeLayerModel::InitializeSingleLayer(const SupportedShaderCatalog& shaderCatalog, const std::string& shaderId, std::string& error)
{ {
Clear(); Clear();
@@ -270,145 +207,6 @@ bool RuntimeLayerModel::ResetParameters(const std::string& layerId, std::string&
return true; 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() void RuntimeLayerModel::Clear()
{ {
mLayers.clear(); mLayers.clear();
@@ -508,37 +306,6 @@ bool RuntimeLayerModel::MarkRenderCommitFailed(const std::string& layerId, const
return MarkBuildFailed(layerId, message, error); 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 std::string RuntimeLayerModel::FirstLayerId() const
{ {
return mLayers.empty() ? std::string() : mLayers.front().id; return mLayers.empty() ? std::string() : mLayers.front().id;
@@ -608,21 +375,6 @@ std::string RuntimeLayerModel::AllocateLayerId()
return "runtime-layer-" + std::to_string(mNextLayerNumber++); 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 double RuntimeLayerModel::RuntimeElapsedSeconds() const
{ {
return std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count(); return std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}

View File

@@ -2,93 +2,14 @@
#include "ShaderPackageRegistry.h" #include "ShaderPackageRegistry.h"
#include "RuntimeJson.h" #include "RuntimeJson.h"
#include "ShaderManifestParser.h"
#include <algorithm> #include <algorithm>
#include <cmath>
#include <cctype>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
namespace 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::string ReadTextFile(const std::filesystem::path& path, std::string& error)
{ {
std::ifstream input(path, std::ios::binary); 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(); 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) std::string UniqueUnavailableShaderId(const std::filesystem::path& manifestPath, const std::string& parsedId)
{ {
const std::string fallbackId = manifestPath.parent_path().filename().string(); const std::string fallbackId = manifestPath.parent_path().filename().string();
@@ -796,6 +127,7 @@ bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestP
return false; return false;
} }
using namespace ShaderManifestParsing;
if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error)) if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
return false; return false;

View File

@@ -42,10 +42,16 @@ add_video_shader_test(RenderCadenceCompositorRuntimeShaderParamsTests
add_video_shader_test(RenderCadenceCompositorRuntimeLayerModelTests add_video_shader_test(RenderCadenceCompositorRuntimeLayerModelTests
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp" "${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
"${SRC_DIR}/runtime/RuntimeLayerModel.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/RuntimeJson.cpp"
"${SRC_DIR}/runtime/RuntimeParameterUtils.cpp" "${SRC_DIR}/runtime/RuntimeParameterUtils.cpp"
"${SRC_DIR}/runtime/RuntimeTextTextureComposer.cpp" "${SRC_DIR}/runtime/RuntimeTextTextureComposer.cpp"
"${SRC_DIR}/runtime/SupportedShaderCatalog.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" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
"${TEST_DIR}/RenderCadenceCompositorRuntimeLayerModelTests.cpp" "${TEST_DIR}/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
) )
@@ -60,6 +66,9 @@ add_video_shader_test(RuntimeStatePersistenceTests
add_video_shader_test(FontAtlasBuilderTests add_video_shader_test(FontAtlasBuilderTests
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp" "${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
"${SRC_DIR}/runtime/RuntimeJson.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" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
"${TEST_DIR}/FontAtlasBuilderTests.cpp" "${TEST_DIR}/FontAtlasBuilderTests.cpp"
) )
@@ -68,6 +77,9 @@ add_video_shader_test(RenderCadenceCompositorSupportedShaderCatalogTests
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp" "${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
"${SRC_DIR}/runtime/RuntimeJson.cpp" "${SRC_DIR}/runtime/RuntimeJson.cpp"
"${SRC_DIR}/runtime/SupportedShaderCatalog.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" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
"${TEST_DIR}/RenderCadenceCompositorSupportedShaderCatalogTests.cpp" "${TEST_DIR}/RenderCadenceCompositorSupportedShaderCatalogTests.cpp"
) )
@@ -79,9 +91,15 @@ add_video_shader_test(RenderCadenceCompositorRuntimeStateJsonTests
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp" "${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
"${SRC_DIR}/runtime/RuntimeJson.cpp" "${SRC_DIR}/runtime/RuntimeJson.cpp"
"${SRC_DIR}/runtime/RuntimeLayerModel.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/RuntimeParameterUtils.cpp"
"${SRC_DIR}/runtime/RuntimeTextTextureComposer.cpp" "${SRC_DIR}/runtime/RuntimeTextTextureComposer.cpp"
"${SRC_DIR}/runtime/SupportedShaderCatalog.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" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
"${TEST_DIR}/RenderCadenceCompositorRuntimeStateJsonTests.cpp" "${TEST_DIR}/RenderCadenceCompositorRuntimeStateJsonTests.cpp"
) )
@@ -118,6 +136,9 @@ add_video_shader_test(RuntimeParameterUtilsTests
add_video_shader_test(ShaderPackageRegistryTests add_video_shader_test(ShaderPackageRegistryTests
"${SRC_DIR}/runtime/RuntimeJson.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" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
"${TEST_DIR}/ShaderPackageRegistryTests.cpp" "${TEST_DIR}/ShaderPackageRegistryTests.cpp"
) )
@@ -125,6 +146,9 @@ add_video_shader_test(ShaderPackageRegistryTests
add_video_shader_test(ShaderSlangValidationTests add_video_shader_test(ShaderSlangValidationTests
"${SRC_DIR}/runtime/RuntimeJson.cpp" "${SRC_DIR}/runtime/RuntimeJson.cpp"
"${SRC_DIR}/shader/ShaderCompiler.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" "${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
"${TEST_DIR}/ShaderSlangValidationTests.cpp" "${TEST_DIR}/ShaderSlangValidationTests.cpp"
) )