/* file VulkanApp.cpp */ #include "VulkanApp.hpp" #include "xo/indentlog/scope.hpp" #include #include #include #include #include #ifdef __linux__ # include #endif using xo::scope; using xo::xtag; constexpr std::size_t c_max_frames_in_flight = 2; VulkanApp::VulkanApp(ImguiDrawFn draw_fn) : imgui_draw_frame_{ draw_fn } { } void VulkanApp::setup(std::function load_fonts) { if (!setup_done_) { this->init_window(); this->init_vulkan(); this->init_imgui(load_fonts); this->setup_done_ = true; } } void VulkanApp::run() { this->setup(nullptr); this->main_loop(); this->cleanup(); } void VulkanApp::init_window() { #ifdef __linux__ // on linux (at least for wsl2) force x11 with xlib instead of xcb // alternatively could set env var SDL_VIDEODRIVER SDL_SetHint(SDL_HINT_VIDEODRIVER, "x11"); #endif if (SDL_Init(SDL_INIT_VIDEO) != 0) { throw std::runtime_error("Failed to initialize SDL!"); } window = SDL_CreateWindow( "Xo ImGui Vulkan SDL2 Frame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE ); if (!window) { throw std::runtime_error("Failed to create SDL window!"); } } void VulkanApp::init_vulkan() { this->create_instance(); this->create_surface(); this->pick_physical_device(); this->create_logical_device(); this->create_swap_chain(); this->create_image_views(); this->create_render_pass(); this->create_framebuffers(); this->create_command_pool(); this->create_command_buffers(); this->create_sync_objects(); this->create_descriptor_pool(); } void VulkanApp::create_instance() { VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "ImGui Vulkan App"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; uint32_t extensionCount = 0; if (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr)) { throw std::runtime_error("Failed to get SDL Vulkan extensions!"); } std::vector extensions(extensionCount); if (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensions.data())) { throw std::runtime_error("Failed to get SDL Vulkan extensions!"); } #ifdef __APPLE__ // Add portability extension for MoltenVK (macOS) extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); // claude.ai says: // extensions.push_back("VK_KHR_get_physical_device_properties2") #endif #ifdef __linux__ extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); // native wayland drivers. or use VK_KHR_XLIB_SURFACE_EXTENSION_NAME here //extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); //extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); #endif extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); createInfo.enabledExtensionCount = extensions.size(); createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.enabledLayerCount = 0; // CRITICAL: Enable portability enumeration flag for MoltenVK createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("Failed to create instance!"); } } void VulkanApp::create_surface() { if (!SDL_Vulkan_CreateSurface(window, instance, &(this->surface_))) { throw std::runtime_error("Failed to create SDL Vulkan surface!"); } } void VulkanApp::pick_physical_device() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("Failed to find GPUs with Vulkan support!"); } std::vector devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); this->physical_device_ = devices[0]; // Just pick the first one for simplicity // Find graphics queue family uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &queueFamilyCount, nullptr); std::vector queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties (physical_device_, &queueFamilyCount, queueFamilies.data()); for (uint32_t i = 0; i < queueFamilies.size(); i++) { if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR (physical_device_, i, surface_, &presentSupport); if (presentSupport) { graphics_queue_family_ = i; break; } } } } void VulkanApp::create_logical_device() { VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = graphics_queue_family_; queueCreateInfo.queueCount = 1; float queuePriority = 1.0f; queueCreateInfo.pQueuePriorities = &queuePriority; VkPhysicalDeviceFeatures deviceFeatures{}; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.pQueueCreateInfos = &queueCreateInfo; createInfo.queueCreateInfoCount = 1; createInfo.pEnabledFeatures = &deviceFeatures; const char* deviceExtensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; createInfo.enabledExtensionCount = 1; createInfo.ppEnabledExtensionNames = deviceExtensions; if (vkCreateDevice(physical_device_, &createInfo, nullptr, &device_) != VK_SUCCESS) { throw std::runtime_error("Failed to create logical device!"); } vkGetDeviceQueue(device_, graphics_queue_family_, 0, &graphics_queue_); } void VulkanApp::create_swap_chain() { VkSurfaceCapabilitiesKHR capabilities; vkGetPhysicalDeviceSurfaceCapabilitiesKHR (physical_device_, surface_, &capabilities); uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device_, surface_, &formatCount, nullptr); std::vector formats(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR (physical_device_, surface_, &formatCount, formats.data()); VkSurfaceFormatKHR surfaceFormat = formats[0]; swapchain_image_format_ = surfaceFormat.format; int width, height; SDL_Vulkan_GetDrawableSize(window, &width, &height); swapchain_extent_ = { static_cast(width), static_cast(height) }; uint32_t imageCount = capabilities.minImageCount + 1; if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { imageCount = capabilities.maxImageCount; } VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface_; createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = swapchain_extent_; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.preTransform = capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; createInfo.clipped = VK_TRUE; if (vkCreateSwapchainKHR (device_, &createInfo, nullptr, &swapchain_) != VK_SUCCESS) { throw std::runtime_error("Failed to create swap chain!"); } vkGetSwapchainImagesKHR(device_, swapchain_, &imageCount, nullptr); swapchainImages.resize(imageCount); vkGetSwapchainImagesKHR(device_, swapchain_, &imageCount, swapchainImages.data()); } void VulkanApp::create_image_views() { swapchain_image_views_.resize(swapchainImages.size()); for (size_t i = 0; i < swapchainImages.size(); i++) { VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapchainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = swapchain_image_format_; createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; createInfo.subresourceRange.baseMipLevel = 0; createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; if (vkCreateImageView (device_, &createInfo, nullptr, &swapchain_image_views_[i]) != VK_SUCCESS) { throw std::runtime_error("Failed to create image views!"); } } } void VulkanApp::create_render_pass() { VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapchain_image_format_; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; VkSubpassDependency dependency{}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcAccessMask = 0; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; if (vkCreateRenderPass (device_, &renderPassInfo, nullptr, &render_pass_) != VK_SUCCESS) { throw std::runtime_error("Failed to create render pass!"); } } void VulkanApp::create_framebuffers() { framebuffers_.resize(swapchain_image_views_.size()); for (size_t i = 0; i < swapchain_image_views_.size(); i++) { VkImageView attachments[] = { swapchain_image_views_[i] }; VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = render_pass_; framebufferInfo.attachmentCount = 1; framebufferInfo.pAttachments = attachments; framebufferInfo.width = swapchain_extent_.width; framebufferInfo.height = swapchain_extent_.height; framebufferInfo.layers = 1; if (vkCreateFramebuffer (device_, &framebufferInfo, nullptr, &framebuffers_[i]) != VK_SUCCESS) { throw std::runtime_error("Failed to create framebuffer!"); } } } void VulkanApp::create_command_pool() { VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; poolInfo.queueFamilyIndex = graphics_queue_family_; if (vkCreateCommandPool (device_, &poolInfo, nullptr, &command_pool_) != VK_SUCCESS) { throw std::runtime_error("Failed to create command pool!"); } } void VulkanApp::create_command_buffers() { this->command_buffers_.resize(c_max_frames_in_flight); VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = command_pool_; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = static_cast(command_buffers_.size()); if (vkAllocateCommandBuffers (device_, &allocInfo, command_buffers_.data()) != VK_SUCCESS) { throw std::runtime_error("Failed to allocate command buffers!"); } } void VulkanApp::create_sync_objects() { this->image_available_semaphores_.resize(c_max_frames_in_flight); this->render_finished_semaphores_.resize(c_max_frames_in_flight); this->in_flight_fences_.resize(c_max_frames_in_flight); VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; for (std::size_t i = 0; i < c_max_frames_in_flight; i++) { if ((vkCreateSemaphore(device_, &semaphoreInfo, nullptr, &image_available_semaphores_[i]) != VK_SUCCESS) || (vkCreateSemaphore(device_, &semaphoreInfo, nullptr, &render_finished_semaphores_[i]) != VK_SUCCESS) || (vkCreateFence(device_, &fenceInfo, nullptr, &in_flight_fences_[i]) != VK_SUCCESS)) { throw std::runtime_error("Failed to create synchronization objects!"); } } } void VulkanApp::create_descriptor_pool() { VkDescriptorPoolSize pool_sizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.pPoolSizes = pool_sizes; if (vkCreateDescriptorPool(device_, &pool_info, nullptr, &descriptor_pool_) != VK_SUCCESS) { throw std::runtime_error("Failed to create descriptor pool!"); } } void VulkanApp::init_imgui(std::function load_fonts) { scope log(XO_DEBUG(true)); // Setup Dear ImGui context IMGUI_CHECKVERSION(); this->imgui_cx_ = ImGui::CreateContext(); log && log(xtag("imgui_cx", (void*)imgui_cx_)); ImGuiIO& io = ImGui::GetIO(); (void)io; if (load_fonts) load_fonts(imgui_cx_); // Setup Dear ImGui style ImGui::StyleColorsDark(); // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForVulkan(window); ImGui_ImplVulkan_InitInfo init_info = {}; init_info.Instance = instance; init_info.PhysicalDevice = physical_device_; init_info.Device = device_; init_info.QueueFamily = graphics_queue_family_; init_info.Queue = graphics_queue_; init_info.PipelineCache = VK_NULL_HANDLE; init_info.DescriptorPool = descriptor_pool_; init_info.RenderPass = render_pass_; init_info.Subpass = 0; init_info.MinImageCount = c_max_frames_in_flight; init_info.ImageCount = static_cast(swapchainImages.size()); init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.Allocator = nullptr; init_info.CheckVkResultFn = nullptr; ImGui_ImplVulkan_Init(&init_info); //ImGui_ImplVulkan_Init(&init_info, renderPass); // Upload Fonts VkCommandBuffer command_buffer = begin_single_time_commands(); ImGui_ImplVulkan_CreateFontsTexture(); this->end_single_time_commands(command_buffer); ImGui_ImplVulkan_DestroyFontsTexture(); } /*init_imgui*/ VkCommandBuffer VulkanApp::begin_single_time_commands() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = command_pool_; allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device_, &allocInfo, &commandBuffer); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); return commandBuffer; } void VulkanApp::end_single_time_commands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; vkQueueSubmit(graphics_queue_, 1, &submitInfo, VK_NULL_HANDLE); vkQueueWaitIdle(graphics_queue_); vkFreeCommandBuffers(device_, command_pool_, 1, &commandBuffer); } void VulkanApp::main_loop() { SDL_Event event; while (!quit_) { while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) { this->quit_ = true; } #ifdef NOT_YET if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_CLOSE) { if (event.window.windowID == SDL_GetWindowID(window)) { done = true; } } else if (event.window.event == SDL_WINDOWEVENT_RESIZED) { // handle resize immediately int w, h; SDL_GetWindowSize(window, &w, &h); glViewport(0, 0, w, h); break; // to force render during resize } } #endif } this->draw_frame(); } vkDeviceWaitIdle(device_); } void VulkanApp::draw_frame() { vkWaitForFences(device_, 1, &in_flight_fences_[current_frame_], VK_TRUE, UINT64_MAX); uint32_t imageIndex; vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, image_available_semaphores_[current_frame_], VK_NULL_HANDLE, &imageIndex); vkResetFences(device_, 1, &in_flight_fences_[current_frame_]); vkResetCommandBuffer(command_buffers_[current_frame_], 0); this->record_command_buffer(command_buffers_[current_frame_], imageIndex); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = {image_available_semaphores_[current_frame_]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &command_buffers_[current_frame_]; VkSemaphore signalSemaphores[] = {render_finished_semaphores_[current_frame_]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; if (vkQueueSubmit(graphics_queue_, 1, &submitInfo, in_flight_fences_[current_frame_]) != VK_SUCCESS) { throw std::runtime_error("Failed to submit draw command buffer!"); } VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; VkSwapchainKHR swapChains[] = {swapchain_}; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &imageIndex; vkQueuePresentKHR(graphics_queue_, &presentInfo); this->current_frame_ = (current_frame_ + 1) % c_max_frames_in_flight; } /*draw_frame*/ void VulkanApp::record_command_buffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { VkCommandBufferBeginInfo begin_info{}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; if (vkBeginCommandBuffer(commandBuffer, &begin_info) != VK_SUCCESS) { throw std::runtime_error("Failed to begin recording command buffer!"); } VkRenderPassBeginInfo render_pass_info{}; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; render_pass_info.renderPass = render_pass_; render_pass_info.framebuffer = framebuffers_[imageIndex]; render_pass_info.renderArea.offset = {0, 0}; render_pass_info.renderArea.extent = swapchain_extent_; VkClearValue clear_color = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; render_pass_info.clearValueCount = 1; render_pass_info.pClearValues = &clear_color; vkCmdBeginRenderPass(commandBuffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); ImDrawData * draw_data = this->imgui_draw_frame_(imgui_cx_); ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffer); vkCmdEndRenderPass(commandBuffer); if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record command buffer!"); } } void VulkanApp::cleanup() { if (cleanup_done_) return; this->cleanup_done_ = true; ImGui_ImplVulkan_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); for (size_t i = 0; i < c_max_frames_in_flight; ++i) { vkDestroySemaphore(device_, render_finished_semaphores_[i], nullptr); vkDestroySemaphore(device_, image_available_semaphores_[i], nullptr); vkDestroyFence(device_, in_flight_fences_[i], nullptr); } vkDestroyCommandPool(device_, command_pool_, nullptr); for (auto framebuffer : framebuffers_) { vkDestroyFramebuffer(device_, framebuffer, nullptr); } vkDestroyRenderPass(device_, render_pass_, nullptr); for (auto imageView : swapchain_image_views_) { vkDestroyImageView(device_, imageView, nullptr); } vkDestroySwapchainKHR(device_, swapchain_, nullptr); vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr); vkDestroyDevice(device_, nullptr); vkDestroySurfaceKHR(instance, surface_, nullptr); vkDestroyInstance(instance, nullptr); SDL_DestroyWindow(window); SDL_Quit(); } /* end VulkanApp.cpp */