Skip to content

Commit dc3d31e

Browse files
committed
Added interface for tests
1 parent 22eb2e6 commit dc3d31e

File tree

3 files changed

+349
-183
lines changed

3 files changed

+349
-183
lines changed
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
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

03_DeviceSelectionAndSharedSources/app_resources/pplnLayoutMergeTest/shader_1.comp.hlsl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
[[vk::binding(4,2)]] RWStructuredBuffer<SomeType> asdf;
55
[[vk::binding(6,3)]] RWByteAddressBuffer outputBuff;
6+
[[vk::binding(2,0)]] RWByteAddressBuffer output2[];
67

78
[numthreads(WorkgroupSize, 1, 1)]
89
void main(uint32_t3 ID : SV_DispatchThreadID)
@@ -11,4 +12,5 @@ void main(uint32_t3 ID : SV_DispatchThreadID)
1112
const uint32_t byteOffset = sizeof(uint32_t)*index;
1213

1314
outputBuff.Store<uint32_t>(byteOffset, asdf[index].a);
15+
output2[0].Store<uint32_t>(byteOffset, asdf[index].a);
1416
}

0 commit comments

Comments
 (0)