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