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 class AtomicOperationCaseInstance : public TestInstance 165 { 166 public: 167 AtomicOperationCaseInstance (Context& context, 168 const ShaderSpec& shaderSpec, 169 glu::ShaderType shaderType, 170 bool sign, 171 AtomicOperation atomicOp); 172 virtual ~AtomicOperationCaseInstance (void); 173 174 virtual tcu::TestStatus iterate (void); 175 176 private: 177 const ShaderSpec& m_shaderSpec; 178 glu::ShaderType m_shaderType; 179 bool m_sign; 180 AtomicOperation m_atomicOp; 181 182 struct BufferInterface 183 { 184 // Use half the number of elements for inout to cause overlap between atomic operations. 185 // Each inout element at index i will have two atomic operations using input from 186 // indices i and i + NUM_ELEMENTS / 2. 187 deInt32 index; 188 deUint32 inout[NUM_ELEMENTS / 2]; 189 deUint32 input[NUM_ELEMENTS]; 190 deUint32 compare[NUM_ELEMENTS]; 191 deUint32 output[NUM_ELEMENTS]; 192 }; 193 194 template<typename T> 195 struct Expected 196 { 197 T m_inout; 198 T m_output[2]; 199 200 Expected (T inout, T output0, T output1) 201 : m_inout(inout) 202 { 203 m_output[0] = output0; 204 m_output[1] = output1; 205 } 206 207 bool compare (deUint32 inout, deUint32 output0, deUint32 output1) 208 { 209 return (deMemCmp((const void*)&m_inout, (const void*)&inout, sizeof(inout)) == 0 210 && deMemCmp((const void*)&m_output[0], (const void*)&output0, sizeof(output0)) == 0 211 && deMemCmp((const void*)&m_output[1], (const void*)&output1, sizeof(output1)) == 0); 212 } 213 }; 214 215 template<typename T> void checkOperation (const BufferInterface& original, 216 const BufferInterface& result, 217 tcu::ResultCollector& resultCollector); 218 219 }; 220 221 AtomicOperationCaseInstance::AtomicOperationCaseInstance (Context& context, 222 const ShaderSpec& shaderSpec, 223 glu::ShaderType shaderType, 224 bool sign, 225 AtomicOperation atomicOp) 226 : TestInstance (context) 227 , m_shaderSpec (shaderSpec) 228 , m_shaderType (shaderType) 229 , m_sign (sign) 230 , m_atomicOp (atomicOp) 231 { 232 } 233 234 AtomicOperationCaseInstance::~AtomicOperationCaseInstance (void) 235 { 236 } 237 238 // Use template to handle both signed and unsigned cases. SPIR-V should 239 // have separate operations for both. 240 template<typename T> 241 void AtomicOperationCaseInstance::checkOperation (const BufferInterface& original, 242 const BufferInterface& result, 243 tcu::ResultCollector& resultCollector) 244 { 245 // originalInout = original inout 246 // input0 = input at index i 247 // iinput1 = input at index i + NUM_ELEMENTS / 2 248 // 249 // atomic operation will return the memory contents before 250 // the operation and this is stored as output. Two operations 251 // are executed for each InOut value (using input0 and input1). 252 // 253 // Since there is an overlap of two operations per each 254 // InOut element, the outcome of the resulting InOut and 255 // the outputs of the operations have two result candidates 256 // depending on the execution order. Verification passes 257 // if the results match one of these options. 258 259 for (int elementNdx = 0; elementNdx < NUM_ELEMENTS / 2; elementNdx++) 260 { 261 // Needed when reinterpeting the data as signed values. 262 const T originalInout = *reinterpret_cast<const T*>(&original.inout[elementNdx]); 263 const T input0 = *reinterpret_cast<const T*>(&original.input[elementNdx]); 264 const T input1 = *reinterpret_cast<const T*>(&original.input[elementNdx + NUM_ELEMENTS / 2]); 265 266 // Expected results are collected to this vector. 267 vector<Expected<T> > exp; 268 269 switch (m_atomicOp) 270 { 271 case ATOMIC_OP_ADD: 272 { 273 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout, originalInout + input0)); 274 exp.push_back(Expected<T>(originalInout + input0 + input1, originalInout + input1, originalInout)); 275 } 276 break; 277 278 case ATOMIC_OP_AND: 279 { 280 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout, originalInout & input0)); 281 exp.push_back(Expected<T>(originalInout & input0 & input1, originalInout & input1, originalInout)); 282 } 283 break; 284 285 case ATOMIC_OP_OR: 286 { 287 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout, originalInout | input0)); 288 exp.push_back(Expected<T>(originalInout | input0 | input1, originalInout | input1, originalInout)); 289 } 290 break; 291 292 case ATOMIC_OP_XOR: 293 { 294 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout, originalInout ^ input0)); 295 exp.push_back(Expected<T>(originalInout ^ input0 ^ input1, originalInout ^ input1, originalInout)); 296 } 297 break; 298 299 case ATOMIC_OP_MIN: 300 { 301 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), originalInout, de::min(originalInout, input0))); 302 exp.push_back(Expected<T>(de::min(de::min(originalInout, input0), input1), de::min(originalInout, input1), originalInout)); 303 } 304 break; 305 306 case ATOMIC_OP_MAX: 307 { 308 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), originalInout, de::max(originalInout, input0))); 309 exp.push_back(Expected<T>(de::max(de::max(originalInout, input0), input1), de::max(originalInout, input1), originalInout)); 310 } 311 break; 312 313 case ATOMIC_OP_EXCHANGE: 314 { 315 exp.push_back(Expected<T>(input1, originalInout, input0)); 316 exp.push_back(Expected<T>(input0, input1, originalInout)); 317 } 318 break; 319 320 case ATOMIC_OP_COMP_SWAP: 321 { 322 if (elementNdx % 2 == 0) 323 { 324 exp.push_back(Expected<T>(input0, originalInout, input0)); 325 exp.push_back(Expected<T>(input0, originalInout, originalInout)); 326 } 327 else 328 { 329 exp.push_back(Expected<T>(input1, input1, originalInout)); 330 exp.push_back(Expected<T>(input1, originalInout, originalInout)); 331 } 332 } 333 break; 334 335 336 default: 337 DE_FATAL("Unexpected atomic operation."); 338 break; 339 }; 340 341 const deUint32 resIo = result.inout[elementNdx]; 342 const deUint32 resOutput0 = result.output[elementNdx]; 343 const deUint32 resOutput1 = result.output[elementNdx + NUM_ELEMENTS / 2]; 344 345 if (!exp[0].compare(resIo, resOutput0, resOutput1) && !exp[1].compare(resIo, resOutput0, resOutput1)) 346 { 347 std::ostringstream errorMessage; 348 errorMessage << "ERROR: Result value check failed at index " << elementNdx 349 << ". Expected one of the two outcomes: InOut = " << tcu::toHex(exp[0].m_inout) 350 << ", Output0 = " << tcu::toHex(exp[0].m_output[0]) << ", Output1 = " 351 << tcu::toHex(exp[0].m_output[1]) << ", or InOut = " << tcu::toHex(exp[1].m_inout) 352 << ", Output0 = " << tcu::toHex(exp[1].m_output[0]) << ", Output1 = " 353 << tcu::toHex(exp[1].m_output[1]) << ". Got: InOut = " << tcu::toHex(resIo) 354 << ", Output0 = " << tcu::toHex(resOutput0) << ", Output1 = " 355 << tcu::toHex(resOutput1) << ". Using Input0 = " << tcu::toHex(original.input[elementNdx]) 356 << " and Input1 = " << tcu::toHex(original.input[elementNdx + NUM_ELEMENTS / 2]) << "."; 357 358 resultCollector.fail(errorMessage.str()); 359 } 360 } 361 } 362 363 tcu::TestStatus AtomicOperationCaseInstance::iterate (void) 364 { 365 //Check stores and atomic operation support. 366 switch (m_shaderType) 367 { 368 case glu::SHADERTYPE_VERTEX: 369 case glu::SHADERTYPE_TESSELLATION_CONTROL: 370 case glu::SHADERTYPE_TESSELLATION_EVALUATION: 371 case glu::SHADERTYPE_GEOMETRY: 372 if(!m_context.getDeviceFeatures().vertexPipelineStoresAndAtomics) 373 TCU_THROW(NotSupportedError, "Stores and atomic operations are not supported in Vertex, Tessellation, and Geometry shader."); 374 break; 375 case glu::SHADERTYPE_FRAGMENT: 376 if(!m_context.getDeviceFeatures().fragmentStoresAndAtomics) 377 TCU_THROW(NotSupportedError, "Stores and atomic operations are not supported in fragment shader."); 378 break; 379 case glu::SHADERTYPE_COMPUTE: 380 break; 381 default: 382 DE_FATAL("Unsupported shader type"); 383 } 384 385 tcu::TestLog& log = m_context.getTestContext().getLog(); 386 const DeviceInterface& vkd = m_context.getDeviceInterface(); 387 const VkDevice device = m_context.getDevice(); 388 de::Random rnd (0x62a15e34); 389 Buffer buffer (m_context, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, sizeof(BufferInterface)); 390 BufferInterface* ptr = (BufferInterface*)buffer.getHostPtr(); 391 392 for (int i = 0; i < NUM_ELEMENTS / 2; i++) 393 { 394 ptr->inout[i] = rnd.getUint32(); 395 // The first half of compare elements match with every even index. 396 // The second half matches with odd indices. This causes the 397 // overlapping operations to only select one. 398 ptr->compare[i] = ptr->inout[i] + (i % 2); 399 ptr->compare[i + NUM_ELEMENTS / 2] = ptr->inout[i] + 1 - (i % 2); 400 } 401 for (int i = 0; i < NUM_ELEMENTS; i++) 402 { 403 ptr->input[i] = rnd.getUint32(); 404 ptr->output[i] = 0xcdcdcdcd; 405 } 406 ptr->index = 0; 407 408 // Take a copy to be used when calculating expected values. 409 BufferInterface original = *ptr; 410 411 buffer.flush(); 412 413 Move<VkDescriptorSetLayout> extraResourcesLayout; 414 Move<VkDescriptorPool> extraResourcesSetPool; 415 Move<VkDescriptorSet> extraResourcesSet; 416 417 const VkDescriptorSetLayoutBinding bindings[] = 418 { 419 { 0u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, DE_NULL } 420 }; 421 422 const VkDescriptorSetLayoutCreateInfo layoutInfo = 423 { 424 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 425 DE_NULL, 426 (VkDescriptorSetLayoutCreateFlags)0u, 427 DE_LENGTH_OF_ARRAY(bindings), 428 bindings 429 }; 430 431 extraResourcesLayout = createDescriptorSetLayout(vkd, device, &layoutInfo); 432 433 const VkDescriptorPoolSize poolSizes[] = 434 { 435 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u } 436 }; 437 const VkDescriptorPoolCreateInfo poolInfo = 438 { 439 VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 440 DE_NULL, 441 (VkDescriptorPoolCreateFlags)VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 442 1u, // maxSets 443 DE_LENGTH_OF_ARRAY(poolSizes), 444 poolSizes 445 }; 446 447 extraResourcesSetPool = createDescriptorPool(vkd, device, &poolInfo); 448 449 const VkDescriptorSetAllocateInfo allocInfo = 450 { 451 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 452 DE_NULL, 453 *extraResourcesSetPool, 454 1u, 455 &extraResourcesLayout.get() 456 }; 457 458 extraResourcesSet = allocateDescriptorSet(vkd, device, &allocInfo); 459 460 VkDescriptorBufferInfo bufferInfo; 461 bufferInfo.buffer = buffer.getBuffer(); 462 bufferInfo.offset = 0u; 463 bufferInfo.range = VK_WHOLE_SIZE; 464 465 const VkWriteDescriptorSet descriptorWrite = 466 { 467 VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 468 DE_NULL, 469 *extraResourcesSet, 470 0u, // dstBinding 471 0u, // dstArrayElement 472 1u, 473 VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 474 (const VkDescriptorImageInfo*)DE_NULL, 475 &bufferInfo, 476 (const VkBufferView*)DE_NULL 477 }; 478 479 480 vkd.updateDescriptorSets(device, 1u, &descriptorWrite, 0u, DE_NULL); 481 482 // Storage for output varying data. 483 std::vector<deUint32> outputs (NUM_ELEMENTS); 484 std::vector<void*> outputPtr (NUM_ELEMENTS); 485 486 for (size_t i = 0; i < NUM_ELEMENTS; i++) 487 { 488 outputs[i] = 0xcdcdcdcd; 489 outputPtr[i] = &outputs[i]; 490 } 491 492 UniquePtr<ShaderExecutor> executor(createExecutor(m_context, m_shaderType, m_shaderSpec, *extraResourcesLayout)); 493 executor->execute(NUM_ELEMENTS, DE_NULL, &outputPtr[0], *extraResourcesSet); 494 buffer.invalidate(); 495 496 tcu::ResultCollector resultCollector(log); 497 498 // Check the results of the atomic operation 499 if (m_sign) 500 checkOperation<deInt32>(original, *ptr, resultCollector); 501 else 502 checkOperation<deUint32>(original, *ptr, resultCollector); 503 504 return tcu::TestStatus(resultCollector.getResult(), resultCollector.getMessage()); 505 } 506 507 class AtomicOperationCase : public TestCase 508 { 509 public: 510 AtomicOperationCase (tcu::TestContext& testCtx, 511 const char* name, 512 const char* description, 513 glu::ShaderType type, 514 bool sign, 515 AtomicOperation atomicOp); 516 virtual ~AtomicOperationCase (void); 517 518 virtual TestInstance* createInstance (Context& ctx) const; 519 virtual void initPrograms (vk::SourceCollections& programCollection) const 520 { 521 generateSources(m_shaderType, m_shaderSpec, programCollection); 522 } 523 524 private: 525 526 void createShaderSpec(); 527 ShaderSpec m_shaderSpec; 528 const glu::ShaderType m_shaderType; 529 const bool m_sign; 530 const AtomicOperation m_atomicOp; 531 }; 532 533 AtomicOperationCase::AtomicOperationCase (tcu::TestContext& testCtx, 534 const char* name, 535 const char* description, 536 glu::ShaderType shaderType, 537 bool sign, 538 AtomicOperation atomicOp) 539 : TestCase (testCtx, name, description) 540 , m_shaderType (shaderType) 541 , m_sign (sign) 542 , m_atomicOp (atomicOp) 543 { 544 createShaderSpec(); 545 init(); 546 } 547 548 AtomicOperationCase::~AtomicOperationCase (void) 549 { 550 } 551 552 TestInstance* AtomicOperationCase::createInstance (Context& ctx) const 553 { 554 return new AtomicOperationCaseInstance(ctx, m_shaderSpec, m_shaderType, m_sign, m_atomicOp); 555 } 556 557 void AtomicOperationCase::createShaderSpec (void) 558 { 559 const tcu::StringTemplate shaderTemplateGlobal( 560 "layout (set = ${SETIDX}, binding = 0, std430) buffer AtomicBuffer\n" 561 "{\n" 562 " highp int index;\n" 563 " highp ${DATATYPE} inoutValues[${N}/2];\n" 564 " highp ${DATATYPE} inputValues[${N}];\n" 565 " highp ${DATATYPE} compareValues[${N}];\n" 566 " highp ${DATATYPE} outputValues[${N}];\n" 567 "} buf;\n"); 568 569 std::map<std::string, std::string> specializations; 570 specializations["DATATYPE"] = m_sign ? "int" : "uint"; 571 specializations["ATOMICOP"] = atomicOp2Str(m_atomicOp); 572 specializations["SETIDX"] = de::toString((int)EXTRA_RESOURCES_DESCRIPTOR_SET_INDEX); 573 specializations["N"] = de::toString((int)NUM_ELEMENTS); 574 specializations["COMPARE_ARG"] = m_atomicOp == ATOMIC_OP_COMP_SWAP ? "buf.compareValues[idx], " : ""; 575 576 const tcu::StringTemplate shaderTemplateSrc( 577 "int idx = atomicAdd(buf.index, 1);\n" 578 "buf.outputValues[idx] = ${ATOMICOP}(buf.inoutValues[idx % (${N}/2)], ${COMPARE_ARG}buf.inputValues[idx]);\n"); 579 580 m_shaderSpec.outputs.push_back(Symbol("outData", glu::VarType(glu::TYPE_UINT, glu::PRECISION_HIGHP))); 581 m_shaderSpec.globalDeclarations = shaderTemplateGlobal.specialize(specializations); 582 m_shaderSpec.source = shaderTemplateSrc.specialize(specializations); 583 } 584 585 void addAtomicOperationTests (tcu::TestCaseGroup* atomicOperationTestsGroup) 586 { 587 tcu::TestContext& testCtx = atomicOperationTestsGroup->getTestContext(); 588 589 static const struct 590 { 591 glu::ShaderType type; 592 const char* name; 593 } shaderTypes[] = 594 { 595 { glu::SHADERTYPE_VERTEX, "vertex" }, 596 { glu::SHADERTYPE_FRAGMENT, "fragment" }, 597 { glu::SHADERTYPE_GEOMETRY, "geometry" }, 598 { glu::SHADERTYPE_TESSELLATION_CONTROL, "tess_ctrl" }, 599 { glu::SHADERTYPE_TESSELLATION_EVALUATION, "tess_eval" }, 600 { glu::SHADERTYPE_COMPUTE, "compute" } 601 }; 602 603 static const struct 604 { 605 bool value; 606 const char* name; 607 const char* description; 608 } dataSign[] = 609 { 610 { true, "signed", "Tests using signed data (int)" }, 611 { false, "unsigned", "Tests using unsigned data (uint)" } 612 }; 613 614 static const struct 615 { 616 AtomicOperation value; 617 const char* name; 618 } atomicOp[] = 619 { 620 { ATOMIC_OP_EXCHANGE, "exchange" }, 621 { ATOMIC_OP_COMP_SWAP, "comp_swap" }, 622 { ATOMIC_OP_ADD, "add" }, 623 { ATOMIC_OP_MIN, "min" }, 624 { ATOMIC_OP_MAX, "max" }, 625 { ATOMIC_OP_AND, "and" }, 626 { ATOMIC_OP_OR, "or" }, 627 { ATOMIC_OP_XOR, "xor" } 628 }; 629 630 for (int opNdx = 0; opNdx < DE_LENGTH_OF_ARRAY(atomicOp); opNdx++) 631 { 632 for (int signNdx = 0; signNdx < DE_LENGTH_OF_ARRAY(dataSign); signNdx++) 633 { 634 for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(shaderTypes); shaderTypeNdx++) 635 { 636 const std::string description = std::string("Tests atomic operation ") + atomicOp2Str(atomicOp[opNdx].value) + std::string("."); 637 std::string name = std::string(atomicOp[opNdx].name) + "_" + std::string(dataSign[signNdx].name) + "_" + std::string(shaderTypes[shaderTypeNdx].name); 638 atomicOperationTestsGroup->addChild(new AtomicOperationCase(testCtx, name.c_str(), description.c_str(), shaderTypes[shaderTypeNdx].type, dataSign[signNdx].value, atomicOp[opNdx].value)); 639 } 640 } 641 } 642 } 643 644 } // anonymous 645 646 tcu::TestCaseGroup* createAtomicOperationTests (tcu::TestContext& testCtx) 647 { 648 return createTestGroup(testCtx, "atomic_operations", "Atomic Operation Tests", addAtomicOperationTests); 649 } 650 651 } // shaderexecutor 652 } // vkt 653