Formalize volume shader include context

This commit is contained in:
2026-04-10 01:05:03 +08:00
parent 34a32b73dd
commit 4111f841d4
9 changed files with 1212 additions and 10 deletions

View File

@@ -49,6 +49,7 @@ enum class ShaderLanguage : uint8_t;
struct ShaderCompileDesc {
std::wstring fileName;
std::vector<std::wstring> includeDirectories;
std::vector<uint8_t> source;
ShaderLanguage sourceLanguage = ShaderLanguage::Unknown;
std::wstring entryPoint;
@@ -323,6 +324,7 @@ struct GraphicsPipelineDesc {
uint32_t renderTargetFormats[8] = { 0 }; // Format
uint32_t depthStencilFormat = 0; // Format
uint32_t sampleCount = 1;
uint32_t sampleQuality = 0;
};
struct RHIDeviceDesc {

View File

@@ -603,10 +603,89 @@ std::string InjectMacrosIntoSource(const std::string& source, const std::vector<
return macroBlock + source;
}
std::filesystem::path MakeAbsoluteNormalizedPath(const std::filesystem::path& path) {
if (path.empty()) {
return {};
}
std::error_code ec;
const std::filesystem::path absolutePath = std::filesystem::absolute(path, ec);
if (ec) {
return path.lexically_normal();
}
return absolutePath.lexically_normal();
}
std::string NormalizeDirectoryKey(const std::filesystem::path& path) {
std::string key = path.generic_string();
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return key;
}
std::vector<std::filesystem::path> CollectShaderIncludeDirectories(const ShaderCompileDesc& desc) {
std::vector<std::filesystem::path> directories;
directories.reserve(desc.includeDirectories.size() + (desc.fileName.empty() ? 0u : 1u));
auto appendDirectory = [&](const std::filesystem::path& candidate) {
if (candidate.empty()) {
return;
}
const std::filesystem::path normalized = MakeAbsoluteNormalizedPath(candidate);
if (normalized.empty()) {
return;
}
const std::string key = NormalizeDirectoryKey(normalized);
const auto existing = std::find_if(directories.begin(), directories.end(), [&](const std::filesystem::path& path) {
return NormalizeDirectoryKey(path) == key;
});
if (existing == directories.end()) {
directories.push_back(normalized);
}
};
if (!desc.fileName.empty()) {
const std::filesystem::path sourcePath(desc.fileName);
if (sourcePath.has_parent_path()) {
appendDirectory(sourcePath.parent_path());
}
}
for (const std::wstring& includeDirectory : desc.includeDirectories) {
appendDirectory(std::filesystem::path(includeDirectory));
}
return directories;
}
std::wstring BuildIncludeDirectoryArguments(const ShaderCompileDesc& desc) {
std::wstring arguments;
const std::vector<std::filesystem::path> includeDirectories = CollectShaderIncludeDirectories(desc);
for (const std::filesystem::path& includeDirectory : includeDirectories) {
arguments += L" -I \"" + includeDirectory.wstring() + L"\"";
}
return arguments;
}
std::wstring ResolveCompilerWorkingDirectory(const ShaderCompileDesc& desc) {
const std::vector<std::filesystem::path> includeDirectories = CollectShaderIncludeDirectories(desc);
if (!includeDirectories.empty()) {
return includeDirectories.front().wstring();
}
return std::wstring();
}
bool RunProcessAndCapture(const std::wstring& executablePath,
const std::wstring& arguments,
DWORD& exitCode,
std::string& output) {
std::string& output,
const wchar_t* workingDirectory = nullptr) {
SECURITY_ATTRIBUTES securityAttributes = {};
securityAttributes.nLength = sizeof(securityAttributes);
securityAttributes.bInheritHandle = TRUE;
@@ -638,7 +717,7 @@ bool RunProcessAndCapture(const std::wstring& executablePath,
TRUE,
CREATE_NO_WINDOW,
nullptr,
nullptr,
workingDirectory,
&startupInfo,
&processInfo);
@@ -701,14 +780,22 @@ bool CompileGlslToSpirv(const ShaderCompileDesc& desc,
return false;
}
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
const std::wstring arguments =
SpirvTargetArguments(targetEnvironment) + L" -S " + ShaderStageArgument(type) +
L" -e " + WidenAscii(entryPoint.c_str()) +
includeArguments +
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
DWORD exitCode = 0;
std::string compilerOutput;
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
const bool ranProcess = RunProcessAndCapture(
validatorPath,
arguments,
exitCode,
compilerOutput,
workingDirectory.empty() ? nullptr : workingDirectory.c_str());
std::vector<uint8_t> bytes;
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
@@ -786,16 +873,24 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
const std::string profile = NarrowAscii(desc.profile);
const std::wstring targetProfile =
WidenAscii(profile.empty() ? "ps_6_0" : profile.c_str());
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
const std::wstring arguments =
L"-spirv -Zpc -fvk-use-dx-layout -fspv-target-env=vulkan1.0 " +
std::wstring(L"-T ") + targetProfile +
L" -E " + WidenAscii(entryPoint.c_str()) +
includeArguments +
L" -Fo \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
DWORD exitCode = 0;
std::string compilerOutput;
const bool ranProcess = RunProcessAndCapture(dxcPath, arguments, exitCode, compilerOutput);
const bool ranProcess = RunProcessAndCapture(
dxcPath,
arguments,
exitCode,
compilerOutput,
workingDirectory.empty() ? nullptr : workingDirectory.c_str());
std::vector<uint8_t> bytes;
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
@@ -861,15 +956,23 @@ bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
return false;
}
const std::wstring includeArguments = BuildIncludeDirectoryArguments(desc);
const std::wstring workingDirectory = ResolveCompilerWorkingDirectory(desc);
const std::wstring arguments =
L"-D --auto-map-bindings " + SpirvTargetArguments(targetEnvironment) +
L" -S " + ShaderStageArgument(type) +
L" -e " + WidenAscii(entryPoint.c_str()) +
includeArguments +
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
DWORD exitCode = 0;
std::string compilerOutput;
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
const bool ranProcess = RunProcessAndCapture(
validatorPath,
arguments,
exitCode,
compilerOutput,
workingDirectory.empty() ? nullptr : workingDirectory.c_str());
std::vector<uint8_t> bytes;
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);

View File

@@ -0,0 +1,448 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/RHI/RHIEnums.h>
#include <XCEngine/RHI/RHIPipelineState.h>
#include <XCEngine/Rendering/Builtin/BuiltinPassLayoutUtils.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Shader/Shader.h>
#include <filesystem>
#include <regex>
#include <string>
namespace XCEngine {
namespace Rendering {
namespace Internal {
inline Resources::ShaderBackend ToShaderBackend(RHI::RHIType backendType) {
switch (backendType) {
case RHI::RHIType::D3D12:
return Resources::ShaderBackend::D3D12;
case RHI::RHIType::Vulkan:
return Resources::ShaderBackend::Vulkan;
case RHI::RHIType::OpenGL:
default:
return Resources::ShaderBackend::OpenGL;
}
}
inline RHI::ShaderLanguage ToRHIShaderLanguage(Resources::ShaderLanguage language) {
switch (language) {
case Resources::ShaderLanguage::HLSL:
return RHI::ShaderLanguage::HLSL;
case Resources::ShaderLanguage::SPIRV:
return RHI::ShaderLanguage::SPIRV;
case Resources::ShaderLanguage::GLSL:
default:
return RHI::ShaderLanguage::GLSL;
}
}
inline std::wstring ToWideAscii(const Containers::String& value) {
std::wstring wide;
wide.reserve(value.Length());
for (size_t index = 0; index < value.Length(); ++index) {
wide.push_back(static_cast<wchar_t>(value[index]));
}
return wide;
}
inline std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr(), value.Length());
}
inline std::string EscapeRegexLiteral(const Containers::String& value) {
std::string escaped;
escaped.reserve(value.Length() * 2u);
for (size_t index = 0; index < value.Length(); ++index) {
const char ch = value[index];
switch (ch) {
case '\\':
case '^':
case '$':
case '.':
case '|':
case '?':
case '*':
case '+':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
escaped.push_back('\\');
break;
default:
break;
}
escaped.push_back(ch);
}
return escaped;
}
inline bool TryCollectShaderPassResourceBindings(
const Resources::ShaderPass& pass,
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings) {
outBindings.Clear();
if (pass.resources.Empty()) {
return false;
}
outBindings.Reserve(pass.resources.Size());
for (const Resources::ShaderResourceBindingDesc& binding : pass.resources) {
outBindings.PushBack(binding);
}
return true;
}
inline bool TryRewriteHlslRegisterBindingWithName(
std::string& sourceText,
const Containers::String& declarationName,
const char* registerPrefix,
Core::uint32 bindingIndex,
Core::uint32 setIndex,
bool includeRegisterSpace,
Resources::ShaderResourceType resourceType) {
if (declarationName.Empty()) {
return false;
}
const std::string registerClause =
includeRegisterSpace
? std::string("register(") + registerPrefix +
std::to_string(bindingIndex) +
", space" +
std::to_string(setIndex) +
")"
: std::string("register(") + registerPrefix +
std::to_string(bindingIndex) +
")";
const std::string escapedName = EscapeRegexLiteral(declarationName);
if (resourceType == Resources::ShaderResourceType::ConstantBuffer) {
const std::regex pattern(
"(cbuffer\\s+" + escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*\\{)",
std::regex::ECMAScript);
const std::string rewritten =
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
if (rewritten != sourceText) {
sourceText = rewritten;
return true;
}
return false;
}
if (resourceType == Resources::ShaderResourceType::StructuredBuffer ||
resourceType == Resources::ShaderResourceType::RWStructuredBuffer) {
const std::regex pattern(
"((?:globallycoherent\\s+)?(?:StructuredBuffer|RWStructuredBuffer)\\s*<[^;\\r\\n>]+>\\s+" +
escapedName + "\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
std::regex::ECMAScript);
const std::string rewritten =
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
if (rewritten != sourceText) {
sourceText = rewritten;
return true;
}
return false;
}
if (resourceType == Resources::ShaderResourceType::RawBuffer ||
resourceType == Resources::ShaderResourceType::RWRawBuffer) {
const std::regex pattern(
"((?:globallycoherent\\s+)?(?:ByteAddressBuffer|RWByteAddressBuffer)\\s+" + escapedName +
"\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
std::regex::ECMAScript);
const std::string rewritten =
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
if (rewritten != sourceText) {
sourceText = rewritten;
return true;
}
return false;
}
const std::regex pattern(
"((?:Texture2D|TextureCube|SamplerState|SamplerComparisonState)\\s+" + escapedName +
"\\s*)(:\\s*register\\s*\\([^\\)]*\\))?(\\s*;)",
std::regex::ECMAScript);
const std::string rewritten =
std::regex_replace(sourceText, pattern, "$1: " + registerClause + "$3");
if (rewritten != sourceText) {
sourceText = rewritten;
return true;
}
return false;
}
inline const char* TryGetHlslRegisterPrefix(Resources::ShaderResourceType type) {
switch (type) {
case Resources::ShaderResourceType::ConstantBuffer:
return "b";
case Resources::ShaderResourceType::Texture2D:
case Resources::ShaderResourceType::TextureCube:
case Resources::ShaderResourceType::StructuredBuffer:
case Resources::ShaderResourceType::RawBuffer:
return "t";
case Resources::ShaderResourceType::Sampler:
return "s";
case Resources::ShaderResourceType::RWStructuredBuffer:
case Resources::ShaderResourceType::RWRawBuffer:
return "u";
default:
return nullptr;
}
}
inline bool TryRewriteHlslRegisterBinding(
std::string& sourceText,
const Resources::ShaderResourceBindingDesc& binding,
bool includeRegisterSpace) {
const char* registerPrefix = TryGetHlslRegisterPrefix(binding.type);
if (registerPrefix == nullptr) {
return false;
}
if (TryRewriteHlslRegisterBindingWithName(
sourceText,
binding.name,
registerPrefix,
binding.binding,
binding.set,
includeRegisterSpace,
binding.type)) {
return true;
}
return false;
}
inline bool TryBuildRuntimeShaderBindings(
const Resources::ShaderPass& pass,
Resources::ShaderBackend backend,
Containers::Array<Resources::ShaderResourceBindingDesc>& outBindings,
bool& outIncludeRegisterSpace) {
outBindings.Clear();
outIncludeRegisterSpace = false;
if (!TryCollectShaderPassResourceBindings(pass, outBindings)) {
return false;
}
if (backend == Resources::ShaderBackend::Vulkan) {
outIncludeRegisterSpace = true;
return true;
}
if (backend != Resources::ShaderBackend::D3D12 &&
backend != Resources::ShaderBackend::OpenGL) {
outBindings.Clear();
return false;
}
Core::uint32 nextConstantBufferRegister = 0;
Core::uint32 nextTextureRegister = 0;
Core::uint32 nextSamplerRegister = 0;
Core::uint32 nextUnorderedAccessRegister = 0;
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
binding.set = 0;
switch (binding.type) {
case Resources::ShaderResourceType::ConstantBuffer:
binding.binding = nextConstantBufferRegister++;
break;
case Resources::ShaderResourceType::Texture2D:
case Resources::ShaderResourceType::TextureCube:
case Resources::ShaderResourceType::StructuredBuffer:
case Resources::ShaderResourceType::RawBuffer:
binding.binding = nextTextureRegister++;
break;
case Resources::ShaderResourceType::Sampler:
binding.binding = nextSamplerRegister++;
break;
case Resources::ShaderResourceType::RWStructuredBuffer:
case Resources::ShaderResourceType::RWRawBuffer:
default:
binding.binding = nextUnorderedAccessRegister++;
break;
}
}
return true;
}
inline std::string BuildRuntimeShaderSource(
const Resources::ShaderPass& pass,
Resources::ShaderBackend backend,
const Resources::ShaderStageVariant& variant) {
std::string sourceText = ToStdString(variant.sourceCode);
if (variant.language != Resources::ShaderLanguage::HLSL ||
backend == Resources::ShaderBackend::Generic) {
return sourceText;
}
Containers::Array<Resources::ShaderResourceBindingDesc> bindings;
bool includeRegisterSpace = false;
if (!TryBuildRuntimeShaderBindings(pass, backend, bindings, includeRegisterSpace)) {
return sourceText;
}
for (const Resources::ShaderResourceBindingDesc& binding : bindings) {
TryRewriteHlslRegisterBinding(sourceText, binding, includeRegisterSpace);
}
return sourceText;
}
inline void AddShaderCompileMacro(
RHI::ShaderCompileDesc& compileDesc,
const wchar_t* name,
const wchar_t* definition = L"1") {
if (name == nullptr || *name == L'\0') {
return;
}
for (const RHI::ShaderCompileMacro& existingMacro : compileDesc.macros) {
if (existingMacro.name == name) {
return;
}
}
RHI::ShaderCompileMacro macro = {};
macro.name = name;
macro.definition = definition != nullptr ? definition : L"";
compileDesc.macros.push_back(std::move(macro));
}
inline void InjectShaderBackendMacros(
Resources::ShaderBackend backend,
RHI::ShaderCompileDesc& compileDesc) {
switch (backend) {
case Resources::ShaderBackend::OpenGL:
AddShaderCompileMacro(compileDesc, L"SHADER_API_GLCORE");
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"0");
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"-1");
break;
case Resources::ShaderBackend::Vulkan:
AddShaderCompileMacro(compileDesc, L"SHADER_API_VULKAN");
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1");
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0");
break;
case Resources::ShaderBackend::D3D12:
AddShaderCompileMacro(compileDesc, L"SHADER_API_D3D12");
AddShaderCompileMacro(compileDesc, L"UNITY_UV_STARTS_AT_TOP", L"1");
AddShaderCompileMacro(compileDesc, L"UNITY_NEAR_CLIP_VALUE", L"0");
break;
case Resources::ShaderBackend::Generic:
default:
break;
}
}
inline void ApplyShaderStageVariant(
const Resources::ShaderStageVariant& variant,
RHI::ShaderCompileDesc& compileDesc) {
compileDesc.fileName.clear();
compileDesc.includeDirectories.clear();
compileDesc.source.assign(
variant.sourceCode.CStr(),
variant.sourceCode.CStr() + variant.sourceCode.Length());
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
compileDesc.profile = ToWideAscii(variant.profile);
}
inline std::wstring ResolveRuntimeShaderSourcePath(const Containers::String& shaderPath) {
Containers::String resolvedPath = shaderPath;
if (resolvedPath.Empty()) {
return std::wstring();
}
if (Resources::IsBuiltinShaderPath(resolvedPath)) {
Containers::String builtinAssetPath;
if (!Resources::TryResolveBuiltinShaderAssetPath(resolvedPath, builtinAssetPath)) {
return std::wstring();
}
resolvedPath = builtinAssetPath;
}
return ToWideAscii(resolvedPath);
}
inline void ApplyShaderStageVariant(
const Containers::String& shaderPath,
const Resources::ShaderPass& pass,
Resources::ShaderBackend backend,
const Resources::ShaderStageVariant& variant,
RHI::ShaderCompileDesc& compileDesc) {
const std::string sourceText = BuildRuntimeShaderSource(pass, backend, variant);
compileDesc.source.assign(sourceText.begin(), sourceText.end());
compileDesc.fileName = ResolveRuntimeShaderSourcePath(shaderPath);
compileDesc.includeDirectories.clear();
if (!compileDesc.fileName.empty()) {
const std::filesystem::path shaderFilePath(compileDesc.fileName);
if (shaderFilePath.has_parent_path()) {
compileDesc.includeDirectories.push_back(shaderFilePath.parent_path().wstring());
}
}
compileDesc.sourceLanguage = ToRHIShaderLanguage(variant.language);
compileDesc.entryPoint = ToWideAscii(variant.entryPoint);
compileDesc.profile = ToWideAscii(variant.profile);
InjectShaderBackendMacros(backend, compileDesc);
}
inline void ApplyShaderStageVariant(
const Resources::ShaderPass& pass,
Resources::ShaderBackend backend,
const Resources::ShaderStageVariant& variant,
RHI::ShaderCompileDesc& compileDesc) {
ApplyShaderStageVariant(Containers::String(), pass, backend, variant, compileDesc);
}
inline Containers::String BuildShaderKeywordSignature(
const Resources::ShaderKeywordSet& keywordSet) {
Resources::ShaderKeywordSet normalizedKeywords = keywordSet;
Resources::NormalizeShaderKeywordSetInPlace(normalizedKeywords);
Containers::String signature;
for (size_t keywordIndex = 0; keywordIndex < normalizedKeywords.enabledKeywords.Size(); ++keywordIndex) {
if (keywordIndex > 0) {
signature += ";";
}
signature += normalizedKeywords.enabledKeywords[keywordIndex];
}
return signature;
}
inline bool ShaderPassHasGraphicsVariants(
const Resources::Shader& shader,
const Containers::String& passName,
Resources::ShaderBackend backend,
const Resources::ShaderKeywordSet& enabledKeywords = Resources::ShaderKeywordSet()) {
return shader.FindVariant(
passName,
Resources::ShaderType::Vertex,
backend,
enabledKeywords) != nullptr &&
shader.FindVariant(
passName,
Resources::ShaderType::Fragment,
backend,
enabledKeywords) != nullptr;
}
} // namespace Internal
} // namespace Rendering
} // namespace XCEngine