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 // preprocessSpirv applies and freezes specializations into constants, and inlines all functions.
preprocessSpirv(std::vector<uint32_t> const & code,VkSpecializationInfo const * specializationInfo,bool optimize)36 std::vector<uint32_t> preprocessSpirv(
37     std::vector<uint32_t> const &code,
38     VkSpecializationInfo const *specializationInfo,
39     bool optimize)
40 {
41 	spvtools::Optimizer opt{ vk::SPIRV_VERSION };
42 
43 	opt.SetMessageConsumer([](spv_message_level_t level, const char *source, const spv_position_t &position, const char *message) {
44 		switch(level)
45 		{
46 			case SPV_MSG_FATAL: sw::warn("SPIR-V FATAL: %d:%d %s\n", int(position.line), int(position.column), message);
47 			case SPV_MSG_INTERNAL_ERROR: sw::warn("SPIR-V INTERNAL_ERROR: %d:%d %s\n", int(position.line), int(position.column), message);
48 			case SPV_MSG_ERROR: sw::warn("SPIR-V ERROR: %d:%d %s\n", int(position.line), int(position.column), message);
49 			case SPV_MSG_WARNING: sw::warn("SPIR-V WARNING: %d:%d %s\n", int(position.line), int(position.column), message);
50 			case SPV_MSG_INFO: sw::trace("SPIR-V INFO: %d:%d %s\n", int(position.line), int(position.column), message);
51 			case SPV_MSG_DEBUG: sw::trace("SPIR-V DEBUG: %d:%d %s\n", int(position.line), int(position.column), message);
52 			default: sw::trace("SPIR-V MESSAGE: %d:%d %s\n", int(position.line), int(position.column), message);
53 		}
54 	});
55 
56 	// If the pipeline uses specialization, apply the specializations before freezing
57 	if(specializationInfo)
58 	{
59 		std::unordered_map<uint32_t, std::vector<uint32_t>> specializations;
60 		for(auto i = 0u; i < specializationInfo->mapEntryCount; ++i)
61 		{
62 			auto const &e = specializationInfo->pMapEntries[i];
63 			auto value_ptr =
64 			    static_cast<uint32_t const *>(specializationInfo->pData) + e.offset / sizeof(uint32_t);
65 			specializations.emplace(e.constantID,
66 			                        std::vector<uint32_t>{ value_ptr, value_ptr + e.size / sizeof(uint32_t) });
67 		}
68 		opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass(specializations));
69 	}
70 
71 	if(optimize)
72 	{
73 		// Full optimization list taken from spirv-opt.
74 		opt.RegisterPerformancePasses();
75 	}
76 
77 	spvtools::OptimizerOptions optimizerOptions = {};
78 #if defined(NDEBUG)
79 	optimizerOptions.set_run_validator(false);
80 #else
81 	optimizerOptions.set_run_validator(true);
82 	spvtools::ValidatorOptions validatorOptions = {};
83 	validatorOptions.SetScalarBlockLayout(true);            // VK_EXT_scalar_block_layout
84 	validatorOptions.SetUniformBufferStandardLayout(true);  // VK_KHR_uniform_buffer_standard_layout
85 	optimizerOptions.set_validator_options(validatorOptions);
86 #endif
87 
88 	std::vector<uint32_t> optimized;
89 	opt.Run(code.data(), code.size(), &optimized, optimizerOptions);
90 
91 	if(false)
92 	{
93 		spvtools::SpirvTools core(vk::SPIRV_VERSION);
94 		std::string preOpt;
95 		core.Disassemble(code, &preOpt, SPV_BINARY_TO_TEXT_OPTION_NONE);
96 		std::string postOpt;
97 		core.Disassemble(optimized, &postOpt, SPV_BINARY_TO_TEXT_OPTION_NONE);
98 		std::cout << "PRE-OPT: " << preOpt << std::endl
99 		          << "POST-OPT: " << postOpt << std::endl;
100 	}
101 
102 	return optimized;
103 }
104 
createShader(const vk::PipelineCache::SpirvShaderKey & key,const vk::ShaderModule * module,bool robustBufferAccess,const std::shared_ptr<vk::dbg::Context> & dbgctx)105 std::shared_ptr<sw::SpirvShader> createShader(
106     const vk::PipelineCache::SpirvShaderKey &key,
107     const vk::ShaderModule *module,
108     bool robustBufferAccess,
109     const std::shared_ptr<vk::dbg::Context> &dbgctx)
110 {
111 	// Do not optimize the shader if we have a debugger context.
112 	// Optimization passes are likely to damage debug information, and reorder
113 	// instructions.
114 	const bool optimize = !dbgctx;
115 
116 	auto code = preprocessSpirv(key.getInsns(), key.getSpecializationInfo(), optimize);
117 	ASSERT(code.size() > 0);
118 
119 	// If the pipeline has specialization constants, assume they're unique and
120 	// use a new serial ID so the shader gets recompiled.
121 	uint32_t codeSerialID = (key.getSpecializationInfo() ? vk::ShaderModule::nextSerialID() : module->getSerialID());
122 
123 	// TODO(b/119409619): use allocator.
124 	return std::make_shared<sw::SpirvShader>(codeSerialID, key.getPipelineStage(), key.getEntryPointName().c_str(),
125 	                                         code, key.getRenderPass(), key.getSubpassIndex(), robustBufferAccess, dbgctx);
126 }
127 
createProgram(vk::Device * device,const vk::PipelineCache::ComputeProgramKey & key)128 std::shared_ptr<sw::ComputeProgram> createProgram(vk::Device *device, const vk::PipelineCache::ComputeProgramKey &key)
129 {
130 	MARL_SCOPED_EVENT("createProgram");
131 
132 	vk::DescriptorSet::Bindings descriptorSets;  // FIXME(b/129523279): Delay code generation until invoke time.
133 	// TODO(b/119409619): use allocator.
134 	auto program = std::make_shared<sw::ComputeProgram>(device, key.getShader(), key.getLayout(), descriptorSets);
135 	program->generate();
136 	program->finalize("ComputeProgram");
137 	return program;
138 }
139 
140 }  // anonymous namespace
141 
142 namespace vk {
143 
Pipeline(PipelineLayout * layout,Device * device)144 Pipeline::Pipeline(PipelineLayout *layout, Device *device)
145     : layout(layout)
146     , device(device)
147     , robustBufferAccess(device->getEnabledFeatures().robustBufferAccess)
148 {
149 	layout->incRefCount();
150 }
151 
destroy(const VkAllocationCallbacks * pAllocator)152 void Pipeline::destroy(const VkAllocationCallbacks *pAllocator)
153 {
154 	destroyPipeline(pAllocator);
155 
156 	vk::release(static_cast<VkPipelineLayout>(*layout), pAllocator);
157 }
158 
GraphicsPipeline(const VkGraphicsPipelineCreateInfo * pCreateInfo,void * mem,Device * device)159 GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo *pCreateInfo, void *mem, Device *device)
160     : Pipeline(vk::Cast(pCreateInfo->layout), device)
161     , state(device, pCreateInfo, layout, robustBufferAccess)
162     , inputs(pCreateInfo->pVertexInputState)
163 {
164 }
165 
destroyPipeline(const VkAllocationCallbacks * pAllocator)166 void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator)
167 {
168 	vertexShader.reset();
169 	fragmentShader.reset();
170 }
171 
ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo * pCreateInfo)172 size_t GraphicsPipeline::ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo *pCreateInfo)
173 {
174 	return 0;
175 }
176 
getIndexBuffers(uint32_t count,uint32_t first,bool indexed,std::vector<std::pair<uint32_t,void * >> * indexBuffers) const177 void GraphicsPipeline::getIndexBuffers(uint32_t count, uint32_t first, bool indexed, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const
178 {
179 	indexBuffer.getIndexBuffers(state.getTopology(), count, first, indexed, state.hasPrimitiveRestartEnable(), indexBuffers);
180 }
181 
containsImageWrite() const182 bool GraphicsPipeline::containsImageWrite() const
183 {
184 	return (vertexShader.get() && vertexShader->containsImageWrite()) ||
185 	       (fragmentShader.get() && fragmentShader->containsImageWrite());
186 }
187 
setShader(const VkShaderStageFlagBits & stage,const std::shared_ptr<sw::SpirvShader> spirvShader)188 void GraphicsPipeline::setShader(const VkShaderStageFlagBits &stage, const std::shared_ptr<sw::SpirvShader> spirvShader)
189 {
190 	switch(stage)
191 	{
192 		case VK_SHADER_STAGE_VERTEX_BIT:
193 			ASSERT(vertexShader.get() == nullptr);
194 			vertexShader = spirvShader;
195 			break;
196 
197 		case VK_SHADER_STAGE_FRAGMENT_BIT:
198 			ASSERT(fragmentShader.get() == nullptr);
199 			fragmentShader = spirvShader;
200 			break;
201 
202 		default:
203 			UNSUPPORTED("Unsupported stage");
204 			break;
205 	}
206 }
207 
getShader(const VkShaderStageFlagBits & stage) const208 const std::shared_ptr<sw::SpirvShader> GraphicsPipeline::getShader(const VkShaderStageFlagBits &stage) const
209 {
210 	switch(stage)
211 	{
212 		case VK_SHADER_STAGE_VERTEX_BIT:
213 			return vertexShader;
214 		case VK_SHADER_STAGE_FRAGMENT_BIT:
215 			return fragmentShader;
216 		default:
217 			UNSUPPORTED("Unsupported stage");
218 			return fragmentShader;
219 	}
220 }
221 
compileShaders(const VkAllocationCallbacks * pAllocator,const VkGraphicsPipelineCreateInfo * pCreateInfo,PipelineCache * pPipelineCache)222 void GraphicsPipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache)
223 {
224 	for(auto pStage = pCreateInfo->pStages; pStage != pCreateInfo->pStages + pCreateInfo->stageCount; pStage++)
225 	{
226 		if(pStage->flags != 0)
227 		{
228 			// Vulkan 1.2: "flags must be 0"
229 			UNSUPPORTED("pStage->flags %d", int(pStage->flags));
230 		}
231 
232 		const ShaderModule *module = vk::Cast(pStage->module);
233 		const PipelineCache::SpirvShaderKey key(pStage->stage, pStage->pName, module->getCode(),
234 		                                        vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass,
235 		                                        pStage->pSpecializationInfo);
236 		auto pipelineStage = key.getPipelineStage();
237 
238 		if(pPipelineCache)
239 		{
240 			auto shader = pPipelineCache->getOrCreateShader(key, [&] {
241 				return createShader(key, module, robustBufferAccess, device->getDebuggerContext());
242 			});
243 			setShader(pipelineStage, shader);
244 		}
245 		else
246 		{
247 			auto shader = createShader(key, module, robustBufferAccess, device->getDebuggerContext());
248 			setShader(pipelineStage, shader);
249 		}
250 	}
251 }
252 
ComputePipeline(const VkComputePipelineCreateInfo * pCreateInfo,void * mem,Device * device)253 ComputePipeline::ComputePipeline(const VkComputePipelineCreateInfo *pCreateInfo, void *mem, Device *device)
254     : Pipeline(vk::Cast(pCreateInfo->layout), device)
255 {
256 }
257 
destroyPipeline(const VkAllocationCallbacks * pAllocator)258 void ComputePipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator)
259 {
260 	shader.reset();
261 	program.reset();
262 }
263 
ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo * pCreateInfo)264 size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo *pCreateInfo)
265 {
266 	return 0;
267 }
268 
compileShaders(const VkAllocationCallbacks * pAllocator,const VkComputePipelineCreateInfo * pCreateInfo,PipelineCache * pPipelineCache)269 void ComputePipeline::compileShaders(const VkAllocationCallbacks *pAllocator, const VkComputePipelineCreateInfo *pCreateInfo, PipelineCache *pPipelineCache)
270 {
271 	auto &stage = pCreateInfo->stage;
272 	const ShaderModule *module = vk::Cast(stage.module);
273 
274 	ASSERT(shader.get() == nullptr);
275 	ASSERT(program.get() == nullptr);
276 
277 	const PipelineCache::SpirvShaderKey shaderKey(
278 	    stage.stage, stage.pName, module->getCode(), nullptr, 0, stage.pSpecializationInfo);
279 	if(pPipelineCache)
280 	{
281 		shader = pPipelineCache->getOrCreateShader(shaderKey, [&] {
282 			return createShader(shaderKey, module, robustBufferAccess, device->getDebuggerContext());
283 		});
284 
285 		const PipelineCache::ComputeProgramKey programKey(shader.get(), layout);
286 		program = pPipelineCache->getOrCreateComputeProgram(programKey, [&] {
287 			return createProgram(device, programKey);
288 		});
289 	}
290 	else
291 	{
292 		shader = createShader(shaderKey, module, robustBufferAccess, device->getDebuggerContext());
293 		const PipelineCache::ComputeProgramKey programKey(shader.get(), layout);
294 		program = createProgram(device, programKey);
295 	}
296 }
297 
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)298 void ComputePipeline::run(uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ,
299                           uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
300                           vk::DescriptorSet::Array const &descriptorSetObjects,
301                           vk::DescriptorSet::Bindings const &descriptorSets,
302                           vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
303                           vk::Pipeline::PushConstantStorage const &pushConstants)
304 {
305 	ASSERT_OR_RETURN(program != nullptr);
306 	program->run(
307 	    descriptorSetObjects, descriptorSets, descriptorDynamicOffsets, pushConstants,
308 	    baseGroupX, baseGroupY, baseGroupZ,
309 	    groupCountX, groupCountY, groupCountZ);
310 }
311 
312 }  // namespace vk
313