#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(); void create_image_views() { swapchainImageViews.resize(swapchain_images_.size()); for (size_t i = 0; i < swapchain_images_.size(); i++) { VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapchain_images_[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, &swapchainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("Failed to create image views!"); } } } void 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, &renderPass) != VK_SUCCESS) { throw std::runtime_error("Failed to create render pass!"); } } void create_framebuffers() { framebuffers.resize(swapchainImageViews.size()); for (size_t i = 0; i < swapchainImageViews.size(); i++) { VkImageView attachments[] = { swapchainImageViews[i] }; VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; 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 = renderPass; 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, renderPass); // 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 = renderPass; 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 : swapchainImageViews) { vkDestroyImageView(device_, imageView, nullptr); } swapchainImageViews.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_, renderPass, 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_; VkSwapchainKHR swapchain_; VkFormat swapchain_image_format_; VkExtent2D swapchain_extent_; std::vector swapchain_images_; std::vector swapchainImageViews; VkRenderPass renderPass; 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*/