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