xref: /aosp_15_r20/external/deqp/external/vulkancts/framework/vulkan/vkPrograms.cpp (revision 35238bce31c2a825756842865a792f8cf7f89930)
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