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