Skip to content

Commit 88f1fff

Browse files
Added SHADER_SOURCE_LANGUAGE_BYTECODE enum value (API256012)
Close #710
1 parent 321e684 commit 88f1fff

File tree

7 files changed

+228
-11
lines changed

7 files changed

+228
-11
lines changed

Graphics/GraphicsEngine/interface/APIInfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
/// \file
3131
/// Diligent API information
3232

33-
#define DILIGENT_API_VERSION 256011
33+
#define DILIGENT_API_VERSION 256012
3434

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

Graphics/GraphicsEngine/interface/Shader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ DILIGENT_TYPED_ENUM(SHADER_SOURCE_LANGUAGE, Uint32)
8181
/// The source language is WebGPU shading language (WGSL)
8282
SHADER_SOURCE_LANGUAGE_WGSL,
8383

84+
/// The shader source is provided as device-specific bytecode
85+
/// (e.g. DXBC or DXIL for Direct3D11/Direct3D12, SPIRV for Vulkan, etc.).
86+
/// The bytecode is used verbatim and no compilation is performed.
87+
///
88+
/// This option is similar to providing the byte code via `ShaderCreateInfo::ByteCode`.
89+
SHADER_SOURCE_LANGUAGE_BYTECODE,
90+
8491
SHADER_SOURCE_LANGUAGE_COUNT
8592
};
8693

