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 "vkObjUtil.hpp"
32 #include "vkBarrierUtil.hpp"
33 #include "vkCmdUtil.hpp"
34 #include "vktTestGroupUtil.hpp"
35
36 #include "tcuTestLog.hpp"
37 #include "tcuStringTemplate.hpp"
38 #include "tcuResultCollector.hpp"
39
40 #include "deFloat16.h"
41 #include "deMath.hpp"
42 #include "deStringUtil.hpp"
43 #include "deSharedPtr.hpp"
44 #include "deRandom.hpp"
45 #include "deArrayUtil.hpp"
46
47 #include <string>
48 #include <memory>
49 #include <cmath>
50
51 namespace vkt
52 {
53 namespace shaderexecutor
54 {
55
56 namespace
57 {
58
59 using de::UniquePtr;
60 using de::MovePtr;
61 using std::vector;
62
63 using namespace vk;
64
65 enum class AtomicMemoryType
66 {
67 BUFFER = 0, // Normal buffer.
68 SHARED, // Shared global struct in a compute workgroup.
69 REFERENCE, // Buffer passed as a reference.
70 PAYLOAD, // Task payload.
71 };
72
73 // Helper struct to indicate the shader type and if it should use shared global memory.
74 class AtomicShaderType
75 {
76 public:
AtomicShaderType(glu::ShaderType type,AtomicMemoryType memoryType)77 AtomicShaderType (glu::ShaderType type, AtomicMemoryType memoryType)
78 : m_type (type)
79 , m_atomicMemoryType (memoryType)
80 {
81 // Shared global memory can only be set to true with compute, task and mesh shaders.
82 DE_ASSERT(memoryType != AtomicMemoryType::SHARED
83 || type == glu::SHADERTYPE_COMPUTE
84 || type == glu::SHADERTYPE_TASK
85 || type == glu::SHADERTYPE_MESH);
86
87 // Task payload memory can only be tested in task shaders.
88 DE_ASSERT(memoryType != AtomicMemoryType::PAYLOAD || type == glu::SHADERTYPE_TASK);
89 }
90
getType(void) const91 glu::ShaderType getType (void) const { return m_type; }
getMemoryType(void) const92 AtomicMemoryType getMemoryType (void) const { return m_atomicMemoryType; }
isSharedLike(void) const93 bool isSharedLike (void) const { return m_atomicMemoryType == AtomicMemoryType::SHARED || m_atomicMemoryType == AtomicMemoryType::PAYLOAD; }
isMeshShadingStage(void) const94 bool isMeshShadingStage (void) const { return (m_type == glu::SHADERTYPE_TASK || m_type == glu::SHADERTYPE_MESH); }
95
96 private:
97 glu::ShaderType m_type;
98 AtomicMemoryType m_atomicMemoryType;
99 };
100
101 // Buffer helper
102 class Buffer
103 {
104 public:
105 Buffer (Context& context, VkBufferUsageFlags usage, size_t size, bool useRef);
106
getBuffer(void) const107 VkBuffer getBuffer (void) const { return *m_buffer; }
getHostPtr(void) const108 void* getHostPtr (void) const { return m_allocation->getHostPtr(); }
109 void flush (void);
110 void invalidate (void);
111
112 private:
113 const DeviceInterface& m_vkd;
114 const VkDevice m_device;
115 const VkQueue m_queue;
116 const deUint32 m_queueIndex;
117 const Unique<VkBuffer> m_buffer;
118 const UniquePtr<Allocation> m_allocation;
119 };
120
121 typedef de::SharedPtr<Buffer> BufferSp;
122
createBuffer(const DeviceInterface & vkd,VkDevice device,VkDeviceSize size,VkBufferUsageFlags usageFlags)123 Move<VkBuffer> createBuffer (const DeviceInterface& vkd, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usageFlags)
124 {
125 const VkBufferCreateInfo createInfo =
126 {
127 VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
128 DE_NULL,
129 (VkBufferCreateFlags)0,
130 size,
131 usageFlags,
132 VK_SHARING_MODE_EXCLUSIVE,
133 0u,
134 DE_NULL
135 };
136 return createBuffer(vkd, device, &createInfo);
137 }
138
allocateAndBindMemory(const DeviceInterface & vkd,VkDevice device,Allocator & allocator,VkBuffer buffer,bool useRef)139 MovePtr<Allocation> allocateAndBindMemory (const DeviceInterface& vkd, VkDevice device, Allocator& allocator, VkBuffer buffer, bool useRef)
140 {
141 const MemoryRequirement allocationType = (MemoryRequirement::HostVisible | (useRef ? MemoryRequirement::DeviceAddress : MemoryRequirement::Any));
142 MovePtr<Allocation> alloc(allocator.allocate(getBufferMemoryRequirements(vkd, device, buffer), allocationType));
143
144 VK_CHECK(vkd.bindBufferMemory(device, buffer, alloc->getMemory(), alloc->getOffset()));
145
146 return alloc;
147 }
148
Buffer(Context & context,VkBufferUsageFlags usage,size_t size,bool useRef)149 Buffer::Buffer (Context& context, VkBufferUsageFlags usage, size_t size, bool useRef)
150 : m_vkd (context.getDeviceInterface())
151 , m_device (context.getDevice())
152 , m_queue (context.getUniversalQueue())
153 , m_queueIndex (context.getUniversalQueueFamilyIndex())
154 , m_buffer (createBuffer (context.getDeviceInterface(),
155 context.getDevice(),
156 (VkDeviceSize)size,
157 usage))
158 , m_allocation (allocateAndBindMemory (context.getDeviceInterface(),
159 context.getDevice(),
160 context.getDefaultAllocator(),
161 *m_buffer,
162 useRef))
163 {
164 }
165
flush(void)166 void Buffer::flush (void)
167 {
168 flushMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE);
169 }
170
invalidate(void)171 void Buffer::invalidate (void)
172 {
173 const auto cmdPool = vk::makeCommandPool(m_vkd, m_device, m_queueIndex);
174 const auto cmdBufferPtr = vk::allocateCommandBuffer(m_vkd, m_device, cmdPool.get(), VK_COMMAND_BUFFER_LEVEL_PRIMARY);
175 const auto cmdBuffer = cmdBufferPtr.get();
176 const auto bufferBarrier = vk::makeBufferMemoryBarrier(VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, m_buffer.get(), 0ull, VK_WHOLE_SIZE);
177
178 beginCommandBuffer(m_vkd, cmdBuffer);
179 m_vkd.cmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0u, 0u, nullptr, 1u, &bufferBarrier, 0u, nullptr);
180 endCommandBuffer(m_vkd, cmdBuffer);
181 submitCommandsAndWait(m_vkd, m_device, m_queue, cmdBuffer);
182
183 invalidateMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE);
184 }
185
186 // Tests
187
188 enum AtomicOperation
189 {
190 ATOMIC_OP_EXCHANGE = 0,
191 ATOMIC_OP_COMP_SWAP,
192 ATOMIC_OP_ADD,
193 ATOMIC_OP_MIN,
194 ATOMIC_OP_MAX,
195 ATOMIC_OP_AND,
196 ATOMIC_OP_OR,
197 ATOMIC_OP_XOR,
198
199 ATOMIC_OP_LAST
200 };
201
atomicOp2Str(AtomicOperation op)202 std::string atomicOp2Str (AtomicOperation op)
203 {
204 static const char* const s_names[] =
205 {
206 "atomicExchange",
207 "atomicCompSwap",
208 "atomicAdd",
209 "atomicMin",
210 "atomicMax",
211 "atomicAnd",
212 "atomicOr",
213 "atomicXor"
214 };
215 return de::getSizedArrayElement<ATOMIC_OP_LAST>(s_names, op);
216 }
217
218 enum
219 {
220 NUM_ELEMENTS = 32
221 };
222
223 enum DataType
224 {
225 DATA_TYPE_FLOAT16 = 0,
226 DATA_TYPE_INT32,
227 DATA_TYPE_UINT32,
228 DATA_TYPE_FLOAT32,
229 DATA_TYPE_INT64,
230 DATA_TYPE_UINT64,
231 DATA_TYPE_FLOAT64,
232
233 DATA_TYPE_LAST
234 };
235
dataType2Str(DataType type)236 std::string dataType2Str(DataType type)
237 {
238 static const char* const s_names[] =
239 {
240 "float16_t",
241 "int",
242 "uint",
243 "float",
244 "int64_t",
245 "uint64_t",
246 "double",
247 };
248 return de::getSizedArrayElement<DATA_TYPE_LAST>(s_names, type);
249 }
250
251 class BufferInterface
252 {
253 public:
254 virtual void setBuffer(void* ptr) = 0;
255
256 virtual size_t bufferSize() = 0;
257
258 virtual void fillWithTestData(de::Random &rnd) = 0;
259
260 virtual void checkResults(tcu::ResultCollector& resultCollector) = 0;
261
~BufferInterface()262 virtual ~BufferInterface() {}
263 };
264
265 template<typename dataTypeT>
266 class TestBuffer : public BufferInterface
267 {
268 public:
269
TestBuffer(AtomicOperation atomicOp)270 TestBuffer(AtomicOperation atomicOp)
271 : m_atomicOp(atomicOp)
272 {}
273
274 template<typename T>
275 struct BufferData
276 {
277 // Use half the number of elements for inout to cause overlap between atomic operations.
278 // Each inout element at index i will have two atomic operations using input from
279 // indices i and i + NUM_ELEMENTS / 2.
280 T inout[NUM_ELEMENTS / 2];
281 T input[NUM_ELEMENTS];
282 T compare[NUM_ELEMENTS];
283 T output[NUM_ELEMENTS];
284 T invocationHitCount[NUM_ELEMENTS];
285 deInt32 index;
286 };
287
setBuffer(void * ptr)288 virtual void setBuffer(void* ptr)
289 {
290 m_ptr = static_cast<BufferData<dataTypeT>*>(ptr);
291 }
292
bufferSize()293 virtual size_t bufferSize()
294 {
295 return sizeof(BufferData<dataTypeT>);
296 }
297
fillWithTestData(de::Random & rnd)298 virtual void fillWithTestData(de::Random &rnd)
299 {
300 dataTypeT pattern;
301 deMemset(&pattern, 0xcd, sizeof(dataTypeT));
302
303 for (int i = 0; i < NUM_ELEMENTS / 2; i++)
304 {
305 m_ptr->inout[i] = static_cast<dataTypeT>(rnd.getUint64());
306 // The first half of compare elements match with every even index.
307 // The second half matches with odd indices. This causes the
308 // overlapping operations to only select one.
309 m_ptr->compare[i] = m_ptr->inout[i] + (i % 2);
310 m_ptr->compare[i + NUM_ELEMENTS / 2] = m_ptr->inout[i] + 1 - (i % 2);
311 }
312 for (int i = 0; i < NUM_ELEMENTS; i++)
313 {
314 m_ptr->input[i] = static_cast<dataTypeT>(rnd.getUint64());
315 m_ptr->output[i] = pattern;
316 m_ptr->invocationHitCount[i] = 0;
317 }
318 m_ptr->index = 0;
319
320 // Take a copy to be used when calculating expected values.
321 m_original = *m_ptr;
322 }
323
checkResults(tcu::ResultCollector & resultCollector)324 virtual void checkResults(tcu::ResultCollector& resultCollector)
325 {
326 checkOperation(m_original, *m_ptr, resultCollector);
327 }
328
329 template<typename T>
330 struct Expected
331 {
332 T m_inout;
333 T m_output[2];
334
Expectedvkt::shaderexecutor::__anon46bf41aa0111::TestBuffer::Expected335 Expected (T inout, T output0, T output1)
336 : m_inout(inout)
337 {
338 m_output[0] = output0;
339 m_output[1] = output1;
340 }
341
comparevkt::shaderexecutor::__anon46bf41aa0111::TestBuffer::Expected342 bool compare (T inout, T output0, T output1)
343 {
344 return (deMemCmp((const void*)&m_inout, (const void*)&inout, sizeof(inout)) == 0
345 && deMemCmp((const void*)&m_output[0], (const void*)&output0, sizeof(output0)) == 0
346 && deMemCmp((const void*)&m_output[1], (const void*)&output1, sizeof(output1)) == 0);
347 }
348 };
349
350 void checkOperation (const BufferData<dataTypeT>& original,
351 const BufferData<dataTypeT>& result,
352 tcu::ResultCollector& resultCollector);
353
354 const AtomicOperation m_atomicOp;
355
356 BufferData<dataTypeT>* m_ptr;
357 BufferData<dataTypeT> m_original;
358
359 };
360
361 template<typename T>
nanSafeSloppyEquals(T x,T y)362 bool nanSafeSloppyEquals(T x, T y)
363 {
364 if (deIsIEEENaN(x) && deIsIEEENaN(y))
365 return true;
366
367 if (deIsIEEENaN(x) || deIsIEEENaN(y))
368 return false;
369
370 return fabs(deToDouble(x) - deToDouble(y)) < 0.00001;
371 }
372
373 template<typename dataTypeT>
374 class TestBufferFloatingPoint : public BufferInterface
375 {
376 public:
377
TestBufferFloatingPoint(AtomicOperation atomicOp)378 TestBufferFloatingPoint(AtomicOperation atomicOp)
379 : m_atomicOp(atomicOp)
380 {}
381
382 template<typename T>
383 struct BufferDataFloatingPoint
384 {
385 // Use half the number of elements for inout to cause overlap between atomic operations.
386 // Each inout element at index i will have two atomic operations using input from
387 // indices i and i + NUM_ELEMENTS / 2.
388 T inout[NUM_ELEMENTS / 2];
389 T input[NUM_ELEMENTS];
390 T compare[NUM_ELEMENTS];
391 T output[NUM_ELEMENTS];
392 deInt32 invocationHitCount[NUM_ELEMENTS];
393 deInt32 index;
394 };
395
setBuffer(void * ptr)396 virtual void setBuffer(void* ptr)
397 {
398 m_ptr = static_cast<BufferDataFloatingPoint<dataTypeT>*>(ptr);
399 }
400
bufferSize()401 virtual size_t bufferSize()
402 {
403 return sizeof(BufferDataFloatingPoint<dataTypeT>);
404 }
405
fillWithTestData(de::Random & rnd)406 virtual void fillWithTestData(de::Random& rnd)
407 {
408 dataTypeT pattern;
409 deMemset(&pattern, 0xcd, sizeof(dataTypeT));
410
411 for (int i = 0; i < NUM_ELEMENTS / 2; i++)
412 {
413 m_ptr->inout[i] = deToFloatType<dataTypeT>(rnd.getFloat());
414 // These aren't used by any of the float tests
415 m_ptr->compare[i] = deToFloatType<dataTypeT>(0.0);
416 }
417 // Add special cases for NaN and +/-0
418 // 0: min(sNaN, x)
419 m_ptr->inout[0] = deSignalingNaN<dataTypeT>();
420 // 1: min(x, sNaN)
421 m_ptr->input[1 * 2 + 0] = deSignalingNaN<dataTypeT>();
422 // 2: min(qNaN, x)
423 m_ptr->inout[2] = deQuietNaN<dataTypeT>();
424 // 3: min(x, qNaN)
425 m_ptr->input[3 * 2 + 0] = deQuietNaN<dataTypeT>();
426 // 4: min(NaN, NaN)
427 m_ptr->inout[4] = deSignalingNaN<dataTypeT>();
428 m_ptr->input[4 * 2 + 0] = deQuietNaN<dataTypeT>();
429 m_ptr->input[4 * 2 + 1] = deQuietNaN<dataTypeT>();
430 // 5: min(+0, -0)
431 m_ptr->inout[5] = deToFloatType<dataTypeT>(-0.0);
432 m_ptr->input[5 * 2 + 0] = deToFloatType<dataTypeT>(0.0);
433 m_ptr->input[5 * 2 + 1] = deToFloatType<dataTypeT>(0.0);
434
435 for (int i = 0; i < NUM_ELEMENTS; i++)
436 {
437 m_ptr->input[i] = deToFloatType<dataTypeT>(rnd.getFloat());
438 m_ptr->output[i] = pattern;
439 m_ptr->invocationHitCount[i] = 0;
440 }
441
442 m_ptr->index = 0;
443
444 // Take a copy to be used when calculating expected values.
445 m_original = *m_ptr;
446 }
447
checkResults(tcu::ResultCollector & resultCollector)448 virtual void checkResults(tcu::ResultCollector& resultCollector)
449 {
450 checkOperationFloatingPoint(m_original, *m_ptr, resultCollector);
451 }
452
453 template<typename T>
454 struct Expected
455 {
456 T m_inout;
457 T m_output[2];
458
Expectedvkt::shaderexecutor::__anon46bf41aa0111::TestBufferFloatingPoint::Expected459 Expected(T inout, T output0, T output1)
460 : m_inout(inout)
461 {
462 m_output[0] = output0;
463 m_output[1] = output1;
464 }
465
comparevkt::shaderexecutor::__anon46bf41aa0111::TestBufferFloatingPoint::Expected466 bool compare(T inout, T output0, T output1)
467 {
468 return nanSafeSloppyEquals(m_inout, inout) &&
469 nanSafeSloppyEquals(m_output[0], output0) &&
470 nanSafeSloppyEquals(m_output[1], output1);
471 }
472 };
473
474 void checkOperationFloatingPoint(const BufferDataFloatingPoint<dataTypeT>& original,
475 const BufferDataFloatingPoint<dataTypeT>& result,
476 tcu::ResultCollector& resultCollector);
477
478 const AtomicOperation m_atomicOp;
479
480 BufferDataFloatingPoint<dataTypeT>* m_ptr;
481 BufferDataFloatingPoint<dataTypeT> m_original;
482
483 };
484
createTestBuffer(DataType type,AtomicOperation atomicOp)485 static BufferInterface* createTestBuffer(DataType type, AtomicOperation atomicOp)
486 {
487 switch (type)
488 {
489 case DATA_TYPE_FLOAT16:
490 return new TestBufferFloatingPoint<deFloat16>(atomicOp);
491 case DATA_TYPE_INT32:
492 return new TestBuffer<deInt32>(atomicOp);
493 case DATA_TYPE_UINT32:
494 return new TestBuffer<deUint32>(atomicOp);
495 case DATA_TYPE_FLOAT32:
496 return new TestBufferFloatingPoint<float>(atomicOp);
497 case DATA_TYPE_INT64:
498 return new TestBuffer<deInt64>(atomicOp);
499 case DATA_TYPE_UINT64:
500 return new TestBuffer<deUint64>(atomicOp);
501 case DATA_TYPE_FLOAT64:
502 return new TestBufferFloatingPoint<double>(atomicOp);
503 default:
504 DE_ASSERT(false);
505 return DE_NULL;
506 }
507 }
508
509 // Use template to handle both signed and unsigned cases. SPIR-V should
510 // have separate operations for both.
511 template<typename T>
checkOperation(const BufferData<T> & original,const BufferData<T> & result,tcu::ResultCollector & resultCollector)512 void TestBuffer<T>::checkOperation (const BufferData<T>& original,
513 const BufferData<T>& result,
514 tcu::ResultCollector& resultCollector)
515 {
516 // originalInout = original inout
517 // input0 = input at index i
518 // iinput1 = input at index i + NUM_ELEMENTS / 2
519 //
520 // atomic operation will return the memory contents before
521 // the operation and this is stored as output. Two operations
522 // are executed for each InOut value (using input0 and input1).
523 //
524 // Since there is an overlap of two operations per each
525 // InOut element, the outcome of the resulting InOut and
526 // the outputs of the operations have two result candidates
527 // depending on the execution order. Verification passes
528 // if the results match one of these options.
529
530 for (int elementNdx = 0; elementNdx < NUM_ELEMENTS / 2; elementNdx++)
531 {
532 // Needed when reinterpeting the data as signed values.
533 const T originalInout = *reinterpret_cast<const T*>(&original.inout[elementNdx]);
534 const T input0 = *reinterpret_cast<const T*>(&original.input[elementNdx]);
535 const T input1 = *reinterpret_cast<const T*>(&original.input[elementNdx + NUM_ELEMENTS / 2]);
536
537 // Expected results are collected to this vector.
538 vector<Expected<T> > exp;
539
540 switch (m_atomicOp)
541 {
542 case ATOMIC_OP_ADD:
543 {
544 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout, originalInout + input0));
545 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout + input1, originalInout));
546 }
547 break;
548
549 case ATOMIC_OP_AND:
550 {
551 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout, originalInout & input0));
552 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout & input1, originalInout));
553 }
554 break;
555
556 case ATOMIC_OP_OR:
557 {
558 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout, originalInout | input0));
559 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout | input1, originalInout));
560 }
561 break;
562
563 case ATOMIC_OP_XOR:
564 {
565 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout, originalInout ^ input0));
566 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout ^ input1, originalInout));
567 }
568 break;
569
570 case ATOMIC_OP_MIN:
571 {
572 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), originalInout, de::min(originalInout, input0)));
573 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), de::min(originalInout, input1), originalInout));
574 }
575 break;
576
577 case ATOMIC_OP_MAX:
578 {
579 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), originalInout, de::max(originalInout, input0)));
580 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), de::max(originalInout, input1), originalInout));
581 }
582 break;
583
584 case ATOMIC_OP_EXCHANGE:
585 {
586 exp.push_back(Expected<T>(input1, originalInout, input0));
587 exp.push_back(Expected<T>(input0, input1, originalInout));
588 }
589 break;
590
591 case ATOMIC_OP_COMP_SWAP:
592 {
593 if (elementNdx % 2 == 0)
594 {
595 exp.push_back(Expected<T>(input0, originalInout, input0));
596 exp.push_back(Expected<T>(input0, originalInout, originalInout));
597 }
598 else
599 {
600 exp.push_back(Expected<T>(input1, input1, originalInout));
601 exp.push_back(Expected<T>(input1, originalInout, originalInout));
602 }
603 }
604 break;
605
606
607 default:
608 DE_FATAL("Unexpected atomic operation.");
609 break;
610 }
611
612 const T resIo = result.inout[elementNdx];
613 const T resOutput0 = result.output[elementNdx];
614 const T resOutput1 = result.output[elementNdx + NUM_ELEMENTS / 2];
615
616
617 if (!exp[0].compare(resIo, resOutput0, resOutput1) && !exp[1].compare(resIo, resOutput0, resOutput1))
618 {
619 std::ostringstream errorMessage;
620 errorMessage << "ERROR: Result value check failed at index " << elementNdx
621 << ". Expected one of the two outcomes: InOut = " << tcu::toHex(exp[0].m_inout)
622 << ", Output0 = " << tcu::toHex(exp[0].m_output[0]) << ", Output1 = "
623 << tcu::toHex(exp[0].m_output[1]) << ", or InOut = " << tcu::toHex(exp[1].m_inout)
624 << ", Output0 = " << tcu::toHex(exp[1].m_output[0]) << ", Output1 = "
625 << tcu::toHex(exp[1].m_output[1]) << ". Got: InOut = " << tcu::toHex(resIo)
626 << ", Output0 = " << tcu::toHex(resOutput0) << ", Output1 = "
627 << tcu::toHex(resOutput1) << ". Using Input0 = " << tcu::toHex(original.input[elementNdx])
628 << " and Input1 = " << tcu::toHex(original.input[elementNdx + NUM_ELEMENTS / 2]) << ".";
629
630 resultCollector.fail(errorMessage.str());
631 }
632 }
633 }
634
635 template<typename T>
handleExceptionalFloatMinMaxValues(vector<T> & values,T x,T y)636 void handleExceptionalFloatMinMaxValues(vector<T> &values, T x, T y)
637 {
638
639 if (deIsSignalingNaN(x) && deIsSignalingNaN(y))
640 {
641 values.push_back(deQuietNaN<T>());
642 values.push_back(deSignalingNaN<T>());
643 }
644 else if (deIsSignalingNaN(x))
645 {
646 values.push_back(deQuietNaN<T>());
647 values.push_back(deSignalingNaN<T>());
648 if (!deIsIEEENaN(y))
649 values.push_back(y);
650 }
651 else if (deIsSignalingNaN(y))
652 {
653 values.push_back(deQuietNaN<T>());
654 values.push_back(deSignalingNaN<T>());
655 if (!deIsIEEENaN(x))
656 values.push_back(x);
657 }
658 else if (deIsIEEENaN(x) && deIsIEEENaN(y))
659 {
660 // Both quiet NaNs
661 values.push_back(deQuietNaN<T>());
662 }
663 else if (deIsIEEENaN(x))
664 {
665 // One quiet NaN and one non-NaN.
666 values.push_back(y);
667 }
668 else if (deIsIEEENaN(y))
669 {
670 // One quiet NaN and one non-NaN.
671 values.push_back(x);
672 }
673 else if ((deIsPositiveZero(x) && deIsNegativeZero(y)) || (deIsNegativeZero(x) && deIsPositiveZero(y)))
674 {
675 values.push_back(deToFloatType<T>(0.0));
676 values.push_back(deToFloatType<T>(-0.0));
677 }
678 }
679
680 template<typename T>
floatAdd(T x,T y)681 T floatAdd(T x, T y)
682 {
683 if (deIsIEEENaN(x) || deIsIEEENaN(y))
684 return deQuietNaN<T>();
685 return deToFloatType<T>(deToDouble(x) + deToDouble(y));
686 }
687
688 template<typename T>
floatMinValues(T x,T y)689 vector<T> floatMinValues(T x, T y)
690 {
691 vector<T> values;
692 handleExceptionalFloatMinMaxValues(values, x, y);
693 if (values.empty())
694 {
695 values.push_back(deToDouble(x) < deToDouble(y) ? x : y);
696 }
697 return values;
698 }
699
700 template<typename T>
floatMaxValues(T x,T y)701 vector<T> floatMaxValues(T x, T y)
702 {
703 vector<T> values;
704 handleExceptionalFloatMinMaxValues(values, x, y);
705 if (values.empty())
706 {
707 values.push_back(deToDouble(x) > deToDouble(y) ? x : y);
708 }
709 return values;
710 }
711
712 // Use template to handle both float and double cases. SPIR-V should
713 // have separate operations for both.
714 template<typename T>
checkOperationFloatingPoint(const BufferDataFloatingPoint<T> & original,const BufferDataFloatingPoint<T> & result,tcu::ResultCollector & resultCollector)715 void TestBufferFloatingPoint<T>::checkOperationFloatingPoint(const BufferDataFloatingPoint<T>& original,
716 const BufferDataFloatingPoint<T>& result,
717 tcu::ResultCollector& resultCollector)
718 {
719 // originalInout = original inout
720 // input0 = input at index i
721 // iinput1 = input at index i + NUM_ELEMENTS / 2
722 //
723 // atomic operation will return the memory contents before
724 // the operation and this is stored as output. Two operations
725 // are executed for each InOut value (using input0 and input1).
726 //
727 // Since there is an overlap of two operations per each
728 // InOut element, the outcome of the resulting InOut and
729 // the outputs of the operations have two result candidates
730 // depending on the execution order. Verification passes
731 // if the results match one of these options.
732
733 for (int elementNdx = 0; elementNdx < NUM_ELEMENTS / 2; elementNdx++)
734 {
735 // Needed when reinterpeting the data as signed values.
736 const T originalInout = *reinterpret_cast<const T*>(&original.inout[elementNdx]);
737 const T input0 = *reinterpret_cast<const T*>(&original.input[elementNdx]);
738 const T input1 = *reinterpret_cast<const T*>(&original.input[elementNdx + NUM_ELEMENTS / 2]);
739
740 // Expected results are collected to this vector.
741 vector<Expected<T> > exp;
742
743 switch (m_atomicOp)
744 {
745 case ATOMIC_OP_ADD:
746 {
747 exp.push_back(Expected<T>(floatAdd(floatAdd(originalInout, input0), input1), originalInout, floatAdd(originalInout, input0)));
748 exp.push_back(Expected<T>(floatAdd(floatAdd(originalInout, input0), input1), floatAdd(originalInout, input1), originalInout));
749 }
750 break;
751
752 case ATOMIC_OP_MIN:
753 {
754 // The case where input0 is combined first
755 vector<T> minOriginalAndInput0 = floatMinValues(originalInout, input0);
756 for (T x : minOriginalAndInput0)
757 {
758 vector<T> minAll = floatMinValues(x, input1);
759 for (T y : minAll)
760 {
761 exp.push_back(Expected<T>(y, originalInout, x));
762 }
763 }
764
765 // The case where input1 is combined first
766 vector<T> minOriginalAndInput1 = floatMinValues(originalInout, input1);
767 for (T x : minOriginalAndInput1)
768 {
769 vector<T> minAll = floatMinValues(x, input0);
770 for (T y : minAll)
771 {
772 exp.push_back(Expected<T>(y, x, originalInout));
773 }
774 }
775 }
776 break;
777
778 case ATOMIC_OP_MAX:
779 {
780 // The case where input0 is combined first
781 vector<T> minOriginalAndInput0 = floatMaxValues(originalInout, input0);
782 for (T x : minOriginalAndInput0)
783 {
784 vector<T> minAll = floatMaxValues(x, input1);
785 for (T y : minAll)
786 {
787 exp.push_back(Expected<T>(y, originalInout, x));
788 }
789 }
790
791 // The case where input1 is combined first
792 vector<T> minOriginalAndInput1 = floatMaxValues(originalInout, input1);
793 for (T x : minOriginalAndInput1)
794 {
795 vector<T> minAll = floatMaxValues(x, input0);
796 for (T y : minAll)
797 {
798 exp.push_back(Expected<T>(y, x, originalInout));
799 }
800 }
801 }
802 break;
803
804 case ATOMIC_OP_EXCHANGE:
805 {
806 exp.push_back(Expected<T>(input1, originalInout, input0));
807 exp.push_back(Expected<T>(input0, input1, originalInout));
808 }
809 break;
810
811 default:
812 DE_FATAL("Unexpected atomic operation.");
813 break;
814 }
815
816 const T resIo = result.inout[elementNdx];
817 const T resOutput0 = result.output[elementNdx];
818 const T resOutput1 = result.output[elementNdx + NUM_ELEMENTS / 2];
819
820
821 bool hasMatch = false;
822 for (Expected<T> e : exp)
823 {
824 if (e.compare(resIo, resOutput0, resOutput1))
825 {
826 hasMatch = true;
827 break;
828 }
829 }
830 if (!hasMatch)
831 {
832 std::ostringstream errorMessage;
833 errorMessage << "ERROR: Result value check failed at index " << elementNdx
834 << ". Expected one of the outcomes:";
835
836 bool first = true;
837 for (Expected<T> e : exp)
838 {
839 if (!first)
840 errorMessage << ", or";
841 first = false;
842
843 errorMessage << " InOut = " << e.m_inout
844 << ", Output0 = " << e.m_output[0]
845 << ", Output1 = " << e.m_output[1];
846 }
847
848 errorMessage << ". Got: InOut = " << resIo
849 << ", Output0 = " << resOutput0
850 << ", Output1 = " << resOutput1
851 << ". Using Input0 = " << original.input[elementNdx]
852 << " and Input1 = " << original.input[elementNdx + NUM_ELEMENTS / 2] << ".";
853
854 resultCollector.fail(errorMessage.str());
855 }
856 }
857 }
858
859 class AtomicOperationCaseInstance : public TestInstance
860 {
861 public:
862 AtomicOperationCaseInstance (Context& context,
863 const ShaderSpec& shaderSpec,
864 AtomicShaderType shaderType,
865 DataType dataType,
866 AtomicOperation atomicOp);
867
868 virtual tcu::TestStatus iterate (void);
869
870 private:
871 const ShaderSpec& m_shaderSpec;
872 AtomicShaderType m_shaderType;
873 const DataType m_dataType;
874 AtomicOperation m_atomicOp;
875
876 };
877
AtomicOperationCaseInstance(Context & context,const ShaderSpec & shaderSpec,AtomicShaderType shaderType,DataType dataType,AtomicOperation atomicOp)878 AtomicOperationCaseInstance::AtomicOperationCaseInstance (Context& context,
879 const ShaderSpec& shaderSpec,
880 AtomicShaderType shaderType,
881 DataType dataType,
882 AtomicOperation atomicOp)
883 : TestInstance (context)
884 , m_shaderSpec (shaderSpec)
885 , m_shaderType (shaderType)
886 , m_dataType (dataType)
887 , m_atomicOp (atomicOp)
888 {
889 }
890
iterate(void)891 tcu::TestStatus AtomicOperationCaseInstance::iterate(void)
892 {
893 de::UniquePtr<BufferInterface> testBuffer (createTestBuffer(m_dataType, m_atomicOp));
894 tcu::TestLog& log = m_context.getTestContext().getLog();
895 const DeviceInterface& vkd = m_context.getDeviceInterface();
896 const VkDevice device = m_context.getDevice();
897 de::Random rnd (0x62a15e34);
898 const bool useRef = (m_shaderType.getMemoryType() == AtomicMemoryType::REFERENCE);
899 const VkDescriptorType descType = (useRef ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
900 const VkBufferUsageFlags usageFlags = (VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | (useRef ? static_cast<VkBufferUsageFlags>(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) : 0u));
901
902 // The main buffer will hold test data. When using buffer references, the buffer's address will be indirectly passed as part of
903 // a uniform buffer. If not, it will be passed directly as a descriptor.
904 Buffer buffer (m_context, usageFlags, testBuffer->bufferSize(), useRef);
905 std::unique_ptr<Buffer> auxBuffer;
906
907 if (useRef)
908 {
909 // Pass the main buffer address inside a uniform buffer.
910 const VkBufferDeviceAddressInfo addressInfo =
911 {
912 VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, // VkStructureType sType;
913 nullptr, // const void* pNext;
914 buffer.getBuffer(), // VkBuffer buffer;
915 };
916 const auto address = vkd.getBufferDeviceAddress(device, &addressInfo);
917
918 auxBuffer.reset(new Buffer(m_context, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(address), false));
919 deMemcpy(auxBuffer->getHostPtr(), &address, sizeof(address));
920 auxBuffer->flush();
921 }
922
923 testBuffer->setBuffer(buffer.getHostPtr());
924 testBuffer->fillWithTestData(rnd);
925
926 buffer.flush();
927
928 Move<VkDescriptorSetLayout> extraResourcesLayout;
929 Move<VkDescriptorPool> extraResourcesSetPool;
930 Move<VkDescriptorSet> extraResourcesSet;
931
932 const VkDescriptorSetLayoutBinding bindings[] =
933 {
934 { 0u, descType, 1, VK_SHADER_STAGE_ALL, DE_NULL }
935 };
936
937 const VkDescriptorSetLayoutCreateInfo layoutInfo =
938 {
939 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
940 DE_NULL,
941 (VkDescriptorSetLayoutCreateFlags)0u,
942 DE_LENGTH_OF_ARRAY(bindings),
943 bindings
944 };
945
946 extraResourcesLayout = createDescriptorSetLayout(vkd, device, &layoutInfo);
947
948 const VkDescriptorPoolSize poolSizes[] =
949 {
950 { descType, 1u }
951 };
952
953 const VkDescriptorPoolCreateInfo poolInfo =
954 {
955 VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
956 DE_NULL,
957 (VkDescriptorPoolCreateFlags)VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
958 1u, // maxSets
959 DE_LENGTH_OF_ARRAY(poolSizes),
960 poolSizes
961 };
962
963 extraResourcesSetPool = createDescriptorPool(vkd, device, &poolInfo);
964
965 const VkDescriptorSetAllocateInfo allocInfo =
966 {
967 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
968 DE_NULL,
969 *extraResourcesSetPool,
970 1u,
971 &extraResourcesLayout.get()
972 };
973
974 extraResourcesSet = allocateDescriptorSet(vkd, device, &allocInfo);
975
976 VkDescriptorBufferInfo bufferInfo;
977 bufferInfo.buffer = (useRef ? auxBuffer->getBuffer() : buffer.getBuffer());
978 bufferInfo.offset = 0u;
979 bufferInfo.range = VK_WHOLE_SIZE;
980
981 const VkWriteDescriptorSet descriptorWrite =
982 {
983 VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
984 DE_NULL,
985 *extraResourcesSet,
986 0u, // dstBinding
987 0u, // dstArrayElement
988 1u,
989 descType,
990 (const VkDescriptorImageInfo*)DE_NULL,
991 &bufferInfo,
992 (const VkBufferView*)DE_NULL
993 };
994
995 vkd.updateDescriptorSets(device, 1u, &descriptorWrite, 0u, DE_NULL);
996
997 // Storage for output varying data.
998 std::vector<deUint32> outputs (NUM_ELEMENTS);
999 std::vector<void*> outputPtr (NUM_ELEMENTS);
1000
1001 for (size_t i = 0; i < NUM_ELEMENTS; i++)
1002 {
1003 outputs[i] = 0xcdcdcdcd;
1004 outputPtr[i] = &outputs[i];
1005 }
1006
1007 const int numWorkGroups = (m_shaderType.isSharedLike() ? 1 : static_cast<int>(NUM_ELEMENTS));
1008 UniquePtr<ShaderExecutor> executor (createExecutor(m_context, m_shaderType.getType(), m_shaderSpec, *extraResourcesLayout));
1009
1010 executor->execute(numWorkGroups, DE_NULL, &outputPtr[0], *extraResourcesSet);
1011 buffer.invalidate();
1012
1013 tcu::ResultCollector resultCollector(log);
1014
1015 // Check the results of the atomic operation
1016 testBuffer->checkResults(resultCollector);
1017
1018 return tcu::TestStatus(resultCollector.getResult(), resultCollector.getMessage());
1019 }
1020
1021 class AtomicOperationCase : public TestCase
1022 {
1023 public:
1024 AtomicOperationCase (tcu::TestContext& testCtx,
1025 const char* name,
1026 AtomicShaderType type,
1027 DataType dataType,
1028 AtomicOperation atomicOp);
1029 virtual ~AtomicOperationCase (void);
1030
1031 virtual TestInstance* createInstance (Context& ctx) const;
1032 virtual void checkSupport (Context& ctx) const;
initPrograms(vk::SourceCollections & programCollection) const1033 virtual void initPrograms (vk::SourceCollections& programCollection) const
1034 {
1035 const bool useSpv14 = m_shaderType.isMeshShadingStage();
1036 const auto spvVersion = (useSpv14 ? vk::SPIRV_VERSION_1_4 : vk::SPIRV_VERSION_1_0);
1037 const ShaderBuildOptions buildOptions (programCollection.usedVulkanVersion, spvVersion, 0u, useSpv14);
1038 ShaderSpec sourcesSpec (m_shaderSpec);
1039
1040 sourcesSpec.buildOptions = buildOptions;
1041 generateSources(m_shaderType.getType(), sourcesSpec, programCollection);
1042 }
1043
1044 private:
1045
1046 void createShaderSpec();
1047 ShaderSpec m_shaderSpec;
1048 const AtomicShaderType m_shaderType;
1049 const DataType m_dataType;
1050 const AtomicOperation m_atomicOp;
1051 };
1052
AtomicOperationCase(tcu::TestContext & testCtx,const char * name,AtomicShaderType shaderType,DataType dataType,AtomicOperation atomicOp)1053 AtomicOperationCase::AtomicOperationCase (tcu::TestContext& testCtx,
1054 const char* name,
1055 AtomicShaderType shaderType,
1056 DataType dataType,
1057 AtomicOperation atomicOp)
1058 : TestCase (testCtx, name)
1059 , m_shaderType (shaderType)
1060 , m_dataType (dataType)
1061 , m_atomicOp (atomicOp)
1062 {
1063 createShaderSpec();
1064 init();
1065 }
1066
~AtomicOperationCase(void)1067 AtomicOperationCase::~AtomicOperationCase (void)
1068 {
1069 }
1070
createInstance(Context & ctx) const1071 TestInstance* AtomicOperationCase::createInstance (Context& ctx) const
1072 {
1073 return new AtomicOperationCaseInstance(ctx, m_shaderSpec, m_shaderType, m_dataType, m_atomicOp);
1074 }
1075
checkSupport(Context & ctx) const1076 void AtomicOperationCase::checkSupport (Context& ctx) const
1077 {
1078 if ((m_dataType == DATA_TYPE_INT64) || (m_dataType == DATA_TYPE_UINT64))
1079 {
1080 ctx.requireDeviceFunctionality("VK_KHR_shader_atomic_int64");
1081
1082 const auto atomicInt64Features = ctx.getShaderAtomicInt64Features();
1083 const bool isSharedMemory = m_shaderType.isSharedLike();
1084
1085 if (!isSharedMemory && atomicInt64Features.shaderBufferInt64Atomics == VK_FALSE)
1086 {
1087 TCU_THROW(NotSupportedError, "VkShaderAtomicInt64: 64-bit integer atomic operations not supported for buffers");
1088 }
1089 if (isSharedMemory && atomicInt64Features.shaderSharedInt64Atomics == VK_FALSE)
1090 {
1091 TCU_THROW(NotSupportedError, "VkShaderAtomicInt64: 64-bit integer atomic operations not supported for shared memory");
1092 }
1093 }
1094
1095 if (m_dataType == DATA_TYPE_FLOAT16)
1096 {
1097 ctx.requireDeviceFunctionality("VK_EXT_shader_atomic_float2");
1098 #ifndef CTS_USES_VULKANSC
1099 if (m_atomicOp == ATOMIC_OP_ADD)
1100 {
1101 if (m_shaderType.isSharedLike())
1102 {
1103 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderSharedFloat16AtomicAdd)
1104 {
1105 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat16: 16-bit floating point shared add atomic operation not supported");
1106 }
1107 }
1108 else
1109 {
1110 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderBufferFloat16AtomicAdd)
1111 {
1112 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat16: 16-bit floating point buffer add atomic operation not supported");
1113 }
1114 }
1115 }
1116 if (m_atomicOp == ATOMIC_OP_MIN || m_atomicOp == ATOMIC_OP_MAX)
1117 {
1118 if (m_shaderType.isSharedLike())
1119 {
1120 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderSharedFloat16AtomicMinMax)
1121 {
1122 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat16: 16-bit floating point shared min/max atomic operation not supported");
1123 }
1124 }
1125 else
1126 {
1127 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderBufferFloat16AtomicMinMax)
1128 {
1129 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat16: 16-bit floating point buffer min/max atomic operation not supported");
1130 }
1131 }
1132 }
1133 if (m_atomicOp == ATOMIC_OP_EXCHANGE)
1134 {
1135 if (m_shaderType.isSharedLike())
1136 {
1137 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderSharedFloat16Atomics)
1138 {
1139 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat16: 16-bit floating point shared atomic operations not supported");
1140 }
1141 }
1142 else
1143 {
1144 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderBufferFloat16Atomics)
1145 {
1146 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat16: 16-bit floating point buffer atomic operations not supported");
1147 }
1148 }
1149 }
1150 #endif // CTS_USES_VULKANSC
1151 }
1152
1153 if (m_dataType == DATA_TYPE_FLOAT32)
1154 {
1155 ctx.requireDeviceFunctionality("VK_EXT_shader_atomic_float");
1156 if (m_atomicOp == ATOMIC_OP_ADD)
1157 {
1158 if (m_shaderType.isSharedLike())
1159 {
1160 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderSharedFloat32AtomicAdd)
1161 {
1162 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat32: 32-bit floating point shared add atomic operation not supported");
1163 }
1164 }
1165 else
1166 {
1167 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderBufferFloat32AtomicAdd)
1168 {
1169 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat32: 32-bit floating point buffer add atomic operation not supported");
1170 }
1171 }
1172 }
1173 if (m_atomicOp == ATOMIC_OP_MIN || m_atomicOp == ATOMIC_OP_MAX)
1174 {
1175 ctx.requireDeviceFunctionality("VK_EXT_shader_atomic_float2");
1176 #ifndef CTS_USES_VULKANSC
1177 if (m_shaderType.isSharedLike())
1178 {
1179 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderSharedFloat32AtomicMinMax)
1180 {
1181 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat32: 32-bit floating point shared min/max atomic operation not supported");
1182 }
1183 }
1184 else
1185 {
1186 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderBufferFloat32AtomicMinMax)
1187 {
1188 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat32: 32-bit floating point buffer min/max atomic operation not supported");
1189 }
1190 }
1191 #endif // CTS_USES_VULKANSC
1192 }
1193 if (m_atomicOp == ATOMIC_OP_EXCHANGE)
1194 {
1195 if (m_shaderType.isSharedLike())
1196 {
1197 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderSharedFloat32Atomics)
1198 {
1199 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat32: 32-bit floating point shared atomic operations not supported");
1200 }
1201 }
1202 else
1203 {
1204 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderBufferFloat32Atomics)
1205 {
1206 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat32: 32-bit floating point buffer atomic operations not supported");
1207 }
1208 }
1209 }
1210 }
1211
1212 if (m_dataType == DATA_TYPE_FLOAT64)
1213 {
1214 ctx.requireDeviceFunctionality("VK_EXT_shader_atomic_float");
1215 if (m_atomicOp == ATOMIC_OP_ADD)
1216 {
1217 if (m_shaderType.isSharedLike())
1218 {
1219 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderSharedFloat64AtomicAdd)
1220 {
1221 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat64: 64-bit floating point shared add atomic operation not supported");
1222 }
1223 }
1224 else
1225 {
1226 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderBufferFloat64AtomicAdd)
1227 {
1228 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat64: 64-bit floating point buffer add atomic operation not supported");
1229 }
1230 }
1231 }
1232 if (m_atomicOp == ATOMIC_OP_MIN || m_atomicOp == ATOMIC_OP_MAX)
1233 {
1234 ctx.requireDeviceFunctionality("VK_EXT_shader_atomic_float2");
1235 #ifndef CTS_USES_VULKANSC
1236 if (m_shaderType.isSharedLike())
1237 {
1238 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderSharedFloat64AtomicMinMax)
1239 {
1240 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat64: 64-bit floating point shared min/max atomic operation not supported");
1241 }
1242 }
1243 else
1244 {
1245 if (!ctx.getShaderAtomicFloat2FeaturesEXT().shaderBufferFloat64AtomicMinMax)
1246 {
1247 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat64: 64-bit floating point buffer min/max atomic operation not supported");
1248 }
1249 }
1250 #endif // CTS_USES_VULKANSC
1251 }
1252 if (m_atomicOp == ATOMIC_OP_EXCHANGE)
1253 {
1254 if (m_shaderType.isSharedLike())
1255 {
1256 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderSharedFloat64Atomics)
1257 {
1258 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat64: 64-bit floating point shared atomic operations not supported");
1259 }
1260 }
1261 else
1262 {
1263 if (!ctx.getShaderAtomicFloatFeaturesEXT().shaderBufferFloat64Atomics)
1264 {
1265 TCU_THROW(NotSupportedError, "VkShaderAtomicFloat64: 64-bit floating point buffer atomic operations not supported");
1266 }
1267 }
1268 }
1269 }
1270
1271 if (m_shaderType.getMemoryType() == AtomicMemoryType::REFERENCE)
1272 {
1273 ctx.requireDeviceFunctionality("VK_KHR_buffer_device_address");
1274 }
1275
1276 checkSupportShader(ctx, m_shaderType.getType());
1277 }
1278
createShaderSpec(void)1279 void AtomicOperationCase::createShaderSpec (void)
1280 {
1281 const AtomicMemoryType memoryType = m_shaderType.getMemoryType();
1282 const bool isSharedLike = m_shaderType.isSharedLike();
1283
1284 // Global declarations.
1285 std::ostringstream shaderTemplateGlobalStream;
1286
1287 // Structure in use for atomic operations.
1288 shaderTemplateGlobalStream
1289 << "${EXTENSIONS}\n"
1290 << "\n"
1291 << "struct AtomicStruct\n"
1292 << "{\n"
1293 << " ${DATATYPE} inoutValues[${N}/2];\n"
1294 << " ${DATATYPE} inputValues[${N}];\n"
1295 << " ${DATATYPE} compareValues[${N}];\n"
1296 << " ${DATATYPE} outputValues[${N}];\n"
1297 << " int invocationHitCount[${N}];\n"
1298 << " int index;\n"
1299 << "};\n"
1300 << "\n"
1301 ;
1302
1303 // The name dance and declarations below will make sure the structure that will be used with atomic operations can be accessed
1304 // as "buf.data", which is the name used in the atomic operation statements.
1305 //
1306 // * When using a buffer directly, RESULT_BUFFER_NAME will be "buf" and the inner struct will be "data".
1307 // * When using a workgroup-shared global variable, the "data" struct will be nested in an auxiliar "buf" struct.
1308 // * When using buffer references, the uniform buffer reference will be called "buf" and its contents "data".
1309 //
1310 if (memoryType != AtomicMemoryType::REFERENCE)
1311 {
1312 shaderTemplateGlobalStream
1313 << "layout (set = ${SETIDX}, binding = 0) buffer AtomicBuffer {\n"
1314 << " AtomicStruct data;\n"
1315 << "} ${RESULT_BUFFER_NAME};\n"
1316 << "\n"
1317 ;
1318
1319 // When using global shared memory in the compute, task or mesh variants, invocations will use a shared global structure
1320 // instead of a descriptor set as the sources and results of each tested operation.
1321 if (memoryType == AtomicMemoryType::SHARED)
1322 {
1323 shaderTemplateGlobalStream
1324 << "shared struct { AtomicStruct data; } buf;\n"
1325 << "\n"
1326 ;
1327 }
1328 else if (memoryType == AtomicMemoryType::PAYLOAD)
1329 {
1330 shaderTemplateGlobalStream
1331 << "struct TaskData { AtomicStruct data; };\n"
1332 << "taskPayloadSharedEXT TaskData buf;\n"
1333 ;
1334 }
1335 }
1336 else
1337 {
1338 shaderTemplateGlobalStream
1339 << "layout (buffer_reference) buffer AtomicBuffer {\n"
1340 << " AtomicStruct data;\n"
1341 << "};\n"
1342 << "\n"
1343 << "layout (set = ${SETIDX}, binding = 0) uniform References {\n"
1344 << " AtomicBuffer buf;\n"
1345 << "};\n"
1346 << "\n"
1347 ;
1348 }
1349
1350 const auto shaderTemplateGlobalString = shaderTemplateGlobalStream.str();
1351 const tcu::StringTemplate shaderTemplateGlobal (shaderTemplateGlobalString);
1352
1353 // Shader body for the non-vertex case.
1354 std::ostringstream nonVertexShaderTemplateStream;
1355
1356 if (isSharedLike)
1357 {
1358 // Invocation zero will initialize the shared structure from the descriptor set.
1359 nonVertexShaderTemplateStream
1360 << "if (gl_LocalInvocationIndex == 0u)\n"
1361 << "{\n"
1362 << " buf.data = ${RESULT_BUFFER_NAME}.data;\n"
1363 << "}\n"
1364 << "barrier();\n"
1365 ;
1366 }
1367
1368 if (m_shaderType.getType() == glu::SHADERTYPE_FRAGMENT)
1369 {
1370 nonVertexShaderTemplateStream
1371 << "if (!gl_HelperInvocation) {\n"
1372 << " int idx = atomicAdd(buf.data.index, 1);\n"
1373 << " buf.data.outputValues[idx] = ${ATOMICOP}(buf.data.inoutValues[idx % (${N}/2)], ${COMPARE_ARG}buf.data.inputValues[idx]);\n"
1374 << "}\n"
1375 ;
1376 }
1377 else
1378 {
1379 nonVertexShaderTemplateStream
1380 << "if (atomicAdd(buf.data.invocationHitCount[0], 1) < ${N})\n"
1381 << "{\n"
1382 << " int idx = atomicAdd(buf.data.index, 1);\n"
1383 << " buf.data.outputValues[idx] = ${ATOMICOP}(buf.data.inoutValues[idx % (${N}/2)], ${COMPARE_ARG}buf.data.inputValues[idx]);\n"
1384 << "}\n"
1385 ;
1386 }
1387
1388 if (isSharedLike)
1389 {
1390 // Invocation zero will copy results back to the descriptor set.
1391 nonVertexShaderTemplateStream
1392 << "barrier();\n"
1393 << "if (gl_LocalInvocationIndex == 0u)\n"
1394 << "{\n"
1395 << " ${RESULT_BUFFER_NAME}.data = buf.data;\n"
1396 << "}\n"
1397 ;
1398 }
1399
1400 const auto nonVertexShaderTemplateStreamStr = nonVertexShaderTemplateStream.str();
1401 const tcu::StringTemplate nonVertexShaderTemplateSrc (nonVertexShaderTemplateStreamStr);
1402
1403 // Shader body for the vertex case.
1404 const tcu::StringTemplate vertexShaderTemplateSrc(
1405 "int idx = gl_VertexIndex;\n"
1406 "if (atomicAdd(buf.data.invocationHitCount[idx], 1) == 0)\n"
1407 "{\n"
1408 " buf.data.outputValues[idx] = ${ATOMICOP}(buf.data.inoutValues[idx % (${N}/2)], ${COMPARE_ARG}buf.data.inputValues[idx]);\n"
1409 "}\n");
1410
1411 // Extensions.
1412 std::ostringstream extensions;
1413
1414 if ((m_dataType == DATA_TYPE_INT64) || (m_dataType == DATA_TYPE_UINT64))
1415 {
1416 extensions
1417 << "#extension GL_EXT_shader_explicit_arithmetic_types_int64 : enable\n"
1418 << "#extension GL_EXT_shader_atomic_int64 : enable\n"
1419 ;
1420 }
1421 else if ((m_dataType == DATA_TYPE_FLOAT16) || (m_dataType == DATA_TYPE_FLOAT32) || (m_dataType == DATA_TYPE_FLOAT64))
1422 {
1423 extensions
1424 << "#extension GL_EXT_shader_explicit_arithmetic_types_float16 : enable\n"
1425 << "#extension GL_EXT_shader_atomic_float : enable\n"
1426 << "#extension GL_EXT_shader_atomic_float2 : enable\n"
1427 << "#extension GL_KHR_memory_scope_semantics : enable\n"
1428 ;
1429 }
1430
1431 if (memoryType == AtomicMemoryType::REFERENCE)
1432 {
1433 extensions << "#extension GL_EXT_buffer_reference : require\n";
1434 }
1435
1436 // Specializations.
1437 std::map<std::string, std::string> specializations;
1438
1439 specializations["EXTENSIONS"] = extensions.str();
1440 specializations["DATATYPE"] = dataType2Str(m_dataType);
1441 specializations["ATOMICOP"] = atomicOp2Str(m_atomicOp);
1442 specializations["SETIDX"] = de::toString((int)EXTRA_RESOURCES_DESCRIPTOR_SET_INDEX);
1443 specializations["N"] = de::toString((int)NUM_ELEMENTS);
1444 specializations["COMPARE_ARG"] = ((m_atomicOp == ATOMIC_OP_COMP_SWAP) ? "buf.data.compareValues[idx], " : "");
1445 specializations["RESULT_BUFFER_NAME"] = (isSharedLike ? "result" : "buf");
1446
1447 // Shader spec.
1448 m_shaderSpec.outputs.push_back(Symbol("outData", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP)));
1449 m_shaderSpec.glslVersion = glu::GLSL_VERSION_450;
1450 m_shaderSpec.globalDeclarations = shaderTemplateGlobal.specialize(specializations);
1451 m_shaderSpec.source = ((m_shaderType.getType() == glu::SHADERTYPE_VERTEX)
1452 ? vertexShaderTemplateSrc.specialize(specializations)
1453 : nonVertexShaderTemplateSrc.specialize(specializations));
1454
1455 if (isSharedLike)
1456 {
1457 // When using global shared memory, use a single workgroup and an appropriate number of local invocations.
1458 m_shaderSpec.localSizeX = static_cast<int>(NUM_ELEMENTS);
1459 }
1460 }
1461
addAtomicOperationTests(tcu::TestCaseGroup * atomicOperationTestsGroup)1462 void addAtomicOperationTests (tcu::TestCaseGroup* atomicOperationTestsGroup)
1463 {
1464 tcu::TestContext& testCtx = atomicOperationTestsGroup->getTestContext();
1465
1466 static const struct
1467 {
1468 glu::ShaderType type;
1469 const char* name;
1470 } shaderTypes[] =
1471 {
1472 { glu::SHADERTYPE_VERTEX, "vertex" },
1473 { glu::SHADERTYPE_FRAGMENT, "fragment" },
1474 { glu::SHADERTYPE_GEOMETRY, "geometry" },
1475 { glu::SHADERTYPE_TESSELLATION_CONTROL, "tess_ctrl" },
1476 { glu::SHADERTYPE_TESSELLATION_EVALUATION, "tess_eval" },
1477 { glu::SHADERTYPE_COMPUTE, "compute" },
1478 { glu::SHADERTYPE_TASK, "task" },
1479 { glu::SHADERTYPE_MESH, "mesh" },
1480 };
1481
1482 static const struct
1483 {
1484 AtomicMemoryType type;
1485 const char* suffix;
1486 } kMemoryTypes[] =
1487 {
1488 { AtomicMemoryType::BUFFER, "" },
1489 { AtomicMemoryType::SHARED, "_shared" },
1490 { AtomicMemoryType::REFERENCE, "_reference" },
1491 { AtomicMemoryType::PAYLOAD, "_payload" },
1492 };
1493
1494 static const struct
1495 {
1496 DataType dataType;
1497 const char* name;
1498 } dataSign[] =
1499 {
1500 #ifndef CTS_USES_VULKANSC
1501 // Tests using 16-bit float data
1502 { DATA_TYPE_FLOAT16,"float16"},
1503 #endif // CTS_USES_VULKANSC
1504 // Tests using signed data (int)
1505 { DATA_TYPE_INT32, "signed"},
1506 // Tests using unsigned data (uint)
1507 { DATA_TYPE_UINT32, "unsigned"},
1508 // Tests using 32-bit float data
1509 { DATA_TYPE_FLOAT32,"float32"},
1510 // Tests using 64 bit signed data (int64)
1511 { DATA_TYPE_INT64, "signed64bit"},
1512 // Tests using 64 bit unsigned data (uint64)
1513 { DATA_TYPE_UINT64, "unsigned64bit"},
1514 // Tests using 64-bit float data)
1515 { DATA_TYPE_FLOAT64,"float64"}
1516 };
1517
1518 static const struct
1519 {
1520 AtomicOperation value;
1521 const char* name;
1522 } atomicOp[] =
1523 {
1524 { ATOMIC_OP_EXCHANGE, "exchange" },
1525 { ATOMIC_OP_COMP_SWAP, "comp_swap" },
1526 { ATOMIC_OP_ADD, "add" },
1527 { ATOMIC_OP_MIN, "min" },
1528 { ATOMIC_OP_MAX, "max" },
1529 { ATOMIC_OP_AND, "and" },
1530 { ATOMIC_OP_OR, "or" },
1531 { ATOMIC_OP_XOR, "xor" }
1532 };
1533
1534 for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(atomicOp); opNdx++)
1535 {
1536 for (int signNdx = 0; signNdx < DE_LENGTH_OF_ARRAY(dataSign); signNdx++)
1537 {
1538 for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++)
1539 {
1540 // Only ADD and EXCHANGE are supported on floating-point
1541 if (dataSign[signNdx].dataType == DATA_TYPE_FLOAT16 || dataSign[signNdx].dataType == DATA_TYPE_FLOAT32 || dataSign[signNdx].dataType == DATA_TYPE_FLOAT64)
1542 {
1543 if (atomicOp[opNdx].value != ATOMIC_OP_ADD &&
1544 #ifndef CTS_USES_VULKANSC
1545 atomicOp[opNdx].value != ATOMIC_OP_MIN &&
1546 atomicOp[opNdx].value != ATOMIC_OP_MAX &&
1547 #endif // CTS_USES_VULKANSC
1548 atomicOp[opNdx].value != ATOMIC_OP_EXCHANGE)
1549 {
1550 continue;
1551 }
1552 }
1553
1554 for (int memoryTypeNdx = 0; memoryTypeNdx < DE_LENGTH_OF_ARRAY(kMemoryTypes); ++memoryTypeNdx)
1555 {
1556 // Shared memory only available in compute, task and mesh shaders.
1557 if (kMemoryTypes[memoryTypeNdx].type == AtomicMemoryType::SHARED
1558 && shaderTypes[shaderTypeNdx].type != glu::SHADERTYPE_COMPUTE
1559 && shaderTypes[shaderTypeNdx].type != glu::SHADERTYPE_TASK
1560 && shaderTypes[shaderTypeNdx].type != glu::SHADERTYPE_MESH)
1561 continue;
1562
1563 // Payload memory is only available for atomics in task shaders (in mesh shaders it's read-only)
1564 if (kMemoryTypes[memoryTypeNdx].type == AtomicMemoryType::PAYLOAD && shaderTypes[shaderTypeNdx].type != glu::SHADERTYPE_TASK)
1565 continue;
1566
1567 const std::string name = std::string(atomicOp[opNdx].name) + "_" + std::string(dataSign[signNdx].name) + "_" + std::string(shaderTypes[shaderTypeNdx].name) + kMemoryTypes[memoryTypeNdx].suffix;
1568
1569 atomicOperationTestsGroup->addChild(new AtomicOperationCase(testCtx, name.c_str(), AtomicShaderType(shaderTypes[shaderTypeNdx].type, kMemoryTypes[memoryTypeNdx].type), dataSign[signNdx].dataType, atomicOp[opNdx].value));
1570 }
1571 }
1572 }
1573 }
1574 }
1575
1576 } // anonymous
1577
createAtomicOperationTests(tcu::TestContext & testCtx)1578 tcu::TestCaseGroup* createAtomicOperationTests (tcu::TestContext& testCtx)
1579 {
1580 return createTestGroup(testCtx, "atomic_operations", addAtomicOperationTests);
1581 }
1582
1583 } // shaderexecutor
1584 } // vkt
1585