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