From 7b12ad2462181eeaa4220aac9b88a098e2abb06e Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sun, 7 Jun 2026 10:52:23 +0800 Subject: [PATCH 01/10] build(dxc): allow overriding Windows DXC binaries Co-Authored-By: GPT 5.5 --- BuildTools/CMake/FindWindowsSDK.cmake | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/BuildTools/CMake/FindWindowsSDK.cmake b/BuildTools/CMake/FindWindowsSDK.cmake index 3ca9f63c38..bde9a7d06b 100644 --- a/BuildTools/CMake/FindWindowsSDK.cmake +++ b/BuildTools/CMake/FindWindowsSDK.cmake @@ -85,6 +85,17 @@ function(find_windows_sdk) endif() endif() + set(DILIGENT_DXC_DIR "" CACHE PATH "Optional DXC bin directory that contains dxcompiler.dll and dxil.dll") + if(DILIGENT_DXC_DIR) + file(TO_CMAKE_PATH "${DILIGENT_DXC_DIR}" DILIGENT_DXC_DIR_NORMALIZED) + if(EXISTS "${DILIGENT_DXC_DIR_NORMALIZED}/dxcompiler.dll" AND EXISTS "${DILIGENT_DXC_DIR_NORMALIZED}/dxil.dll") + set(DXC_COMPILER_PATH "${DILIGENT_DXC_DIR_NORMALIZED}/dxcompiler.dll") + set(DXIL_SIGNER_PATH "${DILIGENT_DXC_DIR_NORMALIZED}/dxil.dll") + else() + message(WARNING "DILIGENT_DXC_DIR does not contain dxcompiler.dll and dxil.dll: ${DILIGENT_DXC_DIR_NORMALIZED}") + endif() + endif() + if(D3D_COMPILER_PATH) if(EXISTS "${D3D_COMPILER_PATH}") message(STATUS "Found D3Dcompiler_47.dll: ${D3D_COMPILER_PATH}") From 2ddc2a3e676560a8170ef7b44611b5b600949313 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 20 Jun 2026 13:45:59 +0800 Subject: [PATCH 02/10] fix(shader): resolve nested includes relative to parent file Co-Authored-By: GPT 5.5 --- .../ShaderTools/src/ShaderToolsCommon.cpp | 34 +++++++++++++++++-- .../IncludeNestedParentRelativeTest.hlsl | 3 ++ .../ShaderPreprocessor/Nested/Config.hlsl | 3 ++ .../ShaderPreprocessor/Nested/Types.hlsl | 3 ++ .../src/ShaderTools/ShaderPreprocessTest.cpp | 32 ++++++++++++++++- 5 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/IncludeNestedParentRelativeTest.hlsl create mode 100644 Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Config.hlsl create mode 100644 Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/Nested/Types.hlsl diff --git a/Graphics/ShaderTools/src/ShaderToolsCommon.cpp b/Graphics/ShaderTools/src/ShaderToolsCommon.cpp index dd6c8bebe4..0e09554adf 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); diff --git a/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/IncludeNestedParentRelativeTest.hlsl b/Tests/DiligentCoreTest/assets/shaders/ShaderPreprocessor/IncludeNestedParentRelativeTest.hlsl new file mode 100644 index 0000000000..4688a6a230 --- /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 0000000000..18ca53212c --- /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 0000000000..2ed4739f7c --- /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/ShaderPreprocessTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp index dda824eafa..072764c181 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; From 12c601a87a62c2b195d4a0978f71b0b6e4f85404 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 20 Jun 2026 14:25:19 +0800 Subject: [PATCH 03/10] fix(shader): resolve unrolled nested includes Co-Authored-By: GPT 5.5 --- .../ShaderTools/src/ShaderToolsCommon.cpp | 9 +++++--- .../src/ShaderTools/ShaderPreprocessTest.cpp | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Graphics/ShaderTools/src/ShaderToolsCommon.cpp b/Graphics/ShaderTools/src/ShaderToolsCommon.cpp index 0e09554adf..5225595560 100644 --- a/Graphics/ShaderTools/src/ShaderToolsCommon.cpp +++ b/Graphics/ShaderTools/src/ShaderToolsCommon.cpp @@ -505,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; @@ -517,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/src/ShaderTools/ShaderPreprocessTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp index 072764c181..1d810923a0 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/ShaderPreprocessTest.cpp @@ -222,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) From 02e10ad05977b1911b801c008529e37d77fcf4ae Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Sat, 20 Jun 2026 14:43:24 +0800 Subject: [PATCH 04/10] cmake: restore the unused DILIGENT_DXC_DIR. --- BuildTools/CMake/FindWindowsSDK.cmake | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/BuildTools/CMake/FindWindowsSDK.cmake b/BuildTools/CMake/FindWindowsSDK.cmake index bde9a7d06b..3ca9f63c38 100644 --- a/BuildTools/CMake/FindWindowsSDK.cmake +++ b/BuildTools/CMake/FindWindowsSDK.cmake @@ -85,17 +85,6 @@ function(find_windows_sdk) endif() endif() - set(DILIGENT_DXC_DIR "" CACHE PATH "Optional DXC bin directory that contains dxcompiler.dll and dxil.dll") - if(DILIGENT_DXC_DIR) - file(TO_CMAKE_PATH "${DILIGENT_DXC_DIR}" DILIGENT_DXC_DIR_NORMALIZED) - if(EXISTS "${DILIGENT_DXC_DIR_NORMALIZED}/dxcompiler.dll" AND EXISTS "${DILIGENT_DXC_DIR_NORMALIZED}/dxil.dll") - set(DXC_COMPILER_PATH "${DILIGENT_DXC_DIR_NORMALIZED}/dxcompiler.dll") - set(DXIL_SIGNER_PATH "${DILIGENT_DXC_DIR_NORMALIZED}/dxil.dll") - else() - message(WARNING "DILIGENT_DXC_DIR does not contain dxcompiler.dll and dxil.dll: ${DILIGENT_DXC_DIR_NORMALIZED}") - endif() - endif() - if(D3D_COMPILER_PATH) if(EXISTS "${D3D_COMPILER_PATH}") message(STATUS "Found D3Dcompiler_47.dll: ${D3D_COMPILER_PATH}") From 69f5b83d489c214ec42f508b31a07a3eb863e979 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 24 Jun 2026 22:16:23 +0800 Subject: [PATCH 05/10] GLSLangUtils: Add ReadIncludeFile to match DXC/FXC include-stack behavior. --- Graphics/ShaderTools/src/GLSLangUtils.cpp | 71 +++++++++++++++++------ 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/Graphics/ShaderTools/src/GLSLangUtils.cpp b/Graphics/ShaderTools/src/GLSLangUtils.cpp index f52082ffd0..6147a74372 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); + if (Attribs.SourceName != nullptr) + { + const char* Names[] = {Attribs.SourceName}; + Shader.setStringsWithLengthsAndNames(ShaderStrings, Lengths, Names, 1); + } + else + { + Shader.setStringsWithLengths(ShaderStrings, Lengths, 1); + } std::string Preamble; if (Attribs.UseRowMajorMatrices) From f08027d7acdd1918832b9519eff06d658a3b453f Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 24 Jun 2026 22:17:43 +0800 Subject: [PATCH 06/10] GLSLtoSPIRVAttribs: Add SourceName, which is needed for resolving nested includes. --- Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp | 1 + Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp | 1 + Graphics/ShaderTools/include/GLSLangUtils.hpp | 1 + 3 files changed, 3 insertions(+) diff --git a/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp b/Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp index 6973a8df73..6c15097c1f 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 bb756b685b..eca349ba04 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 4f5afc00be..f23d7ae8ec 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; From ac8a77f7e5bfa4bec7adcc2dc5189b14edda6442 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 24 Jun 2026 22:18:10 +0800 Subject: [PATCH 07/10] SPIRVShaderResourcesTest: Add tests for NestedParentRelativeIncludes. --- .../IncludeNestedParentRelative/Headers/Config.glsl | 2 ++ .../Headers/Config.hlsli | 2 ++ .../IncludeNestedParentRelative/Headers/Types.glsl | 6 ++++++ .../IncludeNestedParentRelative/Headers/Types.hlsli | 6 ++++++ .../SPIRV/IncludeNestedParentRelative/Main.glsl | 11 +++++++++++ .../SPIRV/IncludeNestedParentRelative/Main.psh | 6 ++++++ .../src/ShaderTools/SPIRVShaderResourcesTest.cpp | 13 +++++++++++++ 7 files changed, 46 insertions(+) create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.glsl create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Config.hlsli create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.glsl create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Headers/Types.hlsli create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.glsl create mode 100644 Tests/DiligentCoreTest/assets/shaders/SPIRV/IncludeNestedParentRelative/Main.psh 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 0000000000..e097a9a8e3 --- /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 0000000000..e097a9a8e3 --- /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 0000000000..a3a33ea15d --- /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 0000000000..0de0a08657 --- /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 0000000000..0abcb50c74 --- /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 0000000000..0f2eadba20 --- /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/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp index e675ba28c1..fcafebe962 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 From beedc8195a2697137aaa4b6cdf318822638d7b81 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 24 Jun 2026 23:49:34 +0800 Subject: [PATCH 08/10] GLSLangUtils: keep glslang source names alive to fix the dangling pointer accessing. --- Graphics/ShaderTools/src/GLSLangUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Graphics/ShaderTools/src/GLSLangUtils.cpp b/Graphics/ShaderTools/src/GLSLangUtils.cpp index 6147a74372..f7083eb79f 100644 --- a/Graphics/ShaderTools/src/GLSLangUtils.cpp +++ b/Graphics/ShaderTools/src/GLSLangUtils.cpp @@ -556,9 +556,9 @@ std::vector GLSLtoSPIRV(const GLSLtoSPIRVAttribs& Attribs) const char* ShaderStrings[] = {Attribs.ShaderSource}; int Lengths[] = {Attribs.SourceCodeLen}; + const char* Names[] = {Attribs.SourceName}; if (Attribs.SourceName != nullptr) { - const char* Names[] = {Attribs.SourceName}; Shader.setStringsWithLengthsAndNames(ShaderStrings, Lengths, Names, 1); } else From 3cb15d973689944210f3b82357a3162f0247b3eb Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 26 Jun 2026 16:18:38 +0800 Subject: [PATCH 09/10] GLSLangUtils: Fix the "slash/key mismatch" concerns in includeLocal mentioned in #797. --- Graphics/ShaderTools/src/GLSLangUtils.cpp | 52 ++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/Graphics/ShaderTools/src/GLSLangUtils.cpp b/Graphics/ShaderTools/src/GLSLangUtils.cpp index f7083eb79f..30a64ccce2 100644 --- a/Graphics/ShaderTools/src/GLSLangUtils.cpp +++ b/Graphics/ShaderTools/src/GLSLangUtils.cpp @@ -296,6 +296,38 @@ std::vector CompileShaderInternal(::glslang::TShader& Sh return spirv; } +static Char GetFirstSlash(const char* Path) +{ + if (Path == nullptr) + return 0; + + for (const char* c = Path; *c != '\0'; ++c) + { + if (BasicFileSystem::IsSlash(*c)) + return *c; + } + + return 0; +} + +static Char GetPreferredIncludePathSlash(const char* IncluderName, const char* HeaderName) +{ + if (const Char HeaderSlash = GetFirstSlash(HeaderName)) + return HeaderSlash; + + if (const Char IncluderSlash = GetFirstSlash(IncluderName)) + return IncluderSlash; + + return BasicFileSystem::SlashSymbol; +} + +static std::string MakeParentRelativeIncludePath(const String& ParentDir, const char* HeaderName, Char Slash) +{ + return BasicFileSystem::SimplifyPath( + (ParentDir + Slash + HeaderName).c_str(), + Slash); +} + class IncluderImpl : public ::glslang::TShader::Includer { @@ -326,21 +358,31 @@ class IncluderImpl : public ::glslang::TShader::Includer const char* includerName, size_t /*inclusionDepth*/) { - if (m_pInputStreamFactory == nullptr) + if (m_pInputStreamFactory == nullptr || headerName == nullptr || *headerName == '\0') return nullptr; if (BasicFileSystem::IsPathAbsolute(headerName)) return ReadIncludeFile(headerName, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT); + if (includerName == nullptr || *includerName == '\0') + return nullptr; + 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); + const Char PreferredSlash = GetPreferredIncludePathSlash(includerName, headerName); + const std::string LocalPath = MakeParentRelativeIncludePath(ParentDir, headerName, PreferredSlash); + if (IncludeResult* pInclude = ReadIncludeFile(LocalPath.c_str(), CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT)) + return pInclude; + + const Char AlternateSlash = PreferredSlash == BasicFileSystem::UnixSlash ? BasicFileSystem::WinSlash : BasicFileSystem::UnixSlash; + const std::string AlternatePath = MakeParentRelativeIncludePath(ParentDir, headerName, AlternateSlash); + if (AlternatePath != LocalPath) + return ReadIncludeFile(AlternatePath.c_str(), CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT); + + return nullptr; } // Signals that the parser will no longer use the contents of the From 8815d02b7b2ae1f79598e1313606f3fbc64c1d34 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Fri, 26 Jun 2026 16:19:35 +0800 Subject: [PATCH 10/10] SPIRVShaderResourcesTest: Add regressions to cover the "slash/key mismatch" risks in includeLocal mentioned in #797. --- .../ShaderTools/SPIRVShaderResourcesTest.cpp | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp index fcafebe962..7796bdb970 100644 --- a/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp +++ b/Tests/DiligentCoreTest/src/ShaderTools/SPIRVShaderResourcesTest.cpp @@ -28,6 +28,7 @@ #include "GLSLangUtils.hpp" #include "DXCompiler.hpp" #include "DefaultShaderSourceStreamFactory.h" +#include "ShaderSourceFactoryUtils.hpp" #include "RefCntAutoPtr.hpp" #include "EngineMemory.h" #include "BasicFileSystem.hpp" @@ -659,4 +660,96 @@ TEST_F(SPIRVShaderResourcesTest, NestedParentRelativeIncludes_GLSL_GLSLang) EXPECT_FALSE(SPIRV.empty()); } +TEST_F(SPIRVShaderResourcesTest, NestedLocalAndSystemIncludesFromMemory_GLSL_GLSLang) +{ + constexpr char MainGLSL[] = + "#version 450\n" + "#extension GL_GOOGLE_include_directive : enable\n" + "\n" + "#include \"Nested/LocalTypes.glsl\"\n" + "#include \"Nested/SystemTypes.glsl\"\n" + "layout(location = 0) out vec4 OutColor;\n" + "void main()\n" + "{\n" + " OutColor = GetLocalColor() + GetSystemColor();\n" + "}\n"; + + auto pShaderSourceFactory = CreateMemoryShaderSourceFactory( + { + {"Nested/LocalTypes.glsl", + "#include \"Config.glsl\"\n" + "vec4 GetLocalColor()\n" + "{\n" + " return vec4(float(LOCAL_CONFIG_VALUE), 0.0, 0.0, 1.0);\n" + "}\n"}, + {"Nested/SystemTypes.glsl", + "#include \n" + "vec4 GetSystemColor()\n" + "{\n" + " return vec4(0.0, float(ROOT_CONFIG_VALUE), 0.0, 1.0);\n" + "}\n"}, + {"Nested/Config.glsl", + "#define LOCAL_CONFIG_VALUE 1\n"}, + {"Config.glsl", + "#define ROOT_CONFIG_VALUE 1\n"}, + }, + false); + ASSERT_NE(pShaderSourceFactory, nullptr); + + GLSLangUtils::GLSLtoSPIRVAttribs Attribs; + Attribs.ShaderType = SHADER_TYPE_PIXEL; + Attribs.ShaderSource = MainGLSL; + Attribs.SourceName = "Main.glsl"; + Attribs.SourceCodeLen = static_cast(sizeof(MainGLSL) - 1); + Attribs.pShaderSourceStreamFactory = pShaderSourceFactory; + Attribs.Version = GLSLangUtils::SpirvVersion::Vk100; + Attribs.AssignBindings = true; + + auto SPIRV = GLSLangUtils::GLSLtoSPIRV(Attribs); + EXPECT_FALSE(SPIRV.empty()); +} + +TEST_F(SPIRVShaderResourcesTest, NestedLocalAndSystemIncludesFromMemory_HLSL_GLSLang) +{ + auto pShaderSourceFactory = CreateMemoryShaderSourceFactory( + { + {"Main.hlsl", + "#include \"Nested/LocalTypes.hlsli\"\n" + "#include \"Nested/SystemTypes.hlsli\"\n" + "float4 main() : SV_Target\n" + "{\n" + " return GetLocalColor() + GetSystemColor();\n" + "}\n"}, + {"Nested/LocalTypes.hlsli", + "#include \"Config.hlsli\"\n" + "float4 GetLocalColor()\n" + "{\n" + " return float4(LOCAL_CONFIG_VALUE, 0.0, 0.0, 1.0);\n" + "}\n"}, + {"Nested/SystemTypes.hlsli", + "#include \n" + "float4 GetSystemColor()\n" + "{\n" + " return float4(0.0, ROOT_CONFIG_VALUE, 0.0, 1.0);\n" + "}\n"}, + {"Nested/Config.hlsli", + "#define LOCAL_CONFIG_VALUE 1.0\n"}, + {"Config.hlsli", + "#define ROOT_CONFIG_VALUE 1.0\n"}, + }, + false); + ASSERT_NE(pShaderSourceFactory, nullptr); + + ShaderCreateInfo ShaderCI; + ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL; + ShaderCI.FilePath = "Main.hlsl"; + ShaderCI.Desc = {"SPIRV memory include test shader", SHADER_TYPE_PIXEL}; + ShaderCI.EntryPoint = "main"; + ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory; + ShaderCI.ShaderOptimizationLevel = SHADER_OPTIMIZATION_LEVEL_DISABLED; + + auto SPIRV = GLSLangUtils::HLSLtoSPIRV(ShaderCI, GLSLangUtils::SpirvVersion::Vk100, nullptr, nullptr); + EXPECT_FALSE(SPIRV.empty()); +} + } // namespace