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 namespace vk
44 {
45 
46 using std::string;
47 using std::vector;
48 using std::map;
49 
50 #if defined(DE_DEBUG)
51 #	define VALIDATE_BINARIES	true
52 #else
53 #	define VALIDATE_BINARIES	false
54 #endif
55 
56 #define SPIRV_BINARY_ENDIANNESS DE_LITTLE_ENDIAN
57 
58 // ProgramBinary
59 
ProgramBinary(ProgramFormat format,size_t binarySize,const deUint8 * binary)60 ProgramBinary::ProgramBinary (ProgramFormat format, size_t binarySize, const deUint8* binary)
61 	: m_format	(format)
62 	, m_binary	(binary, binary+binarySize)
63 	, m_used	(false)
64 {
65 }
66 
67 // Utils
68 
69 namespace
70 {
71 
isNativeSpirVBinaryEndianness(void)72 bool isNativeSpirVBinaryEndianness (void)
73 {
74 #if (DE_ENDIANNESS == SPIRV_BINARY_ENDIANNESS)
75 	return true;
76 #else
77 	return false;
78 #endif
79 }
80 
isSaneSpirVBinary(const ProgramBinary & binary)81 bool isSaneSpirVBinary (const ProgramBinary& binary)
82 {
83 	const deUint32	spirvMagicWord	= 0x07230203;
84 	const deUint32	spirvMagicBytes	= isNativeSpirVBinaryEndianness()
85 									? spirvMagicWord
86 									: deReverseBytes32(spirvMagicWord);
87 
88 	DE_ASSERT(binary.getFormat() == PROGRAM_FORMAT_SPIRV);
89 
90 	if (binary.getSize() % sizeof(deUint32) != 0)
91 		return false;
92 
93 	if (binary.getSize() < sizeof(deUint32))
94 		return false;
95 
96 	if (*(const deUint32*)binary.getBinary() != spirvMagicBytes)
97 		return false;
98 
99 	return true;
100 }
101 
optimizeCompiledBinary(vector<deUint32> & binary,int optimizationRecipe,const SpirvVersion spirvVersion)102 void optimizeCompiledBinary (vector<deUint32>& binary, int optimizationRecipe, const SpirvVersion spirvVersion)
103 {
104 	spv_target_env targetEnv = SPV_ENV_VULKAN_1_0;
105 
106 	// Map SpirvVersion with spv_target_env:
107 	switch (spirvVersion)
108 	{
109 		case SPIRV_VERSION_1_0: targetEnv = SPV_ENV_VULKAN_1_0;	break;
110 		case SPIRV_VERSION_1_1:
111 		case SPIRV_VERSION_1_2:
112 		case SPIRV_VERSION_1_3: targetEnv = SPV_ENV_VULKAN_1_1;	break;
113 		case SPIRV_VERSION_1_4: targetEnv = SPV_ENV_VULKAN_1_1_SPIRV_1_4;	break;
114 		case SPIRV_VERSION_1_5: targetEnv = SPV_ENV_VULKAN_1_2;	break;
115 		default:
116 			TCU_THROW(InternalError, "Unexpected SPIR-V version requested");
117 	}
118 
119 	spvtools::Optimizer optimizer(targetEnv);
120 
121 	switch (optimizationRecipe)
122 	{
123 		case 1:
124 			optimizer.RegisterPerformancePasses();
125 			break;
126 		case 2:
127 			optimizer.RegisterSizePasses();
128 			break;
129 		default:
130 			TCU_THROW(InternalError, "Unknown optimization recipe requested");
131 	}
132 
133 	spvtools::OptimizerOptions optimizer_options;
134 	optimizer_options.set_run_validator(false);
135 	const bool ok = optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
136 
137 	if (!ok)
138 		TCU_THROW(InternalError, "Optimizer call failed");
139 }
140 
createProgramBinaryFromSpirV(const vector<deUint32> & binary)141 ProgramBinary* createProgramBinaryFromSpirV (const vector<deUint32>& binary)
142 {
143 	DE_ASSERT(!binary.empty());
144 
145 	if (isNativeSpirVBinaryEndianness())
146 		return new ProgramBinary(PROGRAM_FORMAT_SPIRV, binary.size()*sizeof(deUint32), (const deUint8*)&binary[0]);
147 	else
148 		TCU_THROW(InternalError, "SPIR-V endianness translation not supported");
149 }
150 
151 } // anonymous
152 
validateCompiledBinary(const vector<deUint32> & binary,glu::ShaderProgramInfo * buildInfo,const SpirvValidatorOptions & options)153 void validateCompiledBinary(const vector<deUint32>& binary, glu::ShaderProgramInfo* buildInfo, const SpirvValidatorOptions& options)
154 {
155 	std::ostringstream validationLog;
156 
157 	if (!validateSpirV(binary.size(), &binary[0], &validationLog, options))
158 	{
159 		buildInfo->program.linkOk	 = false;
160 		buildInfo->program.infoLog	+= "\n" + validationLog.str();
161 
162 		TCU_THROW(InternalError, "Validation failed for compiled SPIR-V binary");
163 	}
164 }
165 
validateCompiledBinary(const vector<deUint32> & binary,SpirVProgramInfo * buildInfo,const SpirvValidatorOptions & options)166 void validateCompiledBinary(const vector<deUint32>& binary, SpirVProgramInfo* buildInfo, const SpirvValidatorOptions& options)
167 {
168 	std::ostringstream validationLog;
169 
170 	if (!validateSpirV(binary.size(), &binary[0], &validationLog, options))
171 	{
172 		buildInfo->compileOk = false;
173 		buildInfo->infoLog += "\n" + validationLog.str();
174 
175 		TCU_THROW(InternalError, "Validation failed for compiled SPIR-V binary");
176 	}
177 }
178 
179 de::Mutex							cacheFileMutex;
180 map<deUint32, vector<deUint32> >	cacheFileIndex;
181 bool								cacheFileFirstRun = true;
182 
shaderCacheFirstRunCheck(const char * shaderCacheFile,bool truncate)183 void shaderCacheFirstRunCheck (const char* shaderCacheFile, bool truncate)
184 {
185 	cacheFileMutex.lock();
186 	if (cacheFileFirstRun)
187 	{
188 		cacheFileFirstRun = false;
189 		if (truncate)
190 		{
191 			// Open file with "w" access to truncate it
192 			FILE* f = fopen(shaderCacheFile, "wb");
193 			if (f)
194 				fclose(f);
195 		}
196 		else
197 		{
198 			// Parse chunked shader cache file for hashes and offsets
199 			FILE* file = fopen(shaderCacheFile, "rb");
200 			int count = 0;
201 			if (file)
202 			{
203 				deUint32 chunksize	= 0;
204 				deUint32 hash		= 0;
205 				deUint32 offset		= 0;
206 				bool ok				= true;
207 				while (ok)
208 				{
209 					offset = (deUint32)ftell(file);
210 					if (ok) ok = fread(&chunksize, 1, 4, file)				== 4;
211 					if (ok) ok = fread(&hash, 1, 4, file)					== 4;
212 					if (ok) cacheFileIndex[hash].push_back(offset);
213 					if (ok) ok = fseek(file, offset + chunksize, SEEK_SET)	== 0;
214 					count++;
215 				}
216 				fclose(file);
217 			}
218 		}
219 	}
220 	cacheFileMutex.unlock();
221 }
222 
intToString(deUint32 integer)223 std::string intToString (deUint32 integer)
224 {
225 	std::stringstream temp_sstream;
226 
227 	temp_sstream << integer;
228 
229 	return temp_sstream.str();
230 }
231 
232 // 32-bit FNV-1 hash
shadercacheHash(const char * str)233 deUint32 shadercacheHash (const char* str)
234 {
235 	deUint32 hash = 0x811c9dc5;
236 	deUint32 c;
237 	while ((c = (deUint32)*str++) != 0)
238 	{
239 		hash *= 16777619;
240 		hash ^= c;
241 	}
242 	return hash;
243 }
244 
shadercacheLoad(const std::string & shaderstring,const char * shaderCacheFilename)245 vk::ProgramBinary* shadercacheLoad (const std::string& shaderstring, const char* shaderCacheFilename)
246 {
247 	deUint32		hash		= shadercacheHash(shaderstring.c_str());
248 	deInt32			format;
249 	deInt32			length;
250 	deInt32			sourcelength;
251 	deUint32		i;
252 	deUint32		temp;
253 	deUint8*		bin			= 0;
254 	char*			source		= 0;
255 	deBool			ok			= true;
256 	deBool			diff		= true;
257 	cacheFileMutex.lock();
258 
259 	if (cacheFileIndex.count(hash) == 0)
260 	{
261 		cacheFileMutex.unlock();
262 		return 0;
263 	}
264 	FILE*			file		= fopen(shaderCacheFilename, "rb");
265 	ok				= file											!= 0;
266 
267 	for (i = 0; i < cacheFileIndex[hash].size(); i++)
268 	{
269 		if (ok) ok = fseek(file, cacheFileIndex[hash][i], SEEK_SET)	== 0;
270 		if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Chunk size (skip)
271 		if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Stored hash
272 		if (ok) ok = temp											== hash; // Double check
273 		if (ok) ok = fread(&format, 1, 4, file)						== 4;
274 		if (ok) ok = fread(&length, 1, 4, file)						== 4;
275 		if (ok) ok = length											> 0; // sanity check
276 		if (ok) bin = new deUint8[length];
277 		if (ok) ok = fread(bin, 1, length, file)					== (size_t)length;
278 		if (ok) ok = fread(&sourcelength, 1, 4, file)				== 4;
279 		if (ok && sourcelength > 0)
280 		{
281 			source = new char[sourcelength + 1];
282 			ok = fread(source, 1, sourcelength, file)				== (size_t)sourcelength;
283 			source[sourcelength] = 0;
284 			diff = shaderstring != std::string(source);
285 		}
286 		if (!ok || diff)
287 		{
288 			// Mismatch, but may still exist in cache if there were hash collisions
289 			delete[] source;
290 			delete[] bin;
291 		}
292 		else
293 		{
294 			delete[] source;
295 			if (file) fclose(file);
296 			cacheFileMutex.unlock();
297 			vk::ProgramBinary* res = new vk::ProgramBinary((vk::ProgramFormat)format, length, bin);
298 			delete[] bin;
299 			return res;
300 		}
301 	}
302 	if (file) fclose(file);
303 	cacheFileMutex.unlock();
304 	return 0;
305 }
306 
shadercacheSave(const vk::ProgramBinary * binary,const std::string & shaderstring,const char * shaderCacheFilename)307 void shadercacheSave (const vk::ProgramBinary* binary, const std::string& shaderstring, const char* shaderCacheFilename)
308 {
309 	if (binary == 0)
310 		return;
311 	deUint32			hash		= shadercacheHash(shaderstring.c_str());
312 	deInt32				format		= binary->getFormat();
313 	deUint32			length		= (deUint32)binary->getSize();
314 	deUint32			chunksize;
315 	deUint32			offset;
316 	const deUint8*		bin			= binary->getBinary();
317 	const de::FilePath	filePath	(shaderCacheFilename);
318 
319 	cacheFileMutex.lock();
320 
321 	if (cacheFileIndex[hash].size())
322 	{
323 		FILE*			file		= fopen(shaderCacheFilename, "rb");
324 		deBool			ok			= (file != 0);
325 		deBool			diff		= DE_TRUE;
326 		deInt32			sourcelength;
327 		deUint32		i;
328 		deUint32		temp;
329 
330 		for (i = 0; i < cacheFileIndex[hash].size(); i++)
331 		{
332 			deUint32	cachedLength	= 0;
333 
334 			if (ok) ok = fseek(file, cacheFileIndex[hash][i], SEEK_SET)	== 0;
335 			if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Chunk size (skip)
336 			if (ok) ok = fread(&temp, 1, 4, file)						== 4; // Stored hash
337 			if (ok) ok = temp											== hash; // Double check
338 			if (ok) ok = fread(&temp, 1, 4, file)						== 4;
339 			if (ok) ok = fread(&cachedLength, 1, 4, file)				== 4;
340 			if (ok) ok = cachedLength									> 0; // sanity check
341 			if (ok) fseek(file, cachedLength, SEEK_CUR); // skip binary
342 			if (ok) ok = fread(&sourcelength, 1, 4, file)				== 4;
343 
344 			if (ok && sourcelength > 0)
345 			{
346 				char* source;
347 				source	= new char[sourcelength + 1];
348 				ok		= fread(source, 1, sourcelength, file)			== (size_t)sourcelength;
349 				source[sourcelength] = 0;
350 				diff	= shaderstring != std::string(source);
351 				delete[] source;
352 			}
353 
354 			if (ok && !diff)
355 			{
356 				// Already in cache (written by another thread, probably)
357 				fclose(file);
358 				cacheFileMutex.unlock();
359 				return;
360 			}
361 		}
362 		fclose(file);
363 	}
364 
365 	if (!de::FilePath(filePath.getDirName()).exists())
366 		de::createDirectoryAndParents(filePath.getDirName().c_str());
367 
368 	FILE*				file		= fopen(shaderCacheFilename, "ab");
369 	if (!file)
370 	{
371 		cacheFileMutex.unlock();
372 		return;
373 	}
374 	// Append mode starts writing from the end of the file,
375 	// but unless we do a seek, ftell returns 0.
376 	fseek(file, 0, SEEK_END);
377 	offset		= (deUint32)ftell(file);
378 	chunksize	= 4 + 4 + 4 + 4 + length + 4 + (deUint32)shaderstring.length();
379 	fwrite(&chunksize, 1, 4, file);
380 	fwrite(&hash, 1, 4, file);
381 	fwrite(&format, 1, 4, file);
382 	fwrite(&length, 1, 4, file);
383 	fwrite(bin, 1, length, file);
384 	length = (deUint32)shaderstring.length();
385 	fwrite(&length, 1, 4, file);
386 	fwrite(shaderstring.c_str(), 1, length, file);
387 	fclose(file);
388 	cacheFileIndex[hash].push_back(offset);
389 
390 	cacheFileMutex.unlock();
391 }
392 
393 // Insert any information that may affect compilation into the shader string.
getCompileEnvironment(std::string & shaderstring)394 void getCompileEnvironment (std::string& shaderstring)
395 {
396 	shaderstring += "GLSL:";
397 	shaderstring += qpGetReleaseGlslName();
398 	shaderstring += "\nSpir-v Tools:";
399 	shaderstring += qpGetReleaseSpirvToolsName();
400 	shaderstring += "\nSpir-v Headers:";
401 	shaderstring += qpGetReleaseSpirvHeadersName();
402 	shaderstring += "\n";
403 }
404 
405 // Insert compilation options into the shader string.
getBuildOptions(std::string & shaderstring,const ShaderBuildOptions & buildOptions,int optimizationRecipe)406 void getBuildOptions (std::string& shaderstring, const ShaderBuildOptions& buildOptions, int optimizationRecipe)
407 {
408 	shaderstring += "Target Spir-V ";
409 	shaderstring += getSpirvVersionName(buildOptions.targetVersion);
410 	shaderstring += "\n";
411 	if (buildOptions.flags & ShaderBuildOptions::FLAG_ALLOW_RELAXED_OFFSETS)
412 		shaderstring += "Flag:Allow relaxed offsets\n";
413 	if (buildOptions.flags & ShaderBuildOptions::FLAG_USE_STORAGE_BUFFER_STORAGE_CLASS)
414 		shaderstring += "Flag:Use storage buffer storage class\n";
415 	if (optimizationRecipe != 0)
416 	{
417 		shaderstring += "Optimization recipe ";
418 		shaderstring += de::toString(optimizationRecipe);
419 		shaderstring += "\n";
420 	}
421 }
422 
buildProgram(const GlslSource & program,glu::ShaderProgramInfo * buildInfo,const tcu::CommandLine & commandLine)423 ProgramBinary* buildProgram (const GlslSource& program, glu::ShaderProgramInfo* buildInfo, const tcu::CommandLine& commandLine)
424 {
425 	const SpirvVersion	spirvVersion		= program.buildOptions.targetVersion;
426 	const bool			validateBinary		= VALIDATE_BINARIES;
427 	vector<deUint32>	binary;
428 	std::string			cachekey;
429 	std::string			shaderstring;
430 	vk::ProgramBinary*	res					= 0;
431 	const int			optimizationRecipe	= commandLine.getOptimizationRecipe();
432 
433 	if (commandLine.isShadercacheEnabled())
434 	{
435 		shaderCacheFirstRunCheck(commandLine.getShaderCacheFilename(), commandLine.isShaderCacheTruncateEnabled());
436 		getCompileEnvironment(cachekey);
437 		getBuildOptions(cachekey, program.buildOptions, optimizationRecipe);
438 
439 		for (int i = 0; i < glu::SHADERTYPE_LAST; i++)
440 		{
441 			if (!program.sources[i].empty())
442 			{
443 				cachekey += glu::getShaderTypeName((glu::ShaderType)i);
444 
445 				for (std::vector<std::string>::const_iterator it = program.sources[i].begin(); it != program.sources[i].end(); ++it)
446 					shaderstring += *it;
447 			}
448 		}
449 
450 		cachekey = cachekey + shaderstring;
451 
452 		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename());
453 
454 		if (res)
455 		{
456 			buildInfo->program.infoLog		= "Loaded from cache";
457 			buildInfo->program.linkOk		= true;
458 			buildInfo->program.linkTimeUs	= 0;
459 
460 			for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
461 			{
462 				if (!program.sources[shaderType].empty())
463 				{
464 					glu::ShaderInfo	shaderBuildInfo;
465 
466 					shaderBuildInfo.type			= (glu::ShaderType)shaderType;
467 					shaderBuildInfo.source			= shaderstring;
468 					shaderBuildInfo.compileTimeUs	= 0;
469 					shaderBuildInfo.compileOk		= true;
470 
471 					buildInfo->shaders.push_back(shaderBuildInfo);
472 				}
473 			}
474 		}
475 	}
476 
477 	if (!res)
478 	{
479 		{
480 			vector<deUint32> nonStrippedBinary;
481 
482 			if (!compileGlslToSpirV(program, &nonStrippedBinary, buildInfo))
483 				TCU_THROW(InternalError, "Compiling GLSL to SPIR-V failed");
484 
485 			TCU_CHECK_INTERNAL(!nonStrippedBinary.empty());
486 			stripSpirVDebugInfo(nonStrippedBinary.size(), &nonStrippedBinary[0], &binary);
487 			TCU_CHECK_INTERNAL(!binary.empty());
488 		}
489 
490 		if (optimizationRecipe != 0)
491 		{
492 			validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
493 			optimizeCompiledBinary(binary, optimizationRecipe, spirvVersion);
494 		}
495 
496 		if (validateBinary)
497 		{
498 			validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
499 		}
500 
501 		res = createProgramBinaryFromSpirV(binary);
502 		if (commandLine.isShadercacheEnabled())
503 			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename());
504 	}
505 	return res;
506 }
507 
buildProgram(const HlslSource & program,glu::ShaderProgramInfo * buildInfo,const tcu::CommandLine & commandLine)508 ProgramBinary* buildProgram (const HlslSource& program, glu::ShaderProgramInfo* buildInfo, const tcu::CommandLine& commandLine)
509 {
510 	const SpirvVersion	spirvVersion		= program.buildOptions.targetVersion;
511 	const bool			validateBinary		= VALIDATE_BINARIES;
512 	vector<deUint32>	binary;
513 	std::string			cachekey;
514 	std::string			shaderstring;
515 	vk::ProgramBinary*	res					= 0;
516 	const int			optimizationRecipe	= commandLine.getOptimizationRecipe();
517 
518 	if (commandLine.isShadercacheEnabled())
519 	{
520 		shaderCacheFirstRunCheck(commandLine.getShaderCacheFilename(), commandLine.isShaderCacheTruncateEnabled());
521 		getCompileEnvironment(cachekey);
522 		getBuildOptions(cachekey, program.buildOptions, optimizationRecipe);
523 
524 		for (int i = 0; i < glu::SHADERTYPE_LAST; i++)
525 		{
526 			if (!program.sources[i].empty())
527 			{
528 				cachekey += glu::getShaderTypeName((glu::ShaderType)i);
529 
530 				for (std::vector<std::string>::const_iterator it = program.sources[i].begin(); it != program.sources[i].end(); ++it)
531 					shaderstring += *it;
532 			}
533 		}
534 
535 		cachekey = cachekey + shaderstring;
536 
537 		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename());
538 
539 		if (res)
540 		{
541 			buildInfo->program.infoLog		= "Loaded from cache";
542 			buildInfo->program.linkOk		= true;
543 			buildInfo->program.linkTimeUs	= 0;
544 
545 			for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; shaderType++)
546 			{
547 				if (!program.sources[shaderType].empty())
548 				{
549 					glu::ShaderInfo	shaderBuildInfo;
550 
551 					shaderBuildInfo.type			= (glu::ShaderType)shaderType;
552 					shaderBuildInfo.source			= shaderstring;
553 					shaderBuildInfo.compileTimeUs	= 0;
554 					shaderBuildInfo.compileOk		= true;
555 
556 					buildInfo->shaders.push_back(shaderBuildInfo);
557 				}
558 			}
559 		}
560 	}
561 
562 	if (!res)
563 	{
564 		{
565 			vector<deUint32> nonStrippedBinary;
566 
567 			if (!compileHlslToSpirV(program, &nonStrippedBinary, buildInfo))
568 				TCU_THROW(InternalError, "Compiling HLSL to SPIR-V failed");
569 
570 			TCU_CHECK_INTERNAL(!nonStrippedBinary.empty());
571 			stripSpirVDebugInfo(nonStrippedBinary.size(), &nonStrippedBinary[0], &binary);
572 			TCU_CHECK_INTERNAL(!binary.empty());
573 		}
574 
575 		if (optimizationRecipe != 0)
576 		{
577 			validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
578 			optimizeCompiledBinary(binary, optimizationRecipe, spirvVersion);
579 		}
580 
581 		if (validateBinary)
582 		{
583 			validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
584 		}
585 
586 		res = createProgramBinaryFromSpirV(binary);
587 		if (commandLine.isShadercacheEnabled())
588 			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename());
589 	}
590 	return res;
591 }
592 
assembleProgram(const SpirVAsmSource & program,SpirVProgramInfo * buildInfo,const tcu::CommandLine & commandLine)593 ProgramBinary* assembleProgram (const SpirVAsmSource& program, SpirVProgramInfo* buildInfo, const tcu::CommandLine& commandLine)
594 {
595 	const SpirvVersion	spirvVersion		= program.buildOptions.targetVersion;
596 	const bool			validateBinary		= VALIDATE_BINARIES;
597 	vector<deUint32>	binary;
598 	vk::ProgramBinary*	res					= 0;
599 	std::string			cachekey;
600 	const int			optimizationRecipe	= commandLine.isSpirvOptimizationEnabled() ? commandLine.getOptimizationRecipe() : 0;
601 
602 	if (commandLine.isShadercacheEnabled())
603 	{
604 		shaderCacheFirstRunCheck(commandLine.getShaderCacheFilename(), commandLine.isShaderCacheTruncateEnabled());
605 		getCompileEnvironment(cachekey);
606 		cachekey += "Target Spir-V ";
607 		cachekey += getSpirvVersionName(spirvVersion);
608 		cachekey += "\n";
609 		if (optimizationRecipe != 0)
610 		{
611 			cachekey += "Optimization recipe ";
612 			cachekey += de::toString(optimizationRecipe);
613 			cachekey += "\n";
614 		}
615 
616 		cachekey += program.source;
617 
618 		res = shadercacheLoad(cachekey, commandLine.getShaderCacheFilename());
619 
620 		if (res)
621 		{
622 			buildInfo->source			= program.source;
623 			buildInfo->compileOk		= true;
624 			buildInfo->compileTimeUs	= 0;
625 			buildInfo->infoLog			= "Loaded from cache";
626 		}
627 	}
628 
629 	if (!res)
630 	{
631 
632 		if (!assembleSpirV(&program, &binary, buildInfo, spirvVersion))
633 			TCU_THROW(InternalError, "Failed to assemble SPIR-V");
634 
635 		if (optimizationRecipe != 0)
636 		{
637 			validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
638 			optimizeCompiledBinary(binary, optimizationRecipe, spirvVersion);
639 		}
640 
641 		if (validateBinary)
642 		{
643 			validateCompiledBinary(binary, buildInfo, program.buildOptions.getSpirvValidatorOptions());
644 		}
645 
646 		res = createProgramBinaryFromSpirV(binary);
647 		if (commandLine.isShadercacheEnabled())
648 			shadercacheSave(res, cachekey, commandLine.getShaderCacheFilename());
649 	}
650 	return res;
651 }
652 
disassembleProgram(const ProgramBinary & program,std::ostream * dst)653 void disassembleProgram (const ProgramBinary& program, std::ostream* dst)
654 {
655 	if (program.getFormat() == PROGRAM_FORMAT_SPIRV)
656 	{
657 		TCU_CHECK_INTERNAL(isSaneSpirVBinary(program));
658 
659 		if (isNativeSpirVBinaryEndianness())
660 			disassembleSpirV(program.getSize()/sizeof(deUint32), (const deUint32*)program.getBinary(), dst,
661 							 extractSpirvVersion(program));
662 		else
663 			TCU_THROW(InternalError, "SPIR-V endianness translation not supported");
664 	}
665 	else
666 		TCU_THROW(NotSupportedError, "Unsupported program format");
667 }
668 
validateProgram(const ProgramBinary & program,std::ostream * dst,const SpirvValidatorOptions & options)669 bool validateProgram (const ProgramBinary& program, std::ostream* dst, const SpirvValidatorOptions& options)
670 {
671 	if (program.getFormat() == PROGRAM_FORMAT_SPIRV)
672 	{
673 		if (!isSaneSpirVBinary(program))
674 		{
675 			*dst << "Binary doesn't look like SPIR-V at all";
676 			return false;
677 		}
678 
679 		if (isNativeSpirVBinaryEndianness())
680 			return validateSpirV(program.getSize()/sizeof(deUint32), (const deUint32*)program.getBinary(), dst, options);
681 		else
682 			TCU_THROW(InternalError, "SPIR-V endianness translation not supported");
683 	}
684 	else
685 		TCU_THROW(NotSupportedError, "Unsupported program format");
686 }
687 
createShaderModule(const DeviceInterface & deviceInterface,VkDevice device,const ProgramBinary & binary,VkShaderModuleCreateFlags flags)688 Move<VkShaderModule> createShaderModule (const DeviceInterface& deviceInterface, VkDevice device, const ProgramBinary& binary, VkShaderModuleCreateFlags flags)
689 {
690 	if (binary.getFormat() == PROGRAM_FORMAT_SPIRV)
691 	{
692 		const struct VkShaderModuleCreateInfo		shaderModuleInfo	=
693 		{
694 			VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
695 			DE_NULL,
696 			flags,
697 			(deUintptr)binary.getSize(),
698 			(const deUint32*)binary.getBinary(),
699 		};
700 
701 		binary.setUsed();
702 
703 		return createShaderModule(deviceInterface, device, &shaderModuleInfo);
704 	}
705 	else
706 		TCU_THROW(NotSupportedError, "Unsupported program format");
707 }
708 
getGluShaderType(VkShaderStageFlagBits shaderStage)709 glu::ShaderType getGluShaderType (VkShaderStageFlagBits shaderStage)
710 {
711 	switch (shaderStage)
712 	{
713 		case VK_SHADER_STAGE_VERTEX_BIT:					return glu::SHADERTYPE_VERTEX;
714 		case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:		return glu::SHADERTYPE_TESSELLATION_CONTROL;
715 		case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:	return glu::SHADERTYPE_TESSELLATION_EVALUATION;
716 		case VK_SHADER_STAGE_GEOMETRY_BIT:					return glu::SHADERTYPE_GEOMETRY;
717 		case VK_SHADER_STAGE_FRAGMENT_BIT:					return glu::SHADERTYPE_FRAGMENT;
718 		case VK_SHADER_STAGE_COMPUTE_BIT:					return glu::SHADERTYPE_COMPUTE;
719 		default:
720 			DE_FATAL("Unknown shader stage");
721 			return glu::SHADERTYPE_LAST;
722 	}
723 }
724 
getVkShaderStage(glu::ShaderType shaderType)725 VkShaderStageFlagBits getVkShaderStage (glu::ShaderType shaderType)
726 {
727 	static const VkShaderStageFlagBits s_shaderStages[] =
728 	{
729 		VK_SHADER_STAGE_VERTEX_BIT,
730 		VK_SHADER_STAGE_FRAGMENT_BIT,
731 		VK_SHADER_STAGE_GEOMETRY_BIT,
732 		VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
733 		VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
734 		VK_SHADER_STAGE_COMPUTE_BIT,
735 		VK_SHADER_STAGE_RAYGEN_BIT_NV,
736 		VK_SHADER_STAGE_ANY_HIT_BIT_NV,
737 		VK_SHADER_STAGE_CLOSEST_HIT_BIT_NV,
738 		VK_SHADER_STAGE_MISS_BIT_NV,
739 		VK_SHADER_STAGE_INTERSECTION_BIT_NV,
740 		VK_SHADER_STAGE_CALLABLE_BIT_NV,
741 	};
742 
743 	return de::getSizedArrayElement<glu::SHADERTYPE_LAST>(s_shaderStages, shaderType);
744 }
745 
746 // Baseline version, to be used for shaders which don't specify a version
getBaselineSpirvVersion(const deUint32)747 vk::SpirvVersion getBaselineSpirvVersion (const deUint32 /* vulkanVersion */)
748 {
749 	return vk::SPIRV_VERSION_1_0;
750 }
751 
752 // Max supported versions for each Vulkan version, without requiring a Vulkan extension.
getMaxSpirvVersionForVulkan(const deUint32 vulkanVersion)753 vk::SpirvVersion getMaxSpirvVersionForVulkan (const deUint32 vulkanVersion)
754 {
755 	vk::SpirvVersion	result			= vk::SPIRV_VERSION_LAST;
756 
757 	deUint32 vulkanVersionMajorMinor = VK_MAKE_VERSION(VK_API_VERSION_MAJOR(vulkanVersion), VK_API_VERSION_MINOR(vulkanVersion), 0);
758 	if (vulkanVersionMajorMinor == VK_API_VERSION_1_0)
759 		result = vk::SPIRV_VERSION_1_0;
760 	else if (vulkanVersionMajorMinor == VK_API_VERSION_1_1)
761 		result = vk::SPIRV_VERSION_1_3;
762 	else if (vulkanVersionMajorMinor >= VK_API_VERSION_1_2)
763 		result = vk::SPIRV_VERSION_1_5;
764 
765 	DE_ASSERT(result < vk::SPIRV_VERSION_LAST);
766 
767 	return result;
768 }
769 
getMaxSpirvVersionForAsm(const deUint32 vulkanVersion)770 vk::SpirvVersion getMaxSpirvVersionForAsm (const deUint32 vulkanVersion)
771 {
772 	return getMaxSpirvVersionForVulkan(vulkanVersion);
773 }
774 
getMaxSpirvVersionForGlsl(const deUint32 vulkanVersion)775 vk::SpirvVersion getMaxSpirvVersionForGlsl (const deUint32 vulkanVersion)
776 {
777 	return getMaxSpirvVersionForVulkan(vulkanVersion);
778 }
779 
extractSpirvVersion(const ProgramBinary & binary)780 SpirvVersion extractSpirvVersion (const ProgramBinary& binary)
781 {
782 	DE_STATIC_ASSERT(SPIRV_VERSION_1_5 + 1 == SPIRV_VERSION_LAST);
783 
784 	if (binary.getFormat() != PROGRAM_FORMAT_SPIRV)
785 		TCU_THROW(InternalError, "Binary is not in SPIR-V format");
786 
787 	if (!isSaneSpirVBinary(binary) || binary.getSize() < sizeof(SpirvBinaryHeader))
788 		TCU_THROW(InternalError, "Invalid SPIR-V header format");
789 
790 	const deUint32				spirvBinaryVersion10	= 0x00010000;
791 	const deUint32				spirvBinaryVersion11	= 0x00010100;
792 	const deUint32				spirvBinaryVersion12	= 0x00010200;
793 	const deUint32				spirvBinaryVersion13	= 0x00010300;
794 	const deUint32				spirvBinaryVersion14	= 0x00010400;
795 	const deUint32				spirvBinaryVersion15	= 0x00010500;
796 	const SpirvBinaryHeader*	header					= reinterpret_cast<const SpirvBinaryHeader*>(binary.getBinary());
797 	const deUint32				spirvVersion			= isNativeSpirVBinaryEndianness()
798 														? header->version
799 														: deReverseBytes32(header->version);
800 	SpirvVersion				result					= SPIRV_VERSION_LAST;
801 
802 	switch (spirvVersion)
803 	{
804 		case spirvBinaryVersion10:	result = SPIRV_VERSION_1_0; break; //!< SPIR-V 1.0
805 		case spirvBinaryVersion11:	result = SPIRV_VERSION_1_1; break; //!< SPIR-V 1.1
806 		case spirvBinaryVersion12:	result = SPIRV_VERSION_1_2; break; //!< SPIR-V 1.2
807 		case spirvBinaryVersion13:	result = SPIRV_VERSION_1_3; break; //!< SPIR-V 1.3
808 		case spirvBinaryVersion14:	result = SPIRV_VERSION_1_4; break; //!< SPIR-V 1.4
809 		case spirvBinaryVersion15:	result = SPIRV_VERSION_1_5; break; //!< SPIR-V 1.5
810 		default:					TCU_THROW(InternalError, "Unknown SPIR-V version detected in binary");
811 	}
812 
813 	return result;
814 }
815 
getSpirvVersionName(const SpirvVersion spirvVersion)816 std::string getSpirvVersionName (const SpirvVersion spirvVersion)
817 {
818 	DE_STATIC_ASSERT(SPIRV_VERSION_1_5 + 1 == SPIRV_VERSION_LAST);
819 	DE_ASSERT(spirvVersion < SPIRV_VERSION_LAST);
820 
821 	std::string result;
822 
823 	switch (spirvVersion)
824 	{
825 		case SPIRV_VERSION_1_0: result = "1.0"; break; //!< SPIR-V 1.0
826 		case SPIRV_VERSION_1_1: result = "1.1"; break; //!< SPIR-V 1.1
827 		case SPIRV_VERSION_1_2: result = "1.2"; break; //!< SPIR-V 1.2
828 		case SPIRV_VERSION_1_3: result = "1.3"; break; //!< SPIR-V 1.3
829 		case SPIRV_VERSION_1_4: result = "1.4"; break; //!< SPIR-V 1.4
830 		case SPIRV_VERSION_1_5: result = "1.5"; break; //!< SPIR-V 1.5
831 		default:				result = "Unknown";
832 	}
833 
834 	return result;
835 }
836 
operator ++(SpirvVersion & spirvVersion)837 SpirvVersion& operator++(SpirvVersion& spirvVersion)
838 {
839 	if (spirvVersion == SPIRV_VERSION_LAST)
840 		spirvVersion = SPIRV_VERSION_1_0;
841 	else
842 		spirvVersion = static_cast<SpirvVersion>(static_cast<deUint32>(spirvVersion) + 1);
843 
844 	return spirvVersion;
845 }
846 
847 } // vk
848