diff --git a/CMakeLists.txt b/CMakeLists.txt index e7abd9e..72dee02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ if(NOT EXISTS "${GPUDIRECT_DIR}/lib/x64/dvp.lib") message(FATAL_ERROR "NVIDIA GPUDirect library not found under ${GPUDIRECT_DIR}") endif() -add_executable(LoopThroughWithOpenGLCompositing WIN32 +set(APP_SOURCES "${APP_DIR}/ControlServer.cpp" "${APP_DIR}/ControlServer.h" "${APP_DIR}/DeckLinkAPI_i.c" @@ -33,6 +33,9 @@ add_executable(LoopThroughWithOpenGLCompositing WIN32 "${APP_DIR}/RuntimeHost.h" "${APP_DIR}/RuntimeJson.cpp" "${APP_DIR}/RuntimeJson.h" + "${APP_DIR}/ShaderCompiler.cpp" + "${APP_DIR}/ShaderCompiler.h" + "${APP_DIR}/ShaderTypes.h" "${APP_DIR}/stdafx.cpp" "${APP_DIR}/stdafx.h" "${APP_DIR}/targetver.h" @@ -40,6 +43,8 @@ add_executable(LoopThroughWithOpenGLCompositing WIN32 "${APP_DIR}/VideoFrameTransfer.h" ) +add_executable(LoopThroughWithOpenGLCompositing WIN32 ${APP_SOURCES}) + target_include_directories(LoopThroughWithOpenGLCompositing PRIVATE "${APP_DIR}" "${GPUDIRECT_DIR}/include" @@ -73,25 +78,4 @@ add_custom_command(TARGET LoopThroughWithOpenGLCompositing POST_BUILD "$/dvp.dll" ) -source_group(TREE "${APP_DIR}" FILES - "${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" -) +source_group(TREE "${APP_DIR}" FILES ${APP_SOURCES}) diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp index 01df860..5cdf72e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp @@ -1,11 +1,10 @@ #include "stdafx.h" #include "RuntimeHost.h" +#include "ShaderCompiler.h" #include #include -#include #include -#include #include #include @@ -24,17 +23,6 @@ std::string Trim(const std::string& text) 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) { return std::isfinite(value) != 0; @@ -102,19 +90,6 @@ std::string ShaderParameterTypeToString(ShaderParameterType type) 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) { if (sourceName == "source") @@ -177,6 +152,329 @@ bool TextureAssetsEqual(const std::vector& left, const std:: 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& 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(requestedHistoryLength); + shaderPackage.temporal.effectiveHistoryLength = std::min(shaderPackage.temporal.requestedHistoryLength, maxTemporalHistoryFrames); + return true; +} + +bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector& 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() @@ -667,24 +965,8 @@ bool RuntimeHost::BuildLayerFragmentShaderSource(const std::string& layerId, std shaderPackage = it->second; } - 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; + ShaderCompiler compiler(mRepoRoot, mWrapperPath, mGeneratedGlslPath, mPatchedGlslPath, mConfig.maxTemporalHistoryFrames); + return compiler.BuildLayerFragmentShaderSource(shaderPackage, fragmentShaderSource, error); } catch (const std::exception& exception) { @@ -977,20 +1259,21 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath, JsonValue manifestJson; if (!ParseJson(manifestText, manifestJson, error)) return false; - - const JsonValue* idValue = manifestJson.find("id"); - const JsonValue* nameValue = manifestJson.find("name"); - if (!idValue || !nameValue) + if (!manifestJson.isObject()) + { + error = "Shader manifest root must be an object: " + manifestPath.string(); + 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; } - 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.shaderPath = shaderPackage.directoryPath / "shader.slang"; 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.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath); - const JsonValue* texturesValue = manifestJson.find("textures"); - if (texturesValue) - { - 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(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; + return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) && + ParseTemporalSettings(manifestJson, shaderPackage, mConfig.maxTemporalHistoryFrames, manifestPath, error) && + ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error); } 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 gVideoInput;\n"; - for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index) - source << "Sampler2D gSourceHistory" << index << ";\n"; - for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index) - source << "Sampler2D gTemporalHistory" << index << ";\n"; - for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets) - source << "Sampler2D " << 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 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::ifstream input(path, std::ios::binary); diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h index 5dfa229..d257a19 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h @@ -1,6 +1,7 @@ #pragma once #include "RuntimeJson.h" +#include "ShaderTypes.h" #include #include @@ -9,102 +10,6 @@ #include #include -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 defaultNumbers; - std::vector minNumbers; - std::vector maxNumbers; - std::vector stepNumbers; - bool defaultBoolean = false; - std::string defaultEnumValue; - std::vector enumOptions; -}; - -struct ShaderParameterValue -{ - std::vector 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 parameters; - std::vector 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 parameterDefinitions; - std::map parameterValues; - std::vector 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 { public: @@ -189,10 +94,6 @@ private: bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const; ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) 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; bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const; bool ResolvePaths(std::string& error); diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp new file mode 100644 index 0000000..dc6f503 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.cpp @@ -0,0 +1,290 @@ +#include "stdafx.h" +#include "ShaderCompiler.h" + +#include +#include +#include +#include +#include + +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 gVideoInput;\n"; + for (unsigned index = 0; index < mMaxTemporalHistoryFrames; ++index) + source << "Sampler2D gSourceHistory" << index << ";\n"; + for (unsigned index = 0; index < mMaxTemporalHistoryFrames; ++index) + source << "Sampler2D gTemporalHistory" << index << ";\n"; + for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets) + source << "Sampler2D " << 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 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(); +} diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h new file mode 100644 index 0000000..b93214f --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderCompiler.h @@ -0,0 +1,34 @@ +#pragma once + +#include "ShaderTypes.h" + +#include +#include + +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; +}; diff --git a/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h b/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h new file mode 100644 index 0000000..19fa473 --- /dev/null +++ b/apps/LoopThroughWithOpenGLCompositing/ShaderTypes.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + +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 defaultNumbers; + std::vector minNumbers; + std::vector maxNumbers; + std::vector stepNumbers; + bool defaultBoolean = false; + std::string defaultEnumValue; + std::vector enumOptions; +}; + +struct ShaderParameterValue +{ + std::vector 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 parameters; + std::vector 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 parameterDefinitions; + std::map parameterValues; + std::vector 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; +};