Graphics/GraphicsEngineD3DBase/src/ShaderD3DBase.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ RefCntAutoPtr<IDataBlob> CompileD3DBytecode(const ShaderCreateInfo& ShaderCI,
135135
IDXCompiler* DxCompiler,
136136
IDataBlob** ppCompilerOutput) noexcept(false)
137137
{
138-
if (ShaderCI.Source || ShaderCI.FilePath)
138+
if (ShaderCI.Source != nullptr || (ShaderCI.FilePath != nullptr && ShaderCI.SourceLanguage != SHADER_SOURCE_LANGUAGE_BYTECODE))
139139
{
140140
DEV_CHECK_ERR(ShaderCI.ByteCode == nullptr, "'ByteCode' must be null when shader is created from the source code or a file");
141141
DEV_CHECK_ERR(ShaderCI.EntryPoint != nullptr, "Entry point must not be null");
@@ -182,11 +182,25 @@ RefCntAutoPtr<IDataBlob> CompileD3DBytecode(const ShaderCreateInfo& ShaderCI,
182182
return DataBlobImpl::Create(pShaderByteCode->GetBufferSize(), pShaderByteCode->GetBufferPointer());
183183
}
184184
}
185-
else if (ShaderCI.ByteCode)
185+
else if (ShaderCI.ByteCode != nullptr)
186186
{
187187
DEV_CHECK_ERR(ShaderCI.ByteCodeSize != 0, "ByteCode size must be greater than 0");
188188
return DataBlobImpl::Create(ShaderCI.ByteCodeSize, ShaderCI.ByteCode);
189189
}
190+
else if (ShaderCI.FilePath != nullptr && ShaderCI.SourceLanguage == SHADER_SOURCE_LANGUAGE_BYTECODE)
191+
{
192+
if (ShaderCI.pShaderSourceStreamFactory == nullptr)
193+
LOG_ERROR_AND_THROW("Shader source stream factory must be provided when loading shader bytecode from a file");
194+
195+
RefCntAutoPtr<IFileStream> pSourceStream;
196+
ShaderCI.pShaderSourceStreamFactory->CreateInputStream(ShaderCI.FilePath, &pSourceStream);
197+
if (!pSourceStream)
198+
LOG_ERROR_AND_THROW("Failed to load shader bytecode from file '", ShaderCI.FilePath, "'. Check that the file exists");
199+
200+
RefCntAutoPtr<DataBlobImpl> pByteCode = DataBlobImpl::Create();
201+
pSourceStream->ReadBlob(pByteCode);
202+
return pByteCode;
203+
}
190204
else
191205
{
192206
LOG_ERROR_AND_THROW("Shader source must be provided through one of the 'Source', 'FilePath' or 'ByteCode' members");

Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ std::vector<uint32_t> CompileShaderGLSLang(const ShaderCreateInfo& Shade
164164
void ShaderVkImpl::Initialize(const ShaderCreateInfo& ShaderCI,
165165
const CreateInfo& VkShaderCI)
166166
{
167-
if (ShaderCI.Source != nullptr || ShaderCI.FilePath != nullptr)
167+
if (ShaderCI.Source != nullptr || (ShaderCI.FilePath != nullptr && ShaderCI.SourceLanguage != SHADER_SOURCE_LANGUAGE_BYTECODE))
168168
{
169169
DEV_CHECK_ERR(ShaderCI.ByteCode == nullptr, "'ByteCode' must be null when shader is created from source code or a file");
170170

@@ -202,10 +202,26 @@ void ShaderVkImpl::Initialize(const ShaderCreateInfo& ShaderCI,
202202
else if (ShaderCI.ByteCode != nullptr)
203203
{
204204
DEV_CHECK_ERR(ShaderCI.ByteCodeSize != 0, "ByteCodeSize must not be 0");
205-
DEV_CHECK_ERR(ShaderCI.ByteCodeSize % 4 == 0, "Byte code size (", ShaderCI.ByteCodeSize, ") is not multiple of 4");
205+
DEV_CHECK_ERR(ShaderCI.ByteCodeSize % 4 == 0, "Byte code size (", ShaderCI.ByteCodeSize, ") is not a multiple of 4");
206206
m_SPIRV.resize(ShaderCI.ByteCodeSize / 4);
207207
memcpy(m_SPIRV.data(), ShaderCI.ByteCode, ShaderCI.ByteCodeSize);
208208
}
209+
else if (ShaderCI.FilePath != nullptr && ShaderCI.SourceLanguage == SHADER_SOURCE_LANGUAGE_BYTECODE)
210+
{
211+
if (ShaderCI.pShaderSourceStreamFactory == nullptr)
212+
LOG_ERROR_AND_THROW("Shader source stream factory must be provided when loading shader bytecode from a file");
213+
214+
RefCntAutoPtr<IFileStream> pSourceStream;
215+
ShaderCI.pShaderSourceStreamFactory->CreateInputStream(ShaderCI.FilePath, &pSourceStream);
216+
if (!pSourceStream)
217+
LOG_ERROR_AND_THROW("Failed to load shader bytecode from file '", ShaderCI.FilePath, "'. Check that the file exists");
218+
219+
const size_t ByteCodeSize = pSourceStream->GetSize();
220+
DEV_CHECK_ERR(ByteCodeSize % 4 == 0, "Byte code size (", ByteCodeSize, ") is not a multiple of 4");
221+
222+
m_SPIRV.resize(ByteCodeSize / 4);
223+
pSourceStream->Read(m_SPIRV.data(), ByteCodeSize);
224+
}
209225
else
210226
{
211227
LOG_ERROR_AND_THROW("Shader source must be provided through one of the 'Source', 'FilePath' or 'ByteCode' members");

ReleaseHistory.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Current progress
44

5+
* Added `SHADER_SOURCE_LANGUAGE_BYTECODE` enum value (API256012)
56
* Replaced `EngineCreateInfo::pRawMemAllocator` with `IEngineFactory::SetMemoryAllocator()`,
67
added `IArchiverFactory::SetMemoryAllocator()` (API256011)
78
* Added `IRenderDeviceVk::GetDXCompiler()` and `IRenderDeviceD3D12::GetDXCompiler()` methods (API256010)
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2025 Diligent Graphics LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* In no event and under no legal theory, whether in tort (including negligence),
17+
* contract, or otherwise, unless required by applicable law (such as deliberate
18+
* and grossly negligent acts) or agreed to in writing, shall any Contributor be
19+
* liable for any damages, including any direct, indirect, special, incidental,
20+
* or consequential damages of any character arising as a result of this License or
21+
* out of the use or inability to use the software (including but not limited to damages
22+
* for loss of goodwill, work stoppage, computer failure or malfunction, or any and
23+
* all other commercial damages or losses), even if such Contributor has been advised
24+
* of the possibility of such damages.
25+
*/
26+
27+
#include "GPUTestingEnvironment.hpp"
28+
29+
#include "ObjectBase.hpp"
30+
#include "MemoryFileStream.hpp"
31+
#include "ProxyDataBlob.hpp"
32+
33+
#include "gtest/gtest.h"
34+
35+
using namespace Diligent;
36+
using namespace Diligent::Testing;
37+
38+
namespace
39+
{
40+
41+
constexpr char g_PS[] = R"(
42+
Texture2D g_Texture;
43+
SamplerState g_Texture_sampler;
44+
45+
float4 main(in float2 UV : TEXCOORD) : SV_Target
46+
{
47+
return g_Texture.Sample(g_Texture_sampler, UV);
48+
}
49+
)";
50+
51+
TEST(ShaderCreationTest, FromSource)
52+
{
53+
GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance();
54+
IRenderDevice* pDevice = pEnv->GetDevice();
55+
56+
ShaderCreateInfo ShaderCI;
57+
ShaderCI.Desc = {"ShaderCreationTest.FromSource", SHADER_TYPE_PIXEL, true};
58+
ShaderCI.Source = g_PS;
59+
ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
60+
61+
{
62+
RefCntAutoPtr<IShader> pShader;
63+
pDevice->CreateShader(ShaderCI, &pShader);
64+
EXPECT_NE(pShader, nullptr);
65+
}
66+
67+
{
68+
std::string Source = g_PS;
69+
Source += "invalid syntax";
70+
71+
ShaderCI.Source = Source.c_str();
72+
ShaderCI.SourceLength = sizeof(g_PS) - 1;
73+
74+
RefCntAutoPtr<IShader> pShader;
75+
pDevice->CreateShader(ShaderCI, &pShader);
76+
EXPECT_NE(pShader, nullptr);
77+
}
78+
}
79+
80+
static std::vector<uint8_t> CompilePS(IRenderDevice* pDevice)
81+
{
82+
ShaderCreateInfo ShaderCI;
83+
ShaderCI.Desc = {"ShaderCreationTest.FromSource", SHADER_TYPE_PIXEL, true};
84+
ShaderCI.Source = g_PS;
85+
ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
86+
87+
std::vector<uint8_t> Bytecode;
88+
RefCntAutoPtr<IShader> pShader;
89+
pDevice->CreateShader(ShaderCI, &pShader);
90+
if (pShader)
91+
{
92+
const void* pBytecode = nullptr;
93+
Uint64 Size = 0;
94+
pShader->GetBytecode(&pBytecode, Size);
95+
96+
Bytecode.resize(static_cast<size_t>(Size));
97+
memcpy(Bytecode.data(), pBytecode, static_cast<size_t>(Size));
98+
}
99+
100+
return Bytecode;
101+
}
102+
103+
TEST(ShaderCreationTest, FromBytecode)
104+
{
105+
GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance();
106+
IRenderDevice* pDevice = pEnv->GetDevice();
107+
const RenderDeviceInfo& DeviceInfo = pDevice->GetDeviceInfo();
108+
109+
if (!(DeviceInfo.IsD3DDevice() || DeviceInfo.IsVulkanDevice()))
110+
{
111+
GTEST_SKIP() << "Creating shader from bytecode is not supported on this device type";
112+
}
113+
114+
std::vector<uint8_t> Bytecode = CompilePS(pDevice);
115+
ASSERT_FALSE(Bytecode.empty()) << "Failed to compile shader";
116+
117+
{
118+
ShaderCreateInfo ShaderCI;
119+
ShaderCI.Desc = {"ShaderCreationTest.FromBytecode - Src", SHADER_TYPE_PIXEL, true};
120+
ShaderCI.ByteCode = Bytecode.data();
121+
ShaderCI.ByteCodeSize = Bytecode.size();
122+
123+
RefCntAutoPtr<IShader> pShader;
124+
pDevice->CreateShader(ShaderCI, &pShader);
125+
EXPECT_NE(pShader, nullptr);
126+
}
127+
128+
{
129+
class TestShaderSourceFactoryImpl : public ObjectBase<IShaderSourceInputStreamFactory>
130+
{
131+
public:
132+
static RefCntAutoPtr<IShaderSourceInputStreamFactory> Create(const std::vector<uint8_t>& Bytecode)
133+
{
134+
return RefCntAutoPtr<IShaderSourceInputStreamFactory>{MakeNewRCObj<TestShaderSourceFactoryImpl>()(Bytecode)};
135+
}
136+
137+
TestShaderSourceFactoryImpl(IReferenceCounters* pRefCounters, const std::vector<uint8_t>& Bytecode) :
138+
ObjectBase<IShaderSourceInputStreamFactory>(pRefCounters),
139+
m_Bytecode{Bytecode}
140+
{
141+
}
142+
143+
virtual void DILIGENT_CALL_TYPE CreateInputStream(const Char* Name, IFileStream** ppStream) override final
144+
{
145+
CreateInputStream2(Name, CREATE_SHADER_SOURCE_INPUT_STREAM_FLAG_NONE, ppStream);
146+
}
147+
148+
virtual void DILIGENT_CALL_TYPE CreateInputStream2(const Char* Name,
149+
CREATE_SHADER_SOURCE_INPUT_STREAM_FLAGS Flags,
150+
IFileStream** ppStream) override final
151+
{
152+
if (strcmp(Name, "ps.bc") != 0)
153+
return;
154+
155+
RefCntAutoPtr<IDataBlob> pDataBlob = ProxyDataBlob::Create(m_Bytecode.data(), m_Bytecode.size());
156+
RefCntAutoPtr<MemoryFileStream> pMemStream{MakeNewRCObj<MemoryFileStream>()(pDataBlob)};
157+
158+
pMemStream->QueryInterface(IID_FileStream, reinterpret_cast<IObject**>(ppStream));
159+
}
160+
161+
private:
162+
const std::vector<uint8_t>& m_Bytecode;
163+
};
164+
165+
RefCntAutoPtr<IShaderSourceInputStreamFactory> pShaderSourceFactory = TestShaderSourceFactoryImpl::Create(Bytecode);
166+
167+
ShaderCreateInfo ShaderCI;
168+
ShaderCI.Desc = {"ShaderCreationTest.FromBytecode", SHADER_TYPE_PIXEL, true};
169+
ShaderCI.pShaderSourceStreamFactory = pShaderSourceFactory;
170+
ShaderCI.FilePath = "ps.bc";
171+
ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_BYTECODE;
172+
173+
RefCntAutoPtr<IShader> pShader;
174+
pDevice->CreateShader(ShaderCI, &pShader);
175+
EXPECT_NE(pShader, nullptr);
176+
}
177+
}
178+
179+
} // namespace

Tests/TestFramework/include/TestingEnvironment.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2024 Diligent Graphics LLC
2+
* Copyright 2019-2025 Diligent Graphics LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -59,24 +59,24 @@ class TestingEnvironment : public ::testing::Environment
5959
{
6060
ErrorScope(const std::initializer_list<const char*>& Messages)
6161
{
62-
auto* pEnv = TestingEnvironment::GetInstance();
62+
TestingEnvironment* pEnv = TestingEnvironment::GetInstance();
6363
pEnv->SetErrorAllowance(static_cast<Int32>(Messages.size()));
6464

65-
for (auto const& Message : Messages)
65+
for (const char* Message : Messages)
6666
pEnv->PushExpectedErrorSubstring(Message, false);
6767
}
6868
ErrorScope(const std::initializer_list<std::string>& Messages)
6969
{
70-
auto* pEnv = TestingEnvironment::GetInstance();
70+
TestingEnvironment* pEnv = TestingEnvironment::GetInstance();
7171
pEnv->SetErrorAllowance(static_cast<Int32>(Messages.size()));
7272

73-
for (auto const& Message : Messages)
73+
for (const std::string& Message : Messages)
7474
pEnv->PushExpectedErrorSubstring(Message.c_str(), false);
7575
}
7676

7777
~ErrorScope()
7878
{
79-
auto* pTestEnvironment = TestingEnvironment::GetInstance();
79+
TestingEnvironment* pTestEnvironment = TestingEnvironment::GetInstance();
8080
pTestEnvironment->SetErrorAllowance(0);
8181
}
8282
};

0 commit comments

Comments
 (0)