• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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