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