aboutsummaryrefslogtreecommitdiff
path: root/demos/smoke/Shell.cpp
diff options
context:
space:
mode:
authorTony Barbour <tony@LunarG.com>2016-02-25 15:44:10 -0700
committerTony Barbour <tony@LunarG.com>2016-02-26 13:53:07 -0700
commit6d366c23f50752c8bc39673416040d89a94a586f (patch)
tree7a9493a747ffad8b708fd373ffbee359459ddd66 /demos/smoke/Shell.cpp
parent2e4f6dfe4624d966fb4db3c477586bd6b1c2b8e1 (diff)
downloadusermoji-6d366c23f50752c8bc39673416040d89a94a586f.tar.xz
demos: Add Hologram snapshot as Smoke test/demo
Diffstat (limited to 'demos/smoke/Shell.cpp')
-rw-r--r--demos/smoke/Shell.cpp591
1 files changed, 591 insertions, 0 deletions
diff --git a/demos/smoke/Shell.cpp b/demos/smoke/Shell.cpp
new file mode 100644
index 00000000..e3fab496
--- /dev/null
+++ b/demos/smoke/Shell.cpp
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2016 Google, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <cassert>
+#include <array>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <set>
+#include "Helpers.h"
+#include "Shell.h"
+#include "Game.h"
+
+Shell::Shell(Game &game)
+ : game_(game), settings_(game.settings()), ctx_(),
+ game_tick_(1.0f / settings_.ticks_per_second), game_time_(game_tick_)
+{
+ // require generic WSI extensions
+ instance_extensions_.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
+ device_extensions_.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+
+ // require "standard" validation layers
+ if (settings_.validate) {
+ device_layers_.push_back("VK_LAYER_LUNARG_standard_validation");
+ instance_layers_.push_back("VK_LAYER_LUNARG_standard_validation");
+
+ instance_extensions_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
+ }
+}
+
+void Shell::log(LogPriority priority, const char *msg)
+{
+ std::ostream &st = (priority >= LOG_ERR) ? std::cerr : std::cout;
+ st << msg << "\n";
+}
+
+void Shell::init_vk()
+{
+ vk::init_dispatch_table_top(load_vk());
+
+ init_instance();
+ vk::init_dispatch_table_middle(ctx_.instance, false);
+
+ init_debug_report();
+ init_physical_dev();
+}
+
+void Shell::cleanup_vk()
+{
+ if (settings_.validate)
+ vk::DestroyDebugReportCallbackEXT(ctx_.instance, ctx_.debug_report, nullptr);
+
+ vk::DestroyInstance(ctx_.instance, nullptr);
+}
+
+bool Shell::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)
+{
+ LogPriority prio = LOG_WARN;
+ if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
+ prio = LOG_ERR;
+ else if (flags & (VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT))
+ prio = LOG_WARN;
+ else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
+ prio = LOG_INFO;
+ else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT)
+ prio = LOG_DEBUG;
+
+ std::stringstream ss;
+ ss << layer_prefix << ": " << msg;
+
+ log(prio, ss.str().c_str());
+
+ return false;
+}
+
+void Shell::assert_all_instance_layers() const
+{
+ // enumerate instance layer
+ std::vector<VkLayerProperties> layers;
+ vk::enumerate(layers);
+
+ std::set<std::string> layer_names;
+ for (const auto &layer : layers)
+ layer_names.insert(layer.layerName);
+
+ // all listed instance layers are required
+ for (const auto &name : instance_layers_) {
+ if (layer_names.find(name) == layer_names.end()) {
+ std::stringstream ss;
+ ss << "instance layer " << name << " is missing";
+ throw std::runtime_error(ss.str());
+ }
+ }
+}
+
+void Shell::assert_all_instance_extensions() const
+{
+ // enumerate instance extensions
+ std::vector<VkExtensionProperties> exts;
+ vk::enumerate(nullptr, exts);
+
+ std::set<std::string> ext_names;
+ for (const auto &ext : exts)
+ ext_names.insert(ext.extensionName);
+
+ // all listed instance extensions are required
+ for (const auto &name : instance_extensions_) {
+ if (ext_names.find(name) == ext_names.end()) {
+ std::stringstream ss;
+ ss << "instance extension " << name << " is missing";
+ throw std::runtime_error(ss.str());
+ }
+ }
+}
+
+bool Shell::has_all_device_layers(VkPhysicalDevice phy) const
+{
+ // enumerate device layers
+ std::vector<VkLayerProperties> layers;
+ vk::enumerate(phy, layers);
+
+ std::set<std::string> layer_names;
+ for (const auto &layer : layers)
+ layer_names.insert(layer.layerName);
+
+ // all listed device layers are required
+ for (const auto &name : device_layers_) {
+ if (layer_names.find(name) == layer_names.end())
+ return false;
+ }
+
+ return true;
+}
+
+bool Shell::has_all_device_extensions(VkPhysicalDevice phy) const
+{
+ // enumerate device extensions
+ std::vector<VkExtensionProperties> exts;
+ vk::enumerate(phy, nullptr, exts);
+
+ std::set<std::string> ext_names;
+ for (const auto &ext : exts)
+ ext_names.insert(ext.extensionName);
+
+ // all listed device extensions are required
+ for (const auto &name : device_extensions_) {
+ if (ext_names.find(name) == ext_names.end())
+ return false;
+ }
+
+ return true;
+}
+
+void Shell::init_instance()
+{
+ assert_all_instance_layers();
+ assert_all_instance_extensions();
+
+ VkApplicationInfo app_info = {};
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.pApplicationName = settings_.name.c_str();
+ app_info.applicationVersion = 0;
+ app_info.apiVersion = VK_MAKE_VERSION(1, 0, 0);
+
+ VkInstanceCreateInfo instance_info = {};
+ instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instance_info.pApplicationInfo = &app_info;
+ instance_info.enabledLayerCount = static_cast<uint32_t>(instance_layers_.size());
+ instance_info.ppEnabledLayerNames = instance_layers_.data();
+ instance_info.enabledExtensionCount = static_cast<uint32_t>(instance_extensions_.size());
+ instance_info.ppEnabledExtensionNames = instance_extensions_.data();
+
+ vk::assert_success(vk::CreateInstance(&instance_info, nullptr, &ctx_.instance));
+}
+
+void Shell::init_debug_report()
+{
+ if (!settings_.validate)
+ return;
+
+ VkDebugReportCallbackCreateInfoEXT debug_report_info = {};
+ debug_report_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
+
+ debug_report_info.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT |
+ VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
+ VK_DEBUG_REPORT_ERROR_BIT_EXT;
+ if (settings_.validate_verbose) {
+ debug_report_info.flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
+ VK_DEBUG_REPORT_DEBUG_BIT_EXT;
+ }
+
+ debug_report_info.pfnCallback = debug_report_callback;
+ debug_report_info.pUserData = reinterpret_cast<void *>(this);
+
+ vk::assert_success(vk::CreateDebugReportCallbackEXT(ctx_.instance,
+ &debug_report_info, nullptr, &ctx_.debug_report));
+}
+
+void Shell::init_physical_dev()
+{
+ // enumerate physical devices
+ std::vector<VkPhysicalDevice> phys;
+ vk::assert_success(vk::enumerate(ctx_.instance, phys));
+
+ ctx_.physical_dev = VK_NULL_HANDLE;
+ for (auto phy : phys) {
+ if (!has_all_device_layers(phy) || !has_all_device_extensions(phy))
+ continue;
+
+ // get queue properties
+ std::vector<VkQueueFamilyProperties> queues;
+ vk::get(phy, queues);
+
+ int game_queue_family = -1, present_queue_family = -1;
+ for (uint32_t i = 0; i < queues.size(); i++) {
+ const VkQueueFamilyProperties &q = queues[i];
+
+ // requires only GRAPHICS for game queues
+ const VkFlags game_queue_flags = VK_QUEUE_GRAPHICS_BIT;
+ if (game_queue_family < 0 &&
+ (q.queueFlags & game_queue_flags) == game_queue_flags)
+ game_queue_family = i;
+
+ // present queue must support the surface
+ if (present_queue_family < 0 && can_present(phy, i))
+ present_queue_family = i;
+
+ if (game_queue_family >= 0 && present_queue_family >= 0)
+ break;
+ }
+
+ if (game_queue_family >= 0 && present_queue_family >= 0) {
+ ctx_.physical_dev = phy;
+ ctx_.game_queue_family = game_queue_family;
+ ctx_.present_queue_family = present_queue_family;
+ break;
+ }
+ }
+
+ if (ctx_.physical_dev == VK_NULL_HANDLE)
+ throw std::runtime_error("failed to find any capable Vulkan physical device");
+}
+
+void Shell::create_context()
+{
+ create_dev();
+ vk::init_dispatch_table_bottom(ctx_.instance, ctx_.dev);
+
+ vk::GetDeviceQueue(ctx_.dev, ctx_.game_queue_family, 0, &ctx_.game_queue);
+ vk::GetDeviceQueue(ctx_.dev, ctx_.present_queue_family, 0, &ctx_.present_queue);
+
+ create_back_buffers();
+
+ // initialize ctx_.{surface,format} before attach_shell
+ create_swapchain();
+
+ game_.attach_shell(*this);
+}
+
+void Shell::destroy_context()
+{
+ if (ctx_.dev == VK_NULL_HANDLE)
+ return;
+
+ vk::DeviceWaitIdle(ctx_.dev);
+
+ destroy_swapchain();
+
+ game_.detach_shell();
+
+ destroy_back_buffers();
+
+ ctx_.game_queue = VK_NULL_HANDLE;
+ ctx_.present_queue = VK_NULL_HANDLE;
+
+ vk::DestroyDevice(ctx_.dev, nullptr);
+ ctx_.dev = VK_NULL_HANDLE;
+}
+
+void Shell::create_dev()
+{
+ VkDeviceCreateInfo dev_info = {};
+ dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+
+ const std::vector<float> queue_priorities(settings_.queue_count, 0.0f);
+ std::array<VkDeviceQueueCreateInfo, 2> queue_info = {};
+ queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_info[0].queueFamilyIndex = ctx_.game_queue_family;
+ queue_info[0].queueCount = settings_.queue_count;
+ queue_info[0].pQueuePriorities = queue_priorities.data();
+
+ if (ctx_.game_queue_family != ctx_.present_queue_family) {
+ queue_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_info[1].queueFamilyIndex = ctx_.present_queue_family;
+ queue_info[1].queueCount = 1;
+ queue_info[1].pQueuePriorities = queue_priorities.data();
+
+ dev_info.queueCreateInfoCount = 2;
+ } else {
+ dev_info.queueCreateInfoCount = 1;
+ }
+
+ dev_info.pQueueCreateInfos = queue_info.data();
+
+ dev_info.enabledLayerCount = static_cast<uint32_t>(device_layers_.size());
+ dev_info.ppEnabledLayerNames = device_layers_.data();
+ dev_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions_.size());
+ dev_info.ppEnabledExtensionNames = device_extensions_.data();
+
+ // disable all features
+ VkPhysicalDeviceFeatures features = {};
+ dev_info.pEnabledFeatures = &features;
+
+ vk::assert_success(vk::CreateDevice(ctx_.physical_dev, &dev_info, nullptr, &ctx_.dev));
+}
+
+void Shell::create_back_buffers()
+{
+ VkSemaphoreCreateInfo sem_info = {};
+ sem_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+ VkFenceCreateInfo fence_info = {};
+ fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+ // BackBuffer is used to track which swapchain image and its associated
+ // sync primitives are busy. Having more BackBuffer's than swapchain
+ // images may allows us to replace CPU wait on present_fence by GPU wait
+ // on acquire_semaphore.
+ const int count = settings_.back_buffer_count + 1;
+ for (int i = 0; i < count; i++) {
+ BackBuffer buf = {};
+ vk::assert_success(vk::CreateSemaphore(ctx_.dev, &sem_info, nullptr, &buf.acquire_semaphore));
+ vk::assert_success(vk::CreateSemaphore(ctx_.dev, &sem_info, nullptr, &buf.render_semaphore));
+ vk::assert_success(vk::CreateFence(ctx_.dev, &fence_info, nullptr, &buf.present_fence));
+
+ ctx_.back_buffers.push(buf);
+ }
+}
+
+void Shell::destroy_back_buffers()
+{
+ while (!ctx_.back_buffers.empty()) {
+ const auto &buf = ctx_.back_buffers.front();
+
+ vk::DestroySemaphore(ctx_.dev, buf.acquire_semaphore, nullptr);
+ vk::DestroySemaphore(ctx_.dev, buf.render_semaphore, nullptr);
+ vk::DestroyFence(ctx_.dev, buf.present_fence, nullptr);
+
+ ctx_.back_buffers.pop();
+ }
+}
+
+void Shell::create_swapchain()
+{
+ ctx_.surface = create_surface(ctx_.instance);
+
+ VkBool32 supported;
+ vk::assert_success(vk::GetPhysicalDeviceSurfaceSupportKHR(ctx_.physical_dev,
+ ctx_.present_queue_family, ctx_.surface, &supported));
+ // this should be guaranteed by the platform-specific can_present call
+ assert(supported);
+
+ std::vector<VkSurfaceFormatKHR> formats;
+ vk::get(ctx_.physical_dev, ctx_.surface, formats);
+ ctx_.format = formats[0];
+
+ // defer to resize_swapchain()
+ ctx_.swapchain = VK_NULL_HANDLE;
+ ctx_.extent.width = (uint32_t) -1;
+ ctx_.extent.height = (uint32_t) -1;
+}
+
+void Shell::destroy_swapchain()
+{
+ if (ctx_.swapchain != VK_NULL_HANDLE) {
+ game_.detach_swapchain();
+
+ vk::DestroySwapchainKHR(ctx_.dev, ctx_.swapchain, nullptr);
+ ctx_.swapchain = VK_NULL_HANDLE;
+ }
+
+ vk::DestroySurfaceKHR(ctx_.instance, ctx_.surface, nullptr);
+ ctx_.surface = VK_NULL_HANDLE;
+}
+
+void Shell::resize_swapchain(uint32_t width_hint, uint32_t height_hint)
+{
+ VkSurfaceCapabilitiesKHR caps;
+ vk::assert_success(vk::GetPhysicalDeviceSurfaceCapabilitiesKHR(ctx_.physical_dev,
+ ctx_.surface, &caps));
+
+ VkExtent2D extent = caps.currentExtent;
+ // use the hints
+ if (extent.width == (uint32_t) -1) {
+ extent.width = width_hint;
+ extent.height = height_hint;
+ }
+ // clamp width; to protect us from broken hints?
+ if (extent.width < caps.minImageExtent.width)
+ extent.width = caps.minImageExtent.width;
+ else if (extent.width > caps.maxImageExtent.width)
+ extent.width = caps.maxImageExtent.width;
+ // clamp height
+ if (extent.height < caps.minImageExtent.height)
+ extent.height = caps.minImageExtent.height;
+ else if (extent.height > caps.maxImageExtent.height)
+ extent.height = caps.maxImageExtent.height;
+
+ if (ctx_.extent.width == extent.width && ctx_.extent.height == extent.height)
+ return;
+
+ uint32_t image_count = settings_.back_buffer_count;
+ if (image_count < caps.minImageCount)
+ image_count = caps.minImageCount;
+ else if (image_count > caps.maxImageCount)
+ image_count = caps.maxImageCount;
+
+ assert(caps.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+ assert(caps.supportedTransforms & caps.currentTransform);
+ assert(caps.supportedCompositeAlpha & (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR |
+ VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR));
+ VkCompositeAlphaFlagBitsKHR composite_alpha =
+ (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) ?
+ VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+
+ std::vector<VkPresentModeKHR> modes;
+ vk::get(ctx_.physical_dev, ctx_.surface, modes);
+
+ // FIFO is the only mode universally supported
+ VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
+ for (auto m : modes) {
+ if ((settings_.vsync && m == VK_PRESENT_MODE_MAILBOX_KHR) ||
+ (!settings_.vsync && m == VK_PRESENT_MODE_IMMEDIATE_KHR)) {
+ mode = m;
+ break;
+ }
+ }
+
+ VkSwapchainCreateInfoKHR swapchain_info = {};
+ swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ swapchain_info.surface = ctx_.surface;
+ swapchain_info.minImageCount = image_count;
+ swapchain_info.imageFormat = ctx_.format.format;
+ swapchain_info.imageColorSpace = ctx_.format.colorSpace;
+ swapchain_info.imageExtent = extent;
+ swapchain_info.imageArrayLayers = 1;
+ swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+ std::vector<uint32_t> queue_families(1, ctx_.game_queue_family);
+ if (ctx_.game_queue_family != ctx_.present_queue_family) {
+ queue_families.push_back(ctx_.present_queue_family);
+
+ swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+ swapchain_info.queueFamilyIndexCount = (uint32_t)queue_families.size();
+ swapchain_info.pQueueFamilyIndices = queue_families.data();
+ } else {
+ swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ }
+
+ swapchain_info.preTransform = caps.currentTransform;;
+ swapchain_info.compositeAlpha = composite_alpha;
+ swapchain_info.presentMode = mode;
+ swapchain_info.clipped = true;
+ swapchain_info.oldSwapchain = ctx_.swapchain;
+
+ vk::assert_success(vk::CreateSwapchainKHR(ctx_.dev, &swapchain_info, nullptr, &ctx_.swapchain));
+ ctx_.extent = extent;
+
+ // destroy the old swapchain
+ if (swapchain_info.oldSwapchain != VK_NULL_HANDLE) {
+ game_.detach_swapchain();
+
+ vk::DeviceWaitIdle(ctx_.dev);
+ vk::DestroySwapchainKHR(ctx_.dev, swapchain_info.oldSwapchain, nullptr);
+ }
+
+ game_.attach_swapchain();
+}
+
+void Shell::add_game_time(float time)
+{
+ int max_ticks = 3;
+
+ if (!settings_.no_tick)
+ game_time_ += time;
+
+ while (game_time_ >= game_tick_ && max_ticks--) {
+ game_.on_tick();
+ game_time_ -= game_tick_;
+ }
+}
+
+void Shell::acquire_back_buffer()
+{
+ // acquire just once when not presenting
+ if (settings_.no_present &&
+ ctx_.acquired_back_buffer.acquire_semaphore != VK_NULL_HANDLE)
+ return;
+
+ auto &buf = ctx_.back_buffers.front();
+
+ // wait until acquire and render semaphores are waited/unsignaled
+ vk::assert_success(vk::WaitForFences(ctx_.dev, 1, &buf.present_fence,
+ true, UINT64_MAX));
+ // reset the fence
+ vk::assert_success(vk::ResetFences(ctx_.dev, 1, &buf.present_fence));
+
+ vk::assert_success(vk::AcquireNextImageKHR(ctx_.dev, ctx_.swapchain,
+ UINT64_MAX, buf.acquire_semaphore, VK_NULL_HANDLE,
+ &buf.image_index));
+
+ ctx_.acquired_back_buffer = buf;
+ ctx_.back_buffers.pop();
+}
+
+void Shell::present_back_buffer()
+{
+ const auto &buf = ctx_.acquired_back_buffer;
+
+ if (!settings_.no_render)
+ game_.on_frame(game_time_ / game_tick_);
+
+ if (settings_.no_present) {
+ fake_present();
+ return;
+ }
+
+ VkPresentInfoKHR present_info = {};
+ present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+ present_info.waitSemaphoreCount = 1;
+ present_info.pWaitSemaphores = (settings_.no_render) ?
+ &buf.acquire_semaphore : &buf.render_semaphore;
+ present_info.swapchainCount = 1;
+ present_info.pSwapchains = &ctx_.swapchain;
+ present_info.pImageIndices = &buf.image_index;
+
+ vk::assert_success(vk::QueuePresentKHR(ctx_.present_queue, &present_info));
+
+ vk::assert_success(vk::QueueSubmit(ctx_.present_queue, 0, nullptr, buf.present_fence));
+ ctx_.back_buffers.push(buf);
+}
+
+void Shell::fake_present()
+{
+ const auto &buf = ctx_.acquired_back_buffer;
+
+ assert(settings_.no_present);
+
+ // wait render semaphore and signal acquire semaphore
+ if (!settings_.no_render) {
+ VkPipelineStageFlags stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+ VkSubmitInfo submit_info = {};
+ submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = &buf.render_semaphore;
+ submit_info.pWaitDstStageMask = &stage;
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &buf.acquire_semaphore;
+ vk::assert_success(vk::QueueSubmit(ctx_.game_queue, 1, &submit_info, VK_NULL_HANDLE));
+ }
+
+ // push the buffer back just once for Shell::cleanup_vk
+ if (buf.acquire_semaphore != ctx_.back_buffers.back().acquire_semaphore)
+ ctx_.back_buffers.push(buf);
+}