1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2015 The Khronos Group Inc.
6 * Copyright (c) 2017 Google Inc.
7 *
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
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
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.
19 *
20 *//*!
21 * \file
22 * \brief Atomic operations (OpAtomic*) tests.
23 *//*--------------------------------------------------------------------*/
24
25 #include "vktAtomicOperationTests.hpp"
26 #include "vktShaderExecutor.hpp"
27
28 #include "vkRefUtil.hpp"
29 #include "vkMemUtil.hpp"
30 #include "vkQueryUtil.hpp"
31 #include "vktTestGroupUtil.hpp"
32
33 #include "tcuTestLog.hpp"
34 #include "tcuStringTemplate.hpp"
35 #include "tcuResultCollector.hpp"
36
37 #include "deStringUtil.hpp"
38 #include "deSharedPtr.hpp"
39 #include "deRandom.hpp"
40 #include "deArrayUtil.hpp"
41
42 #include <string>
43
44 namespace vkt
45 {
46 namespace shaderexecutor
47 {
48
49 namespace
50 {
51
52 using de::UniquePtr;
53 using de::MovePtr;
54 using std::vector;
55
56 using namespace vk;
57
58 // Buffer helper
59 class Buffer
60 {
61 public:
62 Buffer (Context& context, VkBufferUsageFlags usage, size_t size);
63
getBuffer(void) const64 VkBuffer getBuffer (void) const { return *m_buffer; }
getHostPtr(void) const65 void* getHostPtr (void) const { return m_allocation->getHostPtr(); }
66 void flush (void);
67 void invalidate (void);
68
69 private:
70 const DeviceInterface& m_vkd;
71 const VkDevice m_device;
72 const Unique<VkBuffer> m_buffer;
73 const UniquePtr<Allocation> m_allocation;
74 };
75
76 typedef de::SharedPtr<Buffer> BufferSp;
77
createBuffer(const DeviceInterface & vkd,VkDevice device,VkDeviceSize size,VkBufferUsageFlags usageFlags)78 Move<VkBuffer> createBuffer (const DeviceInterface& vkd, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usageFlags)
79 {
80 const VkBufferCreateInfo createInfo =
81 {
82 VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
83 DE_NULL,
84 (VkBufferCreateFlags)0,
85 size,
86 usageFlags,
87 VK_SHARING_MODE_EXCLUSIVE,
88 0u,
89 DE_NULL
90 };
91 return createBuffer(vkd, device, &createInfo);
92 }
93
allocateAndBindMemory(const DeviceInterface & vkd,VkDevice device,Allocator & allocator,VkBuffer buffer)94 MovePtr<Allocation> allocateAndBindMemory (const DeviceInterface& vkd, VkDevice device, Allocator& allocator, VkBuffer buffer)
95 {
96 MovePtr<Allocation> alloc(allocator.allocate(getBufferMemoryRequirements(vkd, device, buffer), MemoryRequirement::HostVisible));
97
98 VK_CHECK(vkd.bindBufferMemory(device, buffer, alloc->getMemory(), alloc->getOffset()));
99
100 return alloc;
101 }
102
Buffer(Context & context,VkBufferUsageFlags usage,size_t size)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(),
107 context.getDevice(),
108 (VkDeviceSize)size,
109 usage))
110 , m_allocation (allocateAndBindMemory (context.getDeviceInterface(),
111 context.getDevice(),
112 context.getDefaultAllocator(),
113 *m_buffer))
114 {
115 }
116
flush(void)117 void Buffer::flush (void)
118 {
119 flushMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE);
120 }
121
invalidate(void)122 void Buffer::invalidate (void)
123 {
124 invalidateMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE);
125 }
126
127 // Tests
128
129 enum AtomicOperation
130 {
131 ATOMIC_OP_EXCHANGE = 0,
132 ATOMIC_OP_COMP_SWAP,
133 ATOMIC_OP_ADD,
134 ATOMIC_OP_MIN,
135 ATOMIC_OP_MAX,
136 ATOMIC_OP_AND,
137 ATOMIC_OP_OR,
138 ATOMIC_OP_XOR,
139
140 ATOMIC_OP_LAST
141 };
142
atomicOp2Str(AtomicOperation op)143 std::string atomicOp2Str (AtomicOperation op)
144 {
145 static const char* const s_names[] =
146 {
147 "atomicExchange",
148 "atomicCompSwap",
149 "atomicAdd",
150 "atomicMin",
151 "atomicMax",
152 "atomicAnd",
153 "atomicOr",
154 "atomicXor"
155 };
156 return de::getSizedArrayElement<ATOMIC_OP_LAST>(s_names, op);
157 }
158
159 enum
160 {
161 NUM_ELEMENTS = 32
162 };
163
164 enum DataType
165 {
166 DATA_TYPE_INT32 = 0,
167 DATA_TYPE_UINT32,
168 DATA_TYPE_INT64,
169 DATA_TYPE_UINT64,
170
171 DATA_TYPE_LAST
172 };
173
dataType2Str(DataType type)174 std::string dataType2Str(DataType type)
175 {
176 static const char* const s_names[] =
177 {
178 "int",
179 "uint",
180 "int64_t",
181 "uint64_t",
182 };
183 return de::getSizedArrayElement<DATA_TYPE_LAST>(s_names, type);
184 }
185
186 class BufferInterface
187 {
188 public:
189 virtual void setBuffer(void* ptr) = 0;
190
191 virtual size_t bufferSize() = 0;
192
193 virtual void fillWithTestData(de::Random &rnd) = 0;
194
195 virtual void checkResults(tcu::ResultCollector& resultCollector) = 0;
196
~BufferInterface()197 virtual ~BufferInterface() {};
198 };
199
200 template<typename dataTypeT>
201 class TestBuffer : public BufferInterface
202 {
203 public:
204
TestBuffer(AtomicOperation atomicOp)205 TestBuffer(AtomicOperation atomicOp)
206 : m_atomicOp(atomicOp)
207 {}
208
209 template<typename T>
210 struct BufferData
211 {
212 // Use half the number of elements for inout to cause overlap between atomic operations.
213 // Each inout element at index i will have two atomic operations using input from
214 // indices i and i + NUM_ELEMENTS / 2.
215 T inout[NUM_ELEMENTS / 2];
216 T input[NUM_ELEMENTS];
217 T compare[NUM_ELEMENTS];
218 T output[NUM_ELEMENTS];
219 deInt32 index;
220 };
221
setBuffer(void * ptr)222 virtual void setBuffer(void* ptr)
223 {
224 m_ptr = static_cast<BufferData<dataTypeT>*>(ptr);
225 }
226
bufferSize()227 virtual size_t bufferSize()
228 {
229 return sizeof(BufferData<dataTypeT>);
230 }
231
fillWithTestData(de::Random & rnd)232 virtual void fillWithTestData(de::Random &rnd)
233 {
234 dataTypeT pattern;
235 deMemset(&pattern, 0xcd, sizeof(dataTypeT));
236
237 for (int i = 0; i < NUM_ELEMENTS / 2; i++)
238 {
239 m_ptr->inout[i] = static_cast<dataTypeT>(rnd.getUint64());
240 // The first half of compare elements match with every even index.
241 // The second half matches with odd indices. This causes the
242 // overlapping operations to only select one.
243 m_ptr->compare[i] = m_ptr->inout[i] + (i % 2);
244 m_ptr->compare[i + NUM_ELEMENTS / 2] = m_ptr->inout[i] + 1 - (i % 2);
245 }
246 for (int i = 0; i < NUM_ELEMENTS; i++)
247 {
248 m_ptr->input[i] = static_cast<dataTypeT>(rnd.getUint64());
249 m_ptr->output[i] = pattern;
250 }
251 m_ptr->index = 0;
252
253 // Take a copy to be used when calculating expected values.
254 m_original = *m_ptr;
255 }
256
checkResults(tcu::ResultCollector & resultCollector)257 virtual void checkResults(tcu::ResultCollector& resultCollector)
258 {
259 checkOperation(m_original, *m_ptr, resultCollector);
260 }
261
262 template<typename T>
263 struct Expected
264 {
265 T m_inout;
266 T m_output[2];
267
Expectedvkt::shaderexecutor::__anonffe0b4930111::TestBuffer::Expected268 Expected (T inout, T output0, T output1)
269 : m_inout(inout)
270 {
271 m_output[0] = output0;
272 m_output[1] = output1;
273 }
274
comparevkt::shaderexecutor::__anonffe0b4930111::TestBuffer::Expected275 bool compare (T inout, T output0, T output1)
276 {
277 return (deMemCmp((const void*)&m_inout, (const void*)&inout, sizeof(inout)) == 0
278 && deMemCmp((const void*)&m_output[0], (const void*)&output0, sizeof(output0)) == 0
279 && deMemCmp((const void*)&m_output[1], (const void*)&output1, sizeof(output1)) == 0);
280 }
281 };
282
283 void checkOperation (const BufferData<dataTypeT>& original,
284 const BufferData<dataTypeT>& result,
285 tcu::ResultCollector& resultCollector);
286
287 const AtomicOperation m_atomicOp;
288
289 BufferData<dataTypeT>* m_ptr;
290 BufferData<dataTypeT> m_original;
291
292 };
293
createTestBuffer(DataType type,AtomicOperation atomicOp)294 static BufferInterface* createTestBuffer(DataType type, AtomicOperation atomicOp)
295 {
296 switch (type)
297 {
298 case DATA_TYPE_INT32:
299 return new TestBuffer<deInt32>(atomicOp);
300 case DATA_TYPE_UINT32:
301 return new TestBuffer<deUint32>(atomicOp);
302 case DATA_TYPE_INT64:
303 return new TestBuffer<deInt64>(atomicOp);
304 case DATA_TYPE_UINT64:
305 return new TestBuffer<deUint64>(atomicOp);
306 default:
307 DE_ASSERT(false);
308 return DE_NULL;
309 }
310 }
311
312 // Use template to handle both signed and unsigned cases. SPIR-V should
313 // have separate operations for both.
314 template<typename T>
checkOperation(const BufferData<T> & original,const BufferData<T> & result,tcu::ResultCollector & resultCollector)315 void TestBuffer<T>::checkOperation (const BufferData<T>& original,
316 const BufferData<T>& result,
317 tcu::ResultCollector& resultCollector)
318 {
319 // originalInout = original inout
320 // input0 = input at index i
321 // iinput1 = input at index i + NUM_ELEMENTS / 2
322 //
323 // atomic operation will return the memory contents before
324 // the operation and this is stored as output. Two operations
325 // are executed for each InOut value (using input0 and input1).
326 //
327 // Since there is an overlap of two operations per each
328 // InOut element, the outcome of the resulting InOut and
329 // the outputs of the operations have two result candidates
330 // depending on the execution order. Verification passes
331 // if the results match one of these options.
332
333 for (int elementNdx = 0; elementNdx < NUM_ELEMENTS / 2; elementNdx++)
334 {
335 // Needed when reinterpeting the data as signed values.
336 const T originalInout = *reinterpret_cast<const T*>(&original.inout[elementNdx]);
337 const T input0 = *reinterpret_cast<const T*>(&original.input[elementNdx]);
338 const T input1 = *reinterpret_cast<const T*>(&original.input[elementNdx + NUM_ELEMENTS / 2]);
339
340 // Expected results are collected to this vector.
341 vector<Expected<T> > exp;
342
343 switch (m_atomicOp)
344 {
345 case ATOMIC_OP_ADD:
346 {
347 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout, originalInout + input0));
348 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout + input1, originalInout));
349 }
350 break;
351
352 case ATOMIC_OP_AND:
353 {
354 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout, originalInout & input0));
355 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout & input1, originalInout));
356 }
357 break;
358
359 case ATOMIC_OP_OR:
360 {
361 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout, originalInout | input0));
362 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout | input1, originalInout));
363 }
364 break;
365
366 case ATOMIC_OP_XOR:
367 {
368 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout, originalInout ^ input0));
369 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout ^ input1, originalInout));
370 }
371 break;
372
373 case ATOMIC_OP_MIN:
374 {
375 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), originalInout, de::min(originalInout, input0)));
376 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), de::min(originalInout, input1), originalInout));
377 }
378 break;
379
380 case ATOMIC_OP_MAX:
381 {
382 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), originalInout, de::max(originalInout, input0)));
383 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), de::max(originalInout, input1), originalInout));
384 }
385 break;
386
387 case ATOMIC_OP_EXCHANGE:
388 {
389 exp.push_back(Expected<T>(input1, originalInout, input0));
390 exp.push_back(Expected<T>(input0, input1, originalInout));
391 }
392 break;
393
394 case ATOMIC_OP_COMP_SWAP:
395 {
396 if (elementNdx % 2 == 0)
397 {
398 exp.push_back(Expected<T>(input0, originalInout, input0));
399 exp.push_back(Expected<T>(input0, originalInout, originalInout));
400 }
401 else
402 {
403 exp.push_back(Expected<T>(input1, input1, originalInout));
404 exp.push_back(Expected<T>(input1, originalInout, originalInout));
405 }
406 }
407 break;
408
409
410 default:
411 DE_FATAL("Unexpected atomic operation.");
412 break;
413 };
414
415 const T resIo = result.inout[elementNdx];
416 const T resOutput0 = result.output[elementNdx];
417 const T resOutput1 = result.output[elementNdx + NUM_ELEMENTS / 2];
418
419
420 if (!exp[0].compare(resIo, resOutput0, resOutput1) && !exp[1].compare(resIo, resOutput0, resOutput1))
421 {
422 std::ostringstream errorMessage;
423 errorMessage << "ERROR: Result value check failed at index " << elementNdx
424 << ". Expected one of the two outcomes: InOut = " << tcu::toHex(exp[0].m_inout)
425 << ", Output0 = " << tcu::toHex(exp[0].m_output[0]) << ", Output1 = "
426 << tcu::toHex(exp[0].m_output[1]) << ", or InOut = " << tcu::toHex(exp[1].m_inout)
427 << ", Output0 = " << tcu::toHex(exp[1].m_output[0]) << ", Output1 = "
428 << tcu::toHex(exp[1].m_output[1]) << ". Got: InOut = " << tcu::toHex(resIo)
429 << ", Output0 = " << tcu::toHex(resOutput0) << ", Output1 = "
430 << tcu::toHex(resOutput1) << ". Using Input0 = " << tcu::toHex(original.input[elementNdx])
431 << " and Input1 = " << tcu::toHex(original.input[elementNdx + NUM_ELEMENTS / 2]) << ".";
432
433 resultCollector.fail(errorMessage.str());
434 }
435 }
436 }
437
438
439 class AtomicOperationCaseInstance : public TestInstance
440 {
441 public:
442 AtomicOperationCaseInstance (Context& context,
443 const ShaderSpec& shaderSpec,
444 glu::ShaderType shaderType,
445 DataType dataType,
446 AtomicOperation atomicOp);
447
448 virtual tcu::TestStatus iterate (void);
449
450 private:
451 const ShaderSpec& m_shaderSpec;
452 glu::ShaderType m_shaderType;
453 const DataType m_dataType;
454 AtomicOperation m_atomicOp;
455
456 };
457
AtomicOperationCaseInstance(Context & context,const ShaderSpec & shaderSpec,glu::ShaderType shaderType,DataType dataType,AtomicOperation atomicOp)458 AtomicOperationCaseInstance::AtomicOperationCaseInstance (Context& context,
459 const ShaderSpec& shaderSpec,
460 glu::ShaderType shaderType,
461 DataType dataType,
462 AtomicOperation atomicOp)
463 : TestInstance (context)
464 , m_shaderSpec (shaderSpec)
465 , m_shaderType (shaderType)
466 , m_dataType (dataType)
467 , m_atomicOp (atomicOp)
468 {
469 if ((m_dataType == DATA_TYPE_INT64) || (m_dataType == DATA_TYPE_UINT64))
470 {
471 if (!isDeviceExtensionSupported(context.getUsedApiVersion(), context.getDeviceExtensions(), "VK_KHR_shader_atomic_int64"))
472 TCU_THROW(NotSupportedError, "Missing extension: VK_KHR_shader_atomic_int64");
473
474 VkPhysicalDeviceShaderAtomicInt64FeaturesKHR shaderAtomicInt64Features;
475 deMemset(&shaderAtomicInt64Features, 0x0, sizeof(VkPhysicalDeviceShaderAtomicInt64FeaturesKHR));
476 shaderAtomicInt64Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES_KHR;
477 shaderAtomicInt64Features.pNext = DE_NULL;
478
479 VkPhysicalDeviceFeatures2 features;
480 deMemset(&features, 0x0, sizeof(VkPhysicalDeviceFeatures2));
481 features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
482 features.pNext = &shaderAtomicInt64Features;
483
484 context.getInstanceInterface().getPhysicalDeviceFeatures2(context.getPhysicalDevice(), &features);
485
486 if (shaderAtomicInt64Features.shaderBufferInt64Atomics == VK_FALSE)
487 {
488 TCU_THROW(NotSupportedError, "VkShaderAtomicInt64: 64-bit unsigned and signed integer atomic operations not supported");
489 }
490 }
491 }
492
iterate(void)493 tcu::TestStatus AtomicOperationCaseInstance::iterate(void)
494 {
495 //Check stores and atomic operation support.
496 switch (m_shaderType)
497 {
498 case glu::SHADERTYPE_VERTEX:
499 case glu::SHADERTYPE_TESSELLATION_CONTROL:
500 case glu::SHADERTYPE_TESSELLATION_EVALUATION:
501 case glu::SHADERTYPE_GEOMETRY:
502 if (!m_context.getDeviceFeatures().vertexPipelineStoresAndAtomics)
503 TCU_THROW(NotSupportedError, "Stores and atomic operations are not supported in Vertex, Tessellation, and Geometry shader.");
504 break;
505 case glu::SHADERTYPE_FRAGMENT:
506 if (!m_context.getDeviceFeatures().fragmentStoresAndAtomics)
507 TCU_THROW(NotSupportedError, "Stores and atomic operations are not supported in fragment shader.");
508 break;
509 case glu::SHADERTYPE_COMPUTE:
510 break;
511 default:
512 DE_FATAL("Unsupported shader type");
513 }
514
515 de::UniquePtr<BufferInterface> testBuffer (createTestBuffer(m_dataType, m_atomicOp));
516 tcu::TestLog& log = m_context.getTestContext().getLog();
517 const DeviceInterface& vkd = m_context.getDeviceInterface();
518 const VkDevice device = m_context.getDevice();
519 de::Random rnd (0x62a15e34);
520 Buffer buffer (m_context, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, testBuffer->bufferSize());
521
522 testBuffer->setBuffer(buffer.getHostPtr());
523 testBuffer->fillWithTestData(rnd);
524
525 buffer.flush();
526
527 Move<VkDescriptorSetLayout> extraResourcesLayout;
528 Move<VkDescriptorPool> extraResourcesSetPool;
529 Move<VkDescriptorSet> extraResourcesSet;
530
531 const VkDescriptorSetLayoutBinding bindings[] =
532 {
533 { 0u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, DE_NULL }
534 };
535
536 const VkDescriptorSetLayoutCreateInfo layoutInfo =
537 {
538 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
539 DE_NULL,
540 (VkDescriptorSetLayoutCreateFlags)0u,
541 DE_LENGTH_OF_ARRAY(bindings),
542 bindings
543 };
544
545 extraResourcesLayout = createDescriptorSetLayout(vkd, device, &layoutInfo);
546
547 const VkDescriptorPoolSize poolSizes[] =
548 {
549 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u }
550 };
551 const VkDescriptorPoolCreateInfo poolInfo =
552 {
553 VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
554 DE_NULL,
555 (VkDescriptorPoolCreateFlags)VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
556 1u, // maxSets
557 DE_LENGTH_OF_ARRAY(poolSizes),
558 poolSizes
559 };
560
561 extraResourcesSetPool = createDescriptorPool(vkd, device, &poolInfo);
562
563 const VkDescriptorSetAllocateInfo allocInfo =
564 {
565 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
566 DE_NULL,
567 *extraResourcesSetPool,
568 1u,
569 &extraResourcesLayout.get()
570 };
571
572 extraResourcesSet = allocateDescriptorSet(vkd, device, &allocInfo);
573
574 VkDescriptorBufferInfo bufferInfo;
575 bufferInfo.buffer = buffer.getBuffer();
576 bufferInfo.offset = 0u;
577 bufferInfo.range = VK_WHOLE_SIZE;
578
579 const VkWriteDescriptorSet descriptorWrite =
580 {
581 VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
582 DE_NULL,
583 *extraResourcesSet,
584 0u, // dstBinding
585 0u, // dstArrayElement
586 1u,
587 VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
588 (const VkDescriptorImageInfo*)DE_NULL,
589 &bufferInfo,
590 (const VkBufferView*)DE_NULL
591 };
592
593
594 vkd.updateDescriptorSets(device, 1u, &descriptorWrite, 0u, DE_NULL);
595
596 // Storage for output varying data.
597 std::vector<deUint32> outputs (NUM_ELEMENTS);
598 std::vector<void*> outputPtr (NUM_ELEMENTS);
599
600 for (size_t i = 0; i < NUM_ELEMENTS; i++)
601 {
602 outputs[i] = 0xcdcdcdcd;
603 outputPtr[i] = &outputs[i];
604 }
605
606 UniquePtr<ShaderExecutor> executor(createExecutor(m_context, m_shaderType, m_shaderSpec, *extraResourcesLayout));
607 executor->execute(NUM_ELEMENTS, DE_NULL, &outputPtr[0], *extraResourcesSet);
608 buffer.invalidate();
609
610 tcu::ResultCollector resultCollector(log);
611
612 // Check the results of the atomic operation
613 testBuffer->checkResults(resultCollector);
614
615 return tcu::TestStatus(resultCollector.getResult(), resultCollector.getMessage());
616 }
617
618 class AtomicOperationCase : public TestCase
619 {
620 public:
621 AtomicOperationCase (tcu::TestContext& testCtx,
622 const char* name,
623 const char* description,
624 glu::ShaderType type,
625 DataType dataType,
626 AtomicOperation atomicOp);
627 virtual ~AtomicOperationCase (void);
628
629 virtual TestInstance* createInstance (Context& ctx) const;
initPrograms(vk::SourceCollections & programCollection) const630 virtual void initPrograms (vk::SourceCollections& programCollection) const
631 {
632 generateSources(m_shaderType, m_shaderSpec, programCollection);
633 }
634
635 private:
636
637 void createShaderSpec();
638 ShaderSpec m_shaderSpec;
639 const glu::ShaderType m_shaderType;
640 const DataType m_dataType;
641 const AtomicOperation m_atomicOp;
642 };
643
AtomicOperationCase(tcu::TestContext & testCtx,const char * name,const char * description,glu::ShaderType shaderType,DataType dataType,AtomicOperation atomicOp)644 AtomicOperationCase::AtomicOperationCase (tcu::TestContext& testCtx,
645 const char* name,
646 const char* description,
647 glu::ShaderType shaderType,
648 DataType dataType,
649 AtomicOperation atomicOp)
650 : TestCase (testCtx, name, description)
651 , m_shaderType (shaderType)
652 , m_dataType (dataType)
653 , m_atomicOp (atomicOp)
654 {
655 createShaderSpec();
656 init();
657 }
658
~AtomicOperationCase(void)659 AtomicOperationCase::~AtomicOperationCase (void)
660 {
661 }
662
createInstance(Context & ctx) const663 TestInstance* AtomicOperationCase::createInstance (Context& ctx) const
664 {
665 return new AtomicOperationCaseInstance(ctx, m_shaderSpec, m_shaderType, m_dataType, m_atomicOp);
666 }
667
createShaderSpec(void)668 void AtomicOperationCase::createShaderSpec (void)
669 {
670 const tcu::StringTemplate shaderTemplateGlobal(
671 "${EXTENSIONS}\n"
672 "layout (set = ${SETIDX}, binding = 0) buffer AtomicBuffer\n"
673 "{\n"
674 " ${DATATYPE} inoutValues[${N}/2];\n"
675 " ${DATATYPE} inputValues[${N}];\n"
676 " ${DATATYPE} compareValues[${N}];\n"
677 " ${DATATYPE} outputValues[${N}];\n"
678 " int index;\n"
679 "} buf;\n");
680
681 std::map<std::string, std::string> specializations;
682 if ((m_dataType == DATA_TYPE_INT64) || (m_dataType == DATA_TYPE_UINT64))
683 {
684 specializations["EXTENSIONS"] = "#extension GL_ARB_gpu_shader_int64 : enable\n"
685 "#extension GL_EXT_shader_atomic_int64 : enable\n";
686 }
687 else
688 {
689 specializations["EXTENSIONS"] = "";
690 }
691 specializations["DATATYPE"] = dataType2Str(m_dataType);
692 specializations["ATOMICOP"] = atomicOp2Str(m_atomicOp);
693 specializations["SETIDX"] = de::toString((int)EXTRA_RESOURCES_DESCRIPTOR_SET_INDEX);
694 specializations["N"] = de::toString((int)NUM_ELEMENTS);
695 specializations["COMPARE_ARG"] = m_atomicOp == ATOMIC_OP_COMP_SWAP ? "buf.compareValues[idx], " : "";
696
697 const tcu::StringTemplate shaderTemplateSrc(
698 "int idx = atomicAdd(buf.index, 1);\n"
699 "buf.outputValues[idx] = ${ATOMICOP}(buf.inoutValues[idx % (${N}/2)], ${COMPARE_ARG}buf.inputValues[idx]);\n");
700
701 m_shaderSpec.outputs.push_back(Symbol("outData", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
702 m_shaderSpec.globalDeclarations = shaderTemplateGlobal.specialize(specializations);
703 m_shaderSpec.source = shaderTemplateSrc.specialize(specializations);
704 m_shaderSpec.glslVersion = glu::GLSL_VERSION_450;
705 }
706
addAtomicOperationTests(tcu::TestCaseGroup * atomicOperationTestsGroup)707 void addAtomicOperationTests (tcu::TestCaseGroup* atomicOperationTestsGroup)
708 {
709 tcu::TestContext& testCtx = atomicOperationTestsGroup->getTestContext();
710
711 static const struct
712 {
713 glu::ShaderType type;
714 const char* name;
715 } shaderTypes[] =
716 {
717 { glu::SHADERTYPE_VERTEX, "vertex" },
718 { glu::SHADERTYPE_FRAGMENT, "fragment" },
719 { glu::SHADERTYPE_GEOMETRY, "geometry" },
720 { glu::SHADERTYPE_TESSELLATION_CONTROL, "tess_ctrl" },
721 { glu::SHADERTYPE_TESSELLATION_EVALUATION, "tess_eval" },
722 { glu::SHADERTYPE_COMPUTE, "compute" }
723 };
724
725 static const struct
726 {
727 DataType dataType;
728 const char* name;
729 const char* description;
730 } dataSign[] =
731 {
732 { DATA_TYPE_INT32, "signed", "Tests using signed data (int)" },
733 { DATA_TYPE_UINT32, "unsigned", "Tests using unsigned data (uint)" },
734 { DATA_TYPE_INT64, "signed64bit", "Tests using 64 bit signed data (int64)" },
735 { DATA_TYPE_UINT64, "unsigned64bit", "Tests using 64 bit unsigned data (uint64)" }
736 };
737
738 static const struct
739 {
740 AtomicOperation value;
741 const char* name;
742 } atomicOp[] =
743 {
744 { ATOMIC_OP_EXCHANGE, "exchange" },
745 { ATOMIC_OP_COMP_SWAP, "comp_swap" },
746 { ATOMIC_OP_ADD, "add" },
747 { ATOMIC_OP_MIN, "min" },
748 { ATOMIC_OP_MAX, "max" },
749 { ATOMIC_OP_AND, "and" },
750 { ATOMIC_OP_OR, "or" },
751 { ATOMIC_OP_XOR, "xor" }
752 };
753
754 for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(atomicOp); opNdx++)
755 {
756 for (int signNdx = 0; signNdx < DE_LENGTH_OF_ARRAY(dataSign); signNdx++)
757 {
758 for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
759 {
760 const std::string description = std::string("Tests atomic operation ") + atomicOp2Str(atomicOp[opNdx].value) + std::string(".");
761 std::string name = std::string(atomicOp[opNdx].name) + "_" + std::string(dataSign[signNdx].name) + "_" + std::string(shaderTypes[shaderTypeNdx].name);
762 atomicOperationTestsGroup->addChild(new AtomicOperationCase(testCtx, name.c_str(), description.c_str(), shaderTypes[shaderTypeNdx].type, dataSign[signNdx].dataType, atomicOp[opNdx].value));
763 }
764 }
765 }
766 }
767
768 } // anonymous
769
createAtomicOperationTests(tcu::TestContext & testCtx)770 tcu::TestCaseGroup* createAtomicOperationTests (tcu::TestContext& testCtx)
771 {
772 return createTestGroup(testCtx, "atomic_operations", "Atomic Operation Tests", addAtomicOperationTests);
773 }
774
775 } // shaderexecutor
776 } // vkt
777