diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp index 6973a8df7..6c15097c1 100644 --- a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp +++ b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp @@ -139,6 +139,7 @@ std::vector CompileShaderGLSLang(const ShaderCreateInfo& Shade GLSLangUtils::GLSLtoSPIRVAttribs Attribs; Attribs.ShaderType = ShaderCI.Desc.ShaderType; Attribs.ShaderSource = SourceData.Source; + Attribs.SourceName = ShaderCI.FilePath; Attribs.SourceCodeLen = static_cast(SourceData.SourceLength); Attribs.Version = GLSLangUtils::SpirvVersion::Vk100; Attribs.Macros = Macros; diff --git a/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp b/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp index bb756b685..eca349ba0 100644 --- a/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp +++ b/Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp @@ -140,6 +140,7 @@ std::vector CompileShaderToSPIRV(const ShaderCreateInfo& S GLSLangUtils::GLSLtoSPIRVAttribs Attribs; Attribs.ShaderType = ShaderCI.Desc.ShaderType; Attribs.ShaderSource = SourceData.Source; + Attribs.SourceName = ShaderCI.FilePath; Attribs.SourceCodeLen = static_cast(SourceData.SourceLength); Attribs.Version = GLSLangUtils::SpirvVersion::Vk100; Attribs.Macros = Macros; diff --git a/Graphics/ShaderTools/include/GLSLangUtils.hpp b/Graphics/ShaderTools/include/GLSLangUtils.hpp index 4f5afc00b..f23d7ae8e 100644 --- a/Graphics/ShaderTools/include/GLSLangUtils.hpp +++ b/Graphics/ShaderTools/include/GLSLangUtils.hpp @@ -57,6 +57,7 @@ struct GLSLtoSPIRVAttribs { SHADER_TYPE ShaderType = SHADER_TYPE_UNKNOWN; const char* ShaderSource = nullptr; + const char* SourceName = nullptr; int SourceCodeLen = 0; ShaderMacroArray Macros; IShaderSourceInputStreamFactory* pShaderSourceStreamFactory = nullptr; diff --git a/Graphics/ShaderTools/src/GLSLangUtils.cpp b/Graphics/ShaderTools/src/GLSLangUtils.cpp index f52082ffd..f7083eb79 100644 --- a/Graphics/ShaderTools/src/GLSLangUtils.cpp +++ b/Graphics/ShaderTools/src/GLSLangUtils.cpp @@ -47,6 +47,7 @@ #include "DataBlobImpl.hpp" #include "RefCntAutoPtr.hpp" #include "ShaderToolsCommon.hpp" +#include "BasicFileSystem.hpp" #ifdef USE_SPIRV_TOOLS # include "SPIRVTools.hpp" # include "spirv-tools/libspirv.h" @@ -308,27 +309,14 @@ class IncluderImpl : public ::glslang::TShader::Includer const char* /*includerName*/, size_t /*inclusionDepth*/) { - DEV_CHECK_ERR(m_pInputStreamFactory != nullptr, "The shader source contains #include directives, but no input stream factory was provided"); - RefCntAutoPtr pSourceStream; - m_pInputStreamFactory->CreateInputStream(headerName, &pSourceStream); - if (pSourceStream == nullptr) + IncludeResult* pInclude = ReadIncludeFile(headerName, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE); + if (pInclude == nullptr) { LOG_ERROR("Failed to open shader include file '", headerName, "'. Check that the file exists"); return nullptr; } - RefCntAutoPtr pFileData = DataBlobImpl::Create(); - pSourceStream->ReadBlob(pFileData); - IncludeResult* pNewInclude = - new IncludeResult{ - headerName, - pFileData->GetConstDataPtr(), - pFileData->GetSize(), - nullptr}; - - m_IncludeRes.emplace(pNewInclude); - m_DataBlobs.emplace(pNewInclude, std::move(pFileData)); - return pNewInclude; + return pInclude; } // For the "local"-only aspect of a "" include. Should not search in the @@ -336,9 +324,23 @@ class IncluderImpl : public ::glslang::TShader::Includer // call includeSystem() to look in the "system" locations. virtual IncludeResult* includeLocal(const char* headerName, const char* includerName, - size_t inclusionDepth) + size_t /*inclusionDepth*/) { - return nullptr; + if (m_pInputStreamFactory == nullptr) + return nullptr; + + if (BasicFileSystem::IsPathAbsolute(headerName)) + return ReadIncludeFile(headerName, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT); + + String ParentDir; + BasicFileSystem::GetPathComponents(includerName, &ParentDir, nullptr); + if (ParentDir.empty()) + return nullptr; + + const std::string LocalPath = BasicFileSystem::SimplifyPath( + (ParentDir + BasicFileSystem::SlashSymbol + headerName).c_str(), + BasicFileSystem::SlashSymbol); + return ReadIncludeFile(LocalPath.c_str(), CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT); } // Signals that the parser will no longer use the contents of the @@ -349,6 +351,29 @@ class IncluderImpl : public ::glslang::TShader::Includer } private: + IncludeResult* ReadIncludeFile(const char* IncludePath, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags) + { + DEV_CHECK_ERR(m_pInputStreamFactory != nullptr, "The shader source contains #include directives, but no input stream factory was provided"); + + RefCntAutoPtr pSourceStream; + m_pInputStreamFactory->CreateInputStream2(IncludePath, Flags, &pSourceStream); + if (pSourceStream == nullptr) + return nullptr; + + RefCntAutoPtr pFileData = DataBlobImpl::Create(); + pSourceStream->ReadBlob(pFileData); + IncludeResult* pNewInclude = + new IncludeResult{ + IncludePath, + pFileData->GetConstDataPtr(), + pFileData->GetSize(), + nullptr}; + + m_IncludeRes.emplace(pNewInclude); + m_DataBlobs.emplace(pNewInclude, std::move(pFileData)); + return pNewInclude; + } + IShaderSourceInputStreamFactory* const m_pInputStreamFactory; std::unordered_set> m_IncludeRes; std::unordered_map> m_DataBlobs; @@ -531,7 +556,15 @@ std::vector GLSLtoSPIRV(const GLSLtoSPIRVAttribs& Attribs) const char* ShaderStrings[] = {Attribs.ShaderSource}; int Lengths[] = {Attribs.SourceCodeLen}; - Shader.setStringsWithLengths(ShaderStrings, Lengths, 1); + const char* Names[] = {Attribs.SourceName}; + if (Attribs.SourceName != nullptr) + { + Shader.setStringsWithLengthsAndNames(ShaderStrings, Lengths, Names, 1); + } + else + { + Shader.setStringsWithLengths(ShaderStrings, Lengths, 1); + } std::string Preamble; if (Attribs.UseRowMajorMatrices) diff --git a/Graphics/ShaderTools/src/ShaderToolsCommon.cpp b/Graphics/ShaderTools/src/ShaderToolsCommon.cpp index dd6c8bebe..522559556 100644 --- a/Graphics/ShaderTools/src/ShaderToolsCommon.cpp +++ b/Graphics/ShaderTools/src/ShaderToolsCommon.cpp @@ -424,6 +424,33 @@ static void ProcessIncludeErrorHandler(const ShaderCreateInfo& ShaderCI, const s throw std::pair{std::move(FileInfo), Error}; } +static std::string ResolveIncludePathForPreprocess(const ShaderCreateInfo& ShaderCI, const std::string& IncludeName) +{ + if (ShaderCI.FilePath == nullptr || BasicFileSystem::IsPathAbsolute(IncludeName.c_str())) + return IncludeName; + + String ParentDir; + BasicFileSystem::GetPathComponents(ShaderCI.FilePath, &ParentDir, nullptr); + if (ParentDir.empty()) + return IncludeName; + + const std::string RelativePath = BasicFileSystem::SimplifyPath( + (ParentDir + BasicFileSystem::SlashSymbol + IncludeName).c_str(), + BasicFileSystem::SlashSymbol); + + if (ShaderCI.pShaderSourceStreamFactory != nullptr) + { + RefCntAutoPtr pSourceStream; + ShaderCI.pShaderSourceStreamFactory->CreateInputStream2(RelativePath.c_str(), CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT, &pSourceStream); + if (pSourceStream) + return RelativePath; + + return IncludeName; + } + + return RelativePath; +} + template void ProcessShaderIncludesImpl(const ShaderCreateInfo& ShaderCI, std::unordered_set& Includes, IncludeHandlerType&& IncludeHandler) noexcept(false) { @@ -436,13 +463,14 @@ void ProcessShaderIncludesImpl(const ShaderCreateInfo& ShaderCI, std::unordered_ FindIncludes( FileInfo.Source, FileInfo.SourceLength, - [&](const std::string& FilePath, size_t Start, size_t End) // + [&](const std::string& IncludeName, size_t /*Start*/, size_t /*End*/) // { - if (!Includes.insert(FilePath).second) + const std::string ResolvedPath = ResolveIncludePathForPreprocess(ShaderCI, IncludeName); + if (!Includes.insert(ResolvedPath).second) return; ShaderCreateInfo IncludeCI{ShaderCI}; - IncludeCI.FilePath = FilePath.c_str(); + IncludeCI.FilePath = ResolvedPath.c_str(); IncludeCI.Source = nullptr; IncludeCI.SourceLength = 0; ProcessShaderIncludesImpl(IncludeCI, Includes, IncludeHandler); @@ -477,6 +505,8 @@ static std::string UnrollShaderIncludesImpl(ShaderCreateInfo ShaderCI, std::unor { const ShaderSourceFileData SourceData = ReadShaderSourceFile(ShaderCI); + const ShaderCreateInfo IncludeContextCI{ShaderCI}; + ShaderCI.Source = SourceData.Source; ShaderCI.SourceLength = SourceData.SourceLength; ShaderCI.FilePath = nullptr; @@ -489,20 +519,21 @@ static std::string UnrollShaderIncludesImpl(ShaderCreateInfo ShaderCI, std::unor // Insert text before the include start Stream.write(ShaderCI.Source + PrevIncludeEnd, IncludeStart - PrevIncludeEnd); - if (AllIncludes.insert(Path).second) + const std::string ResolvedPath = ResolveIncludePathForPreprocess(IncludeContextCI, Path); + if (AllIncludes.insert(ResolvedPath).second) { // Process the #include directive ShaderCreateInfo IncludeCI{ShaderCI}; IncludeCI.Source = nullptr; IncludeCI.SourceLength = 0; - IncludeCI.FilePath = Path.c_str(); + IncludeCI.FilePath = ResolvedPath.c_str(); std::string UnrolledInclude = UnrollShaderIncludesImpl(IncludeCI, AllIncludes); Stream << UnrolledInclude; } PrevIncludeEnd = IncludeEnd; }, - std::bind(ProcessIncludeErrorHandler, ShaderCI, std::placeholders::_1)); + std::bind(ProcessIncludeErrorHandler, IncludeContextCI, std::placeholders::_1)); // Insert text after the last include Stream.write(ShaderCI.Source + PrevIncludeEnd, ShaderCI.SourceLength - PrevIncludeEnd); diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.glsl b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.glsl new file mode 100644 index 000000000..e097a9a8e --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.glsl @@ -0,0 +1,2 @@ +#define NESTED_INCLUDE_R 0.25 +#define NESTED_INCLUDE_G 0.75 diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.hlsli b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.hlsli new file mode 100644 index 000000000..e097a9a8e --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.hlsli @@ -0,0 +1,2 @@ +#define NESTED_INCLUDE_R 0.25 +#define NESTED_INCLUDE_G 0.75 diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.glsl b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.glsl new file mode 100644 index 000000000..a3a33ea15 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.glsl @@ -0,0 +1,6 @@ +#include "Config.glsl" + +vec4 GetNestedIncludeColor() +{ + return vec4(NESTED_INCLUDE_R, NESTED_INCLUDE_G, 0.0, 1.0); +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.hlsli b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.hlsli new file mode 100644 index 000000000..0de0a0865 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.hlsli @@ -0,0 +1,6 @@ +#include "Config.hlsli" + +float4 GetNestedIncludeColor() +{ + return float4(NESTED_INCLUDE_R, NESTED_INCLUDE_G, 0.0, 1.0); +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.glsl b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.glsl new file mode 100644 index 000000000..0abcb50c7 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.glsl @@ -0,0 +1,11 @@ +#version 450 +#extension GL_GOOGLE_include_directive : enable + +#include "IncludeNestedParentRelative/Headers/Types.glsl" + +layout(location = 0) out vec4 FragColor; + +void main() +{ + FragColor = GetNestedIncludeColor(); +} diff --git a/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.psh b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.psh new file mode 100644 index 000000000..0f2eadba2 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.psh @@ -0,0 +1,6 @@ +#include "IncludeNestedParentRelative/Headers/Types.hlsli" + +float4 main() : SV_Target +{ + return GetNestedIncludeColor(); +} diff --git a/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/IncludeNestedParentRelativeTest.hlsl b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/IncludeNestedParentRelativeTest.hlsl new file mode 100644 index 000000000..4688a6a23 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/IncludeNestedParentRelativeTest.hlsl @@ -0,0 +1,3 @@ +// Start IncludeNestedParentRelativeTest.hlsl +#include "Nested/Types.hlsl" +// End IncludeNestedParentRelativeTest.hlsl diff --git a/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Config.hlsl b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Config.hlsl new file mode 100644 index 000000000..18ca53212 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Config.hlsl @@ -0,0 +1,3 @@ +// Start Nested/Config.hlsl +#define NESTED_CONFIG_VALUE 1 +// End Nested/Config.hlsl diff --git a/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Types.hlsl b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Types.hlsl new file mode 100644 index 000000000..2ed4739f7 --- /dev/null +++ b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Types.hlsl @@ -0,0 +1,3 @@ +// Start Nested/Types.hlsl +#include "Config.hlsl" +// End Nested/Types.hlsl diff --git a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp index e675ba28c..fcafebe96 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp @@ -160,6 +160,7 @@ std::vector LoadSPIRVFromGLSL(const char* FilePath, SHADER_TYPE Sh GLSLangUtils::GLSLtoSPIRVAttribs Attribs; Attribs.ShaderType = ShaderType; Attribs.ShaderSource = ShaderSource.data(); + Attribs.SourceName = FilePath; Attribs.SourceCodeLen = static_cast(ShaderSourceSize); Attribs.pShaderSourceStreamFactory = pShaderSourceStreamFactory; Attribs.Version = Version; @@ -646,4 +647,16 @@ TEST_F(SPIRVShaderResourcesTest, SpecializationConstants_DXC) TestSpecializationConstants(SHADER_COMPILER_DXC); } +TEST_F(SPIRVShaderResourcesTest, NestedParentRelativeIncludes_HLSL_GLSLang) +{ + auto SPIRV = LoadSPIRVFromHLSL("IncludeNestedParentRelative/Main.psh", SHADER_TYPE_PIXEL, SHADER_COMPILER_GLSLANG); + EXPECT_FALSE(SPIRV.empty()); +} + +TEST_F(SPIRVShaderResourcesTest, NestedParentRelativeIncludes_GLSL_GLSLang) +{ + auto SPIRV = LoadSPIRVFromGLSL("IncludeNestedParentRelative/Main.glsl"); + EXPECT_FALSE(SPIRV.empty()); +} + } // namespace diff --git a/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp index dda824eaf..1d810923a 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2026 Diligent Graphics LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ #include #include "ShaderToolsCommon.hpp" +#include "BasicFileSystem.hpp" #include "DefaultShaderSourceStreamFactory.h" #include "RenderDevice.h" #include "TestingEnvironment.hpp" @@ -135,6 +136,35 @@ TEST(ShaderPreprocessTest, Include) } } +TEST(ShaderPreprocessTest, IncludeNestedParentRelative) +{ + RefCntAutoPtr pShaderSourceFactory; + CreateDefaultShaderSourceStreamFactory("shaders/ShaderPreprocessor", &pShaderSourceFactory); + ASSERT_NE(pShaderSourceFactory, nullptr); + + const auto NestedTypesPath = std::string{"Nested/Types.hlsl"}; + const auto NestedConfigPath = std::string{"Nested"} + BasicFileSystem::SlashSymbol + "Config.hlsl"; + + std::deque Includes{ + NestedConfigPath, + NestedTypesPath, + "IncludeNestedParentRelativeTest.hlsl"}; + + ShaderCreateInfo ShaderCI{}; + ShaderCI.Desc.Name = "TestShader"; + ShaderCI.FilePath = "IncludeNestedParentRelativeTest.hlsl"; + ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory; + + const auto Result = ProcessShaderIncludes(ShaderCI, [&](const ShaderIncludePreprocessInfo& ProcessInfo) { + ASSERT_FALSE(Includes.empty()); + EXPECT_EQ(ProcessInfo.FilePath, Includes.front()); + Includes.pop_front(); + }); + + EXPECT_EQ(Result, true); + EXPECT_TRUE(Includes.empty()); +} + TEST(ShaderPreprocessTest, InvalidInclude) { RefCntAutoPtr pShaderSourceFactory; @@ -192,6 +222,27 @@ TEST(ShaderPreprocessTest, UnrollIncludes) auto UnrolledStr = UnrollShaderIncludes(ShaderCI); ASSERT_EQ(RefString, UnrolledStr); } + + { + ShaderCreateInfo ShaderCI{}; + ShaderCI.Desc.Name = "TestShader"; + ShaderCI.FilePath = "IncludeNestedParentRelativeTest.hlsl"; + ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory; + + constexpr char RefString[] = + "// Start IncludeNestedParentRelativeTest.hlsl\n" + "// Start Nested/Types.hlsl\n" + "// Start Nested/Config.hlsl\n" + "#define NESTED_CONFIG_VALUE 1\n" + "// End Nested/Config.hlsl\n" + "\n" + "// End Nested/Types.hlsl\n" + "\n" + "// End IncludeNestedParentRelativeTest.hlsl\n"; + + auto UnrolledStr = UnrollShaderIncludes(ShaderCI); + ASSERT_EQ(RefString, UnrolledStr); + } } TEST(ShaderPreprocessTest, ShaderSourceLanguageDefiniton)