2626
2727#include " SPIRVShaderResources.hpp"
2828#include " GLSLangUtils.hpp"
29+ #include " DXCompiler.hpp"
2930#include " DefaultShaderSourceStreamFactory.h"
3031#include " RefCntAutoPtr.hpp"
3132#include " EngineMemory.h"
@@ -46,18 +47,26 @@ namespace
4647
4748class SPIRVShaderResourcesTest : public ::testing::Test
4849{
50+ public:
51+ static std::unique_ptr<IDXCompiler> DXCompiler;
52+
4953protected:
5054 static void SetUpTestSuite ()
5155 {
5256 GLSLangUtils::InitializeGlslang ();
57+
58+ DXCompiler = CreateDXCompiler (DXCompilerTarget::Vulkan, 0 , nullptr );
5359 }
5460
5561 static void TearDownTestSuite ()
5662 {
5763 GLSLangUtils::FinalizeGlslang ();
64+
65+ DXCompiler.reset ();
5866 }
5967};
6068
69+ std::unique_ptr<IDXCompiler> SPIRVShaderResourcesTest::DXCompiler;
6170
6271struct SPIRVShaderResourceRefAttribs
6372{
@@ -70,22 +79,61 @@ struct SPIRVShaderResourceRefAttribs
7079 const Uint32 BufferStride;
7180};
7281
73- std::vector<unsigned int > LoadSPIRVFromHLSL (const char * FilePath, SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL)
82+ std::vector<unsigned int > LoadSPIRVFromHLSL (const char * FilePath, SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL, bool UseDXC = false )
7483{
75- ShaderCreateInfo ShaderCI;
76- ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
77- ShaderCI.FilePath = FilePath;
78- ShaderCI.Desc = {" SPIRV test shader" , ShaderType};
79- ShaderCI.EntryPoint = " main" ;
84+ std::vector<unsigned int > SPIRV;
8085
81- RefCntAutoPtr<IShaderSourceInputStreamFactory> pShaderSourceStreamFactory;
82- CreateDefaultShaderSourceStreamFactory (" shaders/SPIRV" , &pShaderSourceStreamFactory);
83- if (!pShaderSourceStreamFactory)
84- return {};
86+ if (UseDXC)
87+ {
88+ // Use DXC to compile HLSL to SPIR-V.
89+ if (!SPIRVShaderResourcesTest::DXCompiler || !SPIRVShaderResourcesTest::DXCompiler->IsLoaded ())
90+ return {};
91+
92+ ShaderCreateInfo ShaderCI;
93+ ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
94+ ShaderCI.FilePath = FilePath;
95+ ShaderCI.Desc = {" SPIRV test shader" , ShaderType};
96+ ShaderCI.EntryPoint = " main" ;
97+
98+ ShaderMacro Macros[] = {{" DXC" , " 1" }};
99+ ShaderCI.Macros = {Macros, _countof (Macros)};
100+
101+ RefCntAutoPtr<IShaderSourceInputStreamFactory> pShaderSourceStreamFactory;
102+ CreateDefaultShaderSourceStreamFactory (" shaders/SPIRV" , &pShaderSourceStreamFactory);
103+ if (!pShaderSourceStreamFactory)
104+ return {};
105+
106+ ShaderCI.pShaderSourceStreamFactory = pShaderSourceStreamFactory;
107+
108+ RefCntAutoPtr<IDataBlob> pCompilerOutput;
109+ SPIRVShaderResourcesTest::DXCompiler->Compile (ShaderCI, ShaderVersion{6 , 0 }, nullptr , nullptr , &SPIRV, &pCompilerOutput);
110+
111+ if (pCompilerOutput && pCompilerOutput->GetSize () > 0 )
112+ {
113+ std::string CompilerOutput = static_cast <const char *>(pCompilerOutput->GetConstDataPtr ());
114+ LOG_INFO_MESSAGE (" DXC compiler output:\n " , CompilerOutput);
115+ }
116+ }
117+ else
118+ {
119+ // Use Glslang to compile HLSL to SPIR-V.
120+ ShaderCreateInfo ShaderCI;
121+ ShaderCI.SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL;
122+ ShaderCI.FilePath = FilePath;
123+ ShaderCI.Desc = {" SPIRV test shader" , ShaderType};
124+ ShaderCI.EntryPoint = " main" ;
125+
126+ RefCntAutoPtr<IShaderSourceInputStreamFactory> pShaderSourceStreamFactory;
127+ CreateDefaultShaderSourceStreamFactory (" shaders/SPIRV" , &pShaderSourceStreamFactory);
128+ if (!pShaderSourceStreamFactory)
129+ return {};
130+
131+ ShaderCI.pShaderSourceStreamFactory = pShaderSourceStreamFactory;
85132
86- ShaderCI.pShaderSourceStreamFactory = pShaderSourceStreamFactory;
133+ SPIRV = GLSLangUtils::HLSLtoSPIRV (ShaderCI, GLSLangUtils::SpirvVersion::Vk100, nullptr , nullptr );
134+ }
87135
88- return GLSLangUtils::HLSLtoSPIRV (ShaderCI, GLSLangUtils::SpirvVersion::Vk100, nullptr , nullptr ) ;
136+ return SPIRV ;
89137}
90138
91139std::vector<unsigned int > LoadSPIRVFromGLSL (const char * FilePath, SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL)
@@ -130,15 +178,11 @@ std::vector<unsigned int> LoadSPIRVFromGLSL(const char* FilePath, SHADER_TYPE Sh
130178 return GLSLangUtils::GLSLtoSPIRV (Attribs);
131179}
132180
133- void TestSPIRVResources (const char * FilePath,
134- const std::vector<SPIRVShaderResourceRefAttribs>& RefResources,
135- SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL,
136- const char * CombinedSamplerSuffix = nullptr ,
137- bool IsGLSL = false )
181+ void TestSPIRVResourcesInternal (const char * FilePath,
182+ const std::vector<SPIRVShaderResourceRefAttribs>& RefResources,
183+ const std::vector<unsigned int >& SPIRV,
184+ SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL)
138185{
139- const auto SPIRV = IsGLSL ? LoadSPIRVFromGLSL (FilePath, ShaderType) : LoadSPIRVFromHLSL (FilePath, ShaderType);
140- ASSERT_FALSE (SPIRV.empty ()) << " Failed to compile HLSL to SPIRV: " << FilePath;
141-
142186 ShaderDesc ShaderDesc;
143187 ShaderDesc.Name = " SPIRVResources test" ;
144188 ShaderDesc.ShaderType = ShaderType;
@@ -148,7 +192,7 @@ void TestSPIRVResources(const char* FilePa
148192 GetRawAllocator (),
149193 SPIRV,
150194 ShaderDesc,
151- CombinedSamplerSuffix ,
195+ nullptr ,
152196 false , // LoadShaderStageInputs
153197 false , // LoadUniformBufferReflection
154198 EntryPoint};
@@ -184,18 +228,65 @@ void TestSPIRVResources(const char* FilePa
184228 }
185229}
186230
231+ void TestSPIRVResources (const char * FilePath,
232+ const std::vector<SPIRVShaderResourceRefAttribs>& RefResources,
233+ SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL,
234+ SHADER_SOURCE_LANGUAGE SourceLanguage = SHADER_SOURCE_LANGUAGE_HLSL)
235+ {
236+ const auto & SPIRV = (SourceLanguage == SHADER_SOURCE_LANGUAGE_GLSL) ?
237+ LoadSPIRVFromGLSL (FilePath, ShaderType) :
238+ LoadSPIRVFromHLSL (FilePath, ShaderType);
239+ ASSERT_FALSE (SPIRV.empty ()) << (SourceLanguage == SHADER_SOURCE_LANGUAGE_GLSL ?
240+ " Failed to compile GLSL to SPIRV with glslang: " :
241+ " Failed to compile HLSL to SPIRV with glslang: " )
242+ << FilePath;
243+
244+ LOG_INFO_MESSAGE (SourceLanguage == SHADER_SOURCE_LANGUAGE_GLSL ?
245+ " Testing with GLSL->SPIRV with glslang:\n " :
246+ " Testing with HLSL->SPIRV with glslang:\n " ,
247+ FilePath);
248+
249+ TestSPIRVResourcesInternal (FilePath, RefResources, SPIRV, ShaderType);
250+ }
251+
252+ void TestSPIRVResourcesDXC (const char * FilePath,
253+ const std::vector<SPIRVShaderResourceRefAttribs>& RefResources,
254+ SHADER_TYPE ShaderType = SHADER_TYPE_PIXEL)
255+ {
256+ if (!SPIRVShaderResourcesTest::DXCompiler || !SPIRVShaderResourcesTest::DXCompiler->IsLoaded ())
257+ {
258+ LOG_INFO_MESSAGE (" HLSL->SPIRV with DXC skipped because DXCompiler is not available\n " );
259+ return ;
260+ }
261+
262+ const auto & SPIRV_DXC = LoadSPIRVFromHLSL (FilePath, ShaderType, true );
263+ ASSERT_FALSE (SPIRV_DXC.empty ()) << " Failed to compile HLSL to SPIRV with DXC: " << FilePath;
264+
265+ LOG_INFO_MESSAGE (" Testing with HLSL->SPIRV with DXC:\n " , FilePath);
266+
267+ TestSPIRVResourcesInternal (FilePath, RefResources, SPIRV_DXC, ShaderType);
268+ }
269+
187270using SPIRVResourceType = SPIRVShaderResourceAttribs::ResourceType;
188271
189272TEST_F (SPIRVShaderResourcesTest, UniformBuffers)
190273{
191274 TestSPIRVResources (" UniformBuffers.psh" ,
192275 {
193- // CB0 is optimized away as it's not used in the shader
194276 SPIRVShaderResourceRefAttribs{" CB1" , 1 , SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0 , 48 , 0 },
195277 SPIRVShaderResourceRefAttribs{" CB2" , 1 , SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0 , 16 , 0 },
196278 });
197279}
198280
281+ TEST_F (SPIRVShaderResourcesTest, UniformBuffers_DXC)
282+ {
283+ TestSPIRVResourcesDXC (" UniformBuffers.psh" ,
284+ {
285+ SPIRVShaderResourceRefAttribs{" CB1" , 1 , SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0 , 48 , 0 },
286+ SPIRVShaderResourceRefAttribs{" CB2" , 1 , SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0 , 16 , 0 },
287+ });
288+ }
289+
199290TEST_F (SPIRVShaderResourcesTest, StorageBuffers)
200291{
201292 TestSPIRVResources (" StorageBuffers.psh" ,
@@ -209,6 +300,19 @@ TEST_F(SPIRVShaderResourcesTest, StorageBuffers)
209300 });
210301}
211302
303+ TEST_F (SPIRVShaderResourcesTest, StorageBuffers_DXC)
304+ {
305+ TestSPIRVResourcesDXC (" StorageBuffers.psh" ,
306+ {
307+ // StructuredBuffers have BufferStaticSize=0 (runtime array) and BufferStride is the element size
308+ SPIRVShaderResourceRefAttribs{" g_ROBuffer" , 1 , SPIRVResourceType::ROStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 32 },
309+ SPIRVShaderResourceRefAttribs{" g_RWBuffer" , 1 , SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 64 },
310+ // ByteAddressBuffers also have BufferStaticSize=0 and BufferStride=4 (uint size)
311+ SPIRVShaderResourceRefAttribs{" g_ROAtomicBuffer" , 1 , SPIRVResourceType::ROStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 4 },
312+ SPIRVShaderResourceRefAttribs{" g_RWAtomicBuffer" , 1 , SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 4 },
313+ });
314+ }
315+
212316TEST_F (SPIRVShaderResourcesTest, TexelBuffers)
213317{
214318 TestSPIRVResources (" TexelBuffers.psh" ,
@@ -218,6 +322,15 @@ TEST_F(SPIRVShaderResourcesTest, TexelBuffers)
218322 });
219323}
220324
325+ TEST_F (SPIRVShaderResourcesTest, TexelBuffers_DXC)
326+ {
327+ TestSPIRVResourcesDXC (" TexelBuffers.psh" ,
328+ {
329+ SPIRVShaderResourceRefAttribs{" g_UniformTexelBuffer" , 1 , SPIRVResourceType::UniformTexelBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 0 },
330+ SPIRVShaderResourceRefAttribs{" g_StorageTexelBuffer" , 1 , SPIRVResourceType::StorageTexelBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 0 },
331+ });
332+ }
333+
221334TEST_F (SPIRVShaderResourcesTest, Textures)
222335{
223336 TestSPIRVResources (" Textures.psh" ,
@@ -238,6 +351,26 @@ TEST_F(SPIRVShaderResourcesTest, Textures)
238351 });
239352}
240353
354+ TEST_F (SPIRVShaderResourcesTest, Textures_DXC)
355+ {
356+ TestSPIRVResourcesDXC (" Textures.psh" ,
357+ {
358+ // When textures and samplers are declared separately in HLSL, they are compiled as separate_images
359+ // instead of sampled_images. This is the correct behavior for separate sampler/texture declarations.
360+ SPIRVShaderResourceRefAttribs{" g_SampledImage" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0 , 0 , 0 },
361+ SPIRVShaderResourceRefAttribs{" g_SampledImageMS" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 1 , 0 , 0 },
362+ SPIRVShaderResourceRefAttribs{" g_SampledImage3D" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_3D, 0 , 0 , 0 },
363+ SPIRVShaderResourceRefAttribs{" g_SampledImageCube" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_CUBE, 0 , 0 , 0 },
364+ SPIRVShaderResourceRefAttribs{" g_Sampler" , 1 , SPIRVResourceType::SeparateSampler, RESOURCE_DIM_UNDEFINED, 0 , 0 , 0 },
365+ SPIRVShaderResourceRefAttribs{" g_SeparateImage" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0 , 0 , 0 },
366+ // Combined sampler: g_Texture and g_Texture_sampler
367+ // Note: Even with CombinedSamplerSuffix, SPIRV may still classify them as separate_images
368+ // if they are declared separately. The CombinedSamplerSuffix is mainly used for naming convention.
369+ SPIRVShaderResourceRefAttribs{" g_Texture" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0 , 0 , 0 },
370+ SPIRVShaderResourceRefAttribs{" g_Texture_sampler" , 1 , SPIRVResourceType::SeparateSampler, RESOURCE_DIM_UNDEFINED, 0 , 0 , 0 },
371+ });
372+ }
373+
241374TEST_F (SPIRVShaderResourcesTest, StorageImages)
242375{
243376 TestSPIRVResources (" StorageImages.psh" ,
@@ -249,6 +382,17 @@ TEST_F(SPIRVShaderResourcesTest, StorageImages)
249382 });
250383}
251384
385+ TEST_F (SPIRVShaderResourcesTest, StorageImages_DXC)
386+ {
387+ TestSPIRVResourcesDXC (" StorageImages.psh" ,
388+ {
389+ // Note: HLSL does not support RWTextureCube, so we only test 2D, 2DArray, and 3D storage images
390+ SPIRVShaderResourceRefAttribs{" g_RWImage2D" , 1 , SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_2D, 0 , 0 , 0 },
391+ SPIRVShaderResourceRefAttribs{" g_RWImage2DArray" , 1 , SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_2D_ARRAY, 0 , 0 , 0 },
392+ SPIRVShaderResourceRefAttribs{" g_RWImage3D" , 1 , SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_3D, 0 , 0 , 0 },
393+ });
394+ }
395+
252396TEST_F (SPIRVShaderResourcesTest, AtomicCounters)
253397{
254398 // Use GLSL for atomic counters. Note: Vulkan does not support atomic_uint (AtomicCounter storage class).
@@ -260,8 +404,7 @@ TEST_F(SPIRVShaderResourcesTest, AtomicCounters)
260404 SPIRVShaderResourceRefAttribs{" AtomicCounterBuffer" , 1 , SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 4 , 0 },
261405 },
262406 SHADER_TYPE_PIXEL,
263- nullptr ,
264- true ); // IsGLSL = true
407+ SHADER_SOURCE_LANGUAGE_GLSL);
265408}
266409
267410TEST_F (SPIRVShaderResourcesTest, InputAttachments)
@@ -272,6 +415,14 @@ TEST_F(SPIRVShaderResourcesTest, InputAttachments)
272415 });
273416}
274417
418+ TEST_F (SPIRVShaderResourcesTest, InputAttachments_DXC)
419+ {
420+ TestSPIRVResourcesDXC (" InputAttachments.psh" ,
421+ {
422+ SPIRVShaderResourceRefAttribs{" g_InputAttachment" , 1 , SPIRVResourceType::InputAttachment, RESOURCE_DIM_UNDEFINED, 0 , 0 , 0 },
423+ });
424+ }
425+
275426TEST_F (SPIRVShaderResourcesTest, AccelerationStructures)
276427{
277428 // Use GLSL for acceleration structures since HLSLtoSPIRV doesn't support raytracing shaders
@@ -282,8 +433,7 @@ TEST_F(SPIRVShaderResourcesTest, AccelerationStructures)
282433 SPIRVShaderResourceRefAttribs{" g_AccelStruct" , 1 , SPIRVResourceType::AccelerationStructure, RESOURCE_DIM_UNDEFINED, 0 , 0 , 0 },
283434 },
284435 SHADER_TYPE_RAY_GEN,
285- nullptr ,
286- true ); // IsGLSL = true
436+ SHADER_SOURCE_LANGUAGE_GLSL); // IsGLSL = true
287437}
288438
289439TEST_F (SPIRVShaderResourcesTest, PushConstants)
@@ -297,6 +447,17 @@ TEST_F(SPIRVShaderResourcesTest, PushConstants)
297447 });
298448}
299449
450+ TEST_F (SPIRVShaderResourcesTest, PushConstants_DXC)
451+ {
452+ // Push constant ArraySize represents the number of 32-bit words, not array elements
453+ // PushConstants struct: float4x4 (16 floats) + float4 (4 floats) + float2 (2 floats) + float (1 float) + uint (1 uint)
454+ // Total: 16 + 4 + 2 + 1 + 1 = 24 floats/uints = 24 * 4 bytes = 96 bytes = 24 words
455+ TestSPIRVResourcesDXC (" PushConstants.psh" ,
456+ {
457+ SPIRVShaderResourceRefAttribs{" PushConstants" , 1 , SPIRVResourceType::PushConstant, RESOURCE_DIM_BUFFER, 0 , 96 , 0 },
458+ });
459+ }
460+
300461TEST_F (SPIRVShaderResourcesTest, MixedResources)
301462{
302463 TestSPIRVResources (" MixedResources.psh" ,
@@ -317,4 +478,24 @@ TEST_F(SPIRVShaderResourcesTest, MixedResources)
317478 });
318479}
319480
481+ TEST_F (SPIRVShaderResourcesTest, MixedResources_DXC)
482+ {
483+ TestSPIRVResourcesDXC (" MixedResources.psh" ,
484+ {
485+ // UniformBuff: float4x4 (64 bytes) + float4 (16 bytes) = 80 bytes
486+ SPIRVShaderResourceRefAttribs{" UniformBuff" , 1 , SPIRVResourceType::UniformBuffer, RESOURCE_DIM_BUFFER, 0 , 80 , 0 },
487+ // ROStorageBuff: StructuredBuffer<BufferData> where BufferData = float4[4] = 64 bytes
488+ // StructuredBuffers have BufferStaticSize=0 (runtime array) and BufferStride is the element size
489+ SPIRVShaderResourceRefAttribs{" ROStorageBuff" , 1 , SPIRVResourceType::ROStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 64 },
490+ // RWStorageBuff: same as ROStorageBuff
491+ SPIRVShaderResourceRefAttribs{" RWStorageBuff" , 1 , SPIRVResourceType::RWStorageBuffer, RESOURCE_DIM_BUFFER, 0 , 0 , 64 },
492+ // SampledTex: When Texture2D and SamplerState are declared separately, they are compiled as SeparateImage
493+ SPIRVShaderResourceRefAttribs{" SampledTex" , 1 , SPIRVResourceType::SeparateImage, RESOURCE_DIM_TEX_2D, 0 , 0 , 0 },
494+ SPIRVShaderResourceRefAttribs{" StorageTex" , 1 , SPIRVResourceType::StorageImage, RESOURCE_DIM_TEX_2D, 0 , 0 , 0 },
495+ SPIRVShaderResourceRefAttribs{" Sampler" , 1 , SPIRVResourceType::SeparateSampler, RESOURCE_DIM_UNDEFINED, 0 , 0 , 0 },
496+ // PushConstants: float2 (2 floats) + float (1 float) + uint (1 uint) = 4 words = 16 bytes
497+ SPIRVShaderResourceRefAttribs{" PushConstants" , 1 , SPIRVResourceType::PushConstant, RESOURCE_DIM_BUFFER, 0 , 16 , 0 },
498+ });
499+ }
500+
320501} // namespace
0 commit comments