#pragma once #include #include #include #include #include #include #include #include #include class MinimalImGuiVulkan { public: /* run application; borrows calling thread for event loop, * until application exit */ void run(); private: /* create SDL window for application. * populates @ref window_ */ void init_sdl_window(); /* setup vulkan state. swapchain, command buffers etc */ void init_vulkan(); /* create vulkan instance. * populates @ref instance_ */ void create_instance(); /* create vulkan surface. * populates @ref surface_ */ void create_surface(); /* choose physical device (1:1 with graphics card, presumably) * populates @ref physical_device_, @ref graphics_queue_family_ */ void pick_physical_device(); /* * require: pick_physical_device() has run successfully * populates @ref device_, @ref graphics_queue_ */ void create_logical_device(); /* * populates @ref swapchain_, @ref swapchain_images_ */ void create_swapchain(); /* * populate @ref swapchain_image_views_ */ void create_image_views(); /* * populate @ref render_pass_ */ void create_render_pass(); /* * populate @ref framebuffers_ */ void create_framebuffers(); /* * populate @ref command_pool_ */ void create_command_pool(); /* * populate @ref command_buffers_ */ void create_command_buffers(); /* * populate * @ref image_available_semaphores_, * @ref render_finished_semaphores_, * @ref inflight_fences_ */ void create_sync_objects(); /* * populate @ref descriptor_pool_ */ void create_descriptor_pool(); /* setup imgui "framework" */ void init_imgui(); /* Allocate buffer for 'single-time' commands (to be run once?). * Pair with call to end_single_time_commands() */ VkCommandBuffer begin_single_time_commands(); void endSingleTimeCommands(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 main_loop() { SDL_Event event; while (!quit) { while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_QUIT) { quit = true; } } drawFrame(); } vkDeviceWaitIdle(device_); } void drawFrame() { vkWaitForFences(device_, 1, &inflight_fences_[currentFrame], VK_TRUE, UINT64_MAX); uint32_t imageIndex; VkResult result = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, image_available_semaphores_[currentFrame], VK_NULL_HANDLE, &imageIndex); switch (result) { case VK_SUCCESS: case VK_SUBOPTIMAL_KHR: break; case VK_ERROR_OUT_OF_DATE_KHR: recreate_swapchain(); // deliberate earlyexit return; default: throw std::runtime_error("failed to acquire swapchain image!"); break; } vkResetFences(device_, 1, &inflight_fences_[currentFrame]); vkResetCommandBuffer(command_buffers_[currentFrame], 0); recordCommandBuffer(command_buffers_[currentFrame], imageIndex); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = {image_available_semaphores_[currentFrame]}; 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_[currentFrame]; VkSemaphore signalSemaphores[] = {render_finished_semaphores_[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; if (vkQueueSubmit(graphics_queue_, 1, &submitInfo, inflight_fences_[currentFrame]) != 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; result = vkQueuePresentKHR(graphics_queue_, &presentInfo); if (framebuffer_resized_flag_) result = VK_ERROR_OUT_OF_DATE_KHR; switch (result) { case VK_SUCCESS: break; case VK_ERROR_OUT_OF_DATE_KHR: case VK_SUBOPTIMAL_KHR: framebuffer_resized_flag_ = false; this->recreate_swapchain(); break; default: throw std::runtime_error("failed to present swapchain image!"); } currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { throw std::runtime_error("Failed to begin recording command buffer!"); } VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = render_pass_; renderPassInfo.framebuffer = framebuffers_[imageIndex]; renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = swapchain_extent_; VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); // Start the Dear ImGui frame ImGui_ImplVulkan_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // Create a simple ImGui window ImGui::Begin("Hello, Vulkan + SDL2!"); ImGui::Text("This is a minimal ImGui + Vulkan + SDL2 example!"); static float f = 0.0f; static int counter = 0; ImGui::SliderFloat("float", &f, 0.0f, 1.0f); if (ImGui::Button("Button")) counter++; ImGui::SameLine(); ImGui::Text("counter = %d", counter); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::End(); // Rendering ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); ImGui_ImplVulkan_RenderDrawData(draw_data, commandBuffer); vkCmdEndRenderPass(commandBuffer); if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("Failed to record command buffer!"); } } void recreate_swapchain() { // handle window minimization: wait until window has valid size int width = 0; int height = 0; SDL_GetWindowSize(window_, &width, &height); while (width == 0 || height == 0) { SDL_GetWindowSize(window_, &width, &height); SDL_WaitEvent(nullptr); } // wait until device idle before cleaning up resources vkDeviceWaitIdle(device_); // cleanup old swapchain this->cleanupFrameBuffers(); this->cleanupImageViews(); this->cleanupSwapchain(); // create new swapchain this->create_swapchain(); this->create_image_views(); this->create_framebuffers(); } void cleanupFrameBuffers() { for (auto framebuffer : framebuffers_) { vkDestroyFramebuffer(device_, framebuffer, nullptr); } framebuffers_.clear(); } void cleanupImageViews() { for (auto imageView : swapchain_image_views_) { vkDestroyImageView(device_, imageView, nullptr); } swapchain_image_views_.clear(); } void cleanupSwapchain() { vkDestroySwapchainKHR(device_, this->swapchain_, nullptr); } void cleanup() { ImGui_ImplVulkan_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroySemaphore(device_, render_finished_semaphores_[i], nullptr); vkDestroySemaphore(device_, image_available_semaphores_[i], nullptr); vkDestroyFence(device_, inflight_fences_[i], nullptr); } vkDestroyCommandPool(device_, command_pool_, nullptr); this->cleanupFrameBuffers(); this->cleanupImageViews(); this->cleanupSwapchain(); vkDestroyRenderPass(device_, render_pass_, nullptr); vkDestroyDescriptorPool(device_, descriptor_pool_, nullptr); vkDestroyDevice(device_, nullptr); vkDestroySurfaceKHR(instance_, this->surface_, nullptr); vkDestroyInstance(instance_, nullptr); this->instance_ = nullptr; SDL_DestroyWindow(window_); this->window_ = nullptr; SDL_Quit(); } private: SDL_Window* window_ = nullptr; VkInstance instance_; /* abstraction for presentation area (?) */ VkSurfaceKHR surface_; /* physical device (graphics card) */ VkPhysicalDevice physical_device_; uint32_t graphics_queue_family_ = 0; /* logical device (graphics card, abstract api (?)) */ VkDevice device_; VkQueue graphics_queue_; /* drawing state, dependent on window size */ VkFormat swapchain_image_format_; VkExtent2D swapchain_extent_; VkSwapchainKHR swapchain_; std::vector swapchain_images_; std::vector swapchain_image_views_; VkRenderPass render_pass_; std::vector framebuffers_; VkCommandPool command_pool_; std::vector command_buffers_; std::vector image_available_semaphores_; std::vector render_finished_semaphores_; std::vector inflight_fences_; VkDescriptorPool descriptor_pool_; uint32_t currentFrame = 0; const int MAX_FRAMES_IN_FLIGHT = 2; /* true when window resize behavior detected, * until swapchain in consistent state. */ bool framebuffer_resized_flag_ = false; bool quit = false; }; /*MinimalImGuiVulkan*/