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 "device.hpp"
24 #include "timer.hpp"
25 
26 using namespace std;
27 
28 namespace Vulkan
29 {
register_sampler(VkSampler sampler,Fossilize::Hash hash,const VkSamplerCreateInfo & info)30 void Device::register_sampler(VkSampler sampler, Fossilize::Hash hash, const VkSamplerCreateInfo &info)
31 {
32 	state_recorder.record_sampler(sampler, info, hash);
33 }
34 
register_descriptor_set_layout(VkDescriptorSetLayout layout,Fossilize::Hash hash,const VkDescriptorSetLayoutCreateInfo & info)35 void Device::register_descriptor_set_layout(VkDescriptorSetLayout layout, Fossilize::Hash hash, const VkDescriptorSetLayoutCreateInfo &info)
36 {
37 	state_recorder.record_descriptor_set_layout(layout, info, hash);
38 }
39 
register_pipeline_layout(VkPipelineLayout layout,Fossilize::Hash hash,const VkPipelineLayoutCreateInfo & info)40 void Device::register_pipeline_layout(VkPipelineLayout layout, Fossilize::Hash hash, const VkPipelineLayoutCreateInfo &info)
41 {
42 	state_recorder.record_pipeline_layout(layout, info, hash);
43 }
44 
register_shader_module(VkShaderModule module,Fossilize::Hash hash,const VkShaderModuleCreateInfo & info)45 void Device::register_shader_module(VkShaderModule module, Fossilize::Hash hash, const VkShaderModuleCreateInfo &info)
46 {
47 	state_recorder.record_shader_module(module, info, hash);
48 }
49 
register_compute_pipeline(Fossilize::Hash hash,const VkComputePipelineCreateInfo & info)50 void Device::register_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo &info)
51 {
52 	state_recorder.record_compute_pipeline(VK_NULL_HANDLE, info, nullptr, 0, hash);
53 }
54 
register_graphics_pipeline(Fossilize::Hash hash,const VkGraphicsPipelineCreateInfo & info)55 void Device::register_graphics_pipeline(Fossilize::Hash hash, const VkGraphicsPipelineCreateInfo &info)
56 {
57 	state_recorder.record_graphics_pipeline(VK_NULL_HANDLE, info, nullptr, 0, hash);
58 }
59 
register_render_pass(VkRenderPass render_pass,Fossilize::Hash hash,const VkRenderPassCreateInfo & info)60 void Device::register_render_pass(VkRenderPass render_pass, Fossilize::Hash hash, const VkRenderPassCreateInfo &info)
61 {
62 	state_recorder.record_render_pass(render_pass, info, hash);
63 }
64 
enqueue_create_shader_module(Fossilize::Hash hash,const VkShaderModuleCreateInfo * create_info,VkShaderModule * module)65 bool Device::enqueue_create_shader_module(Fossilize::Hash hash, const VkShaderModuleCreateInfo *create_info, VkShaderModule *module)
66 {
67 	auto *ret = shaders.emplace_yield(hash, hash, this, create_info->pCode, create_info->codeSize);
68 	*module = ret->get_module();
69 	replayer_state.shader_map[*module] = ret;
70 	return true;
71 }
72 
notify_replayed_resources_for_type()73 void Device::notify_replayed_resources_for_type()
74 {
75 #ifdef GRANITE_VULKAN_MT
76 	if (replayer_state.pipeline_group)
77 	{
78 		replayer_state.pipeline_group->wait();
79 		replayer_state.pipeline_group.reset();
80 	}
81 #endif
82 }
83 
fossilize_create_graphics_pipeline(Fossilize::Hash hash,VkGraphicsPipelineCreateInfo & info)84 VkPipeline Device::fossilize_create_graphics_pipeline(Fossilize::Hash hash, VkGraphicsPipelineCreateInfo &info)
85 {
86 	if (info.stageCount != 2)
87 		return VK_NULL_HANDLE;
88 	if (info.pStages[0].stage != VK_SHADER_STAGE_VERTEX_BIT)
89 		return VK_NULL_HANDLE;
90 	if (info.pStages[1].stage != VK_SHADER_STAGE_FRAGMENT_BIT)
91 		return VK_NULL_HANDLE;
92 
93 	// Find the Shader* associated with this VkShaderModule and just use that.
94 	auto vertex_itr = replayer_state.shader_map.find(info.pStages[0].module);
95 	if (vertex_itr == end(replayer_state.shader_map))
96 		return VK_NULL_HANDLE;
97 
98 	// Find the Shader* associated with this VkShaderModule and just use that.
99 	auto fragment_itr = replayer_state.shader_map.find(info.pStages[1].module);
100 	if (fragment_itr == end(replayer_state.shader_map))
101 		return VK_NULL_HANDLE;
102 
103 	auto *ret = request_program(vertex_itr->second, fragment_itr->second);
104 
105 	// The layout is dummy, resolve it here.
106 	info.layout = ret->get_pipeline_layout()->get_layout();
107 
108 	register_graphics_pipeline(hash, info);
109 
110 	LOGI("Creating graphics pipeline.\n");
111 	VkPipeline pipeline = VK_NULL_HANDLE;
112 	VkResult res = table->vkCreateGraphicsPipelines(device, pipeline_cache, 1, &info, nullptr, &pipeline);
113 	if (res != VK_SUCCESS)
114 		LOGE("Failed to create graphics pipeline!\n");
115 	return ret->add_pipeline(hash, pipeline);
116 }
117 
fossilize_create_compute_pipeline(Fossilize::Hash hash,VkComputePipelineCreateInfo & info)118 VkPipeline Device::fossilize_create_compute_pipeline(Fossilize::Hash hash, VkComputePipelineCreateInfo &info)
119 {
120 	// Find the Shader* associated with this VkShaderModule and just use that.
121 	auto itr = replayer_state.shader_map.find(info.stage.module);
122 	if (itr == end(replayer_state.shader_map))
123 		return VK_NULL_HANDLE;
124 
125 	auto *ret = request_program(itr->second);
126 
127 	// The layout is dummy, resolve it here.
128 	info.layout = ret->get_pipeline_layout()->get_layout();
129 
130 	register_compute_pipeline(hash, info);
131 
132 	LOGI("Creating compute pipeline.\n");
133 	VkPipeline pipeline = VK_NULL_HANDLE;
134 	VkResult res = table->vkCreateComputePipelines(device, pipeline_cache, 1, &info, nullptr, &pipeline);
135 	if (res != VK_SUCCESS)
136 		LOGE("Failed to create compute pipeline!\n");
137 	return ret->add_pipeline(hash, pipeline);
138 }
139 
enqueue_create_graphics_pipeline(Fossilize::Hash hash,const VkGraphicsPipelineCreateInfo * create_info,VkPipeline * pipeline)140 bool Device::enqueue_create_graphics_pipeline(Fossilize::Hash hash,
141                                               const VkGraphicsPipelineCreateInfo *create_info,
142                                               VkPipeline *pipeline)
143 {
144 #ifdef GRANITE_VULKAN_MT
145 	if (!replayer_state.pipeline_group)
146 		replayer_state.pipeline_group = Granite::Global::thread_group()->create_task();
147 
148 	replayer_state.pipeline_group->enqueue_task([this, info = *create_info, hash, pipeline]() mutable {
149 		*pipeline = fossilize_create_graphics_pipeline(hash, info);
150 	});
151 
152 	return true;
153 #else
154 	auto info = *create_info;
155 	*pipeline = fossilize_create_graphics_pipeline(hash, info);
156 	return *pipeline != VK_NULL_HANDLE;
157 #endif
158 }
159 
enqueue_create_compute_pipeline(Fossilize::Hash hash,const VkComputePipelineCreateInfo * create_info,VkPipeline * pipeline)160 bool Device::enqueue_create_compute_pipeline(Fossilize::Hash hash,
161                                              const VkComputePipelineCreateInfo *create_info,
162                                              VkPipeline *pipeline)
163 {
164 #ifdef GRANITE_VULKAN_MT
165 	if (!replayer_state.pipeline_group)
166 		replayer_state.pipeline_group = Granite::Global::thread_group()->create_task();
167 
168 	replayer_state.pipeline_group->enqueue_task([this, info = *create_info, hash, pipeline]() mutable {
169 		*pipeline = fossilize_create_compute_pipeline(hash, info);
170 	});
171 
172 	return true;
173 #else
174 	auto info = *create_info;
175 	*pipeline = fossilize_create_compute_pipeline(hash, info);
176 	return *pipeline != VK_NULL_HANDLE;
177 #endif
178 }
179 
enqueue_create_render_pass(Fossilize::Hash hash,const VkRenderPassCreateInfo * create_info,VkRenderPass * render_pass)180 bool Device::enqueue_create_render_pass(Fossilize::Hash hash,
181                                         const VkRenderPassCreateInfo *create_info,
182                                         VkRenderPass *render_pass)
183 {
184 	auto *ret = render_passes.emplace_yield(hash, hash, this, *create_info);
185 	*render_pass = ret->get_render_pass();
186 	replayer_state.render_pass_map[*render_pass] = ret;
187 	return true;
188 }
189 
enqueue_create_sampler(Fossilize::Hash hash,const VkSamplerCreateInfo *,VkSampler * sampler)190 bool Device::enqueue_create_sampler(Fossilize::Hash hash, const VkSamplerCreateInfo *, VkSampler *sampler)
191 {
192 	*sampler = get_stock_sampler(static_cast<StockSampler>(hash & 0xffffu)).get_sampler();
193 	return true;
194 }
195 
enqueue_create_descriptor_set_layout(Fossilize::Hash,const VkDescriptorSetLayoutCreateInfo *,VkDescriptorSetLayout * layout)196 bool Device::enqueue_create_descriptor_set_layout(Fossilize::Hash, const VkDescriptorSetLayoutCreateInfo *, VkDescriptorSetLayout *layout)
197 {
198 	// We will create this naturally when building pipelines, can just emit dummy handles.
199 	*layout = (VkDescriptorSetLayout) uint64_t(-1);
200 	return true;
201 }
202 
enqueue_create_pipeline_layout(Fossilize::Hash,const VkPipelineLayoutCreateInfo *,VkPipelineLayout * layout)203 bool Device::enqueue_create_pipeline_layout(Fossilize::Hash, const VkPipelineLayoutCreateInfo *, VkPipelineLayout *layout)
204 {
205 	// We will create this naturally when building pipelines, can just emit dummy handles.
206 	*layout = (VkPipelineLayout) uint64_t(-1);
207 	return true;
208 }
209 
init_pipeline_state()210 void Device::init_pipeline_state()
211 {
212 	state_recorder.init_recording_thread(nullptr);
213 
214 	auto file = Granite::Global::filesystem()->open("assets://pipelines.json", Granite::FileMode::ReadOnly);
215 	if (!file)
216 		file = Granite::Global::filesystem()->open("cache://pipelines.json", Granite::FileMode::ReadOnly);
217 
218 	if (!file)
219 		return;
220 
221 	void *mapped = file->map();
222 	if (!mapped)
223 	{
224 		LOGE("Failed to map pipelines.json.\n");
225 		return;
226 	}
227 
228 	LOGI("Replaying cached state.\n");
229 	Fossilize::StateReplayer replayer;
230 	auto start = Util::get_current_time_nsecs();
231 	replayer.parse(*this, nullptr, static_cast<const char *>(mapped), file->get_size());
232 	auto end = Util::get_current_time_nsecs();
233 	LOGI("Completed replaying cached state in %.3f ms.\n", (end - start) * 1e-6);
234 	replayer_state = {};
235 }
236 
flush_pipeline_state()237 void Device::flush_pipeline_state()
238 {
239 	uint8_t *serialized = nullptr;
240 	size_t serialized_size = 0;
241 	if (!state_recorder.serialize(&serialized, &serialized_size))
242 	{
243 		LOGE("Failed to serialize Fossilize state.\n");
244 		return;
245 	}
246 
247 	auto file = Granite::Global::filesystem()->open("cache://pipelines.json", Granite::FileMode::WriteOnly);
248 	if (file)
249 	{
250 		auto *data = static_cast<uint8_t *>(file->map_write(serialized_size));
251 		if (data)
252 		{
253 			memcpy(data, serialized, serialized_size);
254 			file->unmap();
255 		}
256 		else
257 			LOGE("Failed to serialize pipeline data.\n");
258 
259 	}
260 	state_recorder.free_serialized(serialized);
261 }
262 }
263