1 /*-------------------------------------------------------------------------
2 * Vulkan CTS Framework
3 * --------------------
4 *
5 * Copyright (c) 2019 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 Program utilities.
22 *//*--------------------------------------------------------------------*/
23
24 #include "spirv-tools/optimizer.hpp"
25
26 #include "qpInfo.h"
27
28 #include "vkPrograms.hpp"
29 #include "vkShaderToSpirV.hpp"
30 #include "vkSpirVAsm.hpp"
31 #include "vkRefUtil.hpp"
32
33 #include "deMutex.hpp"
34 #include "deFilePath.hpp"
35 #include "deArrayUtil.hpp"
36 #include "deMemory.h"
37 #include "deInt32.h"
38
39 #include "tcuCommandLine.hpp"
40
41 #include <map>
42 #include <mutex>
43
44 #if DE_OS == DE_OS_ANDROID
45 #define DISABLE_SHADERCACHE_IPC
46 #endif
47
48 namespace vk
49 {
50
51 using std::map;
52 using std::string;
53 using std::vector;
54
55 #if defined(DE_DEBUG)
56 #define VALIDATE_BINARIES true
57 #else
58 #define VALIDATE_BINARIES false
59 #endif
60
61 #define SPIRV_BINARY_ENDIANNESS DE_LITTLE_ENDIAN
62
63 // ProgramBinary
64
ProgramBinary(ProgramFormat format,size_t binarySize,const uint8_t * binary)65 ProgramBinary::ProgramBinary(ProgramFormat format, size_t binarySize, const uint8_t *binary)
66 : m_format(format)
67 , m_binary(binary, binary + binarySize)
68 , m_used(false)
69 {
70 }
71
72 // Utils
73
74 namespace
75 {
76
isNativeSpirVBinaryEndianness(void)77 bool isNativeSpirVBinaryEndianness(void)
78 {
79 #if (DE_ENDIANNESS == SPIRV_BINARY_ENDIANNESS)
80 return true;
81 #else
82 return false;
83 #endif
84 }
85
isSaneSpirVBinary(const ProgramBinary & binary)86 bool isSaneSpirVBinary(const ProgramBinary &binary)
87 {
88 const uint32_t spirvMagicWord = 0x07230203;
89 const uint32_t spirvMagicBytes =
90 isNativeSpirVBinaryEndianness() ? spirvMagicWord : deReverseBytes32(spirvMagicWord);
91
92 DE_ASSERT(binary.getFormat() == PROGRAM_FORMAT_SPIRV);
93
94 if (binary.getSize() % sizeof(uint32_t) != 0)
95 return false;
96
97 if (binary.getSize() < sizeof(uint32_t))
98 return false;
99
100 if (*(const uint32_t *)binary.getBinary() != spirvMagicBytes)
101 return false;
102
103 return true;
104 }
105
optimizeCompiledBinary(vector<uint32_t> & binary,int optimizationRecipe,const SpirvVersion spirvVersion)106 void optimizeCompiledBinary(vector<uint32_t> &binary, int optimizationRecipe, const SpirvVersion spirvVersion)
107 {
108 spv_target_env targetEnv = SPV_ENV_VULKAN_1_0;
109
110 // Map SpirvVersion with spv_target_env:
111 switch (spirvVersion)
112 {
113 case SPIRV_VERSION_1_0:
114 targetEnv = SPV_ENV_VULKAN_1_0;
115 break;
116 case SPIRV_VERSION_1_1:
117 case SPIRV_VERSION_1_2:
118 case SPIRV_VERSION_1_3:
119 targetEnv = SPV_ENV_VULKAN_1_1;
120 break;
121 case SPIRV_VERSION_1_4:
122 targetEnv = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
123 break;
124 case SPIRV_VERSION_1_5:
125 targetEnv = SPV_ENV_VULKAN_1_2;
126 break;
127 case SPIRV_VERSION_1_6:
128 targetEnv = SPV_ENV_VULKAN_1_3;
129 break;
130 default:
131 TCU_THROW(InternalError, "Unexpected SPIR-V version requested");
132 }
133
134 spvtools::Optimizer optimizer(targetEnv);
135
136 switch (optimizationRecipe)
137 {
138 case 1:
139 optimizer.RegisterPerformancePasses();
140 break;
141 case 2:
142 optimizer.RegisterSizePasses();
143 break;
144 default:
145 TCU_THROW(InternalError, "Unknown optimization recipe requested");
146 }
147
148 spvtools::OptimizerOptions optimizer_options;
149 optimizer_options.set_run_validator(false);
150 const bool ok = optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
151
152 if (!ok)
153 TCU_THROW(InternalError, "Optimizer call failed");
154 }
155
createProgramBinaryFromSpirV(const vector<uint32_t> & binary)156 ProgramBinary *createProgramBinaryFromSpirV(const vector<uint32_t> &binary)
157 {
158 DE_ASSERT(!binary.empty());
159
160 if (isNativeSpirVBinaryEndianness())
161 return new ProgramBinary(PROGRAM_FORMAT_SPIRV, binary.size() * sizeof(uint32_t), (const uint8_t *)&binary[0]);
162 else
163 TCU_THROW(InternalError, "SPIR-V endianness translation not supported");
164 }
165
166 } // namespace
167
validateCompiledBinary(const vector<uint32_t> & binary,glu::ShaderProgramInfo * buildInfo,const SpirvValidatorOptions & options)168 void validateCompiledBinary(const vector<uint32_t> &binary, glu::ShaderProgramInfo *buildInfo,
169 const SpirvValidatorOptions &options)
170 {
171 std::ostringstream validationLog;
172
173 if (!validateSpirV(binary.size(), &binary[0], &validationLog, options))
174 {
175 buildInfo->program.linkOk = false;
176 buildInfo->program.infoLog += "\n" + validationLog.str();
177
178 TCU_THROW(InternalError, "Validation failed for compiled SPIR-V binary");
179 }
180 }
181
validateCompiledBinary(const vector<uint32_t> & binary,SpirVProgramInfo * buildInfo,const SpirvValidatorOptions & options)182 void validateCompiledBinary(const vector<uint32_t> &binary, SpirVProgramInfo *buildInfo,
183 const SpirvValidatorOptions &options)
184 {
185 std::ostringstream validationLog;
186
187 if (!validateSpirV(binary.size(), &binary[0], &validationLog, options))
188 {
189 buildInfo->compileOk = false;
190 buildInfo->infoLog += "\n" + validationLog.str();
191
192 TCU_THROW(InternalError, "Validation failed for compiled SPIR-V binary");
193 }
194 }
195
196 // IPC functions
197 #ifndef DISABLE_SHADERCACHE_IPC
198 #include "vkIPC.inl"
199 #endif
200
201 // Overridable wrapper for de::Mutex
202 class cacheMutex
203 {
204 public:
cacheMutex()205 cacheMutex()
206 {
207 }
~cacheMutex()208 virtual ~cacheMutex()
209 {
210 }
lock()211 virtual void lock()
212 {
213 localMutex.lock();
214 }
unlock()215 virtual void unlock()
216 {
217 localMutex.unlock();
218 }
219
220 private:
221 de::Mutex localMutex;
222 };
223
224 #ifndef DISABLE_SHADERCACHE_IPC
225 // Overriden cacheMutex that uses IPC instead
226 class cacheMutexIPC : public cacheMutex
227 {
228 public:
cacheMutexIPC()229 cacheMutexIPC()
230 {
231 ipc_sem_init(&guard, "cts_shadercache_ipc_guard");
232 ipc_sem_create(&guard, 1);
233 }
~cacheMutexIPC()234 virtual ~cacheMutexIPC()
235 {
236 ipc_sem_close(&guard);
237 }
lock()238 virtual void lock()
239 {
240 ipc_sem_decrement(&guard);
241 }
unlock()242 virtual void unlock()
243 {
244 ipc_sem_increment(&guard);
245 }
246
247 private:
248 ipc_sharedsemaphore guard;
249 };
250 #endif
251
252 // Each cache node takes 4 * 4 = 16 bytes; 1M items takes 16M memory.
253 const uint32_t cacheMaxItems = 1024 * 1024;
254 cacheMutex *cacheFileMutex = nullptr;
255 uint32_t *cacheMempool = nullptr;
256 #ifndef DISABLE_SHADERCACHE_IPC
257 ipc_sharedmemory cacheIPCMemory;
258 #endif
259
260 struct cacheNode
261 {
262 uint32_t key;
263 uint32_t data;
264 uint32_t right_child;
265 uint32_t left_child;
266 };
267
cacheSearch(uint32_t key)268 cacheNode *cacheSearch(uint32_t key)
269 {
270 cacheNode *r = (cacheNode *)(cacheMempool + 1);
271 int *tail = (int *)cacheMempool;
272 unsigned int p = 0;
273
274 if (!*tail)
275 {
276 // Cache is empty.
277 return 0;
278 }
279
280 while (1)
281 {
282 if (r[p].key == key)
283 return &r[p];
284
285 if (key > r[p].key)
286 p = r[p].right_child;
287 else
288 p = r[p].left_child;
289
290 if (p == 0)
291 return 0;
292 }
293 }
294
cacheInsert(uint32_t key,uint32_t data)295 void cacheInsert(uint32_t key, uint32_t data)
296 {
297 cacheNode *r = (cacheNode *)(cacheMempool + 1);
298 int *tail = (int *)cacheMempool;
299 int newnode = *tail;
300
301 DE_ASSERT(newnode < cacheMaxItems);
302
303 // If we run out of cache space, reset the cache index.
304 if (newnode >= cacheMaxItems)
305 {
306 *tail = 0;
307 newnode = 0;
308 }
309
310 r[*tail].data = data;
311 r[*tail].key = key;
312 r[*tail].left_child = 0;
313 r[*tail].right_child = 0;
314
315 (*tail)++;
316
317 if (newnode == 0)
318 {
319 // first
320 return;
321 }
322
323 int p = 0;
324 while (1)
325 {
326 if (r[p].key == key)
327 {
328 // collision; use the latest data
329 r[p].data = data;
330 (*tail)--;
331 return;
332 }
333
334 if (key > r[p].key)
335 {
336 if (r[p].right_child != 0)
337 {
338 p = r[p].right_child;
339 }
340 else
341 {
342 r[p].right_child = newnode;
343 return;
344 }
345 }
346 else
347 {
348 if (r[p].left_child != 0)
349 {
350 p = r[p].left_child;
351 }
352 else
353 {
354 r[p].left_child = newnode;
355 return;
356 }
357 }
358 }
359 }
360
361 // Called via atexit()
shaderCacheClean()362 void shaderCacheClean()
363 {
364 delete cacheFileMutex;
365 delete[] cacheMempool;
366 }
367
368 #ifndef DISABLE_SHADERCACHE_IPC
369 // Called via atexit()
shaderCacheCleanIPC()370 void shaderCacheCleanIPC()
371 {
372 delete cacheFileMutex;
373 ipc_mem_close(&cacheIPCMemory);
374 }
375 #endif
376
shaderCacheFirstRunCheck(const tcu::CommandLine & commandLine)377 void shaderCacheFirstRunCheck(const tcu::CommandLine &commandLine)
378 {
379 bool first = true;
380
381 // We need to solve two problems here:
382 // 1) The cache and cache mutex only have to be initialized once by the first thread that arrives here.
383 // 2) We must prevent other threads from exiting early from this function thinking they don't have to initialize the cache and
384 // cache mutex, only to try to lock the cache mutex while the first thread is still initializing it. To prevent this, we must
385 // hold an initialization mutex (declared below) while initializing the cache and cache mutex, making other threads wait.
386
387 // Used to check and set cacheFileFirstRun. We make it static, and C++11 guarantees it will only be initialized once.
388 static std::mutex cacheFileFirstRunMutex;
389 static bool cacheFileFirstRun = true;
390
391 // Is cacheFileFirstRun true for this thread?
392 bool needInit = false;
393
394 // Check cacheFileFirstRun only while holding the mutex, and hold it while initializing the cache.
395 const std::lock_guard<std::mutex> lock(cacheFileFirstRunMutex);
396 if (cacheFileFirstRun)
397 {
398 needInit = true;
399 cacheFileFirstRun = false;
400 }
401
402 if (needInit)
403 {
404 #ifndef DISABLE_SHADERCACHE_IPC
405 if (commandLine.isShaderCacheIPCEnabled())
406 {
407 // IPC path, allocate shared mutex and shared memory
408 cacheFileMutex = new cacheMutexIPC;
409 cacheFileMutex->lock();
410 ipc_mem_init(&cacheIPCMemory, "cts_shadercache_memory", sizeof(uint32_t) * (cacheMaxItems * 4 + 1));
411 if (ipc_mem_open_existing(&cacheIPCMemory) != 0)
412 {
413 ipc_mem_create(&cacheIPCMemory);
414 cacheMempool = (uint32_t *)ipc_mem_access(&cacheIPCMemory);
415 cacheMempool[0] = 0;
416 }
417 else
418 {
419 cacheMempool = (uint32_t *)ipc_mem_access(&cacheIPCMemory);
420 first = false;
421 }
422 atexit(shaderCacheCleanIPC);
423 }
424 else
425 #endif
426 {
427 // Non-IPC path, allocate local mutex and memory
428 cacheFileMutex = new cacheMutex;
429 cacheFileMutex->lock();
430 cacheMempool = new uint32_t[cacheMaxItems * 4 + 1];
431 cacheMempool[0] = 0;
432
433 atexit(shaderCacheClean);
434 }
435
436 if (first)
437 {
438 if (commandLine.isShaderCacheTruncateEnabled())
439 {
440 // Open file with "w" access to truncate it
441 FILE *f = fopen(commandLine.getShaderCacheFilename(), "wb");
442 if (f)
443 fclose(f);
444 }
445 else
446 {
447 // Parse chunked shader cache file for hashes and offsets
448 FILE *file = fopen(commandLine.getShaderCacheFilename(), "rb");
449 int count = 0;
450 if (file)
451 {
452 uint32_t chunksize = 0;
453 uint32_t hash = 0;
454 uint32_t offset = 0;
455 bool ok = true;
456 while (ok)
457 {
458 offset = (uint32_t)ftell(file);
459 if (ok)
460 ok = fread(&chunksize, 1, 4, file) == 4;
461 if (ok)
462 ok = fread(&hash, 1, 4, file) == 4;
463 if (ok)
464 cacheInsert(hash, offset);
465 if (ok)
466 ok = fseek(file, offset + chunksize, SEEK_SET) == 0;
467 count++;
468 }
469 fclose(file);
470 }
471 }
472 }
473 cacheFileMutex->unlock();
474 }
475 }
476
intToString(uint32_t integer)477 std::string intToString(uint32_t integer)
478 {
479 std::stringstream temp_sstream;
480
481 temp_sstream << integer;
482
483 return temp_sstream.str();
484 }
485
486 // 32-bit FNV-1 hash
shadercacheHash(const char * str)487 uint32_t shadercacheHash(const char *str)
488 {
489 uint32_t hash = 0x811c9dc5;
490 uint32_t c;
491 while ((c = (uint32_t)*str++) != 0)
492 {
493 hash *= 16777619;
494 hash ^= c;
495 }
496 return hash;
497 }
498
shadercacheLoad(const std::string & shaderstring,const char * shaderCacheFilename,uint32_t hash)499 vk::ProgramBinary *shadercacheLoad(const std::string &shaderstring, const char *shaderCacheFilename, uint32_t hash)
500 {
501 int32_t format;
502 int32_t length;
503 int32_t sourcelength;
504 uint32_t temp;
505 uint8_t *bin = 0;
506 char *source = 0;
507 bool ok = true;
508 bool diff = true;
509 cacheNode *node = 0;
510 cacheFileMutex->lock();
511
512 node = cacheSearch(hash);
513 if (node == 0)
514 {
515 cacheFileMutex->unlock();
516 return 0;
517 }
518 FILE *file = fopen(shaderCacheFilename, "rb");
519 ok = file != 0;
520
521 if (ok)
522 ok = fseek(file, node->data, SEEK_SET) == 0;
523 if (ok)
524 ok = fread(&temp, 1, 4, file) == 4; // Chunk size (skip)
525 if (ok)
526 ok = fread(&temp, 1, 4, file) == 4; // Stored hash
527 if (ok)
528 ok = temp == hash; // Double check
529 if (ok)
530 ok = fread(&format, 1, 4, file) == 4;
531 if (ok)
532 ok = fread(&length, 1, 4, file) == 4;
533 if (ok)
534 ok = length > 0; // Quick check
535 if (ok)
536 bin = new uint8_t[length];
537 if (ok)
538 ok = fread(bin, 1, length, file) == (size_t)length;
539 if (ok)
540 ok = fread(&sourcelength, 1, 4, file) == 4;
541 if (ok && sourcelength > 0)
542 {
543 source = new char[sourcelength + 1];
544 ok = fread(source, 1, sourcelength, file) == (size_t)sourcelength;
545 source[sourcelength] = 0;
546 diff = shaderstring != std::string(source);
547 }
548 if (!ok || diff)
549 {
550 // Mismatch
551 delete[] source;
552 delete[] bin;
553 }
554 else
555 {
556 delete[] source;
557 if (file)
558 fclose(file);
559 cacheFileMutex->unlock();
560 vk::ProgramBinary *res = new vk::ProgramBinary((vk::ProgramFormat)format, length, bin);
561 delete[] bin;
562 return res;
563 }
564 if (file)
565 fclose(file);
566 cacheFileMutex->unlock();
567 return 0;
568 }
569
shadercacheSave(const vk::ProgramBinary * binary,const std::string & shaderstring,const char * shaderCacheFilename,uint32_t hash)570 void shadercacheSave(const vk::ProgramBinary *binary, const std::string &shaderstring, const char *shaderCacheFilename,
571 uint32_t hash)
572 {
573 if (binary == 0)
574 return;
575 int32_t format = binary->getFormat();
576 uint32_t length = (uint32_t)binary->getSize();
577 uint32_t chunksize;
578 uint32_t offset;
579 const uint8_t *bin = binary->getBinary();
580 const de::FilePath filePath(shaderCacheFilename);
581 cacheNode *node = 0;
582
583 cacheFileMutex->lock();
584
585 node = cacheSearch(hash);
586
587 if (node)
588 {
589 FILE *file = fopen(shaderCacheFilename, "rb");
590 bool ok = (file != 0);
591 bool diff = true;
592 int32_t sourcelength;
593 uint32_t temp;
594
595 uint32_t cachedLength = 0;
596
597 if (ok)
598 ok = fseek(file, node->data, SEEK_SET) == 0;
599 if (ok)
600 ok = fread(&temp, 1, 4, file) == 4; // Chunk size (skip)
601 if (ok)
602 ok = fread(&temp, 1, 4, file) == 4; // Stored hash
603 if (ok)
604 ok = temp == hash; // Double check
605 if (ok)
606 ok = fread(&temp, 1, 4, file) == 4;
607 if (ok)
608 ok = fread(&cachedLength, 1, 4, file) == 4;
609 if (ok)
610 ok = cachedLength > 0; // Quick check
611 if (ok)
612 fseek(file, cachedLength, SEEK_CUR); // skip binary
613 if (ok)
614 ok = fread(&sourcelength, 1, 4, file) == 4;
615
616 if (ok && sourcelength > 0)
617 {
618 char *source;
619 source = new char[sourcelength + 1];
620 ok = fread(source, 1, sourcelength, file) == (size_t)sourcelength;
621 source[sourcelength] = 0;
622 diff = shaderstring != std::string(source);
623 delete[] source;
624 }
625
626 if (ok && !diff)
627 {
628 // Already in cache (written by another thread, probably)
629 fclose(file);
630 cacheFileMutex->unlock();
631 return;
632 }
633 fclose(file);
634 }
635
636 if (!de::FilePath(filePath.getDirName()).exists())
637 de::createDirectoryAndParents(filePath.getDirName().c_str());
638
639 FILE *file = fopen(shaderCacheFilename, "ab");
640 if (!file)
641 {
642 cacheFileMutex->unlock();
643 return;
644 }
645 // Append mode starts writing from the end of the file,
646 // but unless we do a seek, ftell returns 0.
647 fseek(file, 0, SEEK_END);
648 offset = (uint32_t)ftell(file);
649 chunksize = 4 + 4 + 4 + 4 + length + 4 + (uint32_t)shaderstring.length();
650 fwrite(&chunksize, 1, 4, file);
651 fwrite(&hash, 1, 4, file);
652 fwrite(&format, 1, 4, file);
653 fwrite(&length, 1, 4, file);
654 fwrite(bin, 1, length, file);
655 length = (uint32_t)shaderstring.length();
656 fwrite(&length, 1, 4, file);
657 fwrite(shaderstring.c_str(), 1, length, file);
658 fclose(file);
659 cacheInsert(hash, offset);
660
661 cacheFileMutex->unlock();
662 }
663
664 // Insert any information that may affect compilation into the shader string.
getCompileEnvironment(std::string & shaderstring)665 void getCompileEnvironment(std::string &shaderstring)
666 {
667 shaderstring += "GLSL:";
668 shaderstring += qpGetReleaseGlslName();
669 shaderstring += "\nSpir-v Tools:";
670 shaderstring += qpGetReleaseSpirvToolsName();
671 shaderstring += "\nSpir-v Headers:";
672 shaderstring += qpGetReleaseSpirvHeadersName();
673 shaderstring += "\n";
674 }
675
676 // Insert compilation options into the shader string.
getBuildOptions(std::string & shaderstring,const ShaderBuildOptions & buildOptions,int optimizationRecipe)677 void getBuildOptions(std::string &shaderstring, const ShaderBuildOptions &buildOptions, int optimizationRecipe)
678 {
679 shaderstring += "Target Spir-V ";
680 shaderstring += getSpirvVersionName(buildOptions.targetVersion);
681 shaderstring += "\n";
682 if (buildOptions.flags & ShaderBuildOptions::FLAG_ALLOW_RELAXED_OFFSETS)
683 shaderstring += "Flag:Allow relaxed offsets\n";
684 if (buildOptions.flags & ShaderBuildOptions::FLAG_USE_STORAGE_BUFFER_STORAGE_CLASS)
685 shaderstring += "Flag:Use storage buffer storage class\n";
686 if (optimizationRecipe != 0)
687 {
688 shaderstring += "Optimization recipe ";
689 shaderstring += de::toString(optimizationRecipe);
690 shaderstring += "\n";
691 }
692 }
693
buildProgram(const GlslSource & program,glu::ShaderProgramInfo * buildInfo,const tcu::CommandLine & commandLine)694 ProgramBinary *buildProgram(const GlslSource &program, glu::ShaderProgramInfo *buildInfo,
695 const tcu::CommandLine &commandLine)
696 {
697 const SpirvVersion spirvVersion = program.buildOptions.targetVersion;
698 const bool validateBinary = VALIDATE_BINARIES;
699 vector<uint32_t> binary;
700 std::string cachekey;
701 std::string shaderstring;
702 vk::ProgramBinary *res = 0;
703 const int optimizationRecipe = commandLine.getOptimizationRecipe();
704 uint32_t hash = 0;
705
706 if (commandLine.isShadercacheEnabled())
707 {
708 shaderCacheFirstRunCheck(commandLine);
709 getCompileEnvironment(cachekey);
710 getBuildOptions(cachekey, program.buildOptions, optimizationRecipe);
711
712 for (int i = 0; i < glu::SHADERTYPE_LAST; i++)
713 {
714 if (!program.sources[i].empty())
715 {
716 cachekey += glu::getShaderTypeName((glu::ShaderType)i);
717
718 for (std::vector<std::string>::const_iterator it = program.sources[i].begin();
719 it != program.sources[i].end(); ++it)
720 shaderstring += *it;
721 }
722 }
723
724 cachekey = cachekey + shaderstring;
725
726 hash = shadercacheHash(cachekey.c_str());
727
728 res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename(), hash);
729
730 if (res)
731 {
732 buildInfo->program.infoLog = "Loaded from cache";
733 buildInfo->program.linkOk = true;
734 buildInfo->program.linkTimeUs = 0;
735
736 for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
737 {
738 if (!program.sources[shaderType].empty())
739 {
740 glu::ShaderInfo shaderBuildInfo;
741
742 shaderBuildInfo.type = (glu::ShaderType)shaderType;
743 shaderBuildInfo.source = shaderstring;
744 shaderBuildInfo.compileTimeUs = 0;
745 shaderBuildInfo.compileOk = true;
746
747 buildInfo->shaders.push_back(shaderBuildInfo);
748 }
749 }
750 }
751 }
752
753 if (!res)
754 {
755 {
756 vector<uint32_t> nonStrippedBinary;
757
758 if (!compileGlslToSpirV(program, &nonStrippedBinary, buildInfo))
759 TCU_THROW(InternalError, "Compiling GLSL to SPIR-V failed");
760
761 TCU_CHECK_INTERNAL(!nonStrippedBinary.empty());
762 stripSpirVDebugInfo(nonStrippedBinary.size(), &nonStrippedBinary[0], &binary);
763 TCU_CHECK_INTERNAL(!binary.empty());
764 }
765
766 if (optimizationRecipe != 0)
767 {
768 validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
769 optimizeCompiledBinary(binary, optimizationRecipe, spirvVersion);
770 }
771
772 if (validateBinary)
773 {
774 validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
775 }
776
777 res = createProgramBinaryFromSpirV(binary);
778 if (commandLine.isShadercacheEnabled())
779 shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename(), hash);
780 }
781 return res;
782 }
783
buildProgram(const HlslSource & program,glu::ShaderProgramInfo * buildInfo,const tcu::CommandLine & commandLine)784 ProgramBinary *buildProgram(const HlslSource &program, glu::ShaderProgramInfo *buildInfo,
785 const tcu::CommandLine &commandLine)
786 {
787 const SpirvVersion spirvVersion = program.buildOptions.targetVersion;
788 const bool validateBinary = VALIDATE_BINARIES;
789 vector<uint32_t> binary;
790 std::string cachekey;
791 std::string shaderstring;
792 vk::ProgramBinary *res = 0;
793 const int optimizationRecipe = commandLine.getOptimizationRecipe();
794 int32_t hash = 0;
795
796 if (commandLine.isShadercacheEnabled())
797 {
798 shaderCacheFirstRunCheck(commandLine);
799 getCompileEnvironment(cachekey);
800 getBuildOptions(cachekey, program.buildOptions, optimizationRecipe);
801
802 for (int i = 0; i < glu::SHADERTYPE_LAST; i++)
803 {
804 if (!program.sources[i].empty())
805 {
806 cachekey += glu::getShaderTypeName((glu::ShaderType)i);
807
808 for (std::vector<std::string>::const_iterator it = program.sources[i].begin();
809 it != program.sources[i].end(); ++it)
810 shaderstring += *it;
811 }
812 }
813
814 cachekey = cachekey + shaderstring;
815
816 hash = shadercacheHash(cachekey.c_str());
817
818 res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename(), hash);
819
820 if (res)
821 {
822 buildInfo->program.infoLog = "Loaded from cache";
823 buildInfo->program.linkOk = true;
824 buildInfo->program.linkTimeUs = 0;
825
826 for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
827 {
828 if (!program.sources[shaderType].empty())
829 {
830 glu::ShaderInfo shaderBuildInfo;
831
832 shaderBuildInfo.type = (glu::ShaderType)shaderType;
833 shaderBuildInfo.source = shaderstring;
834 shaderBuildInfo.compileTimeUs = 0;
835 shaderBuildInfo.compileOk = true;
836
837 buildInfo->shaders.push_back(shaderBuildInfo);
838 }
839 }
840 }
841 }
842
843 if (!res)
844 {
845 {
846 vector<uint32_t> nonStrippedBinary;
847
848 if (!compileHlslToSpirV(program, &nonStrippedBinary, buildInfo))
849 TCU_THROW(InternalError, "Compiling HLSL to SPIR-V failed");
850
851 TCU_CHECK_INTERNAL(!nonStrippedBinary.empty());
852 stripSpirVDebugInfo(nonStrippedBinary.size(), &nonStrippedBinary[0], &binary);
853 TCU_CHECK_INTERNAL(!binary.empty());
854 }
855
856 if (optimizationRecipe != 0)
857 {
858 validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
859 optimizeCompiledBinary(binary, optimizationRecipe, spirvVersion);
860 }
861
862 if (validateBinary)
863 {
864 validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
865 }
866
867 res = createProgramBinaryFromSpirV(binary);
868 if (commandLine.isShadercacheEnabled())
869 {
870 shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename(), hash);
871 }
872 }
873 return res;
874 }
875
assembleProgram(const SpirVAsmSource & program,SpirVProgramInfo * buildInfo,const tcu::CommandLine & commandLine)876 ProgramBinary *assembleProgram(const SpirVAsmSource &program, SpirVProgramInfo *buildInfo,
877 const tcu::CommandLine &commandLine)
878 {
879 const SpirvVersion spirvVersion = program.buildOptions.targetVersion;
880 const bool validateBinary = VALIDATE_BINARIES;
881 vector<uint32_t> binary;
882 vk::ProgramBinary *res = 0;
883 std::string cachekey;
884 const int optimizationRecipe = commandLine.isSpirvOptimizationEnabled() ? commandLine.getOptimizationRecipe() : 0;
885 uint32_t hash = 0;
886
887 if (commandLine.isShadercacheEnabled())
888 {
889 shaderCacheFirstRunCheck(commandLine);
890 getCompileEnvironment(cachekey);
891 cachekey += "Target Spir-V ";
892 cachekey += getSpirvVersionName(spirvVersion);
893 cachekey += "\n";
894 if (optimizationRecipe != 0)
895 {
896 cachekey += "Optimization recipe ";
897 cachekey += de::toString(optimizationRecipe);
898 cachekey += "\n";
899 }
900
901 cachekey += program.source;
902
903 hash = shadercacheHash(cachekey.c_str());
904
905 res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename(), hash);
906
907 if (res)
908 {
909 buildInfo->source = program.source;
910 buildInfo->compileOk = true;
911 buildInfo->compileTimeUs = 0;
912 buildInfo->infoLog = "Loaded from cache";
913 }
914 }
915
916 if (!res)
917 {
918
919 if (!assembleSpirV(&program, &binary, buildInfo, spirvVersion))
920 TCU_THROW(InternalError, "Failed to assemble SPIR-V");
921
922 if (optimizationRecipe != 0)
923 {
924 validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
925 optimizeCompiledBinary(binary, optimizationRecipe, spirvVersion);
926 }
927
928 if (validateBinary)
929 {
930 validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
931 }
932
933 res = createProgramBinaryFromSpirV(binary);
934 if (commandLine.isShadercacheEnabled())
935 {
936 shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename(), hash);
937 }
938 }
939 return res;
940 }
941
disassembleProgram(const ProgramBinary & program,std::ostream * dst)942 void disassembleProgram(const ProgramBinary &program, std::ostream *dst)
943 {
944 if (program.getFormat() == PROGRAM_FORMAT_SPIRV)
945 {
946 TCU_CHECK_INTERNAL(isSaneSpirVBinary(program));
947
948 if (isNativeSpirVBinaryEndianness())
949 disassembleSpirV(program.getSize() / sizeof(uint32_t), (const uint32_t *)program.getBinary(), dst,
950 extractSpirvVersion(program));
951 else
952 TCU_THROW(InternalError, "SPIR-V endianness translation not supported");
953 }
954 else
955 TCU_THROW(NotSupportedError, "Unsupported program format");
956 }
957
validateProgram(const ProgramBinary & program,std::ostream * dst,const SpirvValidatorOptions & options)958 bool validateProgram(const ProgramBinary &program, std::ostream *dst, const SpirvValidatorOptions &options)
959 {
960 if (program.getFormat() == PROGRAM_FORMAT_SPIRV)
961 {
962 if (!isSaneSpirVBinary(program))
963 {
964 *dst << "Binary doesn't look like SPIR-V at all";
965 return false;
966 }
967
968 if (isNativeSpirVBinaryEndianness())
969 return validateSpirV(program.getSize() / sizeof(uint32_t), (const uint32_t *)program.getBinary(), dst,
970 options);
971 else
972 TCU_THROW(InternalError, "SPIR-V endianness translation not supported");
973 }
974 else
975 TCU_THROW(NotSupportedError, "Unsupported program format");
976 }
977
createShaderModule(const DeviceInterface & deviceInterface,VkDevice device,const ProgramBinary & binary,VkShaderModuleCreateFlags flags)978 Move<VkShaderModule> createShaderModule(const DeviceInterface &deviceInterface, VkDevice device,
979 const ProgramBinary &binary, VkShaderModuleCreateFlags flags)
980 {
981 if (binary.getFormat() == PROGRAM_FORMAT_SPIRV)
982 {
983 const struct VkShaderModuleCreateInfo shaderModuleInfo = {
984 VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, DE_NULL, flags, (uintptr_t)binary.getSize(),
985 (const uint32_t *)binary.getBinary(),
986 };
987
988 binary.setUsed();
989
990 return createShaderModule(deviceInterface, device, &shaderModuleInfo);
991 }
992 else
993 TCU_THROW(NotSupportedError, "Unsupported program format");
994 }
995
getGluShaderType(VkShaderStageFlagBits shaderStage)996 glu::ShaderType getGluShaderType(VkShaderStageFlagBits shaderStage)
997 {
998 switch (shaderStage)
999 {
1000 case VK_SHADER_STAGE_VERTEX_BIT:
1001 return glu::SHADERTYPE_VERTEX;
1002 case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
1003 return glu::SHADERTYPE_TESSELLATION_CONTROL;
1004 case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
1005 return glu::SHADERTYPE_TESSELLATION_EVALUATION;
1006 case VK_SHADER_STAGE_GEOMETRY_BIT:
1007 return glu::SHADERTYPE_GEOMETRY;
1008 case VK_SHADER_STAGE_FRAGMENT_BIT:
1009 return glu::SHADERTYPE_FRAGMENT;
1010 case VK_SHADER_STAGE_COMPUTE_BIT:
1011 return glu::SHADERTYPE_COMPUTE;
1012 default:
1013 DE_FATAL("Unknown shader stage");
1014 return glu::SHADERTYPE_LAST;
1015 }
1016 }
1017
getVkShaderStage(glu::ShaderType shaderType)1018 VkShaderStageFlagBits getVkShaderStage(glu::ShaderType shaderType)
1019 {
1020 static const VkShaderStageFlagBits s_shaderStages[] = {
1021 VK_SHADER_STAGE_VERTEX_BIT,
1022 VK_SHADER_STAGE_FRAGMENT_BIT,
1023 VK_SHADER_STAGE_GEOMETRY_BIT,
1024 VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
1025 VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
1026 VK_SHADER_STAGE_COMPUTE_BIT,
1027 #ifndef CTS_USES_VULKANSC
1028 VK_SHADER_STAGE_RAYGEN_BIT_NV,
1029 VK_SHADER_STAGE_ANY_HIT_BIT_NV,
1030 VK_SHADER_STAGE_CLOSEST_HIT_BIT_NV,
1031 VK_SHADER_STAGE_MISS_BIT_NV,
1032 VK_SHADER_STAGE_INTERSECTION_BIT_NV,
1033 VK_SHADER_STAGE_CALLABLE_BIT_NV,
1034 VK_SHADER_STAGE_TASK_BIT_NV,
1035 VK_SHADER_STAGE_MESH_BIT_NV,
1036 #else // CTS_USES_VULKANSC
1037 (VkShaderStageFlagBits)64u,
1038 (VkShaderStageFlagBits)128u,
1039 (VkShaderStageFlagBits)256u,
1040 (VkShaderStageFlagBits)512u,
1041 (VkShaderStageFlagBits)1024u,
1042 (VkShaderStageFlagBits)2048u,
1043 (VkShaderStageFlagBits)4096u,
1044 (VkShaderStageFlagBits)8192u
1045 #endif // CTS_USES_VULKANSC
1046 };
1047
1048 return de::getSizedArrayElement<glu::SHADERTYPE_LAST>(s_shaderStages, shaderType);
1049 }
1050
1051 // Baseline version, to be used for shaders which don't specify a version
getBaselineSpirvVersion(const uint32_t)1052 vk::SpirvVersion getBaselineSpirvVersion(const uint32_t /* vulkanVersion */)
1053 {
1054 return vk::SPIRV_VERSION_1_0;
1055 }
1056
1057 // Max supported versions for each Vulkan version, without requiring a Vulkan extension.
getMaxSpirvVersionForVulkan(const uint32_t vulkanVersion)1058 vk::SpirvVersion getMaxSpirvVersionForVulkan(const uint32_t vulkanVersion)
1059 {
1060 vk::SpirvVersion result = vk::SPIRV_VERSION_LAST;
1061
1062 uint32_t vulkanVersionVariantMajorMinor =
1063 VK_MAKE_API_VERSION(VK_API_VERSION_VARIANT(vulkanVersion), VK_API_VERSION_MAJOR(vulkanVersion),
1064 VK_API_VERSION_MINOR(vulkanVersion), 0);
1065 if (vulkanVersionVariantMajorMinor == VK_API_VERSION_1_0)
1066 result = vk::SPIRV_VERSION_1_0;
1067 else if (vulkanVersionVariantMajorMinor == VK_API_VERSION_1_1)
1068 result = vk::SPIRV_VERSION_1_3;
1069 #ifndef CTS_USES_VULKANSC
1070 else if (vulkanVersionVariantMajorMinor == VK_API_VERSION_1_2)
1071 result = vk::SPIRV_VERSION_1_5;
1072 else if (vulkanVersionVariantMajorMinor >= VK_API_VERSION_1_3)
1073 result = vk::SPIRV_VERSION_1_6;
1074 #else
1075 else if (vulkanVersionVariantMajorMinor >= VK_API_VERSION_1_2)
1076 result = vk::SPIRV_VERSION_1_5;
1077 #endif // CTS_USES_VULKANSC
1078
1079 DE_ASSERT(result < vk::SPIRV_VERSION_LAST);
1080
1081 return result;
1082 }
1083
getMaxSpirvVersionForAsm(const uint32_t vulkanVersion)1084 vk::SpirvVersion getMaxSpirvVersionForAsm(const uint32_t vulkanVersion)
1085 {
1086 return getMaxSpirvVersionForVulkan(vulkanVersion);
1087 }
1088
getMaxSpirvVersionForGlsl(const uint32_t vulkanVersion)1089 vk::SpirvVersion getMaxSpirvVersionForGlsl(const uint32_t vulkanVersion)
1090 {
1091 return getMaxSpirvVersionForVulkan(vulkanVersion);
1092 }
1093
extractSpirvVersion(const ProgramBinary & binary)1094 SpirvVersion extractSpirvVersion(const ProgramBinary &binary)
1095 {
1096 DE_STATIC_ASSERT(SPIRV_VERSION_1_6 + 1 == SPIRV_VERSION_LAST);
1097
1098 if (binary.getFormat() != PROGRAM_FORMAT_SPIRV)
1099 TCU_THROW(InternalError, "Binary is not in SPIR-V format");
1100
1101 if (!isSaneSpirVBinary(binary) || binary.getSize() < sizeof(SpirvBinaryHeader))
1102 TCU_THROW(InternalError, "Invalid SPIR-V header format");
1103
1104 const uint32_t spirvBinaryVersion10 = 0x00010000;
1105 const uint32_t spirvBinaryVersion11 = 0x00010100;
1106 const uint32_t spirvBinaryVersion12 = 0x00010200;
1107 const uint32_t spirvBinaryVersion13 = 0x00010300;
1108 const uint32_t spirvBinaryVersion14 = 0x00010400;
1109 const uint32_t spirvBinaryVersion15 = 0x00010500;
1110 const uint32_t spirvBinaryVersion16 = 0x00010600;
1111 const SpirvBinaryHeader *header = reinterpret_cast<const SpirvBinaryHeader *>(binary.getBinary());
1112 const uint32_t spirvVersion = isNativeSpirVBinaryEndianness() ? header->version : deReverseBytes32(header->version);
1113 SpirvVersion result = SPIRV_VERSION_LAST;
1114
1115 switch (spirvVersion)
1116 {
1117 case spirvBinaryVersion10:
1118 result = SPIRV_VERSION_1_0;
1119 break; //!< SPIR-V 1.0
1120 case spirvBinaryVersion11:
1121 result = SPIRV_VERSION_1_1;
1122 break; //!< SPIR-V 1.1
1123 case spirvBinaryVersion12:
1124 result = SPIRV_VERSION_1_2;
1125 break; //!< SPIR-V 1.2
1126 case spirvBinaryVersion13:
1127 result = SPIRV_VERSION_1_3;
1128 break; //!< SPIR-V 1.3
1129 case spirvBinaryVersion14:
1130 result = SPIRV_VERSION_1_4;
1131 break; //!< SPIR-V 1.4
1132 case spirvBinaryVersion15:
1133 result = SPIRV_VERSION_1_5;
1134 break; //!< SPIR-V 1.5
1135 case spirvBinaryVersion16:
1136 result = SPIRV_VERSION_1_6;
1137 break; //!< SPIR-V 1.6
1138 default:
1139 TCU_THROW(InternalError, "Unknown SPIR-V version detected in binary");
1140 }
1141
1142 return result;
1143 }
1144
getSpirvVersionName(const SpirvVersion spirvVersion)1145 std::string getSpirvVersionName(const SpirvVersion spirvVersion)
1146 {
1147 DE_STATIC_ASSERT(SPIRV_VERSION_1_6 + 1 == SPIRV_VERSION_LAST);
1148 DE_ASSERT(spirvVersion < SPIRV_VERSION_LAST);
1149
1150 std::string result;
1151
1152 switch (spirvVersion)
1153 {
1154 case SPIRV_VERSION_1_0:
1155 result = "1.0";
1156 break; //!< SPIR-V 1.0
1157 case SPIRV_VERSION_1_1:
1158 result = "1.1";
1159 break; //!< SPIR-V 1.1
1160 case SPIRV_VERSION_1_2:
1161 result = "1.2";
1162 break; //!< SPIR-V 1.2
1163 case SPIRV_VERSION_1_3:
1164 result = "1.3";
1165 break; //!< SPIR-V 1.3
1166 case SPIRV_VERSION_1_4:
1167 result = "1.4";
1168 break; //!< SPIR-V 1.4
1169 case SPIRV_VERSION_1_5:
1170 result = "1.5";
1171 break; //!< SPIR-V 1.5
1172 case SPIRV_VERSION_1_6:
1173 result = "1.6";
1174 break; //!< SPIR-V 1.6
1175 default:
1176 result = "Unknown";
1177 }
1178
1179 return result;
1180 }
1181
operator ++(SpirvVersion & spirvVersion)1182 SpirvVersion &operator++(SpirvVersion &spirvVersion)
1183 {
1184 if (spirvVersion == SPIRV_VERSION_LAST)
1185 spirvVersion = SPIRV_VERSION_1_0;
1186 else
1187 spirvVersion = static_cast<SpirvVersion>(static_cast<uint32_t>(spirvVersion) + 1);
1188
1189 return spirvVersion;
1190 }
1191
1192 } // namespace vk
1193