/* VulkanApp.cpp */ #include "VulkanApp.hpp" void MinimalImGuiVulkan::run() { this->init_sdl_window(); this->init_vulkan(); this->init_imgui(); this->main_loop(); this->cleanup(); } void MinimalImGuiVulkan::init_sdl_window() { if (SDL_Init(SDL_INIT_VIDEO) != 0) { throw std::runtime_error("Failed to initialize SDL!"); } this->window_ = SDL_CreateWindow( "ImGui Vulkan SDL2 Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1000, 800, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE ); if (!window_) { throw std::runtime_error("Failed to create SDL window!"); } } void MinimalImGuiVulkan::init_vulkan() { this->create_instance(); this->create_surface(); this->pick_physical_device(); this->create_logical_device(); this->create_swapchain(); this->create_image_views(); this->create_render_pass(); // must come before createFrameBuffers this->create_framebuffers(); this->create_command_pool(); this->create_command_buffers(); this->create_sync_objects(); this->create_descriptor_pool(); } void MinimalImGuiVulkan::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); #endif createInfo.enabledExtensionCount = extensions.size(); createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.enabledLayerCount = 0; #ifdef __apple__ // CRITICAL: Enable portability enumeration flag for MoltenVK createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif int result = vkCreateInstance(&createInfo, nullptr, &(this->instance_)); if (result != VK_SUCCESS) { printf("vkCreateInstance failed with error: %d\n", result); throw std::runtime_error("Failed to create instance!"); } } void MinimalImGuiVulkan::create_surface() { if (!SDL_Vulkan_CreateSurface(window_, instance_, &(this->surface_))) { throw std::runtime_error("Failed to create SDL Vulkan surface!"); } } void MinimalImGuiVulkan::pick_physical_device() { uint32_t n_device = 0; vkEnumeratePhysicalDevices(instance_, &n_device, nullptr); if (n_device == 0) { throw std::runtime_error("Failed to find GPUs with Vulkan support!"); } std::vector devices(n_device); vkEnumeratePhysicalDevices(instance_, &n_device, devices.data()); this->physical_device_ = devices[0]; // Just pick the first one for simplicity // Find graphics queue family uint32_t n_queue_family = 0; vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &n_queue_family, nullptr); std::vector queue_families(n_queue_family); vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &n_queue_family, queue_families.data()); for (uint32_t i = 0; i < queue_families.size(); i++) { if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { VkBool32 present_support = false; vkGetPhysicalDeviceSurfaceSupportKHR(physical_device_, i, surface_, &present_support); if (present_support) { this->graphics_queue_family_ = i; break; } } } } void MinimalImGuiVulkan::create_logical_device() { VkDeviceQueueCreateInfo queue_create_info{}; queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_info.queueFamilyIndex = graphics_queue_family_; queue_create_info.queueCount = 1; float queuePriority = 1.0f; queue_create_info.pQueuePriorities = &queuePriority; VkPhysicalDeviceFeatures deviceFeatures{}; VkDeviceCreateInfo create_info{}; create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; create_info.pQueueCreateInfos = &queue_create_info; create_info.queueCreateInfoCount = 1; create_info.pEnabledFeatures = &deviceFeatures; const char* deviceExtensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; create_info.enabledExtensionCount = 1; create_info.ppEnabledExtensionNames = deviceExtensions; if (vkCreateDevice(physical_device_, &create_info, nullptr, &(this->device_)) != VK_SUCCESS) { throw std::runtime_error("Failed to create logical device!"); } vkGetDeviceQueue(device_, graphics_queue_family_, 0, &graphics_queue_); } void MinimalImGuiVulkan::create_swapchain() { VkSurfaceCapabilitiesKHR capabilities; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device_, surface_, &capabilities); uint32_t n_format; vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device_, surface_, &n_format, nullptr); std::vector formats(n_format); vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device_, surface_, &n_format, formats.data()); VkSurfaceFormatKHR surface_format = formats[0]; this->swapchain_image_format_ = surface_format.format; int width, height; SDL_Vulkan_GetDrawableSize(window_, &width, &height); this->swapchain_extent_ = { static_cast(width), static_cast(height) }; uint32_t n_image = capabilities.minImageCount + 1; if (capabilities.maxImageCount > 0 && n_image > capabilities.maxImageCount) { n_image = capabilities.maxImageCount; } VkSwapchainCreateInfoKHR create_info{}; create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; create_info.surface = surface_; create_info.minImageCount = n_image; create_info.imageFormat = surface_format.format; create_info.imageColorSpace = surface_format.colorSpace; create_info.imageExtent = swapchain_extent_; create_info.imageArrayLayers = 1; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; create_info.preTransform = capabilities.currentTransform; create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; create_info.clipped = VK_TRUE; if (vkCreateSwapchainKHR(device_, &create_info, nullptr, &(this->swapchain_)) != VK_SUCCESS) { throw std::runtime_error("Failed to create swap chain!"); } vkGetSwapchainImagesKHR(device_, swapchain_, &n_image, nullptr); this->swapchain_images_.resize(n_image); vkGetSwapchainImagesKHR(device_, swapchain_, &n_image, this->swapchain_images_.data()); } void MinimalImGuiVulkan::create_image_views() { swapchain_image_views_.resize(swapchain_images_.size()); for (size_t i = 0; i < swapchain_images_.size(); i++) { VkImageViewCreateInfo create_info{}; create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; create_info.image = swapchain_images_[i]; create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; create_info.format = swapchain_image_format_; create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; create_info.subresourceRange.baseMipLevel = 0; create_info.subresourceRange.levelCount = 1; create_info.subresourceRange.baseArrayLayer = 0; create_info.subresourceRange.layerCount = 1; if (vkCreateImageView(device_, &create_info, nullptr, &swapchain_image_views_[i]) != VK_SUCCESS) { throw std::runtime_error("Failed to create image views!"); } } } void MinimalImGuiVulkan::create_render_pass() { VkAttachmentDescription color_attachment{}; color_attachment.format = swapchain_image_format_; color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference color_attachment_ref{}; color_attachment_ref.attachment = 0; color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_attachment_ref; 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 render_pass_info{}; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_info.attachmentCount = 1; render_pass_info.pAttachments = &color_attachment; render_pass_info.subpassCount = 1; render_pass_info.pSubpasses = &subpass; render_pass_info.dependencyCount = 1; render_pass_info.pDependencies = &dependency; if (vkCreateRenderPass(device_, &render_pass_info, nullptr, &(this->render_pass_)) != VK_SUCCESS) { throw std::runtime_error("Failed to create render pass!"); } } /* end VulkanApp.cpp */