1 /* Copyright (c) 2017-2020 Hans-Kristian Arntzen
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be
12  * included in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "shader.hpp"
24 #include "device.hpp"
25 #include "spirv_cross.hpp"
26 
27 #ifdef GRANITE_SPIRV_DUMP
28 #include "filesystem.hpp"
29 #endif
30 
31 using namespace std;
32 using namespace spirv_cross;
33 using namespace Util;
34 
35 namespace Vulkan
36 {
PipelineLayout(Hash hash,Device * device_,const CombinedResourceLayout & layout_)37 PipelineLayout::PipelineLayout(Hash hash, Device *device_, const CombinedResourceLayout &layout_)
38 	: IntrusiveHashMapEnabled<PipelineLayout>(hash)
39 	, device(device_)
40 	, layout(layout_)
41 {
42 	VkDescriptorSetLayout layouts[VULKAN_NUM_DESCRIPTOR_SETS] = {};
43 	unsigned num_sets = 0;
44 	for (unsigned i = 0; i < VULKAN_NUM_DESCRIPTOR_SETS; i++)
45 	{
46 		set_allocators[i] = device->request_descriptor_set_allocator(layout.sets[i], layout.stages_for_bindings[i]);
47 		layouts[i] = set_allocators[i]->get_layout();
48 		if (layout.descriptor_set_mask & (1u << i))
49 			num_sets = i + 1;
50 	}
51 
52 	if (num_sets > device->get_gpu_properties().limits.maxBoundDescriptorSets)
53 	{
54 		LOGE("Number of sets %u exceeds device limit of %u.\n",
55 		     num_sets, device->get_gpu_properties().limits.maxBoundDescriptorSets);
56 	}
57 
58 	VkPipelineLayoutCreateInfo info = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO };
59 	if (num_sets)
60 	{
61 		info.setLayoutCount = num_sets;
62 		info.pSetLayouts = layouts;
63 	}
64 
65 	if (layout.push_constant_range.stageFlags != 0)
66 	{
67 		info.pushConstantRangeCount = 1;
68 		info.pPushConstantRanges = &layout.push_constant_range;
69 	}
70 
71 #ifdef VULKAN_DEBUG
72 	LOGI("Creating pipeline layout.\n");
73 #endif
74 	auto &table = device->get_device_table();
75 	if (table.vkCreatePipelineLayout(device->get_device(), &info, nullptr, &pipe_layout) != VK_SUCCESS)
76 		LOGE("Failed to create pipeline layout.\n");
77 #ifdef GRANITE_VULKAN_FOSSILIZE
78 	device->register_pipeline_layout(pipe_layout, get_hash(), info);
79 #endif
80 
81 	if (device->get_device_features().supports_update_template)
82 		create_update_templates();
83 }
84 
create_update_templates()85 void PipelineLayout::create_update_templates()
86 {
87 	auto &table = device->get_device_table();
88 	for (unsigned desc_set = 0; desc_set < VULKAN_NUM_DESCRIPTOR_SETS; desc_set++)
89 	{
90 		if ((layout.descriptor_set_mask & (1u << desc_set)) == 0)
91 			continue;
92 		if ((layout.bindless_descriptor_set_mask & (1u << desc_set)) == 0)
93 			continue;
94 
95 		VkDescriptorUpdateTemplateEntryKHR update_entries[VULKAN_NUM_BINDINGS];
96 		uint32_t update_count = 0;
97 
98 		auto &set_layout = layout.sets[desc_set];
99 
100 		for_each_bit(set_layout.uniform_buffer_mask, [&](uint32_t binding) {
101 			unsigned array_size = set_layout.array_size[binding];
102 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
103 			auto &entry = update_entries[update_count++];
104 			entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
105 			entry.dstBinding = binding;
106 			entry.dstArrayElement = 0;
107 			entry.descriptorCount = array_size;
108 			entry.offset = offsetof(ResourceBinding, buffer) + sizeof(ResourceBinding) * binding;
109 			entry.stride = sizeof(ResourceBinding);
110 		});
111 
112 		for_each_bit(set_layout.storage_buffer_mask, [&](uint32_t binding) {
113 			unsigned array_size = set_layout.array_size[binding];
114 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
115 			auto &entry = update_entries[update_count++];
116 			entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
117 			entry.dstBinding = binding;
118 			entry.dstArrayElement = 0;
119 			entry.descriptorCount = array_size;
120 			entry.offset = offsetof(ResourceBinding, buffer) + sizeof(ResourceBinding) * binding;
121 			entry.stride = sizeof(ResourceBinding);
122 		});
123 
124 		for_each_bit(set_layout.sampled_buffer_mask, [&](uint32_t binding) {
125 			unsigned array_size = set_layout.array_size[binding];
126 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
127 			auto &entry = update_entries[update_count++];
128 			entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
129 			entry.dstBinding = binding;
130 			entry.dstArrayElement = 0;
131 			entry.descriptorCount = array_size;
132 			entry.offset = offsetof(ResourceBinding, buffer_view) + sizeof(ResourceBinding) * binding;
133 			entry.stride = sizeof(ResourceBinding);
134 		});
135 
136 		for_each_bit(set_layout.sampled_image_mask, [&](uint32_t binding) {
137 			unsigned array_size = set_layout.array_size[binding];
138 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
139 			auto &entry = update_entries[update_count++];
140 			entry.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
141 			entry.dstBinding = binding;
142 			entry.dstArrayElement = 0;
143 			entry.descriptorCount = array_size;
144 			if (set_layout.fp_mask & (1u << binding))
145 				entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * binding;
146 			else
147 				entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * binding;
148 			entry.stride = sizeof(ResourceBinding);
149 		});
150 
151 		for_each_bit(set_layout.separate_image_mask, [&](uint32_t binding) {
152 			unsigned array_size = set_layout.array_size[binding];
153 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
154 			auto &entry = update_entries[update_count++];
155 			entry.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
156 			entry.dstBinding = binding;
157 			entry.dstArrayElement = 0;
158 			entry.descriptorCount = array_size;
159 			if (set_layout.fp_mask & (1u << binding))
160 				entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * binding;
161 			else
162 				entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * binding;
163 			entry.stride = sizeof(ResourceBinding);
164 		});
165 
166 		for_each_bit(set_layout.sampler_mask & ~set_layout.immutable_sampler_mask, [&](uint32_t binding) {
167 			unsigned array_size = set_layout.array_size[binding];
168 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
169 			auto &entry = update_entries[update_count++];
170 			entry.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
171 			entry.dstBinding = binding;
172 			entry.dstArrayElement = 0;
173 			entry.descriptorCount = array_size;
174 			entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * binding;
175 			entry.stride = sizeof(ResourceBinding);
176 		});
177 
178 		for_each_bit(set_layout.storage_image_mask, [&](uint32_t binding) {
179 			unsigned array_size = set_layout.array_size[binding];
180 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
181 			auto &entry = update_entries[update_count++];
182 			entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
183 			entry.dstBinding = binding;
184 			entry.dstArrayElement = 0;
185 			entry.descriptorCount = array_size;
186 			if (set_layout.fp_mask & (1u << binding))
187 				entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * binding;
188 			else
189 				entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * binding;
190 			entry.stride = sizeof(ResourceBinding);
191 		});
192 
193 		for_each_bit(set_layout.input_attachment_mask, [&](uint32_t binding) {
194 			unsigned array_size = set_layout.array_size[binding];
195 			VK_ASSERT(update_count < VULKAN_NUM_BINDINGS);
196 			auto &entry = update_entries[update_count++];
197 			entry.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
198 			entry.dstBinding = binding;
199 			entry.dstArrayElement = 0;
200 			entry.descriptorCount = array_size;
201 			if (set_layout.fp_mask & (1u << binding))
202 				entry.offset = offsetof(ResourceBinding, image.fp) + sizeof(ResourceBinding) * binding;
203 			else
204 				entry.offset = offsetof(ResourceBinding, image.integer) + sizeof(ResourceBinding) * binding;
205 			entry.stride = sizeof(ResourceBinding);
206 		});
207 
208 		VkDescriptorUpdateTemplateCreateInfoKHR info = {VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR };
209 		info.pipelineLayout = pipe_layout;
210 		info.descriptorSetLayout = set_allocators[desc_set]->get_layout();
211 		info.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR;
212 		info.set = desc_set;
213 		info.descriptorUpdateEntryCount = update_count;
214 		info.pDescriptorUpdateEntries = update_entries;
215 		info.pipelineBindPoint = (layout.stages_for_sets[desc_set] & VK_SHADER_STAGE_COMPUTE_BIT) ?
216 				VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS;
217 
218 		if (table.vkCreateDescriptorUpdateTemplateKHR(device->get_device(), &info, nullptr,
219 		                                              &update_template[desc_set]) != VK_SUCCESS)
220 		{
221 			LOGE("Failed to create descriptor update template.\n");
222 		}
223 	}
224 }
225 
~PipelineLayout()226 PipelineLayout::~PipelineLayout()
227 {
228 	auto &table = device->get_device_table();
229 	if (pipe_layout != VK_NULL_HANDLE)
230 		table.vkDestroyPipelineLayout(device->get_device(), pipe_layout, nullptr);
231 
232 	for (auto &update : update_template)
233 		if (update != VK_NULL_HANDLE)
234 			table.vkDestroyDescriptorUpdateTemplateKHR(device->get_device(), update, nullptr);
235 }
236 
stage_to_name(ShaderStage stage)237 const char *Shader::stage_to_name(ShaderStage stage)
238 {
239 	switch (stage)
240 	{
241 	case ShaderStage::Compute:
242 		return "compute";
243 	case ShaderStage::Vertex:
244 		return "vertex";
245 	case ShaderStage::Fragment:
246 		return "fragment";
247 	case ShaderStage::Geometry:
248 		return "geometry";
249 	case ShaderStage::TessControl:
250 		return "tess_control";
251 	case ShaderStage::TessEvaluation:
252 		return "tess_evaluation";
253 	default:
254 		return "unknown";
255 	}
256 }
257 
get_stock_sampler(StockSampler & sampler,const string & name)258 static bool get_stock_sampler(StockSampler &sampler, const string &name)
259 {
260 	if (name.find("NearestClamp") != string::npos)
261 		sampler = StockSampler::NearestClamp;
262 	else if (name.find("LinearClamp") != string::npos)
263 		sampler = StockSampler::LinearClamp;
264 	else if (name.find("TrilinearClamp") != string::npos)
265 		sampler = StockSampler::TrilinearClamp;
266 	else if (name.find("NearestWrap") != string::npos)
267 		sampler = StockSampler::NearestWrap;
268 	else if (name.find("LinearWrap") != string::npos)
269 		sampler = StockSampler::LinearWrap;
270 	else if (name.find("TrilinearWrap") != string::npos)
271 		sampler = StockSampler::TrilinearWrap;
272 	else if (name.find("NearestShadow") != string::npos)
273 		sampler = StockSampler::NearestShadow;
274 	else if (name.find("LinearShadow") != string::npos)
275 		sampler = StockSampler::LinearShadow;
276 	else if (name.find("LinearYUV420P") != string::npos)
277 		sampler = StockSampler::LinearYUV420P;
278 	else if (name.find("LinearYUV422P") != string::npos)
279 		sampler = StockSampler::LinearYUV422P;
280 	else if (name.find("LinearYUV444P") != string::npos)
281 		sampler = StockSampler::LinearYUV444P;
282 	else
283 		return false;
284 
285 	return true;
286 }
287 
update_array_info(const SPIRType & type,unsigned set,unsigned binding)288 void Shader::update_array_info(const SPIRType &type, unsigned set, unsigned binding)
289 {
290 	auto &size = layout.sets[set].array_size[binding];
291 	if (!type.array.empty())
292 	{
293 		if (type.array.size() != 1)
294 			LOGE("Array dimension must be 1.\n");
295 		else if (!type.array_size_literal.front())
296 			LOGE("Array dimension must be a literal.\n");
297 		else
298 		{
299 			if (type.array.front() == 0)
300 			{
301 				// Runtime array.
302 				if (!device->get_device_features().supports_descriptor_indexing)
303 					LOGE("Sufficient features for descriptor indexing is not supported on this device.\n");
304 
305 				if (binding != 0)
306 					LOGE("Bindless textures can only be used with binding = 0 in a set.\n");
307 
308 				if (type.basetype != SPIRType::Image || type.image.dim == spv::DimBuffer)
309 					LOGE("Can only use bindless for sampled images.\n");
310 				else
311 					layout.bindless_set_mask |= 1u << set;
312 
313 				size = DescriptorSetLayout::UNSIZED_ARRAY;
314 			}
315 			else if (size && size != type.array.front())
316 				LOGE("Array dimension for (%u, %u) is inconsistent.\n", set, binding);
317 			else if (type.array.front() + binding > VULKAN_NUM_BINDINGS)
318 				LOGE("Binding array will go out of bounds.\n");
319 			else
320 				size = uint8_t(type.array.front());
321 		}
322 	}
323 	else
324 	{
325 		if (size && size != 1)
326 			LOGE("Array dimension for (%u, %u) is inconsistent.\n", set, binding);
327 		size = 1;
328 	}
329 }
330 
Shader(Hash hash,Device * device_,const uint32_t * data,size_t size)331 Shader::Shader(Hash hash, Device *device_, const uint32_t *data, size_t size)
332 	: IntrusiveHashMapEnabled<Shader>(hash)
333 	, device(device_)
334 {
335 #ifdef GRANITE_SPIRV_DUMP
336 	if (!Granite::Filesystem::get().write_buffer_to_file(string("cache://spirv/") + to_string(hash) + ".spv", data, size))
337 		LOGE("Failed to dump shader to file.\n");
338 #endif
339 
340 	VkShaderModuleCreateInfo info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
341 	info.codeSize = size;
342 	info.pCode = data;
343 
344 #ifdef VULKAN_DEBUG
345 	LOGI("Creating shader module.\n");
346 #endif
347 	auto &table = device->get_device_table();
348 	if (table.vkCreateShaderModule(device->get_device(), &info, nullptr, &module) != VK_SUCCESS)
349 		LOGE("Failed to create shader module.\n");
350 
351 #ifdef GRANITE_VULKAN_FOSSILIZE
352 	device->register_shader_module(module, get_hash(), info);
353 #endif
354 
355 	Compiler compiler(data, size / sizeof(uint32_t));
356 
357 	auto resources = compiler.get_shader_resources();
358 	for (auto &image : resources.sampled_images)
359 	{
360 		auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
361 		auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
362 		auto &type = compiler.get_type(image.type_id);
363 		if (type.image.dim == spv::DimBuffer)
364 			layout.sets[set].sampled_buffer_mask |= 1u << binding;
365 		else
366 			layout.sets[set].sampled_image_mask |= 1u << binding;
367 
368 		if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
369 			layout.sets[set].fp_mask |= 1u << binding;
370 
371 		const string &name = image.name;
372 		StockSampler sampler;
373 		if (type.image.dim != spv::DimBuffer && get_stock_sampler(sampler, name))
374 		{
375 			if (has_immutable_sampler(layout.sets[set], binding))
376 			{
377 				if (sampler != get_immutable_sampler(layout.sets[set], binding))
378 					LOGE("Immutable sampler mismatch detected!\n");
379 			}
380 			else
381 				set_immutable_sampler(layout.sets[set], binding, sampler);
382 		}
383 
384 		update_array_info(type, set, binding);
385 	}
386 
387 	for (auto &image : resources.subpass_inputs)
388 	{
389 		auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
390 		auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
391 		layout.sets[set].input_attachment_mask |= 1u << binding;
392 
393 		auto &type = compiler.get_type(image.type_id);
394 		if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
395 			layout.sets[set].fp_mask |= 1u << binding;
396 		update_array_info(type, set, binding);
397 	}
398 
399 	for (auto &image : resources.separate_images)
400 	{
401 		auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
402 		auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
403 
404 		auto &type = compiler.get_type(image.type_id);
405 		if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
406 			layout.sets[set].fp_mask |= 1u << binding;
407 
408 		if (type.image.dim == spv::DimBuffer)
409 			layout.sets[set].sampled_buffer_mask |= 1u << binding;
410 		else
411 			layout.sets[set].separate_image_mask |= 1u << binding;
412 
413 		update_array_info(type, set, binding);
414 	}
415 
416 	for (auto &image : resources.separate_samplers)
417 	{
418 		auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
419 		auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
420 		layout.sets[set].sampler_mask |= 1u << binding;
421 
422 		const string &name = image.name;
423 		StockSampler sampler;
424 		if (get_stock_sampler(sampler, name))
425 		{
426 			if (has_immutable_sampler(layout.sets[set], binding))
427 			{
428 				if (sampler != get_immutable_sampler(layout.sets[set], binding))
429 					LOGE("Immutable sampler mismatch detected!\n");
430 			}
431 			else
432 				set_immutable_sampler(layout.sets[set], binding, sampler);
433 		}
434 
435 		update_array_info(compiler.get_type(image.type_id), set, binding);
436 	}
437 
438 	for (auto &image : resources.storage_images)
439 	{
440 		auto set = compiler.get_decoration(image.id, spv::DecorationDescriptorSet);
441 		auto binding = compiler.get_decoration(image.id, spv::DecorationBinding);
442 		layout.sets[set].storage_image_mask |= 1u << binding;
443 
444 		auto &type = compiler.get_type(image.type_id);
445 		if (compiler.get_type(type.image.type).basetype == SPIRType::BaseType::Float)
446 			layout.sets[set].fp_mask |= 1u << binding;
447 
448 		update_array_info(type, set, binding);
449 	}
450 
451 	for (auto &buffer : resources.uniform_buffers)
452 	{
453 		auto set = compiler.get_decoration(buffer.id, spv::DecorationDescriptorSet);
454 		auto binding = compiler.get_decoration(buffer.id, spv::DecorationBinding);
455 		layout.sets[set].uniform_buffer_mask |= 1u << binding;
456 		update_array_info(compiler.get_type(buffer.type_id), set, binding);
457 	}
458 
459 	for (auto &buffer : resources.storage_buffers)
460 	{
461 		auto set = compiler.get_decoration(buffer.id, spv::DecorationDescriptorSet);
462 		auto binding = compiler.get_decoration(buffer.id, spv::DecorationBinding);
463 		layout.sets[set].storage_buffer_mask |= 1u << binding;
464 		update_array_info(compiler.get_type(buffer.type_id), set, binding);
465 	}
466 
467 	for (auto &attrib : resources.stage_inputs)
468 	{
469 		auto location = compiler.get_decoration(attrib.id, spv::DecorationLocation);
470 		layout.input_mask |= 1u << location;
471 	}
472 
473 	for (auto &attrib : resources.stage_outputs)
474 	{
475 		auto location = compiler.get_decoration(attrib.id, spv::DecorationLocation);
476 		layout.output_mask |= 1u << location;
477 	}
478 
479 	if (!resources.push_constant_buffers.empty())
480 	{
481 		// Don't bother trying to extract which part of a push constant block we're using.
482 		// Just assume we're accessing everything. At least on older validation layers,
483 		// it did not do a static analysis to determine similar information, so we got a lot
484 		// of false positives.
485 		layout.push_constant_size =
486 		    compiler.get_declared_struct_size(compiler.get_type(resources.push_constant_buffers.front().base_type_id));
487 	}
488 
489 	auto spec_constants = compiler.get_specialization_constants();
490 	for (auto &c : spec_constants)
491 	{
492 		if (c.constant_id >= VULKAN_NUM_SPEC_CONSTANTS)
493 		{
494 			LOGE("Spec constant ID: %u is out of range, will be ignored.\n", c.constant_id);
495 			continue;
496 		}
497 
498 		layout.spec_constant_mask |= 1u << c.constant_id;
499 	}
500 }
501 
~Shader()502 Shader::~Shader()
503 {
504 	auto &table = device->get_device_table();
505 	if (module)
506 		table.vkDestroyShaderModule(device->get_device(), module, nullptr);
507 }
508 
set_shader(ShaderStage stage,Shader * handle)509 void Program::set_shader(ShaderStage stage, Shader *handle)
510 {
511 	shaders[Util::ecast(stage)] = handle;
512 }
513 
Program(Device * device_,Shader * vertex,Shader * fragment)514 Program::Program(Device *device_, Shader *vertex, Shader *fragment)
515     : device(device_)
516 {
517 	set_shader(ShaderStage::Vertex, vertex);
518 	set_shader(ShaderStage::Fragment, fragment);
519 	device->bake_program(*this);
520 }
521 
Program(Device * device_,Shader * compute_shader)522 Program::Program(Device *device_, Shader *compute_shader)
523     : device(device_)
524 {
525 	set_shader(ShaderStage::Compute, compute_shader);
526 	device->bake_program(*this);
527 }
528 
get_pipeline(Hash hash) const529 VkPipeline Program::get_pipeline(Hash hash) const
530 {
531 	auto *ret = pipelines.find(hash);
532 	return ret ? ret->get() : VK_NULL_HANDLE;
533 }
534 
add_pipeline(Hash hash,VkPipeline pipeline)535 VkPipeline Program::add_pipeline(Hash hash, VkPipeline pipeline)
536 {
537 	return pipelines.emplace_yield(hash, pipeline)->get();
538 }
539 
~Program()540 Program::~Program()
541 {
542 	for (auto &pipe : pipelines)
543 	{
544 		if (internal_sync)
545 			device->destroy_pipeline_nolock(pipe.get());
546 		else
547 			device->destroy_pipeline(pipe.get());
548 	}
549 }
550 }
551