1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
5 * Copyright (c) 2015 The Khronos Group Inc.
6 * Copyright (c) 2017 Google Inc.
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
22 * \brief Atomic operations (OpAtomic*) tests.
23 *//*--------------------------------------------------------------------*/
25 #include "vktAtomicOperationTests.hpp"
26 #include "vktShaderExecutor.hpp"
28 #include "vkRefUtil.hpp"
29 #include "vkMemUtil.hpp"
30 #include "vkQueryUtil.hpp"
31 #include "vktTestGroupUtil.hpp"
33 #include "tcuTestLog.hpp"
34 #include "tcuStringTemplate.hpp"
35 #include "tcuResultCollector.hpp"
37 #include "deStringUtil.hpp"
38 #include "deSharedPtr.hpp"
39 #include "deRandom.hpp"
40 #include "deArrayUtil.hpp"
46 namespace shaderexecutor
62 Buffer (Context& context, VkBufferUsageFlags usage, size_t size);
64 VkBuffer getBuffer (void) const { return *m_buffer; }
65 void* getHostPtr (void) const { return m_allocation->getHostPtr(); }
67 void invalidate (void);
70 const DeviceInterface& m_vkd;
71 const VkDevice m_device;
72 const Unique<VkBuffer> m_buffer;
73 const UniquePtr<Allocation> m_allocation;
76 typedef de::SharedPtr<Buffer> BufferSp;
78 Move<VkBuffer> createBuffer (const DeviceInterface& vkd, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usageFlags)
80 const VkBufferCreateInfo createInfo =
82 VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
84 (VkBufferCreateFlags)0,
87 VK_SHARING_MODE_EXCLUSIVE,
91 return createBuffer(vkd, device, &createInfo);
94 MovePtr<Allocation> allocateAndBindMemory (const DeviceInterface& vkd, VkDevice device, Allocator& allocator, VkBuffer buffer)
96 MovePtr<Allocation> alloc(allocator.allocate(getBufferMemoryRequirements(vkd, device, buffer), MemoryRequirement::HostVisible));
98 VK_CHECK(vkd.bindBufferMemory(device, buffer, alloc->getMemory(), alloc->getOffset()));
103 Buffer::Buffer (Context& context, VkBufferUsageFlags usage, size_t size)
104 : m_vkd (context.getDeviceInterface())
105 , m_device (context.getDevice())
106 , m_buffer (createBuffer (context.getDeviceInterface(),
110 , m_allocation (allocateAndBindMemory (context.getDeviceInterface(),
112 context.getDefaultAllocator(),
117 void Buffer::flush (void)
119 flushMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE);
122 void Buffer::invalidate (void)
124 invalidateMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE);
131 ATOMIC_OP_EXCHANGE = 0,
143 std::string atomicOp2Str (AtomicOperation op)
145 static const char* const s_names[] =
156 return de::getSizedArrayElement<ATOMIC_OP_LAST>(s_names, op);
164 class AtomicOperationCaseInstance : public TestInstance
167 AtomicOperationCaseInstance (Context& context,
168 const ShaderSpec& shaderSpec,
169 glu::ShaderType shaderType,
171 AtomicOperation atomicOp);
172 virtual ~AtomicOperationCaseInstance (void);
174 virtual tcu::TestStatus iterate (void);
177 const ShaderSpec& m_shaderSpec;
178 glu::ShaderType m_shaderType;
180 AtomicOperation m_atomicOp;
182 struct BufferInterface
184 // Use half the number of elements for inout to cause overlap between atomic operations.
185 // Each inout element at index i will have two atomic operations using input from
186 // indices i and i + NUM_ELEMENTS / 2.
188 deUint32 inout[NUM_ELEMENTS / 2];
189 deUint32 input[NUM_ELEMENTS];
190 deUint32 compare[NUM_ELEMENTS];
191 deUint32 output[NUM_ELEMENTS];
200 Expected (T inout, T output0, T output1)
203 m_output[0] = output0;
204 m_output[1] = output1;
207 bool compare (deUint32 inout, deUint32 output0, deUint32 output1)
209 return (deMemCmp((const void*)&m_inout, (const void*)&inout, sizeof(inout)) == 0
210 && deMemCmp((const void*)&m_output[0], (const void*)&output0, sizeof(output0)) == 0
211 && deMemCmp((const void*)&m_output[1], (const void*)&output1, sizeof(output1)) == 0);
215 template<typename T> void checkOperation (const BufferInterface& original,
216 const BufferInterface& result,
217 tcu::ResultCollector& resultCollector);
221 AtomicOperationCaseInstance::AtomicOperationCaseInstance (Context& context,
222 const ShaderSpec& shaderSpec,
223 glu::ShaderType shaderType,
225 AtomicOperation atomicOp)
226 : TestInstance (context)
227 , m_shaderSpec (shaderSpec)
228 , m_shaderType (shaderType)
230 , m_atomicOp (atomicOp)
234 AtomicOperationCaseInstance::~AtomicOperationCaseInstance (void)
238 // Use template to handle both signed and unsigned cases. SPIR-V should
239 // have separate operations for both.
241 void AtomicOperationCaseInstance::checkOperation (const BufferInterface& original,
242 const BufferInterface& result,
243 tcu::ResultCollector& resultCollector)
245 // originalInout = original inout
246 // input0 = input at index i
247 // iinput1 = input at index i + NUM_ELEMENTS / 2
249 // atomic operation will return the memory contents before
250 // the operation and this is stored as output. Two operations
251 // are executed for each InOut value (using input0 and input1).
253 // Since there is an overlap of two operations per each
254 // InOut element, the outcome of the resulting InOut and
255 // the outputs of the operations have two result candidates
256 // depending on the execution order. Verification passes
257 // if the results match one of these options.
259 for (int elementNdx = 0; elementNdx < NUM_ELEMENTS / 2; elementNdx++)
261 // Needed when reinterpeting the data as signed values.
262 const T originalInout = *reinterpret_cast<const T*>(&original.inout[elementNdx]);
263 const T input0 = *reinterpret_cast<const T*>(&original.input[elementNdx]);
264 const T input1 = *reinterpret_cast<const T*>(&original.input[elementNdx + NUM_ELEMENTS / 2]);
266 // Expected results are collected to this vector.
267 vector<Expected<T> > exp;
273 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout, originalInout + input0));
274 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout + input1, originalInout));
280 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout, originalInout & input0));
281 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout & input1, originalInout));
287 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout, originalInout | input0));
288 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout | input1, originalInout));
294 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout, originalInout ^ input0));
295 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout ^ input1, originalInout));
301 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), originalInout, de::min(originalInout, input0)));
302 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), de::min(originalInout, input1), originalInout));
308 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), originalInout, de::max(originalInout, input0)));
309 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), de::max(originalInout, input1), originalInout));
313 case ATOMIC_OP_EXCHANGE:
315 exp.push_back(Expected<T>(input1, originalInout, input0));
316 exp.push_back(Expected<T>(input0, input1, originalInout));
320 case ATOMIC_OP_COMP_SWAP:
322 if (elementNdx % 2 == 0)
324 exp.push_back(Expected<T>(input0, originalInout, input0));
325 exp.push_back(Expected<T>(input0, originalInout, originalInout));
329 exp.push_back(Expected<T>(input1, input1, originalInout));
330 exp.push_back(Expected<T>(input1, originalInout, originalInout));
337 DE_FATAL("Unexpected atomic operation.");
341 const deUint32 resIo = result.inout[elementNdx];
342 const deUint32 resOutput0 = result.output[elementNdx];
343 const deUint32 resOutput1 = result.output[elementNdx + NUM_ELEMENTS / 2];
345 if (!exp[0].compare(resIo, resOutput0, resOutput1) && !exp[1].compare(resIo, resOutput0, resOutput1))
347 std::ostringstream errorMessage;
348 errorMessage << "ERROR: Result value check failed at index " << elementNdx
349 << ". Expected one of the two outcomes: InOut = " << tcu::toHex(exp[0].m_inout)
350 << ", Output0 = " << tcu::toHex(exp[0].m_output[0]) << ", Output1 = "
351 << tcu::toHex(exp[0].m_output[1]) << ", or InOut = " << tcu::toHex(exp[1].m_inout)
352 << ", Output0 = " << tcu::toHex(exp[1].m_output[0]) << ", Output1 = "
353 << tcu::toHex(exp[1].m_output[1]) << ". Got: InOut = " << tcu::toHex(resIo)
354 << ", Output0 = " << tcu::toHex(resOutput0) << ", Output1 = "
355 << tcu::toHex(resOutput1) << ". Using Input0 = " << tcu::toHex(original.input[elementNdx])
356 << " and Input1 = " << tcu::toHex(original.input[elementNdx + NUM_ELEMENTS / 2]) << ".";
358 resultCollector.fail(errorMessage.str());
363 tcu::TestStatus AtomicOperationCaseInstance::iterate (void)
365 //Check stores and atomic operation support.
366 switch (m_shaderType)
368 case glu::SHADERTYPE_VERTEX:
369 case glu::SHADERTYPE_TESSELLATION_CONTROL:
370 case glu::SHADERTYPE_TESSELLATION_EVALUATION:
371 case glu::SHADERTYPE_GEOMETRY:
372 if(!m_context.getDeviceFeatures().vertexPipelineStoresAndAtomics)
373 TCU_THROW(NotSupportedError, "Stores and atomic operations are not supported in Vertex, Tessellation, and Geometry shader.");
375 case glu::SHADERTYPE_FRAGMENT:
376 if(!m_context.getDeviceFeatures().fragmentStoresAndAtomics)
377 TCU_THROW(NotSupportedError, "Stores and atomic operations are not supported in fragment shader.");
379 case glu::SHADERTYPE_COMPUTE:
382 DE_FATAL("Unsupported shader type");
385 tcu::TestLog& log = m_context.getTestContext().getLog();
386 const DeviceInterface& vkd = m_context.getDeviceInterface();
387 const VkDevice device = m_context.getDevice();
388 de::Random rnd (0x62a15e34);
389 Buffer buffer (m_context, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, sizeof(BufferInterface));
390 BufferInterface* ptr = (BufferInterface*)buffer.getHostPtr();
392 for (int i = 0; i < NUM_ELEMENTS / 2; i++)
394 ptr->inout[i] = rnd.getUint32();
395 // The first half of compare elements match with every even index.
396 // The second half matches with odd indices. This causes the
397 // overlapping operations to only select one.
398 ptr->compare[i] = ptr->inout[i] + (i % 2);
399 ptr->compare[i + NUM_ELEMENTS / 2] = ptr->inout[i] + 1 - (i % 2);
401 for (int i = 0; i < NUM_ELEMENTS; i++)
403 ptr->input[i] = rnd.getUint32();
404 ptr->output[i] = 0xcdcdcdcd;
408 // Take a copy to be used when calculating expected values.
409 BufferInterface original = *ptr;
413 Move<VkDescriptorSetLayout> extraResourcesLayout;
414 Move<VkDescriptorPool> extraResourcesSetPool;
415 Move<VkDescriptorSet> extraResourcesSet;
417 const VkDescriptorSetLayoutBinding bindings[] =
419 { 0u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, DE_NULL }
422 const VkDescriptorSetLayoutCreateInfo layoutInfo =
424 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
426 (VkDescriptorSetLayoutCreateFlags)0u,
427 DE_LENGTH_OF_ARRAY(bindings),
431 extraResourcesLayout = createDescriptorSetLayout(vkd, device, &layoutInfo);
433 const VkDescriptorPoolSize poolSizes[] =
435 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u }
437 const VkDescriptorPoolCreateInfo poolInfo =
439 VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
441 (VkDescriptorPoolCreateFlags)VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
443 DE_LENGTH_OF_ARRAY(poolSizes),
447 extraResourcesSetPool = createDescriptorPool(vkd, device, &poolInfo);
449 const VkDescriptorSetAllocateInfo allocInfo =
451 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
453 *extraResourcesSetPool,
455 &extraResourcesLayout.get()
458 extraResourcesSet = allocateDescriptorSet(vkd, device, &allocInfo);
460 VkDescriptorBufferInfo bufferInfo;
461 bufferInfo.buffer = buffer.getBuffer();
462 bufferInfo.offset = 0u;
463 bufferInfo.range = VK_WHOLE_SIZE;
465 const VkWriteDescriptorSet descriptorWrite =
467 VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
471 0u, // dstArrayElement
473 VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
474 (const VkDescriptorImageInfo*)DE_NULL,
476 (const VkBufferView*)DE_NULL
480 vkd.updateDescriptorSets(device, 1u, &descriptorWrite, 0u, DE_NULL);
482 // Storage for output varying data.
483 std::vector<deUint32> outputs (NUM_ELEMENTS);
484 std::vector<void*> outputPtr (NUM_ELEMENTS);
486 for (size_t i = 0; i < NUM_ELEMENTS; i++)
488 outputs[i] = 0xcdcdcdcd;
489 outputPtr[i] = &outputs[i];
492 UniquePtr<ShaderExecutor> executor(createExecutor(m_context, m_shaderType, m_shaderSpec, *extraResourcesLayout));
493 executor->execute(NUM_ELEMENTS, DE_NULL, &outputPtr[0], *extraResourcesSet);
496 tcu::ResultCollector resultCollector(log);
498 // Check the results of the atomic operation
500 checkOperation<deInt32>(original, *ptr, resultCollector);
502 checkOperation<deUint32>(original, *ptr, resultCollector);
504 return tcu::TestStatus(resultCollector.getResult(), resultCollector.getMessage());
507 class AtomicOperationCase : public TestCase
510 AtomicOperationCase (tcu::TestContext& testCtx,
512 const char* description,
513 glu::ShaderType type,
515 AtomicOperation atomicOp);
516 virtual ~AtomicOperationCase (void);
518 virtual TestInstance* createInstance (Context& ctx) const;
519 virtual void initPrograms (vk::SourceCollections& programCollection) const
521 generateSources(m_shaderType, m_shaderSpec, programCollection);
526 void createShaderSpec();
527 ShaderSpec m_shaderSpec;
528 const glu::ShaderType m_shaderType;
530 const AtomicOperation m_atomicOp;
533 AtomicOperationCase::AtomicOperationCase (tcu::TestContext& testCtx,
535 const char* description,
536 glu::ShaderType shaderType,
538 AtomicOperation atomicOp)
539 : TestCase (testCtx, name, description)
540 , m_shaderType (shaderType)
542 , m_atomicOp (atomicOp)
548 AtomicOperationCase::~AtomicOperationCase (void)
552 TestInstance* AtomicOperationCase::createInstance (Context& ctx) const
554 return new AtomicOperationCaseInstance(ctx, m_shaderSpec, m_shaderType, m_sign, m_atomicOp);
557 void AtomicOperationCase::createShaderSpec (void)
559 const tcu::StringTemplate shaderTemplateGlobal(
560 "layout (set = ${SETIDX}, binding = 0, std430) buffer AtomicBuffer\n"
562 " highp int index;\n"
563 " highp ${DATATYPE} inoutValues[${N}/2];\n"
564 " highp ${DATATYPE} inputValues[${N}];\n"
565 " highp ${DATATYPE} compareValues[${N}];\n"
566 " highp ${DATATYPE} outputValues[${N}];\n"
569 std::map<std::string, std::string> specializations;
570 specializations["DATATYPE"] = m_sign ? "int" : "uint";
571 specializations["ATOMICOP"] = atomicOp2Str(m_atomicOp);
572 specializations["SETIDX"] = de::toString((int)EXTRA_RESOURCES_DESCRIPTOR_SET_INDEX);
573 specializations["N"] = de::toString((int)NUM_ELEMENTS);
574 specializations["COMPARE_ARG"] = m_atomicOp == ATOMIC_OP_COMP_SWAP ? "buf.compareValues[idx], " : "";
576 const tcu::StringTemplate shaderTemplateSrc(
577 "int idx = atomicAdd(buf.index, 1);\n"
578 "buf.outputValues[idx] = ${ATOMICOP}(buf.inoutValues[idx % (${N}/2)], ${COMPARE_ARG}buf.inputValues[idx]);\n");
580 m_shaderSpec.outputs.push_back(Symbol("outData", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
581 m_shaderSpec.globalDeclarations = shaderTemplateGlobal.specialize(specializations);
582 m_shaderSpec.source = shaderTemplateSrc.specialize(specializations);
585 void addAtomicOperationTests (tcu::TestCaseGroup* atomicOperationTestsGroup)
587 tcu::TestContext& testCtx = atomicOperationTestsGroup->getTestContext();
591 glu::ShaderType type;
595 { glu::SHADERTYPE_VERTEX, "vertex" },
596 { glu::SHADERTYPE_FRAGMENT, "fragment" },
597 { glu::SHADERTYPE_GEOMETRY, "geometry" },
598 { glu::SHADERTYPE_TESSELLATION_CONTROL, "tess_ctrl" },
599 { glu::SHADERTYPE_TESSELLATION_EVALUATION, "tess_eval" },
600 { glu::SHADERTYPE_COMPUTE, "compute" }
607 const char* description;
610 { true, "signed", "Tests using signed data (int)" },
611 { false, "unsigned", "Tests using unsigned data (uint)" }
616 AtomicOperation value;
620 { ATOMIC_OP_EXCHANGE, "exchange" },
621 { ATOMIC_OP_COMP_SWAP, "comp_swap" },
622 { ATOMIC_OP_ADD, "add" },
623 { ATOMIC_OP_MIN, "min" },
624 { ATOMIC_OP_MAX, "max" },
625 { ATOMIC_OP_AND, "and" },
626 { ATOMIC_OP_OR, "or" },
627 { ATOMIC_OP_XOR, "xor" }
630 for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(atomicOp); opNdx++)
632 for (int signNdx = 0; signNdx < DE_LENGTH_OF_ARRAY(dataSign); signNdx++)
634 for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
636 const std::string description = std::string("Tests atomic operation ") + atomicOp2Str(atomicOp[opNdx].value) + std::string(".");
637 std::string name = std::string(atomicOp[opNdx].name) + "_" + std::string(dataSign[signNdx].name) + "_" + std::string(shaderTypes[shaderTypeNdx].name);
638 atomicOperationTestsGroup->addChild(new AtomicOperationCase(testCtx, name.c_str(), description.c_str(), shaderTypes[shaderTypeNdx].type, dataSign[signNdx].value, atomicOp[opNdx].value));
646 tcu::TestCaseGroup* createAtomicOperationTests (tcu::TestContext& testCtx)
648 return createTestGroup(testCtx, "atomic_operations", "Atomic Operation Tests", addAtomicOperationTests);