1 /*------------------------------------------------------------------------- 2 * Vulkan Conformance Tests 3 * ------------------------ 4 * 5 * Copyright (c) 2016 Google Inc. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 *//*! 20 * \file 21 * \brief Utility for pre-compiling source programs to SPIR-V 22 *//*--------------------------------------------------------------------*/ 23 24 #include "tcuDefs.hpp" 25 #include "tcuCommandLine.hpp" 26 #include "tcuPlatform.hpp" 27 #include "tcuResource.hpp" 28 #include "tcuTestLog.hpp" 29 #include "tcuTestHierarchyIterator.hpp" 30 #include "deUniquePtr.hpp" 31 #include "vkPrograms.hpp" 32 #include "vkBinaryRegistry.hpp" 33 #include "vktTestCase.hpp" 34 #include "vktTestPackage.hpp" 35 #include "deUniquePtr.hpp" 36 #include "deCommandLine.hpp" 37 #include "deSharedPtr.hpp" 38 #include "deThread.hpp" 39 #include "deThreadSafeRingBuffer.hpp" 40 #include "dePoolArray.hpp" 41 42 #include <iostream> 43 44 using std::vector; 45 using std::string; 46 using de::UniquePtr; 47 using de::MovePtr; 48 using de::SharedPtr; 49 50 namespace vkt 51 { 52 53 namespace // anonymous 54 { 55 56 typedef de::SharedPtr<glu::ProgramSources> ProgramSourcesSp; 57 typedef de::SharedPtr<vk::SpirVAsmSource> SpirVAsmSourceSp; 58 typedef de::SharedPtr<vk::ProgramBinary> ProgramBinarySp; 59 60 class Task 61 { 62 public: 63 virtual void execute (void) = 0; 64 }; 65 66 typedef de::ThreadSafeRingBuffer<Task*> TaskQueue; 67 68 class TaskExecutorThread : public de::Thread 69 { 70 public: 71 TaskExecutorThread (TaskQueue& tasks) 72 : m_tasks(tasks) 73 { 74 start(); 75 } 76 77 void run (void) 78 { 79 for (;;) 80 { 81 Task* const task = m_tasks.popBack(); 82 83 if (task) 84 task->execute(); 85 else 86 break; // End of tasks - time to terminate 87 } 88 } 89 90 private: 91 TaskQueue& m_tasks; 92 }; 93 94 class TaskExecutor 95 { 96 public: 97 TaskExecutor (deUint32 numThreads); 98 ~TaskExecutor (void); 99 100 void submit (Task* task); 101 void waitForComplete (void); 102 103 private: 104 typedef de::SharedPtr<TaskExecutorThread> ExecThreadSp; 105 106 std::vector<ExecThreadSp> m_threads; 107 TaskQueue m_tasks; 108 }; 109 110 TaskExecutor::TaskExecutor (deUint32 numThreads) 111 : m_threads (numThreads) 112 , m_tasks (m_threads.size() * 1024u) 113 { 114 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 115 m_threads[ndx] = ExecThreadSp(new TaskExecutorThread(m_tasks)); 116 } 117 118 TaskExecutor::~TaskExecutor (void) 119 { 120 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 121 m_tasks.pushFront(DE_NULL); 122 123 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 124 m_threads[ndx]->join(); 125 } 126 127 void TaskExecutor::submit (Task* task) 128 { 129 DE_ASSERT(task); 130 m_tasks.pushFront(task); 131 } 132 133 class SyncTask : public Task 134 { 135 public: 136 SyncTask (de::Semaphore* enterBarrier, de::Semaphore* inBarrier, de::Semaphore* leaveBarrier) 137 : m_enterBarrier (enterBarrier) 138 , m_inBarrier (inBarrier) 139 , m_leaveBarrier (leaveBarrier) 140 {} 141 142 SyncTask (void) 143 : m_enterBarrier (DE_NULL) 144 , m_inBarrier (DE_NULL) 145 , m_leaveBarrier (DE_NULL) 146 {} 147 148 void execute (void) 149 { 150 m_enterBarrier->increment(); 151 m_inBarrier->decrement(); 152 m_leaveBarrier->increment(); 153 } 154 155 private: 156 de::Semaphore* m_enterBarrier; 157 de::Semaphore* m_inBarrier; 158 de::Semaphore* m_leaveBarrier; 159 }; 160 161 void TaskExecutor::waitForComplete (void) 162 { 163 de::Semaphore enterBarrier (0); 164 de::Semaphore inBarrier (0); 165 de::Semaphore leaveBarrier (0); 166 std::vector<SyncTask> syncTasks (m_threads.size()); 167 168 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 169 { 170 syncTasks[ndx] = SyncTask(&enterBarrier, &inBarrier, &leaveBarrier); 171 submit(&syncTasks[ndx]); 172 } 173 174 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 175 enterBarrier.decrement(); 176 177 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 178 inBarrier.increment(); 179 180 for (size_t ndx = 0; ndx < m_threads.size(); ++ndx) 181 leaveBarrier.decrement(); 182 } 183 184 struct Program 185 { 186 enum Status 187 { 188 STATUS_NOT_COMPLETED = 0, 189 STATUS_FAILED, 190 STATUS_PASSED, 191 192 STATUS_LAST 193 }; 194 195 vk::ProgramIdentifier id; 196 197 Status buildStatus; 198 std::string buildLog; 199 ProgramBinarySp binary; 200 201 Status validationStatus; 202 std::string validationLog; 203 204 vk::SpirvVersion spirvVersion; 205 206 explicit Program (const vk::ProgramIdentifier& id_, const vk::SpirvVersion spirvVersion_) 207 : id (id_) 208 , buildStatus (STATUS_NOT_COMPLETED) 209 , validationStatus (STATUS_NOT_COMPLETED) 210 , spirvVersion (spirvVersion_) 211 {} 212 Program (void) 213 : id ("", "") 214 , buildStatus (STATUS_NOT_COMPLETED) 215 , validationStatus (STATUS_NOT_COMPLETED) 216 , spirvVersion (vk::SPIRV_VERSION_LAST) 217 {} 218 }; 219 220 void writeBuildLogs (const glu::ShaderProgramInfo& buildInfo, std::ostream& dst) 221 { 222 for (size_t shaderNdx = 0; shaderNdx < buildInfo.shaders.size(); shaderNdx++) 223 { 224 const glu::ShaderInfo& shaderInfo = buildInfo.shaders[shaderNdx]; 225 const char* const shaderName = getShaderTypeName(shaderInfo.type); 226 227 dst << shaderName << " source:\n" 228 << "---\n" 229 << shaderInfo.source << "\n" 230 << "---\n" 231 << shaderName << " compile log:\n" 232 << "---\n" 233 << shaderInfo.infoLog << "\n" 234 << "---\n"; 235 } 236 237 dst << "link log:\n" 238 << "---\n" 239 << buildInfo.program.infoLog << "\n" 240 << "---\n"; 241 } 242 243 template <typename Source> 244 class BuildHighLevelShaderTask : public Task 245 { 246 public: 247 248 BuildHighLevelShaderTask (const Source& source, Program* program) 249 : m_source (source) 250 , m_program (program) 251 {} 252 253 BuildHighLevelShaderTask (void) : m_program(DE_NULL) {} 254 255 void execute (void) 256 { 257 glu::ShaderProgramInfo buildInfo; 258 259 try 260 { 261 DE_ASSERT(m_source.buildOptions.targetVersion < vk::SPIRV_VERSION_LAST); 262 263 m_program->binary = ProgramBinarySp(vk::buildProgram(m_source, &buildInfo)); 264 m_program->buildStatus = Program::STATUS_PASSED; 265 } 266 catch (const tcu::Exception&) 267 { 268 std::ostringstream log; 269 270 writeBuildLogs(buildInfo, log); 271 272 m_program->buildStatus = Program::STATUS_FAILED; 273 m_program->buildLog = log.str(); 274 } 275 } 276 277 private: 278 Source m_source; 279 Program* m_program; 280 }; 281 282 void writeBuildLogs (const vk::SpirVProgramInfo& buildInfo, std::ostream& dst) 283 { 284 dst << "source:\n" 285 << "---\n" 286 << buildInfo.source << "\n" 287 << "---\n"; 288 } 289 290 class BuildSpirVAsmTask : public Task 291 { 292 public: 293 BuildSpirVAsmTask (const vk::SpirVAsmSource& source, Program* program) 294 : m_source (source) 295 , m_program (program) 296 {} 297 298 BuildSpirVAsmTask (void) : m_program(DE_NULL) {} 299 300 void execute (void) 301 { 302 vk::SpirVProgramInfo buildInfo; 303 304 try 305 { 306 DE_ASSERT(m_source.buildOptions.targetVersion < vk::SPIRV_VERSION_LAST); 307 308 m_program->binary = ProgramBinarySp(vk::assembleProgram(m_source, &buildInfo)); 309 m_program->buildStatus = Program::STATUS_PASSED; 310 } 311 catch (const tcu::Exception&) 312 { 313 std::ostringstream log; 314 315 writeBuildLogs(buildInfo, log); 316 317 m_program->buildStatus = Program::STATUS_FAILED; 318 m_program->buildLog = log.str(); 319 } 320 } 321 322 private: 323 vk::SpirVAsmSource m_source; 324 Program* m_program; 325 }; 326 327 class ValidateBinaryTask : public Task 328 { 329 public: 330 ValidateBinaryTask (Program* program) 331 : m_program(program) 332 {} 333 334 void execute (void) 335 { 336 DE_ASSERT(m_program->buildStatus == Program::STATUS_PASSED); 337 DE_ASSERT(m_program->binary->getFormat() == vk::PROGRAM_FORMAT_SPIRV); 338 339 std::ostringstream validationLog; 340 341 if (vk::validateProgram(*m_program->binary, &validationLog)) 342 m_program->validationStatus = Program::STATUS_PASSED; 343 else 344 m_program->validationStatus = Program::STATUS_FAILED; 345 } 346 347 private: 348 Program* m_program; 349 }; 350 351 tcu::TestPackageRoot* createRoot (tcu::TestContext& testCtx) 352 { 353 vector<tcu::TestNode*> children; 354 children.push_back(new TestPackage(testCtx)); 355 return new tcu::TestPackageRoot(testCtx, children); 356 } 357 358 } // anonymous 359 360 struct BuildStats 361 { 362 int numSucceeded; 363 int numFailed; 364 int notSupported; 365 366 BuildStats (void) 367 : numSucceeded (0) 368 , numFailed (0) 369 , notSupported (0) 370 { 371 } 372 }; 373 374 BuildStats buildPrograms (tcu::TestContext& testCtx, 375 const std::string& dstPath, 376 const bool validateBinaries, 377 const deUint32 usedVulkanVersion, 378 const vk::SpirvVersion baselineSpirvVersion, 379 const vk::SpirvVersion maxSpirvVersion) 380 { 381 const deUint32 numThreads = deGetNumAvailableLogicalCores(); 382 383 TaskExecutor executor (numThreads); 384 385 // de::PoolArray<> is faster to build than std::vector 386 de::MemPool programPool; 387 de::PoolArray<Program> programs (&programPool); 388 int notSupported = 0; 389 390 { 391 de::MemPool tmpPool; 392 de::PoolArray<BuildHighLevelShaderTask<vk::GlslSource> > buildGlslTasks (&tmpPool); 393 de::PoolArray<BuildHighLevelShaderTask<vk::HlslSource> > buildHlslTasks (&tmpPool); 394 de::PoolArray<BuildSpirVAsmTask> buildSpirvAsmTasks (&tmpPool); 395 396 // Collect build tasks 397 { 398 const UniquePtr<tcu::TestPackageRoot> root (createRoot(testCtx)); 399 tcu::DefaultHierarchyInflater inflater (testCtx); 400 de::MovePtr<tcu::CaseListFilter> caseListFilter (testCtx.getCommandLine().createCaseListFilter(testCtx.getArchive())); 401 tcu::TestHierarchyIterator iterator (*root, inflater, *caseListFilter); 402 403 while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED) 404 { 405 if (iterator.getState() == tcu::TestHierarchyIterator::STATE_ENTER_NODE && 406 tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType())) 407 { 408 const TestCase* const testCase = dynamic_cast<TestCase*>(iterator.getNode()); 409 const string casePath = iterator.getNodePath(); 410 vk::ShaderBuildOptions defaultGlslBuildOptions (baselineSpirvVersion, 0u); 411 vk::ShaderBuildOptions defaultHlslBuildOptions (baselineSpirvVersion, 0u); 412 vk::SpirVAsmBuildOptions defaultSpirvAsmBuildOptions (baselineSpirvVersion); 413 vk::SourceCollections sourcePrograms (usedVulkanVersion, defaultGlslBuildOptions, defaultHlslBuildOptions, defaultSpirvAsmBuildOptions); 414 415 try 416 { 417 testCase->initPrograms(sourcePrograms); 418 } 419 catch (const tcu::NotSupportedError& ) 420 { 421 notSupported++; 422 iterator.next(); 423 continue; 424 } 425 426 for (vk::GlslSourceCollection::Iterator progIter = sourcePrograms.glslSources.begin(); 427 progIter != sourcePrograms.glslSources.end(); 428 ++progIter) 429 { 430 // Source program requires higher SPIR-V version than available: skip it to avoid fail 431 if (progIter.getProgram().buildOptions.targetVersion > maxSpirvVersion) 432 continue; 433 434 programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName()), progIter.getProgram().buildOptions.targetVersion)); 435 buildGlslTasks.pushBack(BuildHighLevelShaderTask<vk::GlslSource>(progIter.getProgram(), &programs.back())); 436 executor.submit(&buildGlslTasks.back()); 437 } 438 439 for (vk::HlslSourceCollection::Iterator progIter = sourcePrograms.hlslSources.begin(); 440 progIter != sourcePrograms.hlslSources.end(); 441 ++progIter) 442 { 443 // Source program requires higher SPIR-V version than available: skip it to avoid fail 444 if (progIter.getProgram().buildOptions.targetVersion > maxSpirvVersion) 445 continue; 446 447 programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName()), progIter.getProgram().buildOptions.targetVersion)); 448 buildHlslTasks.pushBack(BuildHighLevelShaderTask<vk::HlslSource>(progIter.getProgram(), &programs.back())); 449 executor.submit(&buildHlslTasks.back()); 450 } 451 452 for (vk::SpirVAsmCollection::Iterator progIter = sourcePrograms.spirvAsmSources.begin(); 453 progIter != sourcePrograms.spirvAsmSources.end(); 454 ++progIter) 455 { 456 // Source program requires higher SPIR-V version than available: skip it to avoid fail 457 if (progIter.getProgram().buildOptions.targetVersion > maxSpirvVersion) 458 continue; 459 460 programs.pushBack(Program(vk::ProgramIdentifier(casePath, progIter.getName()), progIter.getProgram().buildOptions.targetVersion)); 461 buildSpirvAsmTasks.pushBack(BuildSpirVAsmTask(progIter.getProgram(), &programs.back())); 462 executor.submit(&buildSpirvAsmTasks.back()); 463 } 464 } 465 466 iterator.next(); 467 } 468 } 469 470 // Need to wait until tasks completed before freeing task memory 471 executor.waitForComplete(); 472 } 473 474 if (validateBinaries) 475 { 476 std::vector<ValidateBinaryTask> validationTasks; 477 478 validationTasks.reserve(programs.size()); 479 480 for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter) 481 { 482 if (progIter->buildStatus == Program::STATUS_PASSED) 483 { 484 validationTasks.push_back(ValidateBinaryTask(&*progIter)); 485 executor.submit(&validationTasks.back()); 486 } 487 } 488 489 executor.waitForComplete(); 490 } 491 492 { 493 vk::BinaryRegistryWriter registryWriter (dstPath); 494 495 for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter) 496 { 497 if (progIter->buildStatus == Program::STATUS_PASSED) 498 registryWriter.addProgram(progIter->id, *progIter->binary); 499 } 500 501 registryWriter.write(); 502 } 503 504 { 505 BuildStats stats; 506 stats.notSupported = notSupported; 507 for (de::PoolArray<Program>::iterator progIter = programs.begin(); progIter != programs.end(); ++progIter) 508 { 509 const bool buildOk = progIter->buildStatus == Program::STATUS_PASSED; 510 const bool validationOk = progIter->validationStatus != Program::STATUS_FAILED; 511 512 if (buildOk && validationOk) 513 stats.numSucceeded += 1; 514 else 515 { 516 stats.numFailed += 1; 517 tcu::print("ERROR: %s / %s: %s failed\n", 518 progIter->id.testCasePath.c_str(), 519 progIter->id.programName.c_str(), 520 (buildOk ? "validation" : "build")); 521 tcu::print("%s\n", (buildOk ? progIter->validationLog.c_str() : progIter->buildLog.c_str())); 522 } 523 } 524 525 return stats; 526 } 527 } 528 529 } // vkt 530 531 namespace opt 532 { 533 534 DE_DECLARE_COMMAND_LINE_OPT(DstPath, std::string); 535 DE_DECLARE_COMMAND_LINE_OPT(Cases, std::string); 536 DE_DECLARE_COMMAND_LINE_OPT(Validate, bool); 537 DE_DECLARE_COMMAND_LINE_OPT(VulkanVersion, deUint32); 538 539 void registerOptions (de::cmdline::Parser& parser) 540 { 541 using de::cmdline::Option; 542 using de::cmdline::NamedValue; 543 544 static const NamedValue<deUint32> s_vulkanVersion[] = 545 { 546 { "1.0", VK_MAKE_VERSION(1, 0, 0) }, 547 { "1.1", VK_MAKE_VERSION(1, 1, 0) }, 548 }; 549 550 DE_STATIC_ASSERT(vk::SPIRV_VERSION_1_3 + 1 == vk::SPIRV_VERSION_LAST); 551 552 parser << Option<opt::DstPath> ("d", "dst-path", "Destination path", "out") 553 << Option<opt::Cases> ("n", "deqp-case", "Case path filter (works as in test binaries)") 554 << Option<opt::Validate> ("v", "validate-spv", "Validate generated SPIR-V binaries") 555 << Option<opt::VulkanVersion> ("t", "target-vulkan-version", "Target Vulkan version", s_vulkanVersion, "1.1"); 556 } 557 558 } // opt 559 560 int main (int argc, const char* argv[]) 561 { 562 de::cmdline::CommandLine cmdLine; 563 tcu::CommandLine deqpCmdLine; 564 565 { 566 de::cmdline::Parser parser; 567 opt::registerOptions(parser); 568 if (!parser.parse(argc, argv, &cmdLine, std::cerr)) 569 { 570 parser.help(std::cout); 571 return -1; 572 } 573 } 574 575 { 576 vector<const char*> deqpArgv; 577 578 deqpArgv.push_back("unused"); 579 580 if (cmdLine.hasOption<opt::Cases>()) 581 { 582 deqpArgv.push_back("--deqp-case"); 583 deqpArgv.push_back(cmdLine.getOption<opt::Cases>().c_str()); 584 } 585 586 if (!deqpCmdLine.parse((int)deqpArgv.size(), &deqpArgv[0])) 587 return -1; 588 } 589 590 try 591 { 592 tcu::DirArchive archive ("."); 593 tcu::TestLog log (deqpCmdLine.getLogFileName(), deqpCmdLine.getLogFlags()); 594 tcu::Platform platform; 595 tcu::TestContext testCtx (platform, archive, log, deqpCmdLine, DE_NULL); 596 vk::SpirvVersion baselineSpirvVersion = vk::getBaselineSpirvVersion(cmdLine.getOption<opt::VulkanVersion>()); 597 vk::SpirvVersion maxSpirvVersion = vk::getMaxSpirvVersionForGlsl(cmdLine.getOption<opt::VulkanVersion>()); 598 599 tcu::print("SPIR-V versions: baseline: %s, max supported: %s\n", 600 getSpirvVersionName(baselineSpirvVersion).c_str(), 601 getSpirvVersionName(maxSpirvVersion).c_str()); 602 603 const vkt::BuildStats stats = vkt::buildPrograms(testCtx, 604 cmdLine.getOption<opt::DstPath>(), 605 cmdLine.getOption<opt::Validate>(), 606 cmdLine.getOption<opt::VulkanVersion>(), 607 baselineSpirvVersion, 608 maxSpirvVersion); 609 610 tcu::print("DONE: %d passed, %d failed, %d not supported\n", stats.numSucceeded, stats.numFailed, stats.notSupported); 611 612 return stats.numFailed == 0 ? 0 : -1; 613 } 614 catch (const std::exception& e) 615 { 616 tcu::die("%s", e.what()); 617 } 618 } 619