Compare commits
3 Commits
4096e9c26a
...
4ddb5b6428
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ddb5b6428 | |||
| 108edc096e | |||
| 269dbd0079 |
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 <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();
|
||||||
|
|||||||
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 "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;
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user