Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Graphics/GraphicsEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(INCLUDE
include/ResourceMappingImpl.hpp
include/SamplerBase.hpp
include/ShaderBase.hpp
include/ShaderSourcePath.hpp
include/ShaderResourceBindingBase.hpp
include/ShaderResourceCacheCommon.hpp
include/ShaderResourceVariableBase.hpp
Expand Down
57 changes: 57 additions & 0 deletions Graphics/GraphicsEngine/include/ShaderSourcePath.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* In no event and under no legal theory, whether in tort (including negligence),
* contract, or otherwise, unless required by applicable law (such as deliberate
* and grossly negligent acts) or agreed to in writing, shall any Contributor be
* liable for any damages, including any direct, indirect, special, incidental,
* or consequential damages of any character arising as a result of this License or
* out of the use or inability to use the software (including but not limited to damages
* for loss of goodwill, work stoppage, computer failure or malfunction, or any and
* all other commercial damages or losses), even if such Contributor has been advised
* of the possibility of such damages.
*/

#pragma once

#include "BasicFileSystem.hpp"

namespace Diligent
{

inline String NormalizeShaderSourcePath(const Char* Path, Char Slash = BasicFileSystem::UnixSlash)
{
if (Path == nullptr || Path[0] == '\0')
return {};

if (Slash == 0)
Slash = BasicFileSystem::SlashSymbol;

// Simplify drive and UNC paths using Windows semantics to preserve their roots.
// By default, shader source names use '/' as the canonical separator.
const PathRootType RootType = BasicFileSystem::GetPathRootType(Path);

const Char SimplifySlash =
RootType == PathRootType::WindowsDrive || RootType == PathRootType::WindowsUNC ?
BasicFileSystem::WinSlash :
BasicFileSystem::UnixSlash;

String NormalizedPath = BasicFileSystem::SimplifyPath(Path, SimplifySlash);
if (SimplifySlash != Slash)
BasicFileSystem::CorrectSlashes(NormalizedPath, Slash);
return NormalizedPath;
}

} // namespace Diligent
2 changes: 1 addition & 1 deletion Graphics/GraphicsEngine/interface/APIInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
/// \file
/// Diligent API information

#define DILIGENT_API_VERSION 256019
#define DILIGENT_API_VERSION 256020

#include "../../../Primitives/interface/BasicTypes.h"

Expand Down
21 changes: 16 additions & 5 deletions Graphics/GraphicsEngine/interface/Shader.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,22 @@ static DILIGENT_CONSTEXPR INTERFACE_ID IID_IShaderSourceInputStreamFactory =
IObjectInclusiveMethods; \
IShaderSourceInputStreamFactoryMethods ShaderSourceInputStreamFactory

/// Shader source stream factory interface
/// Shader source stream factory interface.
///
/// File names may use either '/' or '\\' as a path separator. Implementations must
/// treat both separators as equivalent.
DILIGENT_BEGIN_INTERFACE(IShaderSourceInputStreamFactory, IObject)
{
/// Creates a shader source input stream for the specified file name.

/// The stream is used to load the shader source code.
/// \param [in] Name - The name of the file to load.
/// \param [out] ppStream - Pointer to the shader source input stream.
VIRTUAL void METHOD(CreateInputStream)(THIS_
/// \param [out] ppStream - Pointer to the shader source input stream. May be null to only check
/// if the source exists. The check never reports an error if the source
/// is not found.
/// \return True if the source exists and, when \p ppStream is not null, the stream was
/// created successfully. False otherwise.
VIRTUAL Bool METHOD(CreateInputStream)(THIS_
const Char* Name,
IFileStream** ppStream) PURE;

Expand All @@ -252,8 +259,12 @@ DILIGENT_BEGIN_INTERFACE(IShaderSourceInputStreamFactory, IObject)
/// \param [in] Name - The name of the file to load.
/// \param [in] Flags - Flags that control the stream creation,
/// see Diligent::CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS.
/// \param [out] ppStream - Pointer to the shader source input stream.
VIRTUAL void METHOD(CreateInputStream2)(THIS_
/// \param [out] ppStream - Pointer to the shader source input stream. May be null to only check
/// if the source exists. The check never reports an error if the source
/// is not found, regardless of \p Flags.
/// \return True if the source exists and, when \p ppStream is not null, the stream was
/// created successfully. False otherwise.
VIRTUAL Bool METHOD(CreateInputStream2)(THIS_
const Char* Name,
CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags,
IFileStream** ppStream) PURE;
Expand Down
63 changes: 34 additions & 29 deletions Graphics/GraphicsEngine/src/DefaultShaderSourceStreamFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "RefCntAutoPtr.hpp"
#include "EngineMemory.h"
#include "BasicFileStream.hpp"
#include "ShaderSourcePath.hpp"

namespace Diligent
{
Expand All @@ -40,9 +41,9 @@ class DefaultShaderSourceStreamFactory final : public ObjectBase<IShaderSourceIn
public:
DefaultShaderSourceStreamFactory(IReferenceCounters* pRefCounters, const Char* SearchDirectories);

virtual void DILIGENT_CALL_TYPE CreateInputStream(const Char* Name, IFileStream** ppStream) override final;
virtual Bool DILIGENT_CALL_TYPE CreateInputStream(const Char* Name, IFileStream** ppStream) override final;

virtual void DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
virtual Bool DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags,
IFileStream** ppStream) override final;

Expand All @@ -68,56 +69,60 @@ DefaultShaderSourceStreamFactory::DefaultShaderSourceStreamFactory(IReferenceCou
m_SearchDirectories.push_back("");
}

void DefaultShaderSourceStreamFactory::CreateInputStream(const Char* Name,
Bool DefaultShaderSourceStreamFactory::CreateInputStream(const Char* Name,
IFileStream** ppStream)
{
CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
return CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
}

void DefaultShaderSourceStreamFactory::CreateInputStream2(const Char* Name,
Bool DefaultShaderSourceStreamFactory::CreateInputStream2(const Char* Name,
CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags,
IFileStream** ppStream)
{
auto CreateFileStream = [](const char* Path) //
auto TryCreateFileStream = [](const std::string& Path, IFileStream** ppStream) //
{
RefCntAutoPtr<BasicFileStream> pFileStream;
if (FileSystem::FileExists(Path))
Bool SourceFound = FileSystem::FileExists(Path.c_str());
if (SourceFound && ppStream != nullptr)
{
pFileStream = BasicFileStream::Create(Path, EFileAccessMode::Read);
if (!pFileStream->IsValid())
pFileStream.Release();
RefCntAutoPtr<BasicFileStream> pFileStream = BasicFileStream::Create(Path.c_str(), EFileAccessMode::Read);

SourceFound = pFileStream->IsValid();
if (SourceFound)
{
pFileStream->QueryInterface(IID_FileStream, ppStream);
SourceFound = *ppStream != nullptr;
}
}
return pFileStream;
return SourceFound;
};

RefCntAutoPtr<BasicFileStream> pFileStream;
if (FileSystem::IsPathAbsolute(Name))
DEV_CHECK_ERR(ppStream == nullptr || *ppStream == nullptr, "Output stream pointer must be null. Overwriting a non-null output pointer may result in memory leaks.");

const String FileName = NormalizeShaderSourcePath(Name, FileSystem::SlashSymbol);

Bool SourceFound = False;
if (FileSystem::IsPathAbsolute(FileName.c_str()))
{
pFileStream = CreateFileStream(Name);
SourceFound = TryCreateFileStream(FileName, ppStream);
}
else
else if (!FileName.empty())
{
for (const std::string& SearchDir : m_SearchDirectories)
{
const std::string FullPath = SearchDir + ((Name[0] == '\\' || Name[0] == '/') ? Name + 1 : Name);
pFileStream = CreateFileStream(FullPath.c_str());
if (pFileStream)
const std::string FullPath = FileSystem::JoinPath(SearchDir, FileName);

SourceFound = TryCreateFileStream(FullPath, ppStream);
if (SourceFound)
break;
}
}

if (pFileStream)
{
pFileStream->QueryInterface(IID_FileStream, ppStream);
}
else
if (!SourceFound && ppStream != nullptr && (Flags & CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT) == 0)
{
*ppStream = nullptr;
if ((Flags & CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT) == 0)
{
LOG_ERROR("Failed to create input stream for source file ", Name);
}
LOG_ERROR("Failed to create input stream for source file ", Name);
}

return SourceFound;
}

void CreateDefaultShaderSourceStreamFactory(const Char* SearchDirectories,
Expand Down
1 change: 1 addition & 0 deletions Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ std::vector<uint32_t> CompileShaderGLSLang(const ShaderCreateInfo& Shade
GLSLangUtils::GLSLtoSPIRVAttribs Attribs;
Attribs.ShaderType = ShaderCI.Desc.ShaderType;
Attribs.ShaderSource = SourceData.Source;
Attribs.SourceName = ShaderCI.FilePath;
Attribs.SourceCodeLen = static_cast<int>(SourceData.SourceLength);
Attribs.Version = GLSLangUtils::SpirvVersion::Vk100;
Attribs.Macros = Macros;
Expand Down
1 change: 1 addition & 0 deletions Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ std::vector<uint32_t> CompileShaderToSPIRV(const ShaderCreateInfo& S
GLSLangUtils::GLSLtoSPIRVAttribs Attribs;
Attribs.ShaderType = ShaderCI.Desc.ShaderType;
Attribs.ShaderSource = SourceData.Source;
Attribs.SourceName = ShaderCI.FilePath;
Attribs.SourceCodeLen = static_cast<int>(SourceData.SourceLength);
Attribs.Version = GLSLangUtils::SpirvVersion::Vk100;
Attribs.Macros = Macros;
Expand Down
72 changes: 45 additions & 27 deletions Graphics/GraphicsTools/src/ShaderSourceFactoryUtils.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 Diligent Graphics LLC
* Copyright 2023-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.
Expand Down Expand Up @@ -29,12 +29,14 @@
#include <vector>
#include <unordered_map>
#include <string>
#include <utility>

#include "ObjectBase.hpp"
#include "HashUtils.hpp"
#include "RefCntAutoPtr.hpp"
#include "StringDataBlobImpl.hpp"
#include "MemoryFileStream.hpp"
#include "ShaderSourcePath.hpp"

namespace Diligent
{
Expand Down Expand Up @@ -66,41 +68,48 @@ class CompoundShaderSourceFactory : public ObjectBase<IShaderSourceInputStreamFa
{
for (Uint32 i = 0; i < CI.NumFileSubstitutes; ++i)
{
m_FileSubstituteMap.emplace(HashMapStringKey{CI.pFileSubstitutes[i].Name, true}, CI.pFileSubstitutes[i].Substitute);
const String Name = NormalizeShaderSourcePath(CI.pFileSubstitutes[i].Name);
String Substitute = NormalizeShaderSourcePath(CI.pFileSubstitutes[i].Substitute);
m_FileSubstituteMap.emplace(HashMapStringKey{Name}, std::move(Substitute));
}
}
}

IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_IShaderSourceInputStreamFactory, TBase)

virtual void DILIGENT_CALL_TYPE CreateInputStream(const Char* Name,
virtual Bool DILIGENT_CALL_TYPE CreateInputStream(const Char* Name,
IFileStream** ppStream) override final
{
CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
return CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
}

virtual void DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
virtual Bool DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags,
IFileStream** ppStream) override final
{
VERIFY_EXPR(ppStream != nullptr && *ppStream == nullptr);
VERIFY_EXPR(ppStream == nullptr || *ppStream == nullptr);
const String NormalizedName = NormalizeShaderSourcePath(Name);
const Char* SourceName = NormalizedName.c_str();
if (!m_FileSubstituteMap.empty())
{
auto it = m_FileSubstituteMap.find(Name);
auto it = m_FileSubstituteMap.find(SourceName);
if (it != m_FileSubstituteMap.end())
Name = it->second.c_str();
SourceName = it->second.c_str();
}

for (size_t i = 0; i < m_pFactories.size() && *ppStream == nullptr; ++i)
Bool SourceFound = False;
for (size_t i = 0; i < m_pFactories.size() && !SourceFound; ++i)
{
if (m_pFactories[i])
m_pFactories[i]->CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT, ppStream);
SourceFound = m_pFactories[i]->CreateInputStream2(SourceName, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT, ppStream);
}

if (*ppStream == nullptr && (Flags & CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT) != 0)
if (!SourceFound && ppStream != nullptr && (Flags & CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT) == 0)
{
LOG_ERROR("Failed to create input stream for source file ", Name);
LOG_ERROR_MESSAGE("Failed to create input stream for source file ", SourceName);
}

return SourceFound;
}

private:
Expand Down Expand Up @@ -154,35 +163,44 @@ class MemoryShaderSourceFactory final : public ObjectBase<IShaderSourceInputStre
const MemoryShaderSourceFileInfo& Source = CI.pSources[i];
DEV_CHECK_ERR(Source.Name != nullptr && Source.Name[0] != '\0', "Source name must not be null or empty");
DEV_CHECK_ERR(Source.pData != nullptr, "Source data must not be null");
m_NameToSourceMap.emplace(HashMapStringKey{Source.Name, true}, CI.CopySources ? m_Sources[i].c_str() : Source.pData);
m_NameToSourceMap.emplace(HashMapStringKey{NormalizeShaderSourcePath(Source.Name)}, CI.CopySources ? m_Sources[i].c_str() : Source.pData);
}
}

virtual void DILIGENT_CALL_TYPE CreateInputStream(const Char* Name, IFileStream** ppStream) override final
virtual Bool DILIGENT_CALL_TYPE CreateInputStream(const Char* Name, IFileStream** ppStream) override final
{
CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
return CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
}

virtual void DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
virtual Bool DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags,
IFileStream** ppStream) override final
{
auto SourceIt = m_NameToSourceMap.find(Name);
if (SourceIt != m_NameToSourceMap.end())
const String NormalizedName = NormalizeShaderSourcePath(Name);
auto SourceIt = m_NameToSourceMap.find(NormalizedName.c_str());
Bool SourceFound = SourceIt != m_NameToSourceMap.end();
if (ppStream != nullptr)
{
RefCntAutoPtr<StringDataBlobImpl> pDataBlob{MakeNewRCObj<StringDataBlobImpl>()(SourceIt->second)};
RefCntAutoPtr<MemoryFileStream> pMemStream{MakeNewRCObj<MemoryFileStream>()(pDataBlob)};
DEV_CHECK_ERR(*ppStream == nullptr, "Output stream pointer must be null. Overwriting a non-null output pointer may result in memory leaks.");
if (SourceFound)
{
RefCntAutoPtr<StringDataBlobImpl> pDataBlob{MakeNewRCObj<StringDataBlobImpl>()(SourceIt->second)};
RefCntAutoPtr<MemoryFileStream> pMemStream{MakeNewRCObj<MemoryFileStream>()(pDataBlob)};

pMemStream->QueryInterface(IID_FileStream, ppStream);
}
else
{
*ppStream = nullptr;
if ((Flags & CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT) == 0)
pMemStream->QueryInterface(IID_FileStream, ppStream);
SourceFound = *ppStream != nullptr;
}
else
{
LOG_ERROR("Failed to create input stream for source file ", Name);
*ppStream = nullptr;
if ((Flags & CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_SILENT) == 0)
{
LOG_ERROR_MESSAGE("Failed to create input stream for source file ", Name);
}
}
}

return SourceFound;
}

IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_IShaderSourceInputStreamFactory, TBase)
Expand Down
1 change: 1 addition & 0 deletions Graphics/ShaderTools/include/GLSLangUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading