Refactor
This commit is contained in:
@@ -17,7 +17,7 @@ if(NOT EXISTS "${GPUDIRECT_DIR}/lib/x64/dvp.lib")
|
|||||||
message(FATAL_ERROR "NVIDIA GPUDirect library not found under ${GPUDIRECT_DIR}")
|
message(FATAL_ERROR "NVIDIA GPUDirect library not found under ${GPUDIRECT_DIR}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(LoopThroughWithOpenGLCompositing WIN32
|
set(APP_SOURCES
|
||||||
"${APP_DIR}/ControlServer.cpp"
|
"${APP_DIR}/ControlServer.cpp"
|
||||||
"${APP_DIR}/ControlServer.h"
|
"${APP_DIR}/ControlServer.h"
|
||||||
"${APP_DIR}/DeckLinkAPI_i.c"
|
"${APP_DIR}/DeckLinkAPI_i.c"
|
||||||
@@ -33,6 +33,9 @@ add_executable(LoopThroughWithOpenGLCompositing WIN32
|
|||||||
"${APP_DIR}/RuntimeHost.h"
|
"${APP_DIR}/RuntimeHost.h"
|
||||||
"${APP_DIR}/RuntimeJson.cpp"
|
"${APP_DIR}/RuntimeJson.cpp"
|
||||||
"${APP_DIR}/RuntimeJson.h"
|
"${APP_DIR}/RuntimeJson.h"
|
||||||
|
"${APP_DIR}/ShaderCompiler.cpp"
|
||||||
|
"${APP_DIR}/ShaderCompiler.h"
|
||||||
|
"${APP_DIR}/ShaderTypes.h"
|
||||||
"${APP_DIR}/stdafx.cpp"
|
"${APP_DIR}/stdafx.cpp"
|
||||||
"${APP_DIR}/stdafx.h"
|
"${APP_DIR}/stdafx.h"
|
||||||
"${APP_DIR}/targetver.h"
|
"${APP_DIR}/targetver.h"
|
||||||
@@ -40,6 +43,8 @@ add_executable(LoopThroughWithOpenGLCompositing WIN32
|
|||||||
"${APP_DIR}/VideoFrameTransfer.h"
|
"${APP_DIR}/VideoFrameTransfer.h"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES})
|
||||||
|
|
||||||
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
|
target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${GPUDIRECT_DIR}/include"
|
"${GPUDIRECT_DIR}/include"
|
||||||
@@ -73,25 +78,4 @@ add_custom_command(TARGET LoopThroughWithOpenGLCompositing POST_BUILD
|
|||||||
"$<TARGET_FILE_DIR:LoopThroughWithOpenGLCompositing>/dvp.dll"
|
"$<TARGET_FILE_DIR:LoopThroughWithOpenGLCompositing>/dvp.dll"
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group(TREE "${APP_DIR}" FILES
|
source_group(TREE "${APP_DIR}" FILES ${APP_SOURCES})
|
||||||
"${APP_DIR}/ControlServer.cpp"
|
|
||||||
"${APP_DIR}/ControlServer.h"
|
|
||||||
"${APP_DIR}/DeckLinkAPI_i.c"
|
|
||||||
"${APP_DIR}/GLExtensions.cpp"
|
|
||||||
"${APP_DIR}/GLExtensions.h"
|
|
||||||
"${APP_DIR}/LoopThroughWithOpenGLCompositing.cpp"
|
|
||||||
"${APP_DIR}/LoopThroughWithOpenGLCompositing.h"
|
|
||||||
"${APP_DIR}/LoopThroughWithOpenGLCompositing.rc"
|
|
||||||
"${APP_DIR}/OpenGLComposite.cpp"
|
|
||||||
"${APP_DIR}/OpenGLComposite.h"
|
|
||||||
"${APP_DIR}/resource.h"
|
|
||||||
"${APP_DIR}/RuntimeHost.cpp"
|
|
||||||
"${APP_DIR}/RuntimeHost.h"
|
|
||||||
"${APP_DIR}/RuntimeJson.cpp"
|
|
||||||
"${APP_DIR}/RuntimeJson.h"
|
|
||||||
"${APP_DIR}/stdafx.cpp"
|
|
||||||
"${APP_DIR}/stdafx.h"
|
|
||||||
"${APP_DIR}/targetver.h"
|
|
||||||
"${APP_DIR}/VideoFrameTransfer.cpp"
|
|
||||||
"${APP_DIR}/VideoFrameTransfer.h"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
#include "RuntimeHost.h"
|
#include "RuntimeHost.h"
|
||||||
|
#include "ShaderCompiler.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <regex>
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
@@ -24,17 +23,6 @@ std::string Trim(const std::string& text)
|
|||||||
return text.substr(start, end - start);
|
return text.substr(start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ReplaceAll(std::string text, const std::string& from, const std::string& to)
|
|
||||||
{
|
|
||||||
std::size_t offset = 0;
|
|
||||||
while ((offset = text.find(from, offset)) != std::string::npos)
|
|
||||||
{
|
|
||||||
text.replace(offset, from.length(), to);
|
|
||||||
offset += to.length();
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsFiniteNumber(double value)
|
bool IsFiniteNumber(double value)
|
||||||
{
|
{
|
||||||
return std::isfinite(value) != 0;
|
return std::isfinite(value) != 0;
|
||||||
@@ -102,19 +90,6 @@ std::string ShaderParameterTypeToString(ShaderParameterType type)
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SlangTypeForParameter(ShaderParameterType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ShaderParameterType::Float: return "uniform float";
|
|
||||||
case ShaderParameterType::Vec2: return "uniform float2";
|
|
||||||
case ShaderParameterType::Color: return "uniform float4";
|
|
||||||
case ShaderParameterType::Boolean: return "uniform bool";
|
|
||||||
case ShaderParameterType::Enum: return "uniform int";
|
|
||||||
}
|
|
||||||
return "uniform float";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
|
bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
|
||||||
{
|
{
|
||||||
if (sourceName == "source")
|
if (sourceName == "source")
|
||||||
@@ -177,6 +152,329 @@ bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseEnumOptions(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 (!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::Enum)
|
||||||
|
return ParseEnumOptions(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeHost::RuntimeHost()
|
RuntimeHost::RuntimeHost()
|
||||||
@@ -667,24 +965,8 @@ bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std
|
|||||||
shaderPackage = it->second;
|
shaderPackage = it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string wrapperSource = BuildWrapperSlangSource(shaderPackage);
|
ShaderCompiler compiler(mRepoRoot, mWrapperPath, mGeneratedGlslPath, mPatchedGlslPath, mConfig.maxTemporalHistoryFrames);
|
||||||
if (!WriteTextFile(mWrapperPath, wrapperSource, error))
|
return compiler.BuildLayerFragmentShaderSource(shaderPackage, fragmentShaderSource, error);
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!RunSlangCompiler(mWrapperPath, mGeneratedGlslPath, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
fragmentShaderSource = ReadTextFile(mGeneratedGlslPath, error);
|
|
||||||
if (fragmentShaderSource.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!PatchGeneratedGlsl(fragmentShaderSource, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!WriteTextFile(mPatchedGlslPath, fragmentShaderSource, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& exception)
|
catch (const std::exception& exception)
|
||||||
{
|
{
|
||||||
@@ -977,20 +1259,21 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath,
|
|||||||
JsonValue manifestJson;
|
JsonValue manifestJson;
|
||||||
if (!ParseJson(manifestText, manifestJson, error))
|
if (!ParseJson(manifestText, manifestJson, error))
|
||||||
return false;
|
return false;
|
||||||
|
if (!manifestJson.isObject())
|
||||||
const JsonValue* idValue = manifestJson.find("id");
|
{
|
||||||
const JsonValue* nameValue = manifestJson.find("name");
|
error = "Shader manifest root must be an object: " + manifestPath.string();
|
||||||
if (!idValue || !nameValue)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
error = "Shader manifest is missing required 'id' or 'name' field: " + manifestPath.string();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shaderPackage.id = idValue->asString();
|
|
||||||
shaderPackage.displayName = nameValue->asString();
|
|
||||||
shaderPackage.description = manifestJson.find("description") ? manifestJson.find("description")->asString() : "";
|
|
||||||
shaderPackage.category = manifestJson.find("category") ? manifestJson.find("category")->asString() : "";
|
|
||||||
shaderPackage.entryPoint = manifestJson.find("entryPoint") ? manifestJson.find("entryPoint")->asString() : "shadeVideo";
|
|
||||||
shaderPackage.directoryPath = manifestPath.parent_path();
|
shaderPackage.directoryPath = manifestPath.parent_path();
|
||||||
shaderPackage.shaderPath = shaderPackage.directoryPath / "shader.slang";
|
shaderPackage.shaderPath = shaderPackage.directoryPath / "shader.slang";
|
||||||
shaderPackage.manifestPath = manifestPath;
|
shaderPackage.manifestPath = manifestPath;
|
||||||
@@ -1004,194 +1287,9 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath,
|
|||||||
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
||||||
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
||||||
|
|
||||||
const JsonValue* texturesValue = manifestJson.find("textures");
|
return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
||||||
if (texturesValue)
|
ParseTemporalSettings(manifestJson, shaderPackage, mConfig.maxTemporalHistoryFrames, manifestPath, error) &&
|
||||||
{
|
ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
|
||||||
if (!texturesValue->isArray())
|
|
||||||
{
|
|
||||||
error = "Shader manifest 'textures' field must be an array in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const JsonValue& textureJson : texturesValue->asArray())
|
|
||||||
{
|
|
||||||
if (!textureJson.isObject())
|
|
||||||
{
|
|
||||||
error = "Shader texture entry must be an object in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const JsonValue* textureIdValue = textureJson.find("id");
|
|
||||||
const JsonValue* texturePathValue = textureJson.find("path");
|
|
||||||
if (!textureIdValue || Trim(textureIdValue->asString()).empty() || !texturePathValue || Trim(texturePathValue->asString()).empty())
|
|
||||||
{
|
|
||||||
error = "Shader texture is missing required 'id' or 'path' in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderTextureAsset textureAsset;
|
|
||||||
textureAsset.id = textureIdValue->asString();
|
|
||||||
textureAsset.path = shaderPackage.directoryPath / texturePathValue->asString();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const JsonValue* temporalValue = manifestJson.find("temporal"))
|
|
||||||
{
|
|
||||||
if (!temporalValue->isObject())
|
|
||||||
{
|
|
||||||
error = "Shader manifest 'temporal' field must be an object in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const JsonValue* enabledValue = temporalValue->find("enabled");
|
|
||||||
if (enabledValue && enabledValue->asBoolean(false))
|
|
||||||
{
|
|
||||||
const JsonValue* historySourceValue = temporalValue->find("historySource");
|
|
||||||
const JsonValue* historyLengthValue = temporalValue->find("historyLength");
|
|
||||||
if (!historySourceValue || Trim(historySourceValue->asString()).empty())
|
|
||||||
{
|
|
||||||
error = "Temporal shader is missing required 'historySource' in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!historyLengthValue || !historyLengthValue->isNumber())
|
|
||||||
{
|
|
||||||
error = "Temporal shader is missing required numeric 'historyLength' in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
|
||||||
if (!ParseTemporalHistorySource(historySourceValue->asString(), historySource))
|
|
||||||
{
|
|
||||||
error = "Unsupported temporal historySource '" + historySourceValue->asString() + "' in: " + manifestPath.string();
|
|
||||||
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: " + manifestPath.string();
|
|
||||||
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, mConfig.maxTemporalHistoryFrames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const JsonValue* parametersValue = manifestJson.find("parameters");
|
|
||||||
if (parametersValue && parametersValue->isArray())
|
|
||||||
{
|
|
||||||
for (const JsonValue& parameterJson : parametersValue->asArray())
|
|
||||||
{
|
|
||||||
const JsonValue* parameterIdValue = parameterJson.find("id");
|
|
||||||
const JsonValue* parameterLabelValue = parameterJson.find("label");
|
|
||||||
const JsonValue* parameterTypeValue = parameterJson.find("type");
|
|
||||||
if (!parameterIdValue || !parameterLabelValue || !parameterTypeValue)
|
|
||||||
{
|
|
||||||
error = "Shader parameter is missing required fields in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParameterDefinition definition;
|
|
||||||
definition.id = parameterIdValue->asString();
|
|
||||||
definition.label = parameterLabelValue->asString();
|
|
||||||
if (!ParseShaderParameterType(parameterTypeValue->asString(), definition.type))
|
|
||||||
{
|
|
||||||
error = "Unsupported parameter type '" + parameterTypeValue->asString() + "' in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const JsonValue* defaultValue = parameterJson.find("default"))
|
|
||||||
{
|
|
||||||
if (definition.type == ShaderParameterType::Boolean)
|
|
||||||
definition.defaultBoolean = defaultValue->asBoolean(false);
|
|
||||||
else if (definition.type == ShaderParameterType::Enum)
|
|
||||||
definition.defaultEnumValue = defaultValue->asString();
|
|
||||||
else if (defaultValue->isNumber())
|
|
||||||
definition.defaultNumbers.push_back(defaultValue->asNumber());
|
|
||||||
else if (defaultValue->isArray())
|
|
||||||
definition.defaultNumbers = JsonArrayToNumbers(*defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const JsonValue* minValue = parameterJson.find("min"))
|
|
||||||
{
|
|
||||||
if (minValue->isNumber())
|
|
||||||
definition.minNumbers.push_back(minValue->asNumber());
|
|
||||||
else if (minValue->isArray())
|
|
||||||
definition.minNumbers = JsonArrayToNumbers(*minValue);
|
|
||||||
}
|
|
||||||
if (const JsonValue* maxValue = parameterJson.find("max"))
|
|
||||||
{
|
|
||||||
if (maxValue->isNumber())
|
|
||||||
definition.maxNumbers.push_back(maxValue->asNumber());
|
|
||||||
else if (maxValue->isArray())
|
|
||||||
definition.maxNumbers = JsonArrayToNumbers(*maxValue);
|
|
||||||
}
|
|
||||||
if (const JsonValue* stepValue = parameterJson.find("step"))
|
|
||||||
{
|
|
||||||
if (stepValue->isNumber())
|
|
||||||
definition.stepNumbers.push_back(stepValue->asNumber());
|
|
||||||
else if (stepValue->isArray())
|
|
||||||
definition.stepNumbers = JsonArrayToNumbers(*stepValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (definition.type == ShaderParameterType::Enum)
|
|
||||||
{
|
|
||||||
const JsonValue* optionsValue = parameterJson.find("options");
|
|
||||||
if (!optionsValue || !optionsValue->isArray())
|
|
||||||
{
|
|
||||||
error = "Enum parameter is missing 'options' in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const JsonValue& optionJson : optionsValue->asArray())
|
|
||||||
{
|
|
||||||
const JsonValue* value = optionJson.find("value");
|
|
||||||
const JsonValue* label = optionJson.find("label");
|
|
||||||
if (!value || !label)
|
|
||||||
{
|
|
||||||
error = "Enum parameter option is missing 'value' or 'label' in: " + manifestPath.string();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderParameterOption option;
|
|
||||||
option.value = value->asString();
|
|
||||||
option.label = label->asString();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shaderPackage.parameters.push_back(definition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeHost::NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const
|
bool RuntimeHost::NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const
|
||||||
@@ -1310,195 +1408,6 @@ void RuntimeHost::EnsureLayerDefaultsLocked(LayerPersistentState& layerState, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPackage) const
|
|
||||||
{
|
|
||||||
std::ostringstream source;
|
|
||||||
source << "struct FragmentInput\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\tfloat4 position : SV_Position;\n";
|
|
||||||
source << "\tfloat2 texCoord : TEXCOORD0;\n";
|
|
||||||
source << "};\n\n";
|
|
||||||
source << "struct ShaderContext\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\tfloat2 uv;\n";
|
|
||||||
source << "\tfloat4 sourceColor;\n";
|
|
||||||
source << "\tfloat2 inputResolution;\n";
|
|
||||||
source << "\tfloat2 outputResolution;\n";
|
|
||||||
source << "\tfloat time;\n";
|
|
||||||
source << "\tfloat frameCount;\n";
|
|
||||||
source << "\tfloat mixAmount;\n";
|
|
||||||
source << "\tfloat bypass;\n";
|
|
||||||
source << "\tint sourceHistoryLength;\n";
|
|
||||||
source << "\tint temporalHistoryLength;\n";
|
|
||||||
source << "};\n\n";
|
|
||||||
source << "cbuffer GlobalParams\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\tfloat gTime;\n";
|
|
||||||
source << "\tfloat2 gInputResolution;\n";
|
|
||||||
source << "\tfloat2 gOutputResolution;\n";
|
|
||||||
source << "\tfloat gFrameCount;\n";
|
|
||||||
source << "\tfloat gMixAmount;\n";
|
|
||||||
source << "\tfloat gBypass;\n";
|
|
||||||
source << "\tint gSourceHistoryLength;\n";
|
|
||||||
source << "\tint gTemporalHistoryLength;\n";
|
|
||||||
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
|
||||||
source << "\t" << SlangTypeForParameter(definition.type).substr(strlen("uniform ")) << " " << definition.id << ";\n";
|
|
||||||
source << "};\n\n";
|
|
||||||
source << "Sampler2D<float4> gVideoInput;\n";
|
|
||||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
|
||||||
source << "Sampler2D<float4> gSourceHistory" << index << ";\n";
|
|
||||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
|
||||||
source << "Sampler2D<float4> gTemporalHistory" << index << ";\n";
|
|
||||||
for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets)
|
|
||||||
source << "Sampler2D<float4> " << textureAsset.id << ";\n";
|
|
||||||
source << "\n";
|
|
||||||
source << "float4 sampleVideo(float2 tc)\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\treturn gVideoInput.Sample(tc);\n";
|
|
||||||
source << "}\n\n";
|
|
||||||
source << "float4 sampleSourceHistory(int framesAgo, float2 tc)\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\tif (gSourceHistoryLength <= 0)\n";
|
|
||||||
source << "\t\treturn sampleVideo(tc);\n";
|
|
||||||
source << "\tint clampedIndex = framesAgo;\n";
|
|
||||||
source << "\tif (clampedIndex < 0)\n";
|
|
||||||
source << "\t\tclampedIndex = 0;\n";
|
|
||||||
source << "\tif (clampedIndex >= gSourceHistoryLength)\n";
|
|
||||||
source << "\t\tclampedIndex = gSourceHistoryLength - 1;\n";
|
|
||||||
source << "\tswitch (clampedIndex)\n";
|
|
||||||
source << "\t{\n";
|
|
||||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
|
||||||
source << "\tcase " << index << ": return gSourceHistory" << index << ".Sample(tc);\n";
|
|
||||||
source << "\tdefault: return sampleVideo(tc);\n";
|
|
||||||
source << "\t}\n";
|
|
||||||
source << "}\n\n";
|
|
||||||
source << "float4 sampleTemporalHistory(int framesAgo, float2 tc)\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\tif (gTemporalHistoryLength <= 0)\n";
|
|
||||||
source << "\t\treturn sampleVideo(tc);\n";
|
|
||||||
source << "\tint clampedIndex = framesAgo;\n";
|
|
||||||
source << "\tif (clampedIndex < 0)\n";
|
|
||||||
source << "\t\tclampedIndex = 0;\n";
|
|
||||||
source << "\tif (clampedIndex >= gTemporalHistoryLength)\n";
|
|
||||||
source << "\t\tclampedIndex = gTemporalHistoryLength - 1;\n";
|
|
||||||
source << "\tswitch (clampedIndex)\n";
|
|
||||||
source << "\t{\n";
|
|
||||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
|
||||||
source << "\tcase " << index << ": return gTemporalHistory" << index << ".Sample(tc);\n";
|
|
||||||
source << "\tdefault: return sampleVideo(tc);\n";
|
|
||||||
source << "\t}\n";
|
|
||||||
source << "}\n\n";
|
|
||||||
source << "#include \"" << shaderPackage.shaderPath.generic_string() << "\"\n\n";
|
|
||||||
source << "[shader(\"fragment\")]\n";
|
|
||||||
source << "float4 fragmentMain(FragmentInput input) : SV_Target\n";
|
|
||||||
source << "{\n";
|
|
||||||
source << "\tShaderContext context;\n";
|
|
||||||
source << "\tcontext.uv = input.texCoord;\n";
|
|
||||||
source << "\tcontext.sourceColor = sampleVideo(context.uv);\n";
|
|
||||||
source << "\tcontext.inputResolution = gInputResolution;\n";
|
|
||||||
source << "\tcontext.outputResolution = gOutputResolution;\n";
|
|
||||||
source << "\tcontext.time = gTime;\n";
|
|
||||||
source << "\tcontext.frameCount = gFrameCount;\n";
|
|
||||||
source << "\tcontext.mixAmount = gMixAmount;\n";
|
|
||||||
source << "\tcontext.bypass = gBypass;\n";
|
|
||||||
source << "\tcontext.sourceHistoryLength = gSourceHistoryLength;\n";
|
|
||||||
source << "\tcontext.temporalHistoryLength = gTemporalHistoryLength;\n";
|
|
||||||
source << "\tfloat4 effectedColor = " << shaderPackage.entryPoint << "(context);\n";
|
|
||||||
source << "\tfloat mixValue = clamp(gBypass > 0.5 ? 0.0 : gMixAmount, 0.0, 1.0);\n";
|
|
||||||
source << "\treturn lerp(context.sourceColor, effectedColor, mixValue);\n";
|
|
||||||
source << "}\n";
|
|
||||||
return source.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeHost::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const
|
|
||||||
{
|
|
||||||
std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty";
|
|
||||||
if (!std::filesystem::exists(thirdPartyRoot))
|
|
||||||
{
|
|
||||||
error = "3rdParty directory was not found under the repository root.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyRoot))
|
|
||||||
{
|
|
||||||
if (!entry.is_directory())
|
|
||||||
continue;
|
|
||||||
std::filesystem::path candidate = entry.path() / "bin" / "slangc.exe";
|
|
||||||
if (std::filesystem::exists(candidate))
|
|
||||||
{
|
|
||||||
compilerPath = candidate;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error = "Could not find slangc.exe under 3rdParty.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeHost::RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const
|
|
||||||
{
|
|
||||||
std::filesystem::path compilerPath;
|
|
||||||
if (!FindSlangCompiler(compilerPath, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::string commandLine = "\"" + compilerPath.string() + "\" \"" + wrapperPath.string()
|
|
||||||
+ "\" -target glsl -profile glsl_430 -entry fragmentMain -stage fragment -o \"" + outputPath.string() + "\"";
|
|
||||||
|
|
||||||
STARTUPINFOA startupInfo = {};
|
|
||||||
PROCESS_INFORMATION processInfo = {};
|
|
||||||
startupInfo.cb = sizeof(startupInfo);
|
|
||||||
std::vector<char> mutableCommandLine(commandLine.begin(), commandLine.end());
|
|
||||||
mutableCommandLine.push_back('\0');
|
|
||||||
|
|
||||||
if (!CreateProcessA(NULL, mutableCommandLine.data(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, mRepoRoot.string().c_str(), &startupInfo, &processInfo))
|
|
||||||
{
|
|
||||||
error = "Failed to launch slangc.exe.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
|
||||||
|
|
||||||
DWORD exitCode = 0;
|
|
||||||
GetExitCodeProcess(processInfo.hProcess, &exitCode);
|
|
||||||
CloseHandle(processInfo.hThread);
|
|
||||||
CloseHandle(processInfo.hProcess);
|
|
||||||
|
|
||||||
if (exitCode != 0)
|
|
||||||
{
|
|
||||||
error = "slangc.exe returned a non-zero exit code while compiling the active shader package.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeHost::PatchGeneratedGlsl(std::string& shaderText, std::string& error) const
|
|
||||||
{
|
|
||||||
if (shaderText.find("#version 450") == std::string::npos)
|
|
||||||
{
|
|
||||||
error = "Generated GLSL did not include the expected version header.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
shaderText = ReplaceAll(shaderText, "#version 450", "#version 430 core");
|
|
||||||
shaderText = std::regex_replace(shaderText, std::regex(R"(#extension GL_EXT_samplerless_texture_functions : require\r?\n)"), "");
|
|
||||||
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) uniform;\r?\n)"), "");
|
|
||||||
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) buffer;\r?\n)"), "");
|
|
||||||
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(location = 0\)\s*in vec2 ([A-Za-z0-9_]+);)"), "in vec2 vTexCoord;");
|
|
||||||
shaderText = ReplaceAll(shaderText, "input_texCoord_0", "vTexCoord");
|
|
||||||
|
|
||||||
std::smatch match;
|
|
||||||
std::regex outRegex(R"(layout\(location = 0\)\s*out vec4 ([A-Za-z0-9_]+);)");
|
|
||||||
if (std::regex_search(shaderText, match, outRegex))
|
|
||||||
{
|
|
||||||
const std::string outputName = match[1].str();
|
|
||||||
shaderText = std::regex_replace(shaderText, outRegex, "layout(location = 0) out vec4 fragColor;");
|
|
||||||
shaderText = ReplaceAll(shaderText, outputName + " =", "fragColor =");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string RuntimeHost::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
std::string RuntimeHost::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||||
{
|
{
|
||||||
std::ifstream input(path, std::ios::binary);
|
std::ifstream input(path, std::ios::binary);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RuntimeJson.h"
|
#include "RuntimeJson.h"
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -9,102 +10,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum class ShaderParameterType
|
|
||||||
{
|
|
||||||
Float,
|
|
||||||
Vec2,
|
|
||||||
Color,
|
|
||||||
Boolean,
|
|
||||||
Enum
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderParameterOption
|
|
||||||
{
|
|
||||||
std::string value;
|
|
||||||
std::string label;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderParameterDefinition
|
|
||||||
{
|
|
||||||
std::string id;
|
|
||||||
std::string label;
|
|
||||||
ShaderParameterType type = ShaderParameterType::Float;
|
|
||||||
std::vector<double> defaultNumbers;
|
|
||||||
std::vector<double> minNumbers;
|
|
||||||
std::vector<double> maxNumbers;
|
|
||||||
std::vector<double> stepNumbers;
|
|
||||||
bool defaultBoolean = false;
|
|
||||||
std::string defaultEnumValue;
|
|
||||||
std::vector<ShaderParameterOption> enumOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderParameterValue
|
|
||||||
{
|
|
||||||
std::vector<double> numberValues;
|
|
||||||
bool booleanValue = false;
|
|
||||||
std::string enumValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TemporalHistorySource
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Source,
|
|
||||||
PreLayerInput
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TemporalSettings
|
|
||||||
{
|
|
||||||
bool enabled = false;
|
|
||||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
|
||||||
unsigned requestedHistoryLength = 0;
|
|
||||||
unsigned effectiveHistoryLength = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderTextureAsset
|
|
||||||
{
|
|
||||||
std::string id;
|
|
||||||
std::filesystem::path path;
|
|
||||||
std::filesystem::file_time_type writeTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShaderPackage
|
|
||||||
{
|
|
||||||
std::string id;
|
|
||||||
std::string displayName;
|
|
||||||
std::string description;
|
|
||||||
std::string category;
|
|
||||||
std::string entryPoint;
|
|
||||||
std::filesystem::path directoryPath;
|
|
||||||
std::filesystem::path shaderPath;
|
|
||||||
std::filesystem::path manifestPath;
|
|
||||||
std::vector<ShaderParameterDefinition> parameters;
|
|
||||||
std::vector<ShaderTextureAsset> textureAssets;
|
|
||||||
TemporalSettings temporal;
|
|
||||||
std::filesystem::file_time_type shaderWriteTime;
|
|
||||||
std::filesystem::file_time_type manifestWriteTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RuntimeRenderState
|
|
||||||
{
|
|
||||||
std::string layerId;
|
|
||||||
std::string shaderId;
|
|
||||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
|
||||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
|
||||||
std::vector<ShaderTextureAsset> textureAssets;
|
|
||||||
double timeSeconds = 0.0;
|
|
||||||
double frameCount = 0.0;
|
|
||||||
double mixAmount = 1.0;
|
|
||||||
double bypass = 0.0;
|
|
||||||
unsigned inputWidth = 0;
|
|
||||||
unsigned inputHeight = 0;
|
|
||||||
unsigned outputWidth = 0;
|
|
||||||
unsigned outputHeight = 0;
|
|
||||||
bool isTemporal = false;
|
|
||||||
TemporalHistorySource temporalHistorySource = TemporalHistorySource::None;
|
|
||||||
unsigned requestedTemporalHistoryLength = 0;
|
|
||||||
unsigned effectiveTemporalHistoryLength = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeHost
|
class RuntimeHost
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -189,10 +94,6 @@ private:
|
|||||||
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
|
||||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
|
||||||
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
void EnsureLayerDefaultsLocked(LayerPersistentState& layerState, const ShaderPackage& shaderPackage) const;
|
||||||
std::string BuildWrapperSlangSource(const ShaderPackage& shaderPackage) const;
|
|
||||||
bool FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const;
|
|
||||||
bool RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const;
|
|
||||||
bool PatchGeneratedGlsl(std::string& shaderText, std::string& error) const;
|
|
||||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||||
bool ResolvePaths(std::string& error);
|
bool ResolvePaths(std::string& error);
|
||||||
|
|||||||
290
apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp
Normal file
290
apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "ShaderCompiler.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string ReplaceAll(std::string text, const std::string& from, const std::string& to)
|
||||||
|
{
|
||||||
|
std::size_t offset = 0;
|
||||||
|
while ((offset = text.find(from, offset)) != std::string::npos)
|
||||||
|
{
|
||||||
|
text.replace(offset, from.length(), to);
|
||||||
|
offset += to.length();
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SlangTypeForParameter(ShaderParameterType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ShaderParameterType::Float: return "uniform float";
|
||||||
|
case ShaderParameterType::Vec2: return "uniform float2";
|
||||||
|
case ShaderParameterType::Color: return "uniform float4";
|
||||||
|
case ShaderParameterType::Boolean: return "uniform bool";
|
||||||
|
case ShaderParameterType::Enum: return "uniform int";
|
||||||
|
}
|
||||||
|
return "uniform float";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderCompiler::ShaderCompiler(
|
||||||
|
const std::filesystem::path& repoRoot,
|
||||||
|
const std::filesystem::path& wrapperPath,
|
||||||
|
const std::filesystem::path& generatedGlslPath,
|
||||||
|
const std::filesystem::path& patchedGlslPath,
|
||||||
|
unsigned maxTemporalHistoryFrames)
|
||||||
|
: mRepoRoot(repoRoot),
|
||||||
|
mWrapperPath(wrapperPath),
|
||||||
|
mGeneratedGlslPath(generatedGlslPath),
|
||||||
|
mPatchedGlslPath(patchedGlslPath),
|
||||||
|
mMaxTemporalHistoryFrames(maxTemporalHistoryFrames)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::BuildLayerFragmentShaderSource(const ShaderPackage& shaderPackage, std::string& fragmentShaderSource, std::string& error) const
|
||||||
|
{
|
||||||
|
const std::string wrapperSource = BuildWrapperSlangSource(shaderPackage);
|
||||||
|
if (!WriteTextFile(mWrapperPath, wrapperSource, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!RunSlangCompiler(mWrapperPath, mGeneratedGlslPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fragmentShaderSource = ReadTextFile(mGeneratedGlslPath, error);
|
||||||
|
if (fragmentShaderSource.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!PatchGeneratedGlsl(fragmentShaderSource, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!WriteTextFile(mPatchedGlslPath, fragmentShaderSource, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage) const
|
||||||
|
{
|
||||||
|
std::ostringstream source;
|
||||||
|
source << "struct FragmentInput\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\tfloat4 position : SV_Position;\n";
|
||||||
|
source << "\tfloat2 texCoord : TEXCOORD0;\n";
|
||||||
|
source << "};\n\n";
|
||||||
|
source << "struct ShaderContext\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\tfloat2 uv;\n";
|
||||||
|
source << "\tfloat4 sourceColor;\n";
|
||||||
|
source << "\tfloat2 inputResolution;\n";
|
||||||
|
source << "\tfloat2 outputResolution;\n";
|
||||||
|
source << "\tfloat time;\n";
|
||||||
|
source << "\tfloat frameCount;\n";
|
||||||
|
source << "\tfloat mixAmount;\n";
|
||||||
|
source << "\tfloat bypass;\n";
|
||||||
|
source << "\tint sourceHistoryLength;\n";
|
||||||
|
source << "\tint temporalHistoryLength;\n";
|
||||||
|
source << "};\n\n";
|
||||||
|
source << "cbuffer GlobalParams\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\tfloat gTime;\n";
|
||||||
|
source << "\tfloat2 gInputResolution;\n";
|
||||||
|
source << "\tfloat2 gOutputResolution;\n";
|
||||||
|
source << "\tfloat gFrameCount;\n";
|
||||||
|
source << "\tfloat gMixAmount;\n";
|
||||||
|
source << "\tfloat gBypass;\n";
|
||||||
|
source << "\tint gSourceHistoryLength;\n";
|
||||||
|
source << "\tint gTemporalHistoryLength;\n";
|
||||||
|
for (const ShaderParameterDefinition& definition : shaderPackage.parameters)
|
||||||
|
source << "\t" << SlangTypeForParameter(definition.type).substr(strlen("uniform ")) << " " << definition.id << ";\n";
|
||||||
|
source << "};\n\n";
|
||||||
|
source << "Sampler2D<float4> gVideoInput;\n";
|
||||||
|
for (unsigned index = 0; index < mMaxTemporalHistoryFrames; ++index)
|
||||||
|
source << "Sampler2D<float4> gSourceHistory" << index << ";\n";
|
||||||
|
for (unsigned index = 0; index < mMaxTemporalHistoryFrames; ++index)
|
||||||
|
source << "Sampler2D<float4> gTemporalHistory" << index << ";\n";
|
||||||
|
for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets)
|
||||||
|
source << "Sampler2D<float4> " << textureAsset.id << ";\n";
|
||||||
|
source << "\n";
|
||||||
|
source << "float4 sampleVideo(float2 tc)\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\treturn gVideoInput.Sample(tc);\n";
|
||||||
|
source << "}\n\n";
|
||||||
|
source << "float4 sampleSourceHistory(int framesAgo, float2 tc)\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\tif (gSourceHistoryLength <= 0)\n";
|
||||||
|
source << "\t\treturn sampleVideo(tc);\n";
|
||||||
|
source << "\tint clampedIndex = framesAgo;\n";
|
||||||
|
source << "\tif (clampedIndex < 0)\n";
|
||||||
|
source << "\t\tclampedIndex = 0;\n";
|
||||||
|
source << "\tif (clampedIndex >= gSourceHistoryLength)\n";
|
||||||
|
source << "\t\tclampedIndex = gSourceHistoryLength - 1;\n";
|
||||||
|
source << "\tswitch (clampedIndex)\n";
|
||||||
|
source << "\t{\n";
|
||||||
|
for (unsigned index = 0; index < mMaxTemporalHistoryFrames; ++index)
|
||||||
|
source << "\tcase " << index << ": return gSourceHistory" << index << ".Sample(tc);\n";
|
||||||
|
source << "\tdefault: return sampleVideo(tc);\n";
|
||||||
|
source << "\t}\n";
|
||||||
|
source << "}\n\n";
|
||||||
|
source << "float4 sampleTemporalHistory(int framesAgo, float2 tc)\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\tif (gTemporalHistoryLength <= 0)\n";
|
||||||
|
source << "\t\treturn sampleVideo(tc);\n";
|
||||||
|
source << "\tint clampedIndex = framesAgo;\n";
|
||||||
|
source << "\tif (clampedIndex < 0)\n";
|
||||||
|
source << "\t\tclampedIndex = 0;\n";
|
||||||
|
source << "\tif (clampedIndex >= gTemporalHistoryLength)\n";
|
||||||
|
source << "\t\tclampedIndex = gTemporalHistoryLength - 1;\n";
|
||||||
|
source << "\tswitch (clampedIndex)\n";
|
||||||
|
source << "\t{\n";
|
||||||
|
for (unsigned index = 0; index < mMaxTemporalHistoryFrames; ++index)
|
||||||
|
source << "\tcase " << index << ": return gTemporalHistory" << index << ".Sample(tc);\n";
|
||||||
|
source << "\tdefault: return sampleVideo(tc);\n";
|
||||||
|
source << "\t}\n";
|
||||||
|
source << "}\n\n";
|
||||||
|
source << "#include \"" << shaderPackage.shaderPath.generic_string() << "\"\n\n";
|
||||||
|
source << "[shader(\"fragment\")]\n";
|
||||||
|
source << "float4 fragmentMain(FragmentInput input) : SV_Target\n";
|
||||||
|
source << "{\n";
|
||||||
|
source << "\tShaderContext context;\n";
|
||||||
|
source << "\tcontext.uv = input.texCoord;\n";
|
||||||
|
source << "\tcontext.sourceColor = sampleVideo(context.uv);\n";
|
||||||
|
source << "\tcontext.inputResolution = gInputResolution;\n";
|
||||||
|
source << "\tcontext.outputResolution = gOutputResolution;\n";
|
||||||
|
source << "\tcontext.time = gTime;\n";
|
||||||
|
source << "\tcontext.frameCount = gFrameCount;\n";
|
||||||
|
source << "\tcontext.mixAmount = gMixAmount;\n";
|
||||||
|
source << "\tcontext.bypass = gBypass;\n";
|
||||||
|
source << "\tcontext.sourceHistoryLength = gSourceHistoryLength;\n";
|
||||||
|
source << "\tcontext.temporalHistoryLength = gTemporalHistoryLength;\n";
|
||||||
|
source << "\tfloat4 effectedColor = " << shaderPackage.entryPoint << "(context);\n";
|
||||||
|
source << "\tfloat mixValue = clamp(gBypass > 0.5 ? 0.0 : gMixAmount, 0.0, 1.0);\n";
|
||||||
|
source << "\treturn lerp(context.sourceColor, effectedColor, mixValue);\n";
|
||||||
|
source << "}\n";
|
||||||
|
return source.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const
|
||||||
|
{
|
||||||
|
std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty";
|
||||||
|
if (!std::filesystem::exists(thirdPartyRoot))
|
||||||
|
{
|
||||||
|
error = "3rdParty directory was not found under the repository root.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyRoot))
|
||||||
|
{
|
||||||
|
if (!entry.is_directory())
|
||||||
|
continue;
|
||||||
|
std::filesystem::path candidate = entry.path() / "bin" / "slangc.exe";
|
||||||
|
if (std::filesystem::exists(candidate))
|
||||||
|
{
|
||||||
|
compilerPath = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error = "Could not find slangc.exe under 3rdParty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const
|
||||||
|
{
|
||||||
|
std::filesystem::path compilerPath;
|
||||||
|
if (!FindSlangCompiler(compilerPath, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string commandLine = "\"" + compilerPath.string() + "\" \"" + wrapperPath.string()
|
||||||
|
+ "\" -target glsl -profile glsl_430 -entry fragmentMain -stage fragment -o \"" + outputPath.string() + "\"";
|
||||||
|
|
||||||
|
STARTUPINFOA startupInfo = {};
|
||||||
|
PROCESS_INFORMATION processInfo = {};
|
||||||
|
startupInfo.cb = sizeof(startupInfo);
|
||||||
|
std::vector<char> mutableCommandLine(commandLine.begin(), commandLine.end());
|
||||||
|
mutableCommandLine.push_back('\0');
|
||||||
|
|
||||||
|
if (!CreateProcessA(NULL, mutableCommandLine.data(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, mRepoRoot.string().c_str(), &startupInfo, &processInfo))
|
||||||
|
{
|
||||||
|
error = "Failed to launch slangc.exe.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||||
|
|
||||||
|
DWORD exitCode = 0;
|
||||||
|
GetExitCodeProcess(processInfo.hProcess, &exitCode);
|
||||||
|
CloseHandle(processInfo.hThread);
|
||||||
|
CloseHandle(processInfo.hProcess);
|
||||||
|
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
error = "slangc.exe returned a non-zero exit code while compiling the active shader package.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::PatchGeneratedGlsl(std::string& shaderText, std::string& error) const
|
||||||
|
{
|
||||||
|
if (shaderText.find("#version 450") == std::string::npos)
|
||||||
|
{
|
||||||
|
error = "Generated GLSL did not include the expected version header.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
shaderText = ReplaceAll(shaderText, "#version 450", "#version 430 core");
|
||||||
|
shaderText = std::regex_replace(shaderText, std::regex(R"(#extension GL_EXT_samplerless_texture_functions : require\r?\n)"), "");
|
||||||
|
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) uniform;\r?\n)"), "");
|
||||||
|
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) buffer;\r?\n)"), "");
|
||||||
|
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(location = 0\)\s*in vec2 ([A-Za-z0-9_]+);)"), "in vec2 vTexCoord;");
|
||||||
|
shaderText = ReplaceAll(shaderText, "input_texCoord_0", "vTexCoord");
|
||||||
|
|
||||||
|
std::smatch match;
|
||||||
|
std::regex outRegex(R"(layout\(location = 0\)\s*out vec4 ([A-Za-z0-9_]+);)");
|
||||||
|
if (std::regex_search(shaderText, match, outRegex))
|
||||||
|
{
|
||||||
|
const std::string outputName = match[1].str();
|
||||||
|
shaderText = std::regex_replace(shaderText, outRegex, "layout(location = 0) out vec4 fragColor;");
|
||||||
|
shaderText = ReplaceAll(shaderText, outputName + " =", "fragColor =");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ShaderCompiler::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||||
|
{
|
||||||
|
std::ifstream input(path, std::ios::binary);
|
||||||
|
if (!input)
|
||||||
|
{
|
||||||
|
error = "Could not open file: " + path.string();
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream buffer;
|
||||||
|
buffer << input.rdbuf();
|
||||||
|
return buffer.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderCompiler::WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const
|
||||||
|
{
|
||||||
|
std::error_code fsError;
|
||||||
|
std::filesystem::create_directories(path.parent_path(), fsError);
|
||||||
|
|
||||||
|
std::ofstream output(path, std::ios::binary);
|
||||||
|
if (!output)
|
||||||
|
{
|
||||||
|
error = "Could not write file: " + path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
output << contents;
|
||||||
|
return output.good();
|
||||||
|
}
|
||||||
34
apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h
Normal file
34
apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ShaderCompiler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShaderCompiler(
|
||||||
|
const std::filesystem::path& repoRoot,
|
||||||
|
const std::filesystem::path& wrapperPath,
|
||||||
|
const std::filesystem::path& generatedGlslPath,
|
||||||
|
const std::filesystem::path& patchedGlslPath,
|
||||||
|
unsigned maxTemporalHistoryFrames);
|
||||||
|
|
||||||
|
bool BuildLayerFragmentShaderSource(const ShaderPackage& shaderPackage, std::string& fragmentShaderSource, std::string& error) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string BuildWrapperSlangSource(const ShaderPackage& shaderPackage) const;
|
||||||
|
bool FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const;
|
||||||
|
bool RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const;
|
||||||
|
bool PatchGeneratedGlsl(std::string& shaderText, std::string& error) const;
|
||||||
|
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||||
|
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path mRepoRoot;
|
||||||
|
std::filesystem::path mWrapperPath;
|
||||||
|
std::filesystem::path mGeneratedGlslPath;
|
||||||
|
std::filesystem::path mPatchedGlslPath;
|
||||||
|
unsigned mMaxTemporalHistoryFrames;
|
||||||
|
};
|
||||||
102
apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h
Normal file
102
apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
enum class ShaderParameterType
|
||||||
|
{
|
||||||
|
Float,
|
||||||
|
Vec2,
|
||||||
|
Color,
|
||||||
|
Boolean,
|
||||||
|
Enum
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderParameterOption
|
||||||
|
{
|
||||||
|
std::string value;
|
||||||
|
std::string label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderParameterDefinition
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string label;
|
||||||
|
ShaderParameterType type = ShaderParameterType::Float;
|
||||||
|
std::vector<double> defaultNumbers;
|
||||||
|
std::vector<double> minNumbers;
|
||||||
|
std::vector<double> maxNumbers;
|
||||||
|
std::vector<double> stepNumbers;
|
||||||
|
bool defaultBoolean = false;
|
||||||
|
std::string defaultEnumValue;
|
||||||
|
std::vector<ShaderParameterOption> enumOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderParameterValue
|
||||||
|
{
|
||||||
|
std::vector<double> numberValues;
|
||||||
|
bool booleanValue = false;
|
||||||
|
std::string enumValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TemporalHistorySource
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Source,
|
||||||
|
PreLayerInput
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TemporalSettings
|
||||||
|
{
|
||||||
|
bool enabled = false;
|
||||||
|
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||||
|
unsigned requestedHistoryLength = 0;
|
||||||
|
unsigned effectiveHistoryLength = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderTextureAsset
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::filesystem::path path;
|
||||||
|
std::filesystem::file_time_type writeTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderPackage
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::string displayName;
|
||||||
|
std::string description;
|
||||||
|
std::string category;
|
||||||
|
std::string entryPoint;
|
||||||
|
std::filesystem::path directoryPath;
|
||||||
|
std::filesystem::path shaderPath;
|
||||||
|
std::filesystem::path manifestPath;
|
||||||
|
std::vector<ShaderParameterDefinition> parameters;
|
||||||
|
std::vector<ShaderTextureAsset> textureAssets;
|
||||||
|
TemporalSettings temporal;
|
||||||
|
std::filesystem::file_time_type shaderWriteTime;
|
||||||
|
std::filesystem::file_time_type manifestWriteTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RuntimeRenderState
|
||||||
|
{
|
||||||
|
std::string layerId;
|
||||||
|
std::string shaderId;
|
||||||
|
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||||
|
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||||
|
std::vector<ShaderTextureAsset> textureAssets;
|
||||||
|
double timeSeconds = 0.0;
|
||||||
|
double frameCount = 0.0;
|
||||||
|
double mixAmount = 1.0;
|
||||||
|
double bypass = 0.0;
|
||||||
|
unsigned inputWidth = 0;
|
||||||
|
unsigned inputHeight = 0;
|
||||||
|
unsigned outputWidth = 0;
|
||||||
|
unsigned outputHeight = 0;
|
||||||
|
bool isTemporal = false;
|
||||||
|
TemporalHistorySource temporalHistorySource = TemporalHistorySource::None;
|
||||||
|
unsigned requestedTemporalHistoryLength = 0;
|
||||||
|
unsigned effectiveTemporalHistoryLength = 0;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user