1
+ // Copyright (C) 2018-2024 - DevSH Graphics Programming Sp. z O.O.
2
+ // This file is part of the "Nabla Engine".
3
+ // For conditions of distribution and use, see copyright notice in nabla.h
4
+ #ifndef _NBL_TESTERS_H_INCLUDED_
5
+ #define _NBL_TESTERS_H_INCLUDED_
6
+
7
+ #include " nbl/application_templates/MonoDeviceApplication.hpp"
8
+ #include " nbl/application_templates/MonoAssetManagerAndBuiltinResourceApplication.hpp"
9
+
10
+ using namespace nbl ;
11
+
12
+ class IntrospectionTesterBase
13
+ {
14
+
15
+ public:
16
+ IntrospectionTesterBase (const std::string& functionToTestName)
17
+ : m_functionToTestName(functionToTestName) {};
18
+
19
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr) = 0;
20
+
21
+ virtual ~IntrospectionTesterBase () {};
22
+
23
+ protected:
24
+ const std::string m_functionToTestName = " " ;
25
+
26
+ protected:
27
+ static std::pair<smart_refctd_ptr<ICPUShader>, smart_refctd_ptr<const CSPIRVIntrospector::CStageIntrospectionData>> compileHLSLShaderAndTestIntrospection (
28
+ video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr, const std::string& shaderPath, CSPIRVIntrospector& introspector)
29
+ {
30
+ IAssetLoader::SAssetLoadParams lp = {};
31
+ lp.logger = logger;
32
+ lp.workingDirectory = " " ; // virtual root
33
+ // this time we load a shader directly from a file
34
+ auto assetBundle = assetMgr->getAsset (shaderPath, lp);
35
+ const auto assets = assetBundle.getContents ();
36
+ if (assets.empty ())
37
+ {
38
+ logFail (logger, " Could not load shader!" );
39
+ assert (0 );
40
+ }
41
+
42
+ // It would be super weird if loading a shader from a file produced more than 1 asset
43
+ assert (assets.size () == 1 );
44
+ smart_refctd_ptr<ICPUShader> source = IAsset::castDown<ICPUShader>(assets[0 ]);
45
+
46
+ smart_refctd_ptr<const CSPIRVIntrospector::CStageIntrospectionData> introspection;
47
+ {
48
+ // The Asset Manager has a Default Compiler Set which contains all built-in compilers (so it can try them all)
49
+ auto * compilerSet = assetMgr->getCompilerSet ();
50
+
51
+ // This time we use a more "generic" option struct which works with all compilers
52
+ nbl::asset::IShaderCompiler::SCompilerOptions options = {};
53
+ // The Shader Asset Loaders deduce the stage from the file extension,
54
+ // if the extension is generic (.glsl or .hlsl) the stage is unknown.
55
+ // But it can still be overriden from within the source with a `#pragma shader_stage`
56
+ options.stage = source->getStage () == IShader::ESS_COMPUTE ? source->getStage () : IShader::ESS_VERTEX; // TODO: do smth with it
57
+ options.targetSpirvVersion = device->getPhysicalDevice ()->getLimits ().spirvVersion ;
58
+ // we need to perform an unoptimized compilation with source debug info or we'll lose names of variable sin the introspection
59
+ options.spirvOptimizer = nullptr ;
60
+ options.debugInfoFlags |= IShaderCompiler::E_DEBUG_INFO_FLAGS::EDIF_SOURCE_BIT;
61
+ // The nice thing is that when you load a shader from file, it has a correctly set `filePathHint`
62
+ // so it plays nicely with the preprocessor, and finds `#include`s without intervention.
63
+ options.preprocessorOptions .sourceIdentifier = source->getFilepathHint ();
64
+ options.preprocessorOptions .logger = logger;
65
+ options.preprocessorOptions .includeFinder = compilerSet->getShaderCompiler (source->getContentType ())->getDefaultIncludeFinder ();
66
+
67
+ auto spirvUnspecialized = compilerSet->compileToSPIRV (source.get (), options);
68
+ const CSPIRVIntrospector::CStageIntrospectionData::SParams inspctParams = { .entryPoint = " main" , .shader = spirvUnspecialized };
69
+
70
+ introspection = introspector.introspect (inspctParams);
71
+ if (!introspection)
72
+ {
73
+ logFail (logger, " SPIR-V Introspection failed, probably the required SPIR-V compilation failed first!" );
74
+ return std::pair (nullptr , nullptr );
75
+ }
76
+
77
+ {
78
+ auto * srcContent = spirvUnspecialized->getContent ();
79
+
80
+ system::ISystem::future_t <core::smart_refctd_ptr<system::IFile>> future;
81
+ physicalDevice->getSystem ()->createFile (future, system::path (" ../app_resources/compiled.spv" ), system::IFileBase::ECF_WRITE);
82
+ if (auto file = future.acquire (); file && bool (*file))
83
+ {
84
+ system::IFile::success_t succ;
85
+ (*file)->write (succ, srcContent->getPointer (), 0 , srcContent->getSize ());
86
+ succ.getBytesProcessed (true );
87
+ }
88
+ }
89
+
90
+ // now we need to swap out the HLSL for SPIR-V
91
+ source = std::move (spirvUnspecialized);
92
+ }
93
+
94
+ return std::pair (source, introspection);
95
+ }
96
+
97
+ void confirmExpectedOutput (system::ILogger* logger, bool value, bool expectedValue)
98
+ {
99
+ if (value != expectedValue)
100
+ {
101
+ logger->log (" \" CSPIRVIntrospector::CPipelineIntrospectionData::merge\" function FAIL, incorrect output." ,
102
+ ILogger::E_LOG_LEVEL::ELL_ERROR);
103
+ }
104
+ else
105
+ {
106
+ logger->log (" \" CSPIRVIntrospector::CPipelineIntrospectionData::merge\" function SUCCESS, correct output." ,
107
+ ILogger::E_LOG_LEVEL::ELL_PERFORMANCE);
108
+ }
109
+ }
110
+
111
+ template <typename ... Args>
112
+ static inline bool logFail (system::ILogger* logger, const char * msg, Args&&... args)
113
+ {
114
+ logger->log (msg, system::ILogger::ELL_ERROR, std::forward<Args>(args)...);
115
+ return false ;
116
+ }
117
+ };
118
+
119
+ class MergeTester final : public IntrospectionTesterBase
120
+ {
121
+ public:
122
+ MergeTester (const std::string& functionToTestName)
123
+ : IntrospectionTesterBase(functionToTestName) {};
124
+
125
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr)
126
+ {
127
+ constexpr std::array mergeTestShadersPaths = {
128
+ " app_resources/pplnLayoutMergeTest/shader_0.comp.hlsl" ,
129
+ " app_resources/pplnLayoutMergeTest/shader_1.comp.hlsl" ,
130
+ " app_resources/pplnLayoutMergeTest/shader_2.comp.hlsl" ,
131
+ " app_resources/pplnLayoutMergeTest/shader_3.comp.hlsl" ,
132
+ " app_resources/pplnLayoutMergeTest/shader_4.comp.hlsl" ,
133
+ " app_resources/pplnLayoutMergeTest/shader_5.comp.hlsl"
134
+ };
135
+ constexpr uint32_t MERGE_TEST_SHADERS_CNT = mergeTestShadersPaths.size ();
136
+
137
+ CSPIRVIntrospector introspector[MERGE_TEST_SHADERS_CNT];
138
+ smart_refctd_ptr<const CSPIRVIntrospector::CStageIntrospectionData> introspections[MERGE_TEST_SHADERS_CNT];
139
+
140
+ for (uint32_t i = 0u ; i < MERGE_TEST_SHADERS_CNT; ++i)
141
+ {
142
+ auto sourceIntrospectionPair = compileHLSLShaderAndTestIntrospection (physicalDevice, device, logger, assetMgr, mergeTestShadersPaths[i], introspector[i]);
143
+ introspections[i] = sourceIntrospectionPair.second ;
144
+ }
145
+
146
+ core::smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData> pplnIntroData;
147
+ pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
148
+
149
+ // should merge successfully since shader is not messed up and it is the first merge
150
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[0 ].get ()), true );
151
+ // should merge successfully since pipeline layout of "shader_1.comp.hlsl" is compatible with "shader_0.comp.hlsl"
152
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[1 ].get ()), true );
153
+ // should merge since pipeline layout of "shader_2.comp.hlsl" is not compatible with "shader_0.comp.hlsl"
154
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[2 ].get ()), true );
155
+
156
+ pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
157
+
158
+ // should not merge since run-time sized destriptor of "shader_3.comp.hlsl" is not last
159
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[3 ].get ()), false );
160
+
161
+ pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
162
+
163
+ // should merge successfully since shader is not messed up and it is the first merge
164
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[4 ].get ()), true );
165
+ // TODO: should merge successfully since shader 5 is compatible with shader 4, it is allowed for last binding in one shader to be run-time sized and statically sized in the other
166
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[5 ].get ()), true );
167
+ }
168
+ };
169
+
170
+ class PredefinedLayoutTester final : public IntrospectionTesterBase
171
+ {
172
+ public:
173
+ PredefinedLayoutTester (const std::string& functionToTestName)
174
+ : IntrospectionTesterBase(functionToTestName) {};
175
+
176
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr)
177
+ {
178
+ constexpr std::array mergeTestShadersPaths = {
179
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_0.comp.hlsl" ,
180
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_1.comp.hlsl" ,
181
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_2.comp.hlsl" ,
182
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_3.comp.hlsl" ,
183
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_4.comp.hlsl" ,
184
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_5.comp.hlsl"
185
+ };
186
+ constexpr uint32_t MERGE_TEST_SHADERS_CNT = mergeTestShadersPaths.size ();
187
+
188
+ CSPIRVIntrospector introspector[MERGE_TEST_SHADERS_CNT];
189
+ smart_refctd_ptr<ICPUShader> sources[MERGE_TEST_SHADERS_CNT];
190
+
191
+ for (uint32_t i = 0u ; i < MERGE_TEST_SHADERS_CNT; ++i)
192
+ {
193
+ auto sourceIntrospectionPair = compileHLSLShaderAndTestIntrospection (physicalDevice, device, logger, assetMgr, mergeTestShadersPaths[i], introspector[i]);
194
+ // TODO: disctinct functions for shader compilation and introspection
195
+ sources[i] = sourceIntrospectionPair.first ;
196
+ }
197
+
198
+ constexpr uint32_t BINDINGS_DS_0_CNT = 1u ;
199
+ const ICPUDescriptorSetLayout::SBinding bindingsDS0[BINDINGS_DS_0_CNT] = {
200
+ {
201
+ .binding = 0 ,
202
+ .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER,
203
+ .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE,
204
+ .stageFlags = ICPUShader::ESS_COMPUTE,
205
+ .count = 1 ,
206
+ .samplers = nullptr
207
+ }
208
+ };
209
+
210
+ constexpr uint32_t BINDINGS_DS_1_CNT = 2u ;
211
+ const ICPUDescriptorSetLayout::SBinding bindingsDS1[BINDINGS_DS_1_CNT] = {
212
+ {
213
+ .binding = 0 ,
214
+ .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER,
215
+ .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE,
216
+ .stageFlags = ICPUShader::ESS_COMPUTE,
217
+ .count = 1 ,
218
+ .samplers = nullptr
219
+ },
220
+ {
221
+ .binding = 1 ,
222
+ .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER,
223
+ .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE,
224
+ .stageFlags = ICPUShader::ESS_COMPUTE,
225
+ .count = 2 ,
226
+ .samplers = nullptr
227
+ }
228
+ };
229
+
230
+ core::smart_refctd_ptr<ICPUDescriptorSetLayout> dsLayout0 = core::make_smart_refctd_ptr<ICPUDescriptorSetLayout>(bindingsDS0, bindingsDS0 + BINDINGS_DS_0_CNT);
231
+ core::smart_refctd_ptr<ICPUDescriptorSetLayout> dsLayout1 = core::make_smart_refctd_ptr<ICPUDescriptorSetLayout>(bindingsDS1, bindingsDS1 + BINDINGS_DS_1_CNT);
232
+
233
+ if (!dsLayout0 || !dsLayout1)
234
+ {
235
+ logFail (logger, " Failed to create a Descriptor Layout!\n " );
236
+ return ;
237
+ }
238
+
239
+ SPushConstantRange pc;
240
+ pc.offset = 0u ;
241
+ pc.size = 5 * sizeof (uint32_t );
242
+ pc.stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE;
243
+
244
+ smart_refctd_ptr<ICPUPipelineLayout> predefinedPplnLayout = core::make_smart_refctd_ptr<ICPUPipelineLayout>(std::span<const asset::SPushConstantRange>({ pc }), std::move (dsLayout0), std::move (dsLayout1), nullptr , nullptr );
245
+ if (!predefinedPplnLayout)
246
+ {
247
+ logFail (logger, " Failed to create a Pipeline Layout!\n " );
248
+ return ;
249
+ }
250
+
251
+ bool pplnCreationSuccess[MERGE_TEST_SHADERS_CNT];
252
+ for (uint32_t i = 0u ; i < MERGE_TEST_SHADERS_CNT; ++i)
253
+ {
254
+ ICPUShader::SSpecInfo specInfo;
255
+ specInfo.entryPoint = " main" ;
256
+ specInfo.shader = sources[i].get ();
257
+ pplnCreationSuccess[i] = static_cast <bool >(introspector[i].createApproximateComputePipelineFromIntrospection (specInfo, core::smart_refctd_ptr<ICPUPipelineLayout>(predefinedPplnLayout)));
258
+ }
259
+
260
+ // DESCRIPTOR VALIDATION TESTS
261
+ // layout from introspection is a subset of pre-defined layout, hence ppln creation should SUCCEED
262
+ confirmExpectedOutput (logger, pplnCreationSuccess[0 ], true );
263
+ // layout from introspection is NOT a subset (too many bindings in descriptor set 0) of pre-defined layout, hence ppln creation should FAIL
264
+ confirmExpectedOutput (logger, pplnCreationSuccess[1 ], false );
265
+ // layout from introspection is NOT a subset (pre-defined layout doesn't have descriptor set 2) of pre-defined layout, hence ppln creation should FAIL
266
+ confirmExpectedOutput (logger, pplnCreationSuccess[2 ], false );
267
+ // layout from introspection is NOT a subset (same bindings, different type of one of the bindings) of pre-defined layout, hence ppln creation should FAIL
268
+ confirmExpectedOutput (logger, pplnCreationSuccess[3 ], false );
269
+
270
+ // PUSH CONSTANTS VALIDATION TESTS
271
+ // layout from introspection is a subset of pre-defined layout (Push constant size declared in shader are compatible), hence ppln creation should SUCCEED
272
+ confirmExpectedOutput (logger, pplnCreationSuccess[4 ], true );
273
+ // layout from introspection is NOT a subset of pre-defined layout (Push constant size declared in shader are NOT compatible), hence ppln creation should FAIL
274
+ confirmExpectedOutput (logger, pplnCreationSuccess[5 ], false );
275
+ }
276
+ };
277
+
278
+ class SandboxTester final : public IntrospectionTesterBase
279
+ {
280
+ public:
281
+ SandboxTester (const std::string& functionToTestName)
282
+ : IntrospectionTesterBase(functionToTestName) {};
283
+
284
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr)
285
+ {
286
+ CSPIRVIntrospector introspector;
287
+ auto sourceIntrospectionPair = compileHLSLShaderAndTestIntrospection (physicalDevice, device, logger, assetMgr, " app_resources/test.hlsl" , introspector);
288
+ auto pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
289
+ confirmExpectedOutput (logger, pplnIntroData->merge (sourceIntrospectionPair.second .get ()), true );
290
+
291
+ sourceIntrospectionPair.second ->debugPrint (logger);
292
+
293
+ // TODO
294
+ /* CSPIRVIntrospector introspector_test1;
295
+ auto vtx_test1 = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/vtx_test1.hlsl", introspector_test1);
296
+ auto test1_frag = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/frag_test1.hlsl", introspector_test1);
297
+
298
+ CSPIRVIntrospector introspector_test2;
299
+ auto test2_comp = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/comp_test2_nestedStructs.hlsl", introspector_test2);
300
+
301
+ CSPIRVIntrospector introspector_test3;
302
+ auto test3_comp = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/comp_test3_ArraysAndMatrices.hlsl", introspector_test3);
303
+
304
+ CSPIRVIntrospector introspector_test4;
305
+ auto test4_comp = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/frag_test4_SamplersTexBuffAndImgStorage.hlsl", introspector_test4);*/
306
+ }
307
+ };
308
+
309
+ #endif
0 commit comments