• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 Google, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <cassert>
18 #include <array>
19 #include <iostream>
20 #include <string>
21 #include <sstream>
22 #include <set>
23 #include "Helpers.h"
24 #include "Shell.h"
25 #include "Game.h"
26 
Shell(Game & game)27 Shell::Shell(Game &game)
28     : game_(game), settings_(game.settings()), ctx_(), game_tick_(1.0f / settings_.ticks_per_second), game_time_(game_tick_) {
29     // require generic WSI extensions
30     instance_extensions_.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
31     device_extensions_.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
32 
33     if (settings_.validate) {
34         instance_extensions_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
35     }
36 }
37 
log(LogPriority priority,const char * msg)38 void Shell::log(LogPriority priority, const char *msg) {
39     std::ostream &st = (priority >= LOG_ERR) ? std::cerr : std::cout;
40     st << msg << "\n";
41 }
42 
init_vk()43 void Shell::init_vk() {
44     vk::init_dispatch_table_top(load_vk());
45 
46     init_instance();
47     vk::init_dispatch_table_middle(ctx_.instance, false);
48 
49     init_debug_report();
50     init_physical_dev();
51 }
52 
cleanup_vk()53 void Shell::cleanup_vk() {
54     if (settings_.validate) vk::DestroyDebugReportCallbackEXT(ctx_.instance, ctx_.debug_report, nullptr);
55 
56     vk::DestroyInstance(ctx_.instance, nullptr);
57 }
58 
debug_report_callback(VkDebugReportFlagsEXT flags,VkDebugReportObjectTypeEXT obj_type,uint64_t object,size_t location,int32_t msg_code,const char * layer_prefix,const char * msg)59 bool Shell::debug_report_callback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT obj_type, uint64_t object,
60                                   size_t location, int32_t msg_code, const char *layer_prefix, const char *msg) {
61     LogPriority prio = LOG_WARN;
62     if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
63         prio = LOG_ERR;
64     else if (flags & (VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT))
65         prio = LOG_WARN;
66     else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
67         prio = LOG_INFO;
68     else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT)
69         prio = LOG_DEBUG;
70 
71     std::stringstream ss;
72     ss << layer_prefix << ": " << msg;
73 
74     log(prio, ss.str().c_str());
75 
76     return false;
77 }
78 
assert_all_instance_layers() const79 void Shell::assert_all_instance_layers() const {
80     // enumerate instance layer
81     std::vector<VkLayerProperties> layers;
82     vk::enumerate(layers);
83 
84     std::set<std::string> layer_names;
85     for (const auto &layer : layers) layer_names.insert(layer.layerName);
86 
87     // all listed instance layers are required
88     for (const auto &name : instance_layers_) {
89         if (layer_names.find(name) == layer_names.end()) {
90             std::stringstream ss;
91             ss << "instance layer " << name << " is missing";
92             throw std::runtime_error(ss.str());
93         }
94     }
95 }
96 
assert_all_instance_extensions() const97 void Shell::assert_all_instance_extensions() const {
98     // enumerate instance extensions
99     std::vector<VkExtensionProperties> exts;
100     vk::enumerate(nullptr, exts);
101 
102     std::set<std::string> ext_names;
103     for (const auto &ext : exts) ext_names.insert(ext.extensionName);
104 
105     // all listed instance extensions are required
106     for (const auto &name : instance_extensions_) {
107         if (ext_names.find(name) == ext_names.end()) {
108             std::stringstream ss;
109             ss << "instance extension " << name << " is missing";
110             throw std::runtime_error(ss.str());
111         }
112     }
113 }
114 
has_all_device_extensions(VkPhysicalDevice phy) const115 bool Shell::has_all_device_extensions(VkPhysicalDevice phy) const {
116     // enumerate device extensions
117     std::vector<VkExtensionProperties> exts;
118     vk::enumerate(phy, nullptr, exts);
119 
120     std::set<std::string> ext_names;
121     for (const auto &ext : exts) ext_names.insert(ext.extensionName);
122 
123     // all listed device extensions are required
124     for (const auto &name : device_extensions_) {
125         if (ext_names.find(name) == ext_names.end()) return false;
126     }
127 
128     return true;
129 }
130 
init_instance()131 void Shell::init_instance() {
132     assert_all_instance_layers();
133     assert_all_instance_extensions();
134 
135     VkApplicationInfo app_info = {};
136     app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
137     app_info.pApplicationName = settings_.name.c_str();
138     app_info.applicationVersion = 0;
139     app_info.apiVersion = VK_API_VERSION_1_0;
140 
141     VkInstanceCreateInfo instance_info = {};
142     instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
143     instance_info.pApplicationInfo = &app_info;
144     instance_info.enabledLayerCount = static_cast<uint32_t>(instance_layers_.size());
145     instance_info.ppEnabledLayerNames = instance_layers_.data();
146     instance_info.enabledExtensionCount = static_cast<uint32_t>(instance_extensions_.size());
147     instance_info.ppEnabledExtensionNames = instance_extensions_.data();
148 
149     vk::assert_success(vk::CreateInstance(&instance_info, nullptr, &ctx_.instance));
150 }
151 
init_debug_report()152 void Shell::init_debug_report() {
153     if (!settings_.validate) return;
154 
155     VkDebugReportCallbackCreateInfoEXT debug_report_info = {};
156     debug_report_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
157 
158     debug_report_info.flags =
159         VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT;
160     if (settings_.validate_verbose) {
161         debug_report_info.flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT;
162     }
163 
164     debug_report_info.pfnCallback = debug_report_callback;
165     debug_report_info.pUserData = reinterpret_cast<void *>(this);
166 
167     vk::assert_success(vk::CreateDebugReportCallbackEXT(ctx_.instance, &debug_report_info, nullptr, &ctx_.debug_report));
168 }
169 
init_physical_dev()170 void Shell::init_physical_dev() {
171     // enumerate physical devices
172     std::vector<VkPhysicalDevice> phys;
173     vk::assert_success(vk::enumerate(ctx_.instance, phys));
174 
175     ctx_.physical_dev = VK_NULL_HANDLE;
176     for (auto phy : phys) {
177         if (!has_all_device_extensions(phy)) continue;
178 
179         // get queue properties
180         std::vector<VkQueueFamilyProperties> queues;
181         vk::get(phy, queues);
182 
183         int game_queue_family = -1, present_queue_family = -1;
184         for (uint32_t i = 0; i < queues.size(); i++) {
185             const VkQueueFamilyProperties &q = queues[i];
186 
187             // requires only GRAPHICS for game queues
188             const VkFlags game_queue_flags = VK_QUEUE_GRAPHICS_BIT;
189             if (game_queue_family < 0 && (q.queueFlags & game_queue_flags) == game_queue_flags) game_queue_family = i;
190 
191             // present queue must support the surface
192             if (present_queue_family < 0 && can_present(phy, i)) present_queue_family = i;
193 
194             if (game_queue_family >= 0 && present_queue_family >= 0) break;
195         }
196 
197         if (game_queue_family >= 0 && present_queue_family >= 0) {
198             ctx_.physical_dev = phy;
199             ctx_.game_queue_family = game_queue_family;
200             ctx_.present_queue_family = present_queue_family;
201             break;
202         }
203     }
204 
205     if (ctx_.physical_dev == VK_NULL_HANDLE) throw std::runtime_error("failed to find any capable Vulkan physical device");
206 }
207 
create_context()208 void Shell::create_context() {
209     create_dev();
210     vk::init_dispatch_table_bottom(ctx_.instance, ctx_.dev);
211 
212     vk::GetDeviceQueue(ctx_.dev, ctx_.game_queue_family, 0, &ctx_.game_queue);
213     vk::GetDeviceQueue(ctx_.dev, ctx_.present_queue_family, 0, &ctx_.present_queue);
214 
215     create_back_buffers();
216 
217     // initialize ctx_.{surface,format} before attach_shell
218     create_swapchain();
219 
220     game_.attach_shell(*this);
221 }
222 
destroy_context()223 void Shell::destroy_context() {
224     if (ctx_.dev == VK_NULL_HANDLE) return;
225 
226     vk::DeviceWaitIdle(ctx_.dev);
227 
228     destroy_swapchain();
229 
230     game_.detach_shell();
231 
232     destroy_back_buffers();
233 
234     ctx_.game_queue = VK_NULL_HANDLE;
235     ctx_.present_queue = VK_NULL_HANDLE;
236 
237     vk::DestroyDevice(ctx_.dev, nullptr);
238     ctx_.dev = VK_NULL_HANDLE;
239 }
240 
create_dev()241 void Shell::create_dev() {
242     VkDeviceCreateInfo dev_info = {};
243     dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
244 
245     const std::vector<float> queue_priorities(settings_.queue_count, 0.0f);
246     std::array<VkDeviceQueueCreateInfo, 2> queue_info = {};
247     queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
248     queue_info[0].queueFamilyIndex = ctx_.game_queue_family;
249     queue_info[0].queueCount = settings_.queue_count;
250     queue_info[0].pQueuePriorities = queue_priorities.data();
251 
252     if (ctx_.game_queue_family != ctx_.present_queue_family) {
253         queue_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
254         queue_info[1].queueFamilyIndex = ctx_.present_queue_family;
255         queue_info[1].queueCount = 1;
256         queue_info[1].pQueuePriorities = queue_priorities.data();
257 
258         dev_info.queueCreateInfoCount = 2;
259     } else {
260         dev_info.queueCreateInfoCount = 1;
261     }
262 
263     dev_info.pQueueCreateInfos = queue_info.data();
264 
265     dev_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions_.size());
266     dev_info.ppEnabledExtensionNames = device_extensions_.data();
267 
268     // disable all features
269     VkPhysicalDeviceFeatures features = {};
270     dev_info.pEnabledFeatures = &features;
271 
272     vk::assert_success(vk::CreateDevice(ctx_.physical_dev, &dev_info, nullptr, &ctx_.dev));
273 }
274 
create_back_buffers()275 void Shell::create_back_buffers() {
276     VkSemaphoreCreateInfo sem_info = {};
277     sem_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
278 
279     VkFenceCreateInfo fence_info = {};
280     fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
281     fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
282 
283     // BackBuffer is used to track which swapchain image and its associated
284     // sync primitives are busy.  Having more BackBuffer's than swapchain
285     // images may allows us to replace CPU wait on present_fence by GPU wait
286     // on acquire_semaphore.
287     const int count = settings_.back_buffer_count + 1;
288     for (int i = 0; i < count; i++) {
289         BackBuffer buf = {};
290         vk::assert_success(vk::CreateSemaphore(ctx_.dev, &sem_info, nullptr, &buf.acquire_semaphore));
291         vk::assert_success(vk::CreateSemaphore(ctx_.dev, &sem_info, nullptr, &buf.render_semaphore));
292         vk::assert_success(vk::CreateFence(ctx_.dev, &fence_info, nullptr, &buf.present_fence));
293 
294         ctx_.back_buffers.push(buf);
295     }
296 }
297 
destroy_back_buffers()298 void Shell::destroy_back_buffers() {
299     while (!ctx_.back_buffers.empty()) {
300         const auto &buf = ctx_.back_buffers.front();
301 
302         vk::DestroySemaphore(ctx_.dev, buf.acquire_semaphore, nullptr);
303         vk::DestroySemaphore(ctx_.dev, buf.render_semaphore, nullptr);
304         vk::DestroyFence(ctx_.dev, buf.present_fence, nullptr);
305 
306         ctx_.back_buffers.pop();
307     }
308 }
309 
create_swapchain()310 void Shell::create_swapchain() {
311     ctx_.surface = create_surface(ctx_.instance);
312 
313     VkBool32 supported;
314     vk::assert_success(
315         vk::GetPhysicalDeviceSurfaceSupportKHR(ctx_.physical_dev, ctx_.present_queue_family, ctx_.surface, &supported));
316     // this should be guaranteed by the platform-specific can_present call
317     assert(supported);
318 
319     std::vector<VkSurfaceFormatKHR> formats;
320     vk::get(ctx_.physical_dev, ctx_.surface, formats);
321     ctx_.format = formats[0];
322 
323     // defer to resize_swapchain()
324     ctx_.swapchain = VK_NULL_HANDLE;
325     ctx_.extent.width = (uint32_t)-1;
326     ctx_.extent.height = (uint32_t)-1;
327 }
328 
destroy_swapchain()329 void Shell::destroy_swapchain() {
330     if (ctx_.swapchain != VK_NULL_HANDLE) {
331         game_.detach_swapchain();
332 
333         vk::DestroySwapchainKHR(ctx_.dev, ctx_.swapchain, nullptr);
334         ctx_.swapchain = VK_NULL_HANDLE;
335     }
336 
337     vk::DestroySurfaceKHR(ctx_.instance, ctx_.surface, nullptr);
338     ctx_.surface = VK_NULL_HANDLE;
339 }
340 
resize_swapchain(uint32_t width_hint,uint32_t height_hint)341 void Shell::resize_swapchain(uint32_t width_hint, uint32_t height_hint) {
342     VkSurfaceCapabilitiesKHR caps;
343     vk::assert_success(vk::GetPhysicalDeviceSurfaceCapabilitiesKHR(ctx_.physical_dev, ctx_.surface, &caps));
344 
345     VkExtent2D extent = caps.currentExtent;
346     // use the hints
347     if (extent.width == (uint32_t)-1) {
348         extent.width = width_hint;
349         extent.height = height_hint;
350     }
351     // clamp width; to protect us from broken hints?
352     if (extent.width < caps.minImageExtent.width)
353         extent.width = caps.minImageExtent.width;
354     else if (extent.width > caps.maxImageExtent.width)
355         extent.width = caps.maxImageExtent.width;
356     // clamp height
357     if (extent.height < caps.minImageExtent.height)
358         extent.height = caps.minImageExtent.height;
359     else if (extent.height > caps.maxImageExtent.height)
360         extent.height = caps.maxImageExtent.height;
361 
362     if (ctx_.extent.width == extent.width && ctx_.extent.height == extent.height) return;
363 
364     uint32_t image_count = settings_.back_buffer_count;
365     if (image_count < caps.minImageCount)
366         image_count = caps.minImageCount;
367     else if (image_count > caps.maxImageCount)
368         image_count = caps.maxImageCount;
369 
370     assert(caps.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
371     assert(caps.supportedTransforms & caps.currentTransform);
372     assert(caps.supportedCompositeAlpha & (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR));
373     VkCompositeAlphaFlagBitsKHR composite_alpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
374                                                       ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
375                                                       : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
376 
377     std::vector<VkPresentModeKHR> modes;
378     vk::get(ctx_.physical_dev, ctx_.surface, modes);
379 
380     // FIFO is the only mode universally supported
381     VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
382     for (auto m : modes) {
383         if ((settings_.vsync && m == VK_PRESENT_MODE_MAILBOX_KHR) || (!settings_.vsync && m == VK_PRESENT_MODE_IMMEDIATE_KHR)) {
384             mode = m;
385             break;
386         }
387     }
388 
389     VkSwapchainCreateInfoKHR swapchain_info = {};
390     swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
391     swapchain_info.surface = ctx_.surface;
392     swapchain_info.minImageCount = image_count;
393     swapchain_info.imageFormat = ctx_.format.format;
394     swapchain_info.imageColorSpace = ctx_.format.colorSpace;
395     swapchain_info.imageExtent = extent;
396     swapchain_info.imageArrayLayers = 1;
397     swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
398 
399     std::vector<uint32_t> queue_families(1, ctx_.game_queue_family);
400     if (ctx_.game_queue_family != ctx_.present_queue_family) {
401         queue_families.push_back(ctx_.present_queue_family);
402 
403         swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
404         swapchain_info.queueFamilyIndexCount = (uint32_t)queue_families.size();
405         swapchain_info.pQueueFamilyIndices = queue_families.data();
406     } else {
407         swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
408     }
409 
410     swapchain_info.preTransform = caps.currentTransform;
411     ;
412     swapchain_info.compositeAlpha = composite_alpha;
413     swapchain_info.presentMode = mode;
414     swapchain_info.clipped = true;
415     swapchain_info.oldSwapchain = ctx_.swapchain;
416 
417     vk::assert_success(vk::CreateSwapchainKHR(ctx_.dev, &swapchain_info, nullptr, &ctx_.swapchain));
418     ctx_.extent = extent;
419 
420     // destroy the old swapchain
421     if (swapchain_info.oldSwapchain != VK_NULL_HANDLE) {
422         game_.detach_swapchain();
423 
424         vk::DeviceWaitIdle(ctx_.dev);
425         vk::DestroySwapchainKHR(ctx_.dev, swapchain_info.oldSwapchain, nullptr);
426     }
427 
428     game_.attach_swapchain();
429 }
430 
add_game_time(float time)431 void Shell::add_game_time(float time) {
432     int max_ticks = 3;
433 
434     if (!settings_.no_tick) game_time_ += time;
435 
436     while (game_time_ >= game_tick_ && max_ticks--) {
437         game_.on_tick();
438         game_time_ -= game_tick_;
439     }
440 }
441 
acquire_back_buffer()442 void Shell::acquire_back_buffer() {
443     // acquire just once when not presenting
444     if (settings_.no_present && ctx_.acquired_back_buffer.acquire_semaphore != VK_NULL_HANDLE) return;
445 
446     auto &buf = ctx_.back_buffers.front();
447 
448     // wait until acquire and render semaphores are waited/unsignaled
449     vk::assert_success(vk::WaitForFences(ctx_.dev, 1, &buf.present_fence, true, UINT64_MAX));
450     // reset the fence
451     vk::assert_success(vk::ResetFences(ctx_.dev, 1, &buf.present_fence));
452 
453     vk::assert_success(
454         vk::AcquireNextImageKHR(ctx_.dev, ctx_.swapchain, UINT64_MAX, buf.acquire_semaphore, VK_NULL_HANDLE, &buf.image_index));
455 
456     ctx_.acquired_back_buffer = buf;
457     ctx_.back_buffers.pop();
458 }
459 
present_back_buffer()460 void Shell::present_back_buffer() {
461     const auto &buf = ctx_.acquired_back_buffer;
462 
463     if (!settings_.no_render) game_.on_frame(game_time_ / game_tick_);
464 
465     if (settings_.no_present) {
466         fake_present();
467         return;
468     }
469 
470     VkPresentInfoKHR present_info = {};
471     present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
472     present_info.waitSemaphoreCount = 1;
473     present_info.pWaitSemaphores = (settings_.no_render) ? &buf.acquire_semaphore : &buf.render_semaphore;
474     present_info.swapchainCount = 1;
475     present_info.pSwapchains = &ctx_.swapchain;
476     present_info.pImageIndices = &buf.image_index;
477 
478     vk::assert_success(vk::QueuePresentKHR(ctx_.present_queue, &present_info));
479 
480     vk::assert_success(vk::QueueSubmit(ctx_.present_queue, 0, nullptr, buf.present_fence));
481     ctx_.back_buffers.push(buf);
482 }
483 
fake_present()484 void Shell::fake_present() {
485     const auto &buf = ctx_.acquired_back_buffer;
486 
487     assert(settings_.no_present);
488 
489     // wait render semaphore and signal acquire semaphore
490     if (!settings_.no_render) {
491         VkPipelineStageFlags stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
492         VkSubmitInfo submit_info = {};
493         submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
494         submit_info.waitSemaphoreCount = 1;
495         submit_info.pWaitSemaphores = &buf.render_semaphore;
496         submit_info.pWaitDstStageMask = &stage;
497         submit_info.signalSemaphoreCount = 1;
498         submit_info.pSignalSemaphores = &buf.acquire_semaphore;
499         vk::assert_success(vk::QueueSubmit(ctx_.game_queue, 1, &submit_info, VK_NULL_HANDLE));
500     }
501 
502     // push the buffer back just once for Shell::cleanup_vk
503     if (buf.acquire_semaphore != ctx_.back_buffers.back().acquire_semaphore) ctx_.back_buffers.push(buf);
504 }
505