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
+ // requires two compute shaders for simplicity of the example
119
+ // core::smart_refctd_ptr<ICPUComputePipeline> createComputePipelinesWithCompatibleLayout(std::span<core::smart_refctd_ptr<ICPUShader>> spirvShaders)
120
+ // {
121
+ // CSPIRVIntrospector introspector;
122
+ // auto pplnIntroData = CSPIRVIntrospector::CPipelineIntrospectionData();
123
+
124
+ // for (auto it = spirvShaders.begin(); it != spirvShaders.end(); ++it)
125
+ // {
126
+ // CSPIRVIntrospector::CStageIntrospectionData::SParams params;
127
+ // params.entryPoint = "main"; //whatever
128
+ // params.shader = *it;
129
+
130
+ // auto stageIntroData = introspector.introspect(params);
131
+ // const bool hasMerged = pplnIntroData.merge(stageIntroData.get());
132
+
133
+ // if (!hasMerged)
134
+ // {
135
+ // m_logger->log("Unable to create a compatible layout.", ILogger::ELL_PERFORMANCE);
136
+ // return nullptr;
137
+ // }
138
+ // }
139
+
140
+ // // TODO: create ppln from `pplnIntroData`..
141
+ // //return core::make_smart_refctd_ptr<ICPUComputePipeline>();
142
+ // return nullptr;
143
+ // }
144
+
145
+ };
146
+
147
+ class MergeTester final : public IntrospectionTesterBase
148
+ {
149
+ public:
150
+ MergeTester (const std::string& functionToTestName)
151
+ : IntrospectionTesterBase(functionToTestName) {};
152
+
153
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr)
154
+ {
155
+ constexpr std::array mergeTestShadersPaths = {
156
+ " app_resources/pplnLayoutMergeTest/shader_0.comp.hlsl" ,
157
+ " app_resources/pplnLayoutMergeTest/shader_1.comp.hlsl" ,
158
+ " app_resources/pplnLayoutMergeTest/shader_2.comp.hlsl" ,
159
+ " app_resources/pplnLayoutMergeTest/shader_3.comp.hlsl" ,
160
+ " app_resources/pplnLayoutMergeTest/shader_4.comp.hlsl" ,
161
+ " app_resources/pplnLayoutMergeTest/shader_5.comp.hlsl"
162
+ };
163
+ constexpr uint32_t MERGE_TEST_SHADERS_CNT = mergeTestShadersPaths.size ();
164
+
165
+ CSPIRVIntrospector introspector[MERGE_TEST_SHADERS_CNT];
166
+ smart_refctd_ptr<const CSPIRVIntrospector::CStageIntrospectionData> introspections[MERGE_TEST_SHADERS_CNT];
167
+
168
+ for (uint32_t i = 0u ; i < MERGE_TEST_SHADERS_CNT; ++i)
169
+ {
170
+ auto sourceIntrospectionPair = compileHLSLShaderAndTestIntrospection (physicalDevice, device, logger, assetMgr, mergeTestShadersPaths[i], introspector[i]);
171
+ introspections[i] = sourceIntrospectionPair.second ;
172
+ }
173
+
174
+ core::smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData> pplnIntroData;
175
+ pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
176
+
177
+ // should merge successfully since shader is not messed up and it is the first merge
178
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[0 ].get ()), true );
179
+ // should merge successfully since pipeline layout of "shader_1.comp.hlsl" is compatible with "shader_0.comp.hlsl"
180
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[1 ].get ()), true );
181
+ // should not merge since pipeline layout of "shader_2.comp.hlsl" is not compatible with "shader_0.comp.hlsl"
182
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[2 ].get ()), false );
183
+
184
+ pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
185
+
186
+ // should not merge since run-time sized destriptor of "shader_3.comp.hlsl" is not last
187
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[3 ].get ()), false );
188
+
189
+ pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
190
+
191
+ // should merge successfully since shader is not messed up and it is the first merge
192
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[4 ].get ()), true );
193
+ // TODO: idk what should happen when last binding in introspection is fixed-length sized array and last binding in introspection to merge is run-time sized array
194
+ confirmExpectedOutput (logger, pplnIntroData->merge (introspections[5 ].get ()), false );
195
+ }
196
+ };
197
+
198
+ class PredefinedLayoutTester final : public IntrospectionTesterBase
199
+ {
200
+ public:
201
+ PredefinedLayoutTester (const std::string& functionToTestName)
202
+ : IntrospectionTesterBase(functionToTestName) {};
203
+
204
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr)
205
+ {
206
+ constexpr std::array mergeTestShadersPaths = {
207
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_0.comp.hlsl" ,
208
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_1.comp.hlsl" ,
209
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_2.comp.hlsl" ,
210
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_3.comp.hlsl" ,
211
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_4.comp.hlsl" ,
212
+ " app_resources/pplnLayoutCreationWithPredefinedLayoutTest/shader_5.comp.hlsl"
213
+ };
214
+ constexpr uint32_t MERGE_TEST_SHADERS_CNT = mergeTestShadersPaths.size ();
215
+
216
+ CSPIRVIntrospector introspector[MERGE_TEST_SHADERS_CNT];
217
+ smart_refctd_ptr<ICPUShader> sources[MERGE_TEST_SHADERS_CNT];
218
+
219
+ for (uint32_t i = 0u ; i < MERGE_TEST_SHADERS_CNT; ++i)
220
+ {
221
+ auto sourceIntrospectionPair = compileHLSLShaderAndTestIntrospection (physicalDevice, device, logger, assetMgr, mergeTestShadersPaths[i], introspector[i]);
222
+ // TODO: disctinct functions for shader compilation and introspection
223
+ sources[i] = sourceIntrospectionPair.first ;
224
+ }
225
+
226
+ constexpr uint32_t BINDINGS_DS_0_CNT = 1u ;
227
+ const ICPUDescriptorSetLayout::SBinding bindingsDS0[BINDINGS_DS_0_CNT] = {
228
+ {
229
+ .binding = 0 ,
230
+ .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER,
231
+ .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE,
232
+ .stageFlags = ICPUShader::ESS_COMPUTE,
233
+ .count = 1 ,
234
+ .samplers = nullptr
235
+ }
236
+ };
237
+
238
+ constexpr uint32_t BINDINGS_DS_1_CNT = 2u ;
239
+ const ICPUDescriptorSetLayout::SBinding bindingsDS1[BINDINGS_DS_1_CNT] = {
240
+ {
241
+ .binding = 0 ,
242
+ .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER,
243
+ .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE,
244
+ .stageFlags = ICPUShader::ESS_COMPUTE,
245
+ .count = 1 ,
246
+ .samplers = nullptr
247
+ },
248
+ {
249
+ .binding = 1 ,
250
+ .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER,
251
+ .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE,
252
+ .stageFlags = ICPUShader::ESS_COMPUTE,
253
+ .count = 2 ,
254
+ .samplers = nullptr
255
+ }
256
+ };
257
+
258
+ core::smart_refctd_ptr<ICPUDescriptorSetLayout> dsLayout0 = core::make_smart_refctd_ptr<ICPUDescriptorSetLayout>(bindingsDS0, bindingsDS0 + BINDINGS_DS_0_CNT);
259
+ core::smart_refctd_ptr<ICPUDescriptorSetLayout> dsLayout1 = core::make_smart_refctd_ptr<ICPUDescriptorSetLayout>(bindingsDS1, bindingsDS1 + BINDINGS_DS_1_CNT);
260
+
261
+ if (!dsLayout0 || !dsLayout1)
262
+ {
263
+ logFail (logger, " Failed to create a Descriptor Layout!\n " );
264
+ return ;
265
+ }
266
+
267
+ SPushConstantRange pc;
268
+ pc.offset = 0u ;
269
+ pc.size = 5 * sizeof (uint32_t );
270
+ pc.stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE;
271
+
272
+ 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 );
273
+ if (!predefinedPplnLayout)
274
+ {
275
+ logFail (logger, " Failed to create a Pipeline Layout!\n " );
276
+ return ;
277
+ }
278
+
279
+ bool pplnCreationSuccess[MERGE_TEST_SHADERS_CNT];
280
+ for (uint32_t i = 0u ; i < MERGE_TEST_SHADERS_CNT; ++i)
281
+ {
282
+ ICPUShader::SSpecInfo specInfo;
283
+ specInfo.entryPoint = " main" ;
284
+ specInfo.shader = sources[i].get ();
285
+ pplnCreationSuccess[i] = static_cast <bool >(introspector[i].createApproximateComputePipelineFromIntrospection (specInfo, core::smart_refctd_ptr<ICPUPipelineLayout>(predefinedPplnLayout)));
286
+ }
287
+
288
+ // DESCRIPTOR VALIDATION TESTS
289
+ // layout from introspection is a subset of pre-defined layout, hence ppln creation should SUCCEED
290
+ confirmExpectedOutput (logger, pplnCreationSuccess[0 ], true );
291
+ // layout from introspection is NOT a subset (too many bindings in descriptor set 0) of pre-defined layout, hence ppln creation should FAIL
292
+ confirmExpectedOutput (logger, pplnCreationSuccess[1 ], false );
293
+ // 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
294
+ confirmExpectedOutput (logger, pplnCreationSuccess[2 ], false );
295
+ // 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
296
+ confirmExpectedOutput (logger, pplnCreationSuccess[3 ], false );
297
+
298
+ // PUSH CONSTANTS VALIDATION TESTS
299
+ // layout from introspection is a subset of pre-defined layout (Push constant size declared in shader are compatible), hence ppln creation should SUCCEED
300
+ confirmExpectedOutput (logger, pplnCreationSuccess[4 ], true );
301
+ // 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
302
+ confirmExpectedOutput (logger, pplnCreationSuccess[5 ], false );
303
+ }
304
+ };
305
+
306
+ class SandboxTester final : public IntrospectionTesterBase
307
+ {
308
+ public:
309
+ SandboxTester (const std::string& functionToTestName)
310
+ : IntrospectionTesterBase(functionToTestName) {};
311
+
312
+ void virtual performTests (video::IPhysicalDevice* physicalDevice, video::ILogicalDevice* device, system::ILogger* logger, asset::IAssetManager* assetMgr)
313
+ {
314
+ CSPIRVIntrospector introspector;
315
+ auto sourceIntrospectionPair = compileHLSLShaderAndTestIntrospection (physicalDevice, device, logger, assetMgr, " app_resources/test.hlsl" , introspector);
316
+ auto pplnIntroData = core::make_smart_refctd_ptr<CSPIRVIntrospector::CPipelineIntrospectionData>();
317
+ confirmExpectedOutput (logger, pplnIntroData->merge (sourceIntrospectionPair.second .get ()), true );
318
+
319
+ // TODO
320
+ /* CSPIRVIntrospector introspector_test1;
321
+ auto vtx_test1 = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/vtx_test1.hlsl", introspector_test1);
322
+ auto test1_frag = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/frag_test1.hlsl", introspector_test1);
323
+
324
+ CSPIRVIntrospector introspector_test2;
325
+ auto test2_comp = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/comp_test2_nestedStructs.hlsl", introspector_test2);
326
+
327
+ CSPIRVIntrospector introspector_test3;
328
+ auto test3_comp = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/comp_test3_ArraysAndMatrices.hlsl", introspector_test3);
329
+
330
+ CSPIRVIntrospector introspector_test4;
331
+ auto test4_comp = compileHLSLShaderAndTestIntrospection(physicalDevice, device, logger, assetMgr, "app_resources/frag_test4_SamplersTexBuffAndImgStorage.hlsl", introspector_test4);*/
332
+ }
333
+ };
334
+
335
+ #endif
0 commit comments