1 /*------------------------------------------------------------------------
2  * Vulkan Conformance Tests
3  * ------------------------
4  *
5  * Copyright (c) 2020 The Khronos Group Inc.
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  *//*!
20  * \file
21  * \brief Test copying struct which contains an empty struct.
22           Test pointer comparisons of empty struct members.
23  *//*--------------------------------------------------------------------*/
24 
25 #include "vktSpvAsmComputeShaderTestUtil.hpp"
26 #include "vktSpvAsmComputeShaderCase.hpp"
27 #include "vktSpvAsmEmptyStructTests.hpp"
28 #include "tcuStringTemplate.hpp"
29 #include "vktTestGroupUtil.hpp"
30 #include "vktSpvAsmUtils.hpp"
31 
32 namespace vkt
33 {
34 namespace SpirVAssembly
35 {
36 namespace
37 {
38 
verifyResult(const std::vector<Resource> &,const std::vector<AllocationSp> & outputAllocs,const std::vector<Resource> & expectedOutputs,tcu::TestLog &)39 bool verifyResult(const std::vector<Resource> &, const std::vector<AllocationSp> &outputAllocs,
40                   const std::vector<Resource> &expectedOutputs, tcu::TestLog &)
41 {
42     for (uint32_t outputNdx = 0; outputNdx < static_cast<uint32_t>(outputAllocs.size()); ++outputNdx)
43     {
44         std::vector<uint8_t> expectedBytes;
45         expectedOutputs[outputNdx].getBytes(expectedBytes);
46 
47         const uint32_t itemCount = static_cast<uint32_t>(expectedBytes.size()) / 4u;
48         const uint32_t *returned = static_cast<const uint32_t *>(outputAllocs[outputNdx]->getHostPtr());
49         const uint32_t *expected = reinterpret_cast<const uint32_t *>(&expectedBytes.front());
50 
51         for (uint32_t i = 0; i < itemCount; ++i)
52         {
53             // skip items with 0 as this is used to mark empty structure
54             if (expected[i] == 0)
55                 continue;
56             if (expected[i] != returned[i])
57                 return false;
58         }
59     }
60     return true;
61 }
62 
addCopyingComputeGroup(tcu::TestCaseGroup * group)63 void addCopyingComputeGroup(tcu::TestCaseGroup *group)
64 {
65     const tcu::StringTemplate shaderTemplate(
66         "OpCapability Shader\n"
67 
68         "OpExtension \"SPV_KHR_storage_buffer_storage_class\"\n"
69 
70         "OpMemoryModel Logical GLSL450\n"
71         "OpEntryPoint GLCompute %main \"main\" %var_id\n"
72         "OpExecutionMode %main LocalSize 1 1 1\n"
73 
74         "OpDecorate %var_id BuiltIn GlobalInvocationId\n"
75         "OpDecorate %var_input Binding 0\n"
76         "OpDecorate %var_input DescriptorSet 0\n"
77 
78         "OpDecorate %var_outdata Binding 1\n"
79         "OpDecorate %var_outdata DescriptorSet 0\n"
80 
81         "OpMemberDecorate %type_container_struct 0 Offset 0\n"
82         "OpMemberDecorate %type_container_struct 1 Offset ${OFFSET_1}\n"
83         "OpMemberDecorate %type_container_struct 2 Offset ${OFFSET_2}\n"
84         "OpMemberDecorate %type_container_struct 3 Offset ${OFFSET_3}\n"
85         "OpDecorate %type_container_struct Block\n"
86 
87         + std::string(getComputeAsmCommonTypes()) +
88 
89         //struct EmptyStruct {};
90         //struct ContainerStruct {
91         //  int i;
92         //  A a1;
93         //  A a2;
94         //  int j;
95         //};
96         //layout(set=, binding = ) buffer block B b;
97 
98         // types
99         "%type_empty_struct = OpTypeStruct\n"
100         "%type_container_struct = OpTypeStruct %i32 %type_empty_struct %type_empty_struct %i32\n"
101 
102         "%type_container_struct_ubo_ptr = OpTypePointer Uniform %type_container_struct\n"
103         "%type_container_struct_ssbo_ptr = OpTypePointer StorageBuffer %type_container_struct\n"
104 
105         // variables
106         "%var_id = OpVariable %uvec3ptr Input\n"
107         "${VARIABLES}\n"
108 
109         // void main function
110         "%main = OpFunction %void None %voidf\n"
111         "%label = OpLabel\n"
112 
113         "${COPYING_METHOD}"
114 
115         "OpReturn\n"
116         "OpFunctionEnd\n");
117 
118     struct BufferType
119     {
120         std::string name;
121         VkDescriptorType descriptorType;
122         std::vector<uint32_t> offsets;
123         std::vector<int> input;
124         std::vector<int> expectedOutput;
125         std::string spirvVariables;
126         std::string spirvCopyObject;
127 
128         BufferType(const std::string &name_, VkDescriptorType descriptorType_, const std::vector<uint32_t> &offsets_,
129                    const std::vector<int> &input_, const std::vector<int> &expectedOutput_,
130                    const std::string &spirvVariables_, const std::string &spirvCopyObject_)
131             : name(name_)
132             , descriptorType(descriptorType_)
133             , offsets(offsets_)
134             , input(input_)
135             , expectedOutput(expectedOutput_)
136             , spirvVariables(spirvVariables_)
137             , spirvCopyObject(spirvCopyObject_)
138         {
139         }
140     };
141     const std::vector<BufferType> bufferTypes{
142         {"ubo",
143          VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
144 
145          // structure decorated as Block for variable in Uniform storage class
146          // must follow relaxed uniform buffer layout rules and be aligned to 16
147          {0, 16, 32, 48},
148          {2, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0},
149          {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0},
150 
151          "%var_input = OpVariable %type_container_struct_ubo_ptr Uniform\n"
152          "%var_outdata = OpVariable %type_container_struct_ssbo_ptr StorageBuffer\n",
153 
154          "%input_copy = OpCopyObject %type_container_struct_ubo_ptr %var_input\n"},
155         {"ssbo",
156          VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
157 
158          {0, 4, 8, 12},
159          {2, 3, 5, 7},
160          {2, 0, 0, 7},
161 
162          "%var_input = OpVariable %type_container_struct_ssbo_ptr StorageBuffer\n"
163          "%var_outdata = OpVariable %type_container_struct_ssbo_ptr StorageBuffer\n",
164 
165          "%input_copy = OpCopyObject %type_container_struct_ssbo_ptr %var_input\n"}};
166 
167     struct CopyingMethod
168     {
169         std::string name;
170         std::string spirvCopyCode;
171 
172         CopyingMethod(const std::string &name_, const std::string &spirvCopyCode_)
173             : name(name_)
174             , spirvCopyCode(spirvCopyCode_)
175         {
176         }
177     };
178     const std::vector<CopyingMethod> copyingMethods{{"copy_object",
179 
180                                                      "%result = OpLoad %type_container_struct %input_copy\n"
181                                                      "OpStore %var_outdata %result\n"},
182                                                     {"copy_memory",
183 
184                                                      "OpCopyMemory %var_outdata %var_input\n"}};
185 
186     for (const auto &bufferType : bufferTypes)
187     {
188         for (const auto &copyingMethod : copyingMethods)
189         {
190             std::string name = copyingMethod.name + "_" + bufferType.name;
191 
192             std::map<std::string, std::string> specializationMap{
193                 {"OFFSET_1", de::toString(bufferType.offsets[1])},
194                 {"OFFSET_2", de::toString(bufferType.offsets[2])},
195                 {"OFFSET_3", de::toString(bufferType.offsets[3])},
196                 {"VARIABLES", bufferType.spirvVariables},
197 
198                 // NOTE: to simlify code spirvCopyObject is added also when OpCopyMemory is used
199                 {"COPYING_METHOD", bufferType.spirvCopyObject + copyingMethod.spirvCopyCode},
200             };
201 
202             ComputeShaderSpec spec;
203             spec.assembly      = shaderTemplate.specialize(specializationMap);
204             spec.numWorkGroups = tcu::IVec3(1, 1, 1);
205             spec.verifyIO      = verifyResult;
206             spec.inputs.push_back(Resource(BufferSp(new Int32Buffer(bufferType.input)), bufferType.descriptorType));
207             spec.outputs.push_back(Resource(BufferSp(new Int32Buffer(bufferType.expectedOutput))));
208             group->addChild(new SpvAsmComputeShaderCase(group->getTestContext(), name.c_str(), spec));
209         }
210     }
211 }
212 
addPointerComparisionComputeGroup(tcu::TestCaseGroup * group)213 void addPointerComparisionComputeGroup(tcu::TestCaseGroup *group)
214 {
215     // NOTE: pointer comparison is possible only for StorageBuffer storage class
216 
217     std::string computeSource = "OpCapability Shader\n"
218                                 "OpCapability VariablePointersStorageBuffer\n"
219 
220                                 "OpMemoryModel Logical GLSL450\n"
221                                 "OpEntryPoint GLCompute %main \"main\" %var_id %var_input %var_outdata\n"
222                                 "OpExecutionMode %main LocalSize 1 1 1\n"
223 
224                                 "OpDecorate %var_id BuiltIn GlobalInvocationId\n"
225                                 "OpDecorate %var_input Binding 0\n"
226                                 "OpDecorate %var_input DescriptorSet 0\n"
227 
228                                 "OpDecorate %var_outdata Binding 1\n"
229                                 "OpDecorate %var_outdata DescriptorSet 0\n"
230 
231                                 "OpMemberDecorate %type_container_struct 0 Offset 0\n"
232                                 "OpMemberDecorate %type_container_struct 1 Offset 4\n"
233                                 "OpMemberDecorate %type_container_struct 2 Offset 8\n"
234                                 "OpMemberDecorate %type_container_struct 3 Offset 12\n"
235                                 "OpDecorate %type_container_struct Block\n"
236 
237                                 "OpMemberDecorate %type_i32_struct 0 Offset 0\n"
238                                 "OpDecorate %type_i32_struct Block\n"
239 
240                                 + std::string(getComputeAsmCommonTypes("StorageBuffer")) +
241 
242                                 // struct EmptyStruct {};
243                                 // struct ContainerStruct {
244                                 //  int i;
245                                 //  A a1;
246                                 //  A a2;
247                                 //  int j;
248                                 //};
249                                 // layout(set=, binding = ) buffer block B b;
250 
251                                 // types
252                                 "%type_empty_struct = "
253                                 "OpTypeStruct\n"
254                                 "%type_container_struct = OpTypeStruct %i32 "
255                                 "%type_empty_struct %type_empty_struct %i32\n"
256                                 "%type_i32_struct = OpTypeStruct %i32\n"
257 
258                                 // constants
259                                 "%c_i32_0 = OpConstant "
260                                 "%i32 0\n"
261                                 "%c_i32_1 = OpConstant "
262                                 "%i32 1\n"
263                                 "%c_i32_2 = OpConstant "
264                                 "%i32 2\n"
265 
266                                 "%type_container_struct_in_ptr = OpTypePointer StorageBuffer "
267                                 "%type_container_struct\n"
268                                 "%type_i32_struct_out_ptr = OpTypePointer StorageBuffer "
269                                 "%type_i32_struct\n"
270 
271                                 "%type_func_struct_ptr_ptr = OpTypePointer "
272                                 "StorageBuffer %type_empty_struct\n"
273 
274                                 // variables
275                                 "%var_id = OpVariable "
276                                 "%uvec3ptr Input\n"
277                                 "%var_input = "
278                                 "OpVariable %type_container_struct_in_ptr StorageBuffer\n"
279                                 "%var_outdata = OpVariable "
280                                 "%type_i32_struct_out_ptr StorageBuffer\n"
281 
282                                 // void main function
283                                 "%main = "
284                                 "OpFunction %void None %voidf\n"
285                                 "%label = "
286                                 "OpLabel\n"
287 
288                                 // compare pointers to empty structures
289                                 "%ptr_to_first = "
290                                 "OpAccessChain %type_func_struct_ptr_ptr %var_input %c_i32_1\n"
291                                 "%ptr_to_second = "
292                                 "OpAccessChain %type_func_struct_ptr_ptr %var_input %c_i32_2\n"
293                                 "%pointers_not_equal = OpPtrNotEqual %bool "
294                                 "%ptr_to_first %ptr_to_second\n"
295                                 "%result = OpSelect "
296                                 "%i32 %pointers_not_equal %c_i32_1 %c_i32_0\n"
297                                 "%outloc = "
298                                 "OpAccessChain %i32ptr %var_outdata %c_i32_0\n"
299                                 "OpStore %outloc %result\n"
300 
301                                 "OpReturn\n"
302                                 "OpFunctionEnd\n";
303 
304     tcu::TestContext &testCtx       = group->getTestContext();
305     std::vector<int> input          = {2, 3, 5, 7};
306     std::vector<int> expectedOutput = {1};
307 
308     ComputeShaderSpec spec;
309     spec.assembly                                                                  = computeSource;
310     spec.numWorkGroups                                                             = tcu::IVec3(1, 1, 1);
311     spec.spirvVersion                                                              = SPIRV_VERSION_1_4;
312     spec.requestedVulkanFeatures.extVariablePointers.variablePointersStorageBuffer = true;
313     spec.inputs.push_back(Resource(BufferSp(new Int32Buffer(input))));
314     spec.outputs.push_back(Resource(BufferSp(new Int32Buffer(expectedOutput))));
315     spec.extensions.push_back("VK_KHR_spirv_1_4");
316     group->addChild(new SpvAsmComputeShaderCase(testCtx, "ssbo", spec));
317 }
318 
addFunctionArgumentReturnValueGroup(tcu::TestCaseGroup * group)319 void addFunctionArgumentReturnValueGroup(tcu::TestCaseGroup *group)
320 {
321     const tcu::StringTemplate shaderTemplate(
322         "      OpCapability Shader\n"
323         " %1 = OpExtInstImport \"GLSL.std.450\"\n"
324         "      OpMemoryModel Logical GLSL450\n"
325         "      OpEntryPoint GLCompute %4 \"main\" %29 %42 %51 ${GLOBAL_VARIABLE} %79\n"
326         "      OpExecutionMode %4 LocalSize 2 1 1\n"
327         "      OpSource GLSL 460\n"
328         "      OpDecorate %29 BuiltIn LocalInvocationId\n"
329         "      OpMemberDecorate %40 0 Offset 0\n"
330         "      OpMemberDecorate %40 1 Offset 4\n"
331         "      OpMemberDecorate %40 2 Offset 8\n"
332         "      OpDecorate %40 Block\n"
333         "      OpDecorate %42 DescriptorSet 0\n"
334         "      OpDecorate %42 Binding 1\n"
335         "      OpMemberDecorate %49 0 Offset 0\n"
336         "      OpDecorate %49 Block\n"
337         "      OpDecorate %51 DescriptorSet 0\n"
338         "      OpDecorate %51 Binding 0\n"
339         "      OpMemberDecorate %77 0 Offset 0\n"
340         "      OpMemberDecorate %77 1 Offset 4\n"
341         "      OpMemberDecorate %77 2 Offset 8\n"
342         "      OpDecorate %77 Block\n"
343         "      OpDecorate %79 DescriptorSet 0\n"
344         "      OpDecorate %79 Binding 2\n"
345         "      OpDecorate %96 BuiltIn WorkgroupSize\n"
346         " %2 = OpTypeVoid\n"
347         " %3 = OpTypeFunction %2\n"
348         " %7 = OpTypeStruct\n"
349         " %8 = OpTypePointer Function %7\n"
350         " %9 = OpTypeBool\n"
351         "%10 = OpTypePointer Function %9\n"
352         "%11 = OpTypeFunction %7 %8 %8 %10\n"
353         "%26 = OpTypeInt 32 0\n"
354         "%27 = OpTypeVector %26 3\n"
355         "%28 = OpTypePointer Input %27\n"
356         "%29 = OpVariable %28 Input\n"
357         "%30 = OpConstant %26 0\n"
358         "%31 = OpTypePointer Input %26\n"
359         "%34 = OpConstant %26 2\n"
360         "%39 = OpTypeStruct\n"
361         "%40 = OpTypeStruct %26 %39 %26\n"
362         "%41 = OpTypePointer StorageBuffer %40\n"
363         "%42 = OpVariable %41 StorageBuffer\n"
364         "%43 = OpTypeInt 32 1\n"
365         "%44 = OpConstant %43 0\n"
366         "%45 = OpConstant %26 1\n"
367         "%46 = OpTypePointer StorageBuffer %26\n"
368         "%48 = OpConstant %43 1\n"
369         "%49 = OpTypeStruct %39\n"
370         "%50 = OpTypePointer StorageBuffer %49\n"
371         "%51 = OpVariable %50 StorageBuffer\n"
372 
373         "${VARIABLE_DEFINITION}\n"
374 
375         "%59 = OpTypePointer StorageBuffer %39\n"
376         "%69 = OpConstant %43 2\n"
377         "%77 = OpTypeStruct %26 %39 %26\n"
378         "%78 = OpTypePointer StorageBuffer %77\n"
379         "%79 = OpVariable %78 StorageBuffer\n"
380         "%96 = OpConstantComposite %27 %34 %45 %45\n"
381         " %4 = OpFunction %2 None %3\n"
382         " %5 = OpLabel\n"
383 
384         "${VARIABLE_FUNCTION_DEFINITION}\n"
385 
386         "%58 = OpVariable %8 Function\n"
387         "%63 = OpVariable %8 Function\n"
388         "%65 = OpVariable %10 Function\n"
389         "%85 = OpVariable %8 Function\n"
390         "%89 = OpVariable %8 Function\n"
391         "%91 = OpVariable %10 Function\n"
392         "%32 = OpAccessChain %31 %29 %30\n"
393         "%33 = OpLoad %26 %32\n"
394         "%35 = OpUMod %26 %33 %34\n"
395         "%36 = OpIEqual %9 %35 %30\n"
396         "      OpSelectionMerge %38 None\n"
397         "      OpBranchConditional %36 %37 %38\n"
398         "%37 = OpLabel\n"
399         "%47 = OpAccessChain %46 %42 %44\n"
400         "      OpStore %47 %45\n"
401         "%54 = OpAccessChain %31 %29 %30\n"
402         "%55 = OpLoad %26 %54\n"
403         "%56 = OpUMod %26 %55 %34\n"
404         "%57 = OpIEqual %9 %56 %30\n"
405         "%60 = OpAccessChain %59 %51 %44\n"
406         "%61 = OpLoad %39 %60\n"
407         "%62 = OpCopyLogical %7 %61\n"
408         "      OpStore %58 %62\n"
409         "%64 = OpLoad %7 %53\n"
410         "      OpStore %63 %64\n"
411         "      OpStore %65 %57\n"
412         "%66 = OpFunctionCall %7 %15 %58 %63 %65\n"
413         "%67 = OpAccessChain %59 %42 %48\n"
414         "%68 = OpCopyLogical %39 %66\n"
415         "      OpStore %67 %68\n"
416         "%70 = OpAccessChain %46 %42 %69\n"
417         "      OpStore %70 %45\n"
418         "      OpBranch %38\n"
419         "%38 = OpLabel\n"
420         "%71 = OpAccessChain %31 %29 %30\n"
421         "%72 = OpLoad %26 %71\n"
422         "%73 = OpUMod %26 %72 %34\n"
423         "%74 = OpIEqual %9 %73 %45\n"
424         "      OpSelectionMerge %76 None\n"
425         "      OpBranchConditional %74 %75 %76\n"
426         "%75 = OpLabel\n"
427         "%80 = OpAccessChain %46 %79 %44\n"
428         "      OpStore %80 %45\n"
429         "%81 = OpAccessChain %31 %29 %30\n"
430         "%82 = OpLoad %26 %81\n"
431         "%83 = OpUMod %26 %82 %34\n"
432         "%84 = OpIEqual %9 %83 %45\n"
433         "%86 = OpAccessChain %59 %51 %44\n"
434         "%87 = OpLoad %39 %86\n"
435         "%88 = OpCopyLogical %7 %87\n"
436         "      OpStore %85 %88\n"
437         "%90 = OpLoad %7 %53\n"
438         "      OpStore %89 %90\n"
439         "      OpStore %91 %84\n"
440         "%92 = OpFunctionCall %7 %15 %85 %89 %91\n"
441         "%93 = OpAccessChain %59 %79 %48\n"
442         "%94 = OpCopyLogical %39 %92\n"
443         "      OpStore %93 %94\n"
444         "%95 = OpAccessChain %46 %79 %69\n"
445         "      OpStore %95 %45\n"
446         "      OpBranch %76\n"
447         "%76 = OpLabel\n"
448         "      OpReturn\n"
449         "      OpFunctionEnd\n"
450         "%15 = OpFunction %7 None %11\n"
451         "%12 = OpFunctionParameter %8\n"
452         "%13 = OpFunctionParameter %8\n"
453         "%14 = OpFunctionParameter %10\n"
454         "%16 = OpLabel\n"
455         "%17 = OpLoad %9 %14\n"
456         "      OpSelectionMerge %19 None\n"
457         "      OpBranchConditional %17 %18 %22\n"
458         "%18 = OpLabel\n"
459         "%20 = OpLoad %7 %12\n"
460         "      OpReturnValue %20\n"
461         "%22 = OpLabel\n"
462         "%23 = OpLoad %7 %13\n"
463         "      OpReturnValue %23\n"
464         "%19 = OpLabel\n"
465         "      OpUnreachable\n"
466         "      OpFunctionEnd\n");
467 
468     struct VariableDefinition
469     {
470         std::string name;
471         std::string globalVariable;
472         std::string spirvVariableDefinitionCode;
473         std::string spirvVariableFunctionDefinitionCode;
474     };
475 
476     std::vector<VariableDefinition> variableDefinitions{
477         {"global_variable_private",
478 
479          "%53",
480 
481          "%52 = OpTypePointer Private %7\n"
482          "%53 = OpVariable %52 Private\n",
483 
484          ""},
485 
486         {"global_variable_shared",
487 
488          "%53",
489 
490          "%52 = OpTypePointer Workgroup %7\n"
491          "%53 = OpVariable %52 Workgroup\n",
492 
493          ""},
494 
495         {"local_variable",
496 
497          "",
498 
499          "",
500 
501          "%53 = OpVariable %8 Function\n"},
502     };
503 
504     tcu::TestContext &testCtx = group->getTestContext();
505     std::vector<int> input    = {2};
506 
507     std::vector<uint32_t> expectedOutput = {1, 0xffffffff, 1};
508 
509     for (const auto &variableDefinition : variableDefinitions)
510     {
511         std::map<std::string, std::string> specializationMap{
512             {"GLOBAL_VARIABLE", variableDefinition.globalVariable},
513             {"VARIABLE_DEFINITION", variableDefinition.spirvVariableDefinitionCode},
514             {"VARIABLE_FUNCTION_DEFINITION", variableDefinition.spirvVariableFunctionDefinitionCode},
515         };
516 
517         ComputeShaderSpec spec;
518         spec.assembly      = shaderTemplate.specialize(specializationMap);
519         spec.numWorkGroups = tcu::IVec3(2, 1, 1);
520         spec.spirvVersion  = SPIRV_VERSION_1_4;
521         spec.requestedVulkanFeatures.extVariablePointers.variablePointersStorageBuffer = true;
522         spec.inputs.push_back(Resource(BufferSp(new Int32Buffer(input))));
523         spec.outputs.push_back(Resource(BufferSp(new Uint32Buffer(expectedOutput))));
524         spec.outputs.push_back(Resource(BufferSp(new Uint32Buffer(expectedOutput))));
525         spec.extensions.push_back("VK_KHR_spirv_1_4");
526         group->addChild(new SpvAsmComputeShaderCase(testCtx, variableDefinition.name.c_str(), spec));
527     }
528 }
529 
530 } // namespace
531 
createEmptyStructComputeGroup(tcu::TestContext & testCtx)532 tcu::TestCaseGroup *createEmptyStructComputeGroup(tcu::TestContext &testCtx)
533 {
534     // Tests empty structs in UBOs and SSBOs
535     de::MovePtr<tcu::TestCaseGroup> group(new tcu::TestCaseGroup(testCtx, "empty_struct"));
536 
537     // Test copying struct which contains an empty struct
538     addTestGroup(group.get(), "copying", addCopyingComputeGroup);
539     // Test pointer comparisons of empty struct members
540     addTestGroup(group.get(), "pointer_comparison", addPointerComparisionComputeGroup);
541     // Test empty structs as function arguments or return type
542     addTestGroup(group.get(), "function", addFunctionArgumentReturnValueGroup);
543 
544     return group.release();
545 }
546 
547 } // namespace SpirVAssembly
548 } // namespace vkt
549