370 lines
11 KiB
C++
370 lines
11 KiB
C++
#pragma once
|
|
|
|
#include <vulkan/vulkan.h>
|
|
#include <SDL.h>
|
|
#include <SDL_vulkan.h>
|
|
#include <imgui.h>
|
|
#include <backends/imgui_impl_sdl2.h>
|
|
#include <backends/imgui_impl_vulkan.h>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <stdexcept>
|
|
|
|
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();
|
|
|
|
/* complete command buffer begun with begin_single_time_commands();
|
|
* also submit, wait for completion + cleanup
|
|
*/
|
|
void end_single_time_commands(VkCommandBuffer 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<VkImage> swapchain_images_;
|
|
|
|
std::vector<VkImageView> swapchain_image_views_;
|
|
|
|
VkRenderPass render_pass_;
|
|
|
|
std::vector<VkFramebuffer> framebuffers_;
|
|
|
|
VkCommandPool command_pool_;
|
|
|
|
std::vector<VkCommandBuffer> command_buffers_;
|
|
|
|
std::vector<VkSemaphore> image_available_semaphores_;
|
|
std::vector<VkSemaphore> render_finished_semaphores_;
|
|
std::vector<VkFence> 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*/
|