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 64 VkBuffer getBuffer (void) const { return *m_buffer; } 65 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 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 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 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 117 void Buffer::flush (void) 118 { 119 flushMappedMemoryRange(m_vkd, m_device, m_allocation->getMemory(), m_allocation->getOffset(), VK_WHOLE_SIZE); 120 } 121 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 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 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 197 virtual ~BufferInterface() {}; 198 }; 199 200 template<typename dataTypeT> 201 class TestBuffer : public BufferInterface 202 { 203 public: 204 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 222 virtual void setBuffer(void* ptr) 223 { 224 m_ptr = static_cast<BufferData<dataTypeT>*>(ptr); 225 } 226 227 virtual size_t bufferSize() 228 { 229 return sizeof(BufferData<dataTypeT>); 230 } 231 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 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 268 Expected (T inout, T output0, T output1) 269 : m_inout(inout) 270 { 271 m_output[0] = output0; 272 m_output[1] = output1; 273 } 274 275 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 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> 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 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 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; 630 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 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 659 AtomicOperationCase::~AtomicOperationCase (void) 660 { 661 } 662 663 TestInstance* AtomicOperationCase::createInstance (Context& ctx) const 664 { 665 return new AtomicOperationCaseInstance(ctx, m_shaderSpec, m_shaderType, m_dataType, m_atomicOp); 666 } 667 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 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 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