split shaders
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user