• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The SwiftShader Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "VkPipeline.hpp"
16 
17 #include "VkDestroy.hpp"
18 #include "VkDevice.hpp"
19 #include "VkPipelineCache.hpp"
20 #include "VkPipelineLayout.hpp"
21 #include "VkRenderPass.hpp"
22 #include "VkShaderModule.hpp"
23 #include "VkStringify.hpp"
24 #include "Pipeline/ComputeProgram.hpp"
25 #include "Pipeline/SpirvShader.hpp"
26 
27 #include "marl/trace.h"
28 
29 #include "spirv-tools/optimizer.hpp"
30 
31 #include <iostream>
32 
33 namespace {
34 
35 // optimizeSpirv() applies and freezes specializations into constants, and runs spirv-opt.
optimizeSpirv(const vk::PipelineCache::SpirvBinaryKey & key)36 sw::SpirvBinary optimizeSpirv(const vk::PipelineCache::SpirvBinaryKey &key)
37 {
38 	const sw::SpirvBinary &code = key.getBinary();
39 	const VkSpecializationInfo *specializationInfo = key.getSpecializationInfo();
40 	bool optimize = key.getOptimization();
41 
42 	spvtools::Optimizer opt{ vk::SPIRV_VERSION };
43 
44 	opt.SetMessageConsumer([](spv_message_level_t level, const char *source, const spv_position_t &position, const char *message) {
45 		switch(level)
46 		{
47 		case SPV_MSG_FATAL: sw::warn("SPIR-V FATAL: %d:%d %s\n", int(position.line), int(position.column), message);
48 		case SPV_MSG_INTERNAL_ERROR: sw::warn("SPIR-V INTERNAL_ERROR: %d:%d %s\n", int(position.line), int(position.column), message);
49 		case SPV_MSG_ERROR: sw::warn("SPIR-V ERROR: %d:%d %s\n", int(position.line), int(position.column), message);
50 		case SPV_MSG_WARNING: sw::warn("SPIR-V WARNING: %d:%d %s\n", int(position.line), int(position.column), message);
51 		case SPV_MSG_INFO: sw::trace("SPIR-V INFO: %d:%d %s\n", int(position.line), int(position.column), message);
52 		case SPV_MSG_DEBUG: sw::trace("SPIR-V DEBUG: %d:%d %s\n", int(position.line), int(position.column), message);
53 		default: sw::trace("SPIR-V MESSAGE: %d:%d %s\n", int(position.line), int(position.column), message);
54 		}
55 	});
56 
57 	// If the pipeline uses specialization, apply the specializations before freezing
58 	if(specializationInfo)
59 	{
60 		std::unordered_map<uint32_t, std::vector<uint32_t>> specializations;
61 		const uint8_t *specializationData = static_cast<const uint8_t *>(specializationInfo->pData);
62 
63 		for(uint32_t i = 0; i < specializationInfo->mapEntryCount; i++)
64 		{
65 			const VkSpecializationMapEntry &entry = specializationInfo->pMapEntries[i];
66 			const uint8_t *value_ptr = specializationData + entry.offset;
67 			std::vector<uint32_t> value(reinterpret_cast<const uint32_t *>(value_ptr),
68 			                            reinterpret_cast<const uint32_t *>(value_ptr + entry.size));
69 			specializations.emplace(entry.constantID, std::move(value));
70 		}
71 
72 		opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass(specializations));
73 	}
74 
75 	if(optimize)
76 	{
77 		// Full optimization list taken from spirv-opt.
78 		opt.RegisterPerformancePasses();
79 	}
80 
81 	spvtools::OptimizerOptions optimizerOptions = {};
82 #if defined(NDEBUG)
83 	optimizerOptions.set_run_validator(false);
84 #else
85 	optimizerOptions.set_run_validator(true);
86 	spvtools::ValidatorOptions validatorOptions = {};
87 	validatorOptions.SetScalarBlockLayout(true);            // VK_EXT_scalar_block_layout
88 	validatorOptions.SetUniformBufferStandardLayout(true);  // VK_KHR_uniform_buffer_standard_layout
89 	optimizerOptions.set_validator_options(validatorOptions);
90 #endif
91 
92 	sw::SpirvBinary optimized;
93 	opt.Run(code.data(), code.size(), &optimized, optimizerOptions);
94 	ASSERT(optimized.size() > 0);
95 
96 	if(false)
97 	{
98 		spvtools::SpirvTools core(vk::SPIRV_VERSION);
99 		std::string preOpt;
100 		core.Disassemble(code, &preOpt, SPV_BINARY_TO_TEXT_OPTION_NONE);
101 		std::string postOpt;
102 		core.Disassemble(optimized, &postOpt, SPV_BINARY_TO_TEXT_OPTION_NONE);
103 		std::cout << "PRE-OPT: " << preOpt << std::endl
104 		          << "POST-OPT: " << postOpt << std::endl;
105 	}
106 
107 	return optimized;
108 }
109 
createProgram(vk::Device * device,std::shared_ptr<sw::SpirvShader> shader,const vk::PipelineLayout * layout)110 std::shared_ptr<sw::ComputeProgram> createProgram(vk::Device *device, std::shared_ptr<sw::SpirvShader> shader, const vk::PipelineLayout *layout)
111 {
112 	MARL_SCOPED_EVENT("createProgram");
113 
114 	vk::DescriptorSet::Bindings descriptorSets;  // TODO(b/129523279): Delay code generation until dispatch time.
115 	// TODO(b/119409619): use allocator.
116 	auto program = std::make_shared<sw::ComputeProgram>(device, shader, layout, descriptorSets);
117 	program->generate();
118 	program->finalize("ComputeProgram");
119 
120 	return program;
121 }
122 
123 class PipelineCreationFeedback
124 {
125 public:
PipelineCreationFeedback(const VkGraphicsPipelineCreateInfo * pCreateInfo)126 	PipelineCreationFeedback(const VkGraphicsPipelineCreateInfo *pCreateInfo)
127 	    : pipelineCreationFeedback(GetPipelineCreationFeedback(pCreateInfo->pNext))
128 	{
129 		pipelineCreationBegins();
130 	}
131 
PipelineCreationFeedback(const VkComputePipelineCreateInfo * pCreateInfo)132 	PipelineCreationFeedback(const VkComputePipelineCreateInfo *pCreateInfo)
133 	    : pipelineCreationFeedback(GetPipelineCreationFeedback(pCreateInfo->pNext))
134 	{
135 		pipelineCreationBegins();
136 	}
137 
~PipelineCreationFeedback()138 	~PipelineCreationFeedback()
139 	{
140 		pipelineCreationEnds();
141 	}
142 
stageCreationBegins(uint32_t stage)143 	void stageCreationBegins(uint32_t stage)
144 	{
145 		if(pipelineCreationFeedback)
146 		{
147 			// Record stage creation begin time
148 			pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].duration = now();
149 		}
150 	}
151 
cacheHit(uint32_t stage)152 	void cacheHit(uint32_t stage)
153 	{
154 		if(pipelineCreationFeedback)
155 		{
156 			pipelineCreationFeedback->pPipelineCreationFeedback->flags |=
157 			    VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT_EXT;
158 			pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].flags |=
159 			    VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT_EXT;
160 		}
161 	}
162 
stageCreationEnds(uint32_t stage)163 	void stageCreationEnds(uint32_t stage)
164 	{
165 		if(pipelineCreationFeedback)
166 		{
167 			pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].flags |=
168 			    VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT;
169 			pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].duration =
170 			    now() - pipelineCreationFeedback->pPipelineStageCreationFeedbacks[stage].duration;
171 		}
172 	}
173 
pipelineCreationError()174 	void pipelineCreationError()
175 	{
176 		clear();
177 		pipelineCreationFeedback = nullptr;
178 	}
179 
180 private:
GetPipelineCreationFeedback(const void * pNext)181 	static const VkPipelineCreationFeedbackCreateInfoEXT *GetPipelineCreationFeedback(const void *pNext)
182 	{
183 		const VkBaseInStructure *extensionCreateInfo = reinterpret_cast<const VkBaseInStructure *>(pNext);
184 		while(extensionCreateInfo)
185 		{
186 			if(extensionCreateInfo->sType == VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT)
187 			{
188 				return reinterpret_cast<const VkPipelineCreationFeedbackCreateInfoEXT *>(extensionCreateInfo);
189 			}
190 
191 			extensionCreateInfo = extensionCreateInfo->pNext;
192 		}
193 
194 		return nullptr;
195 	}
196 
pipelineCreationBegins()197 	void pipelineCreationBegins()
198 	{
199 		if(pipelineCreationFeedback)
200 		{
201 			clear();
202 
203 			// Record pipeline creation begin time
204 			pipelineCreationFeedback->pPipelineCreationFeedback->duration = now();
205 		}
206 	}
207 
pipelineCreationEnds()208 	void pipelineCreationEnds()
209 	{
210 		if(pipelineCreationFeedback)
211 		{
212 			pipelineCreationFeedback->pPipelineCreationFeedback->flags |=
213 			    VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT;
214 			pipelineCreationFeedback->pPipelineCreationFeedback->duration =
215 			    now() - pipelineCreationFeedback->pPipelineCreationFeedback->duration;
216 		}
217 	}
218 
clear()219 	void clear()
220 	{
221 		if(pipelineCreationFeedback)
222 		{
223 			// Clear all flags and durations
224 			pipelineCreationFeedback->pPipelineCreationFeedback->flags = 0;
225 			pipelineCreationFeedback->pPipelineCreationFeedback->duration = 0;
226 			for(uint32_t i = 0; i < pipelineCreationFeedback->pipelineStageCreationFeedbackCount; i++)
227 			{
228 				pipelineCreationFeedback->pPipelineStageCreationFeedbacks[i].flags = 0;
229 				pipelineCreationFeedback->pPipelineStageCreationFeedbacks[i].duration = 0;
230 			}
231 		}
232 	}
233 
now()234 	uint64_t now()
235 	{
236 		return std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
237 	}
238 
239 	const VkPipelineCreationFeedbackCreateInfoEXT *pipelineCreationFeedback = nullptr;
240 };
241 
242 }  // anonymous namespace
243 
244 namespace vk {
245 
Pipeline(PipelineLayout * layout,Device * device)246 Pipeline::Pipeline(PipelineLayout *layout, Device *device)
247     : layout(layout)
248     , device(device)
249     , robustBufferAccess(device->getEnabledFeatures().robustBufferAccess)
250 {
251 	layout->incRefCount();
252 }
253 
destroy(const VkAllocationCallbacks * pAllocator)254 void Pipeline::destroy(const VkAllocationCallbacks *pAllocator)
255 {
256 	destroyPipeline(pAllocator);
257 
258 	vk::release(static_cast<VkPipelineLayout>(*layout), pAllocator);
259 }
260 
GraphicsPipeline(const VkGraphicsPipelineCreateInfo * pCreateInfo,void * mem,Device * device)261 GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo *pCreateInfo, void *mem, Device *device)
262     : Pipeline(vk::Cast(pCreateInfo->layout), device)
263     , state(device, pCreateInfo, layout, robustBufferAccess)
264     , inputs(pCreateInfo->pVertexInputState)
265 {
266 }
267 
destroyPipeline(const VkAllocationCallbacks * pAllocator)268 void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator)
269 {
270 	vertexShader.reset();
271 	fragmentShader.reset();
272 }
273 
ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo * pCreateInfo)274 size_t GraphicsPipeline::ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo *pCreateInfo)
275 {
276 	return 0;
277 }
278 
getIndexBuffers(uint32_t count,uint32_t first,bool indexed,std::vector<std::pair<uint32_t,void * >> * indexBuffers) const279 void GraphicsPipeline::getIndexBuffers(uint32_t count, uint32_t first, bool indexed, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const
280 {
281 	indexBuffer.getIndexBuffers(state.getTopology(), count, first, indexed, state.hasPrimitiveRestartEnable(), indexBuffers);
282 }
283 
containsImageWrite() const284 bool GraphicsPipeline::containsImageWrite() const
285 {
286 	return (vertexShader.get() && vertexShader->containsImageWrite()) ||
287 	       (fragmentShader.get() && fragmentShader->containsImageWrite());
288 }
289 
setShader(const VkShaderStageFlagBits & stage,const std::shared_ptr<sw::SpirvShader> spirvShader)290 void GraphicsPipeline::setShader(const VkShaderStageFlagBits &stage, const std::shared_ptr<sw::SpirvShader> spirvShader)
291 {
292 	switch(stage)
293 	{
294 	case VK_SHADER_STAGE_VERTEX_BIT:
295 		ASSERT(vertexShader.get() == nullptr);
296 		vertexShader = spirvShader;
297 		break;
298 
299 	case VK_SHADER_STAGE_FRAGMENT_BIT:
300 		ASSERT(fragmentShader.get() == nullptr);
301 		fragmentShader = spirvShader;
302 		break;
303 
304 	default:
305 		UNSUPPORTED("Unsupported stage");
306 		break;
307 	}
308 }
309 
getShader(const VkShaderStageFlagBits & stage) const310 const std::shared_ptr<sw::SpirvShader> GraphicsPipeline::getShader(const VkShaderStageFlagBits &stage) const
311 {
312 	switch(stage)
313 	{
314 	case VK_SHADER_STAGE_VERTEX_BIT:
315 		return vertexShader;
316 	case VK_SHADER_STAGE_FRAGMENT_BIT:
317 		return fragmentShader;
318 	default:
319 		UNSUPPORTED("Unsupported stage");
320 		return fragmentShader;
321 	}
322 }
323 
compileShaders(const VkAllocationCallbacks * pAllocator,const VkGraphicsPipelineCreateInfo * pCreateInfo,PipelineCache * pPipelineCache)324 VkResult GraphicsPipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache)
325 {
326 	PipelineCreationFeedback pipelineCreationFeedback(pCreateInfo);
327 
328 	for(uint32_t stageIndex = 0; stageIndex < pCreateInfo->stageCount; stageIndex++)
329 	{
330 		const VkPipelineShaderStageCreateInfo &stageInfo = pCreateInfo->pStages[stageIndex];
331 
332 		pipelineCreationFeedback.stageCreationBegins(stageIndex);
333 
334 		if(stageInfo.flags != 0)
335 		{
336 			// Vulkan 1.2: "flags must be 0"
337 			UNSUPPORTED("pStage->flags %d", int(stageInfo.flags));
338 		}
339 
340 		auto dbgctx = device->getDebuggerContext();
341 		// Do not optimize the shader if we have a debugger context.
342 		// Optimization passes are likely to damage debug information, and reorder
343 		// instructions.
344 		const bool optimize = !dbgctx;
345 
346 		const ShaderModule *module = vk::Cast(stageInfo.module);
347 		const PipelineCache::SpirvBinaryKey key(module->getBinary(), stageInfo.pSpecializationInfo, optimize);
348 
349 		if((pCreateInfo->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT) &&
350 		   (!pPipelineCache || !pPipelineCache->contains(key)))
351 		{
352 			pipelineCreationFeedback.pipelineCreationError();
353 			return VK_PIPELINE_COMPILE_REQUIRED_EXT;
354 		}
355 
356 		sw::SpirvBinary spirv;
357 
358 		if(pPipelineCache)
359 		{
360 			auto onCacheMiss = [&] { return optimizeSpirv(key); };
361 			auto onCacheHit = [&] { pipelineCreationFeedback.cacheHit(stageIndex); };
362 			spirv = pPipelineCache->getOrOptimizeSpirv(key, onCacheMiss, onCacheHit);
363 		}
364 		else
365 		{
366 			spirv = optimizeSpirv(key);
367 
368 			// If the pipeline does not have specialization constants, there's a 1-to-1 mapping between the unoptimized and optimized SPIR-V,
369 			// so we should use a 1-to-1 mapping of the identifiers to avoid JIT routine recompiles.
370 			if(!key.getSpecializationInfo())
371 			{
372 				spirv.mapOptimizedIdentifier(key.getBinary());
373 			}
374 		}
375 
376 		// TODO(b/201798871): use allocator.
377 		auto shader = std::make_shared<sw::SpirvShader>(stageInfo.stage, stageInfo.pName, spirv,
378 		                                                vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass, robustBufferAccess, dbgctx);
379 
380 		setShader(stageInfo.stage, shader);
381 
382 		pipelineCreationFeedback.stageCreationEnds(stageIndex);
383 	}
384 
385 	return VK_SUCCESS;
386 }
387 
ComputePipeline(const VkComputePipelineCreateInfo * pCreateInfo,void * mem,Device * device)388 ComputePipeline::ComputePipeline(const VkComputePipelineCreateInfo *pCreateInfo, void *mem, Device *device)
389     : Pipeline(vk::Cast(pCreateInfo->layout), device)
390 {
391 }
392 
destroyPipeline(const VkAllocationCallbacks * pAllocator)393 void ComputePipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator)
394 {
395 	shader.reset();
396 	program.reset();
397 }
398 
ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo * pCreateInfo)399 size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo *pCreateInfo)
400 {
401 	return 0;
402 }
403 
compileShaders(const VkAllocationCallbacks * pAllocator,const VkComputePipelineCreateInfo * pCreateInfo,PipelineCache * pPipelineCache)404 VkResult ComputePipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkComputePipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache)
405 {
406 	PipelineCreationFeedback pipelineCreationFeedback(pCreateInfo);
407 	pipelineCreationFeedback.stageCreationBegins(0);
408 
409 	auto &stage = pCreateInfo->stage;
410 	const ShaderModule *module = vk::Cast(stage.module);
411 
412 	ASSERT(shader.get() == nullptr);
413 	ASSERT(program.get() == nullptr);
414 
415 	auto dbgctx = device->getDebuggerContext();
416 	// Do not optimize the shader if we have a debugger context.
417 	// Optimization passes are likely to damage debug information, and reorder
418 	// instructions.
419 	const bool optimize = !dbgctx;
420 
421 	const PipelineCache::SpirvBinaryKey shaderKey(module->getBinary(), stage.pSpecializationInfo, optimize);
422 
423 	if((pCreateInfo->flags & VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT) &&
424 	   (!pPipelineCache || !pPipelineCache->contains(shaderKey)))
425 	{
426 		pipelineCreationFeedback.pipelineCreationError();
427 		return VK_PIPELINE_COMPILE_REQUIRED_EXT;
428 	}
429 
430 	sw::SpirvBinary spirv;
431 
432 	if(pPipelineCache)
433 	{
434 		auto onCacheMiss = [&] { return optimizeSpirv(shaderKey); };
435 		auto onCacheHit = [&] { pipelineCreationFeedback.cacheHit(0); };
436 		spirv = pPipelineCache->getOrOptimizeSpirv(shaderKey, onCacheMiss, onCacheHit);
437 	}
438 	else
439 	{
440 		spirv = optimizeSpirv(shaderKey);
441 
442 		// If the pipeline does not have specialization constants, there's a 1-to-1 mapping between the unoptimized and optimized SPIR-V,
443 		// so we should use a 1-to-1 mapping of the identifiers to avoid JIT routine recompiles.
444 		if(!shaderKey.getSpecializationInfo())
445 		{
446 			spirv.mapOptimizedIdentifier(shaderKey.getBinary());
447 		}
448 	}
449 
450 	// TODO(b/201798871): use allocator.
451 	shader = std::make_shared<sw::SpirvShader>(stage.stage, stage.pName, spirv,
452 	                                           nullptr, 0, robustBufferAccess, dbgctx);
453 
454 	const PipelineCache::ComputeProgramKey programKey(shader->getIdentifier(), layout->identifier);
455 
456 	if(pPipelineCache)
457 	{
458 		program = pPipelineCache->getOrCreateComputeProgram(programKey, [&] {
459 			return createProgram(device, shader, layout);
460 		});
461 	}
462 	else
463 	{
464 		program = createProgram(device, shader, layout);
465 	}
466 
467 	pipelineCreationFeedback.stageCreationEnds(0);
468 
469 	return VK_SUCCESS;
470 }
471 
run(uint32_t baseGroupX,uint32_t baseGroupY,uint32_t baseGroupZ,uint32_t groupCountX,uint32_t groupCountY,uint32_t groupCountZ,vk::DescriptorSet::Array const & descriptorSetObjects,vk::DescriptorSet::Bindings const & descriptorSets,vk::DescriptorSet::DynamicOffsets const & descriptorDynamicOffsets,vk::Pipeline::PushConstantStorage const & pushConstants)472 void ComputePipeline::run(uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ,
473                           uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
474                           vk::DescriptorSet::Array const &descriptorSetObjects,
475                           vk::DescriptorSet::Bindings const &descriptorSets,
476                           vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
477                           vk::Pipeline::PushConstantStorage const &pushConstants)
478 {
479 	ASSERT_OR_RETURN(program != nullptr);
480 	program->run(
481 	    descriptorSetObjects, descriptorSets, descriptorDynamicOffsets, pushConstants,
482 	    baseGroupX, baseGroupY, baseGroupZ,
483 	    groupCountX, groupCountY, groupCountZ);
484 }
485 
486 }  // namespace vk
487