#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(); void 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 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, &commandPool) != VK_SUCCESS) { throw std::runtime_error("Failed to create command pool!"); } } void create_command_buffers() { commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = static_cast(commandBuffers.size()); if (vkAllocateCommandBuffers(device_, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { throw std::runtime_error("Failed to allocate command buffers!"); } } void create_sync_objects() { imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); inFlightFences.resize(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 (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { if (vkCreateSemaphore(device_, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || vkCreateSemaphore(device_, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS || vkCreateFence(device_, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) { throw std::runtime_error("Failed to create synchronization objects!"); } } } void 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, &descriptorPool) != VK_SUCCESS) { throw std::runtime_error("Failed to create descriptor pool!"); } } void init_imgui() { // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; // 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 = descriptorPool; init_info.RenderPass = render_pass_; init_info.Subpass = 0; init_info.MinImageCount = MAX_FRAMES_IN_FLIGHT; init_info.ImageCount = static_cast(swapchain_images_.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, render_pass_); // Upload Fonts VkCommandBuffer command_buffer = beginSingleTimeCommands(); ImGui_ImplVulkan_CreateFontsTexture(); endSingleTimeCommands(command_buffer); //ImGui_ImplVulkan_DestroyFontUploadObjects(); } VkCommandBuffer beginSingleTimeCommands() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; 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 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_, commandPool, 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, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); uint32_t imageIndex; VkResult result = vkAcquireNextImageKHR(device_, swapchain_, UINT64_MAX, imageAvailableSemaphores[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, &inFlightFences[currentFrame]); vkResetCommandBuffer(commandBuffers[currentFrame], 0); recordCommandBuffer(commandBuffers[currentFrame], imageIndex); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[currentFrame]; VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; if (vkQueueSubmit(graphics_queue_, 1, &submitInfo, inFlightFences[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_, renderFinishedSemaphores[i], nullptr); vkDestroySemaphore(device_, imageAvailableSemaphores[i], nullptr); vkDestroyFence(device_, inFlightFences[i], nullptr); } vkDestroyCommandPool(device_, commandPool, nullptr); this->cleanupFrameBuffers(); this->cleanupImageViews(); this->cleanupSwapchain(); vkDestroyRenderPass(device_, render_pass_, nullptr); vkDestroyDescriptorPool(device_, descriptorPool, 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 commandPool; std::vector commandBuffers; std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; std::vector inFlightFences; VkDescriptorPool descriptorPool; 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*/