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