diff --git a/CMakeLists.txt b/CMakeLists.txt index 4258fbe..42a5ca6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(vk_expe src/utils.cpp src/Logger.cpp src/VkExpe.cpp + src/VulkanContext.cpp src/VulkanTutorial.cpp ) diff --git a/src/VkExpe.cpp b/src/VkExpe.cpp index 9e5355b..ab999bb 100644 --- a/src/VkExpe.cpp +++ b/src/VkExpe.cpp @@ -83,5 +83,6 @@ void VkExpe::run() { } m_vulkan.draw_frame(); + // m_running = false; } } diff --git a/src/VulkanContext.cpp b/src/VulkanContext.cpp new file mode 100644 index 0000000..6fccc72 --- /dev/null +++ b/src/VulkanContext.cpp @@ -0,0 +1,1019 @@ +#include "VulkanContext.h" + +#include "utils.h" +#include "Logger.h" + +#include + +#include +#include +#include +#include + + +VulkanContextSettings::VulkanContextSettings() { +} + +VulkanContextSettings::~VulkanContextSettings() { +} + +bool VulkanContextSettings::debug() const { + return m_debug; +} + +VulkanContextSettings& VulkanContextSettings::with_debug(bool enabled) { + m_debug = enabled; + return *this; +} + +const std::optional& VulkanContextSettings::physical_device() const { + return m_physical_device; +} + +VulkanContextSettings& VulkanContextSettings::with_physical_device(Uuid uuid) { + m_physical_device = uuid; + return *this; +} + +const std::vector& VulkanContextSettings::queues() const { + return m_queues; +} + +VulkanContextSettings& VulkanContextSettings::with_queue( + uint32_t index, VkQueueFlagBits flags, bool use_swapchain_images +) { + m_queues.emplace_back(QueueInfo { index, flags, use_swapchain_images }); + return *this; +} + +SDL_Window* VulkanContextSettings::window() const { + return m_window; +} + +VulkanContextSettings& VulkanContextSettings::with_window(SDL_Window* window) { + m_window = window; + return *this; +} + + +////////////////////////////////////////////////////////////////////////////// +// VulkanContext + + +VulkanContext::VulkanContext() { +} + +VulkanContext::~VulkanContext() { + shutdown(); +} + +VkInstance VulkanContext::instance() { + return m_instance; +} + +VkPhysicalDevice VulkanContext::physical_device() { + return m_physical_device; +} + +VkDevice VulkanContext::device() { + return m_device; +} + +uint32_t VulkanContext::queue_family(size_t queue_index) const { + return m_queue_families[queue_index]; +} + +VkQueue VulkanContext::queue(size_t queue_index) { + return m_queues[queue_index]; +} + +VkSurfaceKHR VulkanContext::surface() { + return m_surface; +} + +VkSwapchainKHR VulkanContext::swapchain() { + return m_swapchain; +} + +VkSurfaceFormatKHR VulkanContext::surface_format() const { + return m_surface_format; +} + +VkPresentModeKHR VulkanContext::present_mode() const { + return m_present_mode; +} + +VkExtent2D VulkanContext::swapchain_extent() const { + return m_swapchain_extent; +} + +size_t VulkanContext::swapchain_image_count() const { + return m_image_resources.size(); +} + +uint32_t VulkanContext::current_image_index() const { + return m_current_image_index; +} + +VkImage VulkanContext::swapchain_image() { + assert(m_current_image_index != CURRENT_IMAGE_INDEX); + return m_image_resources[m_current_image_index].image; +} + +VkImage VulkanContext::swapchain_image(size_t image_index) { + return m_image_resources[image_index].image; +} + +VkImageView VulkanContext::swapchain_image_view() { + assert(m_current_image_index != CURRENT_IMAGE_INDEX); + return m_image_resources[m_current_image_index].view; +} + +VkImageView VulkanContext::swapchain_image_view(size_t image_index) { + return m_image_resources[image_index].view; +} + +VkSemaphore VulkanContext::ready_to_render() { + return m_frame_resources[m_frame_resources_index].ready_to_render; +} + +VkFence VulkanContext::render_done() { + return m_frame_resources[m_frame_resources_index].render_done; +} + +void VulkanContext::initialize(const VulkanContextSettings& settings) { + m_window = settings.window(); + + create_instance(settings); + create_surface(settings); + choose_physical_device(settings); + create_device(settings); + create_swapchain(settings); +} + +void VulkanContext::shutdown() { + if(!m_instance) + return; + + vkDeviceWaitIdle(m_device); + + for(auto& callback: m_context_destruction_callbacks) + callback(); + + destroy_swapchain(); + + destroy_device(m_device); + destroy_surface(m_surface); + destroy_debug_messenger(m_debug_messenger); + destroy_instance(m_instance); +} + +void VulkanContext::begin_frame() { + assert(m_current_image_index == CURRENT_IMAGE_INDEX); + + auto& frame_resources = m_frame_resources[m_frame_resources_index]; + + // frame_resources are used cyclically. We wait for the current one to be + // done rendering to not render more that MAX_FRAMES_IN_FLIGHT frames at + // the same time. + if(frame_resources.render_done != VK_NULL_HANDLE) { + vkWaitForFences( + m_device, + 1, &frame_resources.render_done, + VK_TRUE, + UINT64_MAX + ); + } + + bool acquired_image = false; + while(!acquired_image) { + VkResult const result = vkAcquireNextImageKHR( + m_device, + m_swapchain, + UINT64_MAX, + frame_resources.ready_to_render, + VK_NULL_HANDLE, + &m_current_image_index + ); + if(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { + acquired_image = true; + } + else if(result == VK_ERROR_OUT_OF_DATE_KHR) { + recreate_swapchain(); + } + else { + throw std::runtime_error("failed to acquire swapchain image"); + } + } + + logger.info() << "begin frame " << m_frame_index + << ": image " << m_current_image_index + << ", frame: " << m_frame_resources_index; + + auto& image_resources = m_image_resources[m_current_image_index]; + + // In case vkAcquireNextImageKHR doesn't return images in-order, we wait + // again to be sure. + if(image_resources.render_done != VK_NULL_HANDLE) { + vkWaitForFences( + m_device, + 1, &image_resources.render_done, + VK_TRUE, + UINT64_MAX + ); + } + + // Remember the right frame to wait for. + frame_resources.render_done = image_resources.render_done; + + vkResetFences(m_device, 1, &frame_resources.render_done); +} + +void VulkanContext::swap_buffers(uint32_t semaphore_count, VkSemaphore* wait_semaphores) { + assert(m_current_image_index != CURRENT_IMAGE_INDEX); + + VkPresentInfoKHR present_info { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = semaphore_count, + .pWaitSemaphores = wait_semaphores, + .swapchainCount = 1, + .pSwapchains = &m_swapchain, + .pImageIndices = &m_current_image_index, + .pResults = nullptr, + }; + + VkResult const result = vkQueuePresentKHR(m_presentation_queue, &present_info); + if(result == VK_SUCCESS) { + // Nothing to see here ! + } + else if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { + m_invalid_swapchain = true; + } + else { + throw std::runtime_error("failed to present swapchain image"); + } + + if(m_invalid_swapchain) + recreate_swapchain(); + + m_frame_index += 1; + m_frame_resources_index = m_frame_index % MAX_FRAMES_IN_FLIGHT; + + m_current_image_index = CURRENT_IMAGE_INDEX; +} + +void VulkanContext::invalidate_swapchain() { + m_invalid_swapchain = true; +} + +void VulkanContext::register_swapchain_creation_callback(SwapchainCreationCallback callback) { + m_swapchain_creation_callbacks.emplace_back(std::move(callback)); +} + +void VulkanContext::register_swapchain_destruction_callback(SwapchainDestructionCallback callback) { + m_swapchain_destruction_callbacks.emplace_back(std::move(callback)); +} + +void VulkanContext::register_context_destruction_callback(ContextDestructionCallback callback) { + m_context_destruction_callbacks.emplace_back(std::move(callback)); +} + +std::vector VulkanContext::available_extensions() { + uint32_t count; + vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + + std::vector extensions(count); + vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data()); + + return extensions; +} + +std::vector VulkanContext::available_layers() { + uint32_t count; + vkEnumerateInstanceLayerProperties(&count, nullptr); + + std::vector layers(count); + vkEnumerateInstanceLayerProperties(&count, layers.data()); + + return layers; +} + +std::vector VulkanContext::sdl_vulkan_extensions() const { + unsigned count; + if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, nullptr)) + throw std::runtime_error("failed to get window's vulkan extensions"); + + std::vector extensions(count); + if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, extensions.data())) + throw std::runtime_error("failed to get window's vulkan extensions"); + + return extensions; +} + +std::vector VulkanContext::physical_devices() const { + uint32_t devices_count = 0; + vkEnumeratePhysicalDevices(m_instance, &devices_count, nullptr); + + std::vector physical_devices(devices_count); + vkEnumeratePhysicalDevices(m_instance, &devices_count, physical_devices.data()); + + return physical_devices; +} + +std::vector VulkanContext::queue_families(VkPhysicalDevice physical_device) const { + uint32_t queue_families_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, nullptr); + + std::vector queue_families(queue_families_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, queue_families.data()); + + return queue_families; +} + +std::vector VulkanContext::device_extensions(VkPhysicalDevice physical_device) const { + uint32_t extensions_count = 0; + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, nullptr); + + std::vector extensions(extensions_count); + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, extensions.data()); + + return extensions; +} + +std::vector VulkanContext::surface_formats(VkPhysicalDevice physical_device) const { + uint32_t surface_formats_count = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, nullptr); + + std::vector surface_formats(surface_formats_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, surface_formats.data()); + + return surface_formats; +} + +std::vector VulkanContext::present_modes(VkPhysicalDevice physical_device) const { + uint32_t present_modes_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, nullptr); + + std::vector present_modes(present_modes_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, present_modes.data()); + + return present_modes; +} + + +void VulkanContext::set_object_name(VkObjectType type, uint64_t object, const char* name) { + VkDebugUtilsObjectNameInfoEXT name_info { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, + .objectType = type, + .objectHandle = object, + .pObjectName = name + }; + if(vkeSetDebugUtilsObjectNameEXT(m_device, &name_info) != VK_SUCCESS) + throw std::runtime_error("failed to set debug name"); +} + +void VulkanContext::set_object_name(VkObjectType type, uint64_t object, const std::string& name) { + set_object_name(type, object, name.c_str()); +} + + +void VulkanContext::destroy_instance(VkInstance& instance) { + if(instance == nullptr) + return; + + vkDestroyInstance(instance, nullptr); + instance = nullptr; +} + +void VulkanContext::destroy_debug_messenger(VkDebugUtilsMessengerEXT& debug_messenger) { + if(debug_messenger == VK_NULL_HANDLE) + return; + + vkeDestroyDebugUtilsMessengerEXT(m_instance, debug_messenger, nullptr); + debug_messenger = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_surface(VkSurfaceKHR& surface) { + if(surface == VK_NULL_HANDLE) + return; + + vkDestroySurfaceKHR(m_instance, surface, nullptr); + surface = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_device(VkDevice& device) { + if(device == nullptr) + return; + + vkDestroyDevice(device, nullptr); + device = nullptr; +} + +void VulkanContext::destroy_swapchain(VkSwapchainKHR& swapchain) { + if(swapchain == VK_NULL_HANDLE) + return; + + vkDestroySwapchainKHR(m_device, swapchain, nullptr); + swapchain = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_image(VkImage& image) { + if(image == VK_NULL_HANDLE) + return; + + vkDestroyImage(m_device, image, nullptr); + image = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_image_view(VkImageView& image_view) { + if(image_view == VK_NULL_HANDLE) + return; + + vkDestroyImageView(m_device, image_view, nullptr); + image_view = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_framebuffer(VkFramebuffer& framebuffer) { + if(framebuffer == VK_NULL_HANDLE) + return; + + vkDestroyFramebuffer(m_device, framebuffer, nullptr); + framebuffer = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_buffer(VkBuffer& buffer) { + if(buffer == VK_NULL_HANDLE) + return; + + vkDestroyBuffer(m_device, buffer, nullptr); + buffer = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_command_pool(VkCommandPool& command_pool) { + if(command_pool == VK_NULL_HANDLE) + return; + + vkDestroyCommandPool(m_device, command_pool, nullptr); + command_pool = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_render_pass(VkRenderPass& render_pass) { + if(render_pass == VK_NULL_HANDLE) + return; + + vkDestroyRenderPass(m_device, render_pass, nullptr); + render_pass = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_pipeline_layout(VkPipelineLayout& pipeline_layout) { + if(pipeline_layout == VK_NULL_HANDLE) + return; + + vkDestroyPipelineLayout(m_device, pipeline_layout, nullptr); + pipeline_layout = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_pipeline(VkPipeline& pipeline) { + if(pipeline == VK_NULL_HANDLE) + return; + + vkDestroyPipeline(m_device, pipeline, nullptr); + pipeline = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_semaphore(VkSemaphore& semaphore) { + if(semaphore == VK_NULL_HANDLE) + return; + + vkDestroySemaphore(m_device, semaphore, nullptr); + semaphore = VK_NULL_HANDLE; +} + +void VulkanContext::destroy_fence(VkFence& fence) { + if(fence == VK_NULL_HANDLE) + return; + + vkDestroyFence(m_device, fence, nullptr); + fence = VK_NULL_HANDLE; +} + +void VulkanContext::free_memory(VkDeviceMemory& memory) { + if(memory == VK_NULL_HANDLE) + return; + + vkFreeMemory(m_device, memory, nullptr); + memory = VK_NULL_HANDLE; +} + +void VulkanContext::create_instance(const VulkanContextSettings& settings) { + if(m_instance) + return; + + char const* const name = "vk_expe"; + uint32_t const version = VK_MAKE_VERSION(0, 1, 0); + + VkApplicationInfo const app_info { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = name, + .applicationVersion = version, + .pEngineName = name, + .engineVersion = version, + .apiVersion = VK_API_VERSION_1_1, + }; + + std::vector layers; + std::vector extensions = sdl_vulkan_extensions(); + + if(settings.debug()) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + auto const available_layers = VulkanContext::available_layers(); + + auto add_layer_if_available = [&](const char* requested_layer) { + if(std::find_if( + available_layers.begin(), + available_layers.end(), + [requested_layer](const VkLayerProperties& layer) { + return std::strcmp(layer.layerName, requested_layer) == 0; + } + ) != available_layers.end()) { + layers.push_back(requested_layer); + } + else { + logger.warning() << "requested layer '" << requested_layer + << "' is not available"; + } + }; + + add_layer_if_available("VK_LAYER_KHRONOS_validation"); + } + + VkDebugUtilsMessengerCreateInfoEXT const debug_info { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = &log_debug_message, + .pUserData = nullptr, + }; + + VkInstanceCreateInfo const instance_info { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = &debug_info, + .pApplicationInfo = &app_info, + .enabledLayerCount = uint32_t(layers.size()), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = uint32_t(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + }; + + logger.debug() << "Requested extensions:"; + for(char const* extension: extensions) + logger.debug() << " " << extension; + logger.debug() << "Requested layers:"; + for(char const* layer: layers) + logger.debug() << " " << layer; + + logger.debug() << "create vulkan instance..."; + if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS) + throw std::runtime_error("failed to create vulkan instance"); + + initialize_extension_functions(); + + if(settings.debug()) { + vkeCreateDebugUtilsMessengerEXT( + m_instance, &debug_info, nullptr, &m_debug_messenger); + } +} + +void VulkanContext::create_surface(const VulkanContextSettings& settings) { + if(!SDL_Vulkan_CreateSurface(m_window, m_instance, &m_surface)) + throw std::runtime_error("failed to create surface"); +} + +void VulkanContext::choose_physical_device(const VulkanContextSettings& settings) { + auto physical_devices = this->physical_devices(); + if(settings.physical_device()) { + auto const device_it = std::find_if( + physical_devices.begin(), + physical_devices.end(), + [&settings] (VkPhysicalDevice physical_device) { + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(physical_device, &device_properties); + return make_uuid(device_properties.pipelineCacheUUID) + == *settings.physical_device(); + } + ); + if(device_it != physical_devices.end()) { + using std::swap; + swap(physical_devices[0], *device_it); + } + } + + for(auto const physical_device: physical_devices) { + if(select_physical_device(physical_device, settings)) { + m_physical_device = physical_device; + break; + } + } + + if(!m_physical_device) + throw std::runtime_error("failed to find suitable physical device"); + + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(m_physical_device, &device_properties); + + logger.info() << "use suitable device: " << device_properties.deviceName; + logger.debug() << "swapchain format: " << m_surface_format.format; + logger.debug() << "present mode: " << m_present_mode; +} + +bool VulkanContext::select_physical_device( + VkPhysicalDevice physical_device, + const VulkanContextSettings& settings +) { + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(physical_device, &device_properties); + + m_queue_families.assign(settings.queues().size(), INVALID_QUEUE_FAMILY); + m_presentation_queue_family = INVALID_QUEUE_FAMILY; + + auto const queue_families = this->queue_families(physical_device); + uint32_t queue_family_index = 0; + for(auto const& queue_family: queue_families) { + if(m_window != nullptr) { + VkBool32 support_surface = false; + vkGetPhysicalDeviceSurfaceSupportKHR( + physical_device, + queue_family_index, + m_surface, + &support_surface + ); + if(support_surface) + m_presentation_queue_family = queue_family_index; + } + + for(auto const queue_info: settings.queues()) { + uint32_t const index = queue_info.index; + VkQueueFlagBits flags = queue_info.flags; + + if(m_queue_families[index] != INVALID_QUEUE_FAMILY) + continue; + + if((queue_family.queueFlags & flags) == flags) + m_queue_families[index] = queue_family_index; + } + + queue_family_index += 1; + } + + for(auto const queue_family: m_queue_families) { + if(queue_family == INVALID_QUEUE_FAMILY) + return false; + } + + auto const available_extensions = this->device_extensions(physical_device); + auto has_extension = [&available_extensions](const char* required_extension) { + return std::find_if( + available_extensions.begin(), + available_extensions.end(), + [required_extension](const VkExtensionProperties& extension) { + return strcmp(extension.extensionName, required_extension) == 0; + } + ) != available_extensions.end(); + }; + + if(m_window) { + if(!has_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) + return false; + + auto const surface_formats = this->surface_formats(physical_device); + if(surface_formats.empty()) + return false; + + auto const present_modes = this->present_modes(physical_device); + if(present_modes.empty()) + return false; + + auto const surface_format_it = std::find_if( + surface_formats.begin(), + surface_formats.end(), + [](const VkSurfaceFormatKHR& surface_format) { + return surface_format.format == VK_FORMAT_B8G8R8A8_SRGB + && surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } + ); + m_surface_format = (surface_format_it != surface_formats.end())? + *surface_format_it: + surface_formats[0]; + + m_present_mode = VK_PRESENT_MODE_FIFO_KHR; + } + + return true; +} + +void VulkanContext::create_device(const VulkanContextSettings& settings) { + std::vector queue_families = m_queue_families; + std::sort(queue_families.begin(), queue_families.end()); + queue_families.erase( + std::unique(queue_families.begin(), queue_families.end()), + queue_families.end() + ); + + float const queue_priority = 1.0f; + std::vector queue_infos; + for(uint32_t queue_family: queue_families) { + queue_infos.emplace_back(VkDeviceQueueCreateInfo { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queue_family, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }); + } + + VkPhysicalDeviceFeatures const device_features { + }; + + std::vector extensions; + if(m_window) { + extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + }; + + VkDeviceCreateInfo const device_info { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = uint32_t(queue_infos.size()), + .pQueueCreateInfos = queue_infos.data(), + .enabledExtensionCount = uint32_t(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + .pEnabledFeatures = &device_features, + }; + + logger.debug() << "create vulkan device..."; + if(vkCreateDevice( + m_physical_device, + &device_info, + nullptr, + &m_device + ) != VK_SUCCESS) + throw std::runtime_error("failed to create logical device"); + + m_queues.resize(m_queue_families.size()); + for(size_t index = 0; index < m_queue_families.size(); ++index) { + vkGetDeviceQueue( + m_device, + m_queue_families[index], + 0, + &m_queues[index] + ); + } + vkGetDeviceQueue(m_device, m_presentation_queue_family, 0, &m_presentation_queue); +} + +void VulkanContext::create_swapchain(const VulkanContextSettings& settings) { + m_swapchain_queue_families = { + m_presentation_queue_family, + }; + for(size_t index = 0; index < m_queue_families.size(); index += 1) { + if(settings.queues()[index].use_swapchain_images) + m_swapchain_queue_families.push_back(m_queue_families[index]); + } + std::sort(m_swapchain_queue_families.begin(), m_swapchain_queue_families.end()); + m_swapchain_queue_families.erase( + std::unique(m_swapchain_queue_families.begin(), m_swapchain_queue_families.end()), + m_swapchain_queue_families.end() + ); + + create_swapchain(); +} + +void VulkanContext::create_swapchain() { + VkSurfaceCapabilitiesKHR capabilities; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &capabilities); + + m_swapchain_extent = capabilities.currentExtent; + if(m_swapchain_extent.width == UINT32_MAX) { + int width; + int height; + SDL_Vulkan_GetDrawableSize(m_window, &width, &height); + + m_swapchain_extent.width = std::clamp( + uint32_t(width), + capabilities.minImageExtent.width, + capabilities.maxImageExtent.width + ); + m_swapchain_extent.height = std::clamp( + uint32_t(height), + capabilities.minImageExtent.height, + capabilities.maxImageExtent.height + ); + } + logger.debug() << "swapchain extent: " << m_swapchain_extent.width + << ", " << m_swapchain_extent.height; + + uint32_t image_count = capabilities.minImageCount; + if(capabilities.maxImageCount != 0) + image_count = std::min(image_count, capabilities.maxImageCount); + logger.debug() << "swapchain image count: " << image_count; + + bool share_image = m_swapchain_queue_families.size() > 1; + + VkSwapchainCreateInfoKHR swapchain_info { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = m_surface, + .minImageCount = image_count, + .imageFormat = m_surface_format.format, + .imageColorSpace = m_surface_format.colorSpace, + .imageExtent = m_swapchain_extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = share_image? + VK_SHARING_MODE_CONCURRENT: + VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = share_image? + uint32_t(m_swapchain_queue_families.size()): + 0u, + .pQueueFamilyIndices = share_image? + m_swapchain_queue_families.data(): + nullptr, + .preTransform = capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = m_present_mode, + .clipped = VK_TRUE, + .oldSwapchain = VK_NULL_HANDLE, + }; + + if(vkCreateSwapchainKHR(m_device, &swapchain_info, nullptr, &m_swapchain) != VK_SUCCESS) + throw std::runtime_error("failed to create swapchain"); + + vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, nullptr); + VkImage swapchain_images[16]; + vkGetSwapchainImagesKHR( + m_device, + m_swapchain, + &image_count, + swapchain_images + ); + + m_image_resources.resize(image_count, {}); + for(size_t index = 0; index < image_count; index += 1) { + auto& image_resources = m_image_resources[index]; + + image_resources.image = swapchain_images[index]; + + VkImageViewCreateInfo view_info { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = image_resources.image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = m_surface_format.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + if(vkCreateImageView( + m_device, + &view_info, + nullptr, + &image_resources.view + ) != VK_SUCCESS) { + throw std::runtime_error("failed to create swapchain image view"); + } + + VkFenceCreateInfo fence_info { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + if(vkCreateFence( + m_device, + &fence_info, + nullptr, + &image_resources.render_done + ) != VK_SUCCESS) + throw std::runtime_error("failed to create fence"); + } + + m_frame_resources.resize(MAX_FRAMES_IN_FLIGHT, {}); + + int index = 0; + for(auto& frame_resources: m_frame_resources) { + VkSemaphoreCreateInfo semaphore_info { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + if(vkCreateSemaphore( + m_device, + &semaphore_info, + nullptr, + &frame_resources.ready_to_render + ) != VK_SUCCESS) + throw std::runtime_error("failed to create semaphore"); + + index += 1; + } + + for(auto& callback: m_swapchain_creation_callbacks) + callback(image_count); +} + +void VulkanContext::destroy_swapchain() { + for(auto& callback: m_swapchain_destruction_callbacks) + callback(); + + for(auto& frame_resources: m_frame_resources) { + destroy_semaphore(frame_resources.ready_to_render); + // This is a reference to an ImageResources.render_done + frame_resources.render_done = VK_NULL_HANDLE; + } + + for(auto& image_resources: m_image_resources) { + destroy_fence(image_resources.render_done); + destroy_image_view(image_resources.view); + } + + destroy_swapchain(m_swapchain); +} + +void VulkanContext::recreate_swapchain() { + destroy_swapchain(); + create_swapchain(); +} + +void VulkanContext::initialize_extension_functions() { + uint32_t errors_count = 0; + + #define GET_PROC_ADDR(func_name) \ + vke ## func_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \ + m_instance, "vk" #func_name); \ + if(vke ## func_name == nullptr) \ + { \ + logger.error() << "failed to load extension function 'vk" #func_name "'"; \ + errors_count += 1; \ + } + + GET_PROC_ADDR(CreateDebugUtilsMessengerEXT) + GET_PROC_ADDR(DestroyDebugUtilsMessengerEXT) + GET_PROC_ADDR(SetDebugUtilsObjectNameEXT) + + if(errors_count != 0) + throw std::runtime_error("failed to load extensions"); +} + +VKAPI_ATTR VkBool32 VKAPI_CALL VulkanContext::log_debug_message( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* user_data +) { + auto stream = [severity]() { + switch(severity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + return logger.debug(); + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + return logger.info(); + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + return logger.warning(); + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + return logger.error(); + } + return logger.debug(); + }(); + + switch(type) { + case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: + stream << "[vk:general] "; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: + stream << "[vk:validation] "; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: + stream << "[vk:performance] "; + break; + } + + stream << data->pMessage; + + if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + abort(); + + return VK_FALSE; +} + +PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT = nullptr; +PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT = nullptr; +PFN_vkSetDebugUtilsObjectNameEXT vkeSetDebugUtilsObjectNameEXT = nullptr; diff --git a/src/VulkanContext.h b/src/VulkanContext.h new file mode 100644 index 0000000..3fdaae8 --- /dev/null +++ b/src/VulkanContext.h @@ -0,0 +1,215 @@ +#pragma once + +#include "utils.h" +#include "Logger.h" + +#include +#include + +#include +#include +#include +#include + + +class VulkanContext; + +class VulkanContextSettings { +public: + struct QueueInfo { + uint32_t index; + VkQueueFlagBits flags; + bool use_swapchain_images; + }; + +public: + VulkanContextSettings(); + ~VulkanContextSettings(); + + bool debug() const; + VulkanContextSettings& with_debug(bool enabled=true); + + const std::optional& physical_device() const; + VulkanContextSettings& with_physical_device(Uuid uuid); + + const std::vector& queues() const; + VulkanContextSettings& with_queue(uint32_t index, VkQueueFlagBits flags, bool use_swapchain_images=false); + + SDL_Window* window() const; + VulkanContextSettings& with_window(SDL_Window* window); + +private: + bool m_debug = false; + std::optional m_physical_device; + std::vector m_queues; + SDL_Window* m_window = nullptr; +}; + +constexpr uint32_t INVALID_QUEUE_FAMILY = UINT32_MAX; + +class VulkanContext { +public: + static constexpr uint32_t CURRENT_IMAGE_INDEX = UINT32_MAX; + static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; + + using SwapchainCreationCallback = std::function; + using SwapchainDestructionCallback = std::function; + using ContextDestructionCallback = std::function; + +public: + VulkanContext(); + VulkanContext(const VulkanContext&) = delete; + ~VulkanContext(); + + VulkanContext& operator=(const VulkanContext&) = delete; + + VkInstance instance(); + VkPhysicalDevice physical_device(); + VkDevice device(); + uint32_t queue_family(size_t queue_index) const; + VkQueue queue(size_t queue_index); + + VkSurfaceKHR surface(); + VkSwapchainKHR swapchain(); + + VkSurfaceFormatKHR surface_format() const; + VkPresentModeKHR present_mode() const; + VkExtent2D swapchain_extent() const; + size_t swapchain_image_count() const; + uint32_t current_image_index() const; + VkImage swapchain_image(); + VkImage swapchain_image(size_t image_index); + VkImageView swapchain_image_view(); + VkImageView swapchain_image_view(size_t image_index); + // VkFramebuffer framebuffer(); + VkSemaphore ready_to_render(); + VkFence render_done(); + + + void initialize(const VulkanContextSettings& settings); + void shutdown(); + + void begin_frame(); + void swap_buffers(uint32_t semaphore_count, VkSemaphore* wait_semaphores); + + void invalidate_swapchain(); + + void register_swapchain_creation_callback(SwapchainCreationCallback callback); + void register_swapchain_destruction_callback(SwapchainDestructionCallback callback); + void register_context_destruction_callback(ContextDestructionCallback callback); + + + static std::vector available_extensions(); + static std::vector available_layers(); + + std::vector sdl_vulkan_extensions() const; + + std::vector physical_devices() const; + std::vector queue_families(VkPhysicalDevice physical_device) const; + std::vector device_extensions(VkPhysicalDevice physical_device) const; + std::vector surface_formats(VkPhysicalDevice physical_device) const; + std::vector present_modes(VkPhysicalDevice physical_device) const; + + + void set_object_name(VkObjectType type, uint64_t object, const char* name); + void set_object_name(VkObjectType type, uint64_t object, const std::string& name); + + + void destroy_instance(VkInstance& instance); + void destroy_debug_messenger(VkDebugUtilsMessengerEXT& debug_messenger); + void destroy_surface(VkSurfaceKHR& surface); + void destroy_device(VkDevice& device); + void destroy_swapchain(VkSwapchainKHR& swapchain); + + void destroy_image(VkImage& image); + void destroy_image_view(VkImageView& image_view); + void destroy_framebuffer(VkFramebuffer& framebuffer); + + void destroy_buffer(VkBuffer& buffer); + + void destroy_command_pool(VkCommandPool& command_pool); + + void destroy_render_pass(VkRenderPass& render_pass); + void destroy_pipeline_layout(VkPipelineLayout& pipeline_layout); + void destroy_pipeline(VkPipeline& pipeline); + + void destroy_semaphore(VkSemaphore& semaphore); + void destroy_fence(VkFence& fence); + + void free_memory(VkDeviceMemory& memory); + +private: + struct ImageResources { + VkImage image = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VkFence render_done = VK_NULL_HANDLE; + }; + + struct FrameResources { + VkSemaphore ready_to_render = VK_NULL_HANDLE; + VkFence render_done = VK_NULL_HANDLE; + }; + +private: + void create_instance(const VulkanContextSettings& settings); + void create_surface(const VulkanContextSettings& settings); + void choose_physical_device(const VulkanContextSettings& settings); + bool select_physical_device( + VkPhysicalDevice physical_device, + const VulkanContextSettings& settings + ); + void create_device(const VulkanContextSettings& settings); + void create_swapchain(const VulkanContextSettings& settings); + void create_swapchain(); + + void destroy_swapchain(); + void recreate_swapchain(); + + void initialize_extension_functions(); + + static VKAPI_ATTR VkBool32 VKAPI_CALL log_debug_message( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* user_data + ); + + +private: + SDL_Window* m_window = nullptr; + + VkInstance m_instance = nullptr; + VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE; + VkPhysicalDevice m_physical_device = nullptr; + VkDevice m_device = nullptr; + + std::vector m_queue_families; + uint32_t m_presentation_queue_family = UINT32_MAX; + std::vector m_queues; + VkQueue m_presentation_queue = nullptr; + + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; + + VkSurfaceFormatKHR m_surface_format; + VkPresentModeKHR m_present_mode; + VkExtent2D m_swapchain_extent; + std::vector m_swapchain_queue_families; + std::vector m_image_resources; + std::vector m_frame_resources; + bool m_invalid_swapchain = false; + + std::vector m_swapchain_creation_callbacks; + std::vector m_swapchain_destruction_callbacks; + std::vector m_context_destruction_callbacks; + + uint32_t m_current_image_index = CURRENT_IMAGE_INDEX; + size_t m_frame_index = 0; + size_t m_frame_resources_index = 0; + + friend class VulkanContextSettings; +}; + +extern PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT; +extern PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT; +extern PFN_vkSetDebugUtilsObjectNameEXT vkeSetDebugUtilsObjectNameEXT; diff --git a/src/VulkanTutorial.cpp b/src/VulkanTutorial.cpp index ad2db88..edfa9d3 100644 --- a/src/VulkanTutorial.cpp +++ b/src/VulkanTutorial.cpp @@ -13,10 +13,6 @@ #include -PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT = nullptr; -PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT = nullptr; - - VkVertexInputBindingDescription Vertex::binding_description() { return { .binding = 0, @@ -51,6 +47,10 @@ const std::vector vertices = { VulkanTutorial::VulkanTutorial() { + m_context.register_swapchain_creation_callback( + std::bind(&VulkanTutorial::create_swapchain_objects, this, std::placeholders::_1)); + m_context.register_swapchain_destruction_callback( + std::bind(&VulkanTutorial::destroy_swapchain_objects, this)); } VulkanTutorial::~VulkanTutorial() { @@ -58,106 +58,48 @@ VulkanTutorial::~VulkanTutorial() { } void VulkanTutorial::initialize(SDL_Window* window) { - m_window = window; + auto const settings = VulkanContextSettings() +#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG) + .with_debug(true) +#endif + .with_queue(GRAPHIC_QUEUE, VK_QUEUE_GRAPHICS_BIT) + .with_window(window); + m_context.initialize(settings); - create_instance(); - create_surface(); - find_physical_device(); - create_device(); - create_swapchain(); - create_render_pass(); - create_graphic_pipeline(); - create_framebuffers(); create_command_pool(); create_vertex_buffer(); - create_command_buffers(); - create_sync_objects(); } void VulkanTutorial::shutdown() { - if(!m_instance) + if(!m_context.instance()) return; - vkDeviceWaitIdle(m_device); - + vkDeviceWaitIdle(m_context.device()); - for(VkFence fence: m_in_flight_fence) - vkDestroyFence(m_device, fence, nullptr); - m_in_flight_fence.clear(); + destroy_swapchain_objects(); - for(VkSemaphore semaphore: m_render_finished_semaphores) - vkDestroySemaphore(m_device, semaphore, nullptr); - m_render_finished_semaphores.clear(); + m_context.free_memory(m_vertex_buffer_memory); + m_context.destroy_command_pool(m_command_pool); + m_context.destroy_buffer(m_vertex_buffer); + m_context.free_memory(m_vertex_buffer_memory); - for(VkSemaphore semaphore: m_image_available_semaphores) - vkDestroySemaphore(m_device, semaphore, nullptr); - m_image_available_semaphores.clear(); - - if(m_command_pool != VK_NULL_HANDLE) { - vkDestroyCommandPool(m_device, m_command_pool, nullptr); - m_command_pool = VK_NULL_HANDLE; - } - - if(m_vertex_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(m_device, m_vertex_buffer, nullptr); - m_vertex_buffer = VK_NULL_HANDLE; - } - - if(m_vertex_buffer_memory != VK_NULL_HANDLE) { - vkFreeMemory(m_device, m_vertex_buffer_memory, nullptr); - m_vertex_buffer_memory = VK_NULL_HANDLE; - } + for(VkSemaphore semaphore: m_render_done) + m_context.destroy_semaphore(semaphore); + m_render_done.clear(); - cleanup_swap_chain(); - - if(m_device) { - vkDestroyDevice(m_device, nullptr); - m_device = nullptr; - } - - if(m_surface) { - vkDestroySurfaceKHR(m_instance, m_surface, nullptr); - m_surface = nullptr; - } - - if(m_debug_messenger != VK_NULL_HANDLE) { - vkeDestroyDebugUtilsMessengerEXT( - m_instance, m_debug_messenger, nullptr); - m_debug_messenger = VK_NULL_HANDLE; - } - - vkDestroyInstance(m_instance, nullptr); - m_instance = nullptr; - m_physical_device = nullptr; - - m_window = nullptr; + m_context.shutdown(); } void VulkanTutorial::draw_frame() { - vkWaitForFences(m_device, 1, &m_in_flight_fence[m_frame_index], VK_TRUE, UINT64_MAX); - - uint32_t image_index; - VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, m_image_available_semaphores[m_frame_index], VK_NULL_HANDLE, &image_index); - if(result == VK_ERROR_OUT_OF_DATE_KHR) { - recreate_swap_chain(); - return; - } - else if(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) - throw std::runtime_error("failed to acquire swapchain image"); - - if(m_images_in_flight[image_index] != VK_NULL_HANDLE) - vkWaitForFences(m_device, 1, &m_images_in_flight[image_index], VK_TRUE, UINT64_MAX); - m_images_in_flight[image_index] = m_in_flight_fence[image_index]; + m_context.begin_frame(); + auto const image_index = m_context.current_image_index(); VkSemaphore wait_semaphores[] = { - m_image_available_semaphores[m_frame_index], + m_context.ready_to_render(), }; VkPipelineStageFlags stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, }; - VkSemaphore signal_semaphores[] = { - m_render_finished_semaphores[m_frame_index], - }; VkSubmitInfo submit_info { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, @@ -166,415 +108,70 @@ void VulkanTutorial::draw_frame() { .commandBufferCount = 1, .pCommandBuffers = &m_command_buffers[image_index], .signalSemaphoreCount = 1, - .pSignalSemaphores = signal_semaphores, + .pSignalSemaphores = &m_render_done[image_index], }; - vkResetFences(m_device, 1, &m_in_flight_fence[m_frame_index]); - if(vkQueueSubmit(m_graphic_queue, 1, &submit_info, m_in_flight_fence[m_frame_index])) + if(vkQueueSubmit( + m_context.queue(GRAPHIC_QUEUE), + 1, &submit_info, + m_context.render_done() + )) throw std::runtime_error("failed to submit draw command buffer"); - VkPresentInfoKHR present_info { - .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, - .waitSemaphoreCount = 1, - .pWaitSemaphores = signal_semaphores, - .swapchainCount = 1, - .pSwapchains = &m_swapchain, - .pImageIndices = &image_index, - .pResults = nullptr, - }; - - result = vkQueuePresentKHR(m_presentation_queue, &present_info); - if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || m_invalid_swapchain) - recreate_swap_chain(); - if(result != VK_SUCCESS && result != VK_ERROR_OUT_OF_DATE_KHR && result != VK_SUBOPTIMAL_KHR) - throw std::runtime_error("failed to present swapchain image"); - - m_frame_index = (m_frame_index + 1) % MAX_FRAMES_IN_FLIGHT; + m_context.swap_buffers(1, &m_render_done[image_index]); } void VulkanTutorial::invalidate_swapchain() { - m_invalid_swapchain = true; -} - -std::vector VulkanTutorial::available_extensions() { - uint32_t count; - vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); - - std::vector extensions(count); - vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data()); - - return extensions; -} - -std::vector VulkanTutorial::available_layers() { - uint32_t count; - vkEnumerateInstanceLayerProperties(&count, nullptr); - - std::vector layers(count); - vkEnumerateInstanceLayerProperties(&count, layers.data()); - - return layers; + m_context.invalidate_swapchain(); } -VKAPI_ATTR VkBool32 VKAPI_CALL VulkanTutorial::print_debug_message( - VkDebugUtilsMessageSeverityFlagBitsEXT severity, - VkDebugUtilsMessageTypeFlagsEXT type, - const VkDebugUtilsMessengerCallbackDataEXT* data, - void* user_data -) { - auto stream = [severity]() { - switch(severity) { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: - return logger.debug(); - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - return logger.info(); - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - return logger.warning(); - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - return logger.error(); - } - return logger.debug(); - }(); - - switch(type) { - case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: - stream << "[vk:general] "; - break; - case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: - stream << "[vk:validation] "; - break; - case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: - stream << "[vk:performance] "; - break; - } - - stream << data->pMessage; - - if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) - abort(); - - return VK_FALSE; -} - -void VulkanTutorial::create_instance() { - if(m_instance) - return; - - const char* name = "vk_expe"; - const uint32_t version = VK_MAKE_VERSION(0, 1, 0); - - VkApplicationInfo app_info { - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pApplicationName = name, - .applicationVersion = version, - .pEngineName = name, - .engineVersion = version, - .apiVersion = VK_API_VERSION_1_1, - }; - - std::vector layers; - std::vector extensions; - - { - unsigned count; - if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, nullptr)) - throw std::runtime_error("failed to get window's vulkan extensions"); - extensions.resize(count); - - if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, extensions.data())) - throw std::runtime_error("failed to get window's vulkan extensions"); - } - - if(m_enableValidation) { - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - - auto const available_layers = VulkanTutorial::available_layers(); - - auto add_layer_if_available = [&](const char* requested_layer) { - if(std::find_if( - available_layers.begin(), - available_layers.end(), - [requested_layer](const VkLayerProperties& layer) { - return std::strcmp(layer.layerName, requested_layer) == 0; - } - ) != available_layers.end()) { - layers.push_back(requested_layer); - } - else { - logger.warning() << "requested layer '" << requested_layer - << "' is not available"; - } - }; - - add_layer_if_available("VK_LAYER_KHRONOS_validation"); - } +void VulkanTutorial::create_swapchain_objects(uint32_t image_count) { + create_render_pass(); + create_framebuffers(); + create_graphic_pipeline(); + create_command_buffers(); - m_debug_info = VkDebugUtilsMessengerCreateInfoEXT { - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - .messageSeverity = - VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, - .messageType = - VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, - .pfnUserCallback = &print_debug_message, - .pUserData = nullptr, - }; + m_render_done.resize(m_context.swapchain_image_count()); - VkInstanceCreateInfo instance_info { - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pNext = &m_debug_info, - .pApplicationInfo = &app_info, - .enabledLayerCount = uint32_t(layers.size()), - .ppEnabledLayerNames = layers.data(), - .enabledExtensionCount = uint32_t(extensions.size()), - .ppEnabledExtensionNames = extensions.data(), + VkSemaphoreCreateInfo semaphore_info { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; - logger.debug() << "Requested extensions:"; - for(const char* extension: extensions) - logger.debug() << " " << extension; - logger.debug() << "Requested layers:"; - for(const char* layer: layers) - logger.debug() << " " << layer; - - logger.debug() << "create vulkan instance..."; - if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS) - throw std::runtime_error("failed to create vulkan instance"); - - initialize_extension_functions(); - - if(m_enableValidation) { - vkeCreateDebugUtilsMessengerEXT( - m_instance, &m_debug_info, nullptr, &m_debug_messenger); + for(size_t index = 0; index < m_render_done.size(); index += 1) { + if(vkCreateSemaphore( + m_context.device(), + &semaphore_info, + nullptr, + &m_render_done[index] + ) != VK_SUCCESS) + throw std::runtime_error("failed to create semaphore"); } } -void VulkanTutorial::create_surface() { - if(!SDL_Vulkan_CreateSurface(m_window, m_instance, &m_surface)) - throw std::runtime_error("failed to create surface"); -} - -void VulkanTutorial::find_physical_device() { - auto const physical_devices = get_physical_devices(); - for(auto const physical_device: physical_devices) { - VkPhysicalDeviceProperties device_properties; - vkGetPhysicalDeviceProperties(physical_device, &device_properties); - - int graphic_family = -1; - int presentation_family = -1; - - auto const queue_families = get_queue_families(physical_device); - int index = 0; - for(auto const& queue_family: queue_families) { - if(queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) - graphic_family = index; - - VkBool32 support_surface = false; - vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, index, m_surface, &support_surface); - if(support_surface) - presentation_family = index; - - index += 1; - } - - if(graphic_family < 0 || presentation_family < 0) - continue; - - auto const available_extensions = get_device_extensions(physical_device); - auto has_extension = [&available_extensions](const char* required_extension) { - return std::find_if( - available_extensions.begin(), - available_extensions.end(), - [required_extension](const VkExtensionProperties& extension) { - return strcmp(extension.extensionName, required_extension) == 0; - } - ) != available_extensions.end(); - }; - - if(!has_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) - continue; - - auto const surface_formats = get_surface_formats(physical_device); - if(surface_formats.empty()) - continue; - - auto const present_modes = get_present_modes(physical_device); - if(present_modes.empty()) - continue; - - logger.info() << "use suitable device: " << device_properties.deviceName; - m_physical_device = physical_device; - m_graphic_queue_family = uint32_t(graphic_family); - m_presentation_queue_family = uint32_t(presentation_family); - - auto const surface_format_it = std::find_if( - surface_formats.begin(), - surface_formats.end(), - [](const VkSurfaceFormatKHR& surface_format) { - return surface_format.format == VK_FORMAT_B8G8R8A8_SRGB - && surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - } - ); - m_swapchain_format = (surface_format_it != surface_formats.end())? - *surface_format_it: - surface_formats[0]; - logger.debug() << "swapchain format: " << m_swapchain_format.format; - - m_present_mode = VK_PRESENT_MODE_FIFO_KHR; - logger.debug() << "present mode: " << m_present_mode; - - break; - } - - if(!m_physical_device) - throw std::runtime_error("failed to find suitable physical device"); -} +void VulkanTutorial::destroy_swapchain_objects() { + if(m_command_pool == VK_NULL_HANDLE) + return; -void VulkanTutorial::create_device() { - std::vector queue_families { - m_graphic_queue_family, - m_presentation_queue_family, - }; - std::sort(queue_families.begin(), queue_families.end()); - queue_families.erase( - std::unique(queue_families.begin(), queue_families.end()), - queue_families.end() + vkFreeCommandBuffers( + m_context.device(), + m_command_pool, + uint32_t(m_command_buffers.size()), + m_command_buffers.data() ); + m_command_buffers.clear(); - float queue_priority = 1.0f; - std::vector queue_infos; - for(uint32_t queue_family: queue_families) { - queue_infos.emplace_back(VkDeviceQueueCreateInfo { - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .queueFamilyIndex = queue_family, - .queueCount = 1, - .pQueuePriorities = &queue_priority, - }); - } - - VkPhysicalDeviceFeatures device_features { - }; - - std::vector extensions { - VK_KHR_SWAPCHAIN_EXTENSION_NAME, - }; - - VkDeviceCreateInfo device_info { - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .queueCreateInfoCount = uint32_t(queue_infos.size()), - .pQueueCreateInfos = queue_infos.data(), - .enabledExtensionCount = uint32_t(extensions.size()), - .ppEnabledExtensionNames = extensions.data(), - .pEnabledFeatures = &device_features, - }; - - logger.debug() << "create vulkan device..."; - if(vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device) != VK_SUCCESS) - throw std::runtime_error("failed to create logical device"); - - vkGetDeviceQueue(m_device, m_graphic_queue_family, 0, &m_graphic_queue); - vkGetDeviceQueue(m_device, m_presentation_queue_family, 0, &m_presentation_queue); -} - -void VulkanTutorial::create_swapchain() { - VkSurfaceCapabilitiesKHR capabilities; - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &capabilities); - - m_swapchain_extent = capabilities.currentExtent; - if(m_swapchain_extent.width == UINT32_MAX) { - int width; - int height; - SDL_Vulkan_GetDrawableSize(m_window, &width, &height); - - m_swapchain_extent.width = std::clamp( - uint32_t(width), - capabilities.minImageExtent.width, - capabilities.maxImageExtent.width - ); - m_swapchain_extent.height = std::clamp( - uint32_t(height), - capabilities.minImageExtent.height, - capabilities.maxImageExtent.height - ); - } - logger.debug() << "swapchain extent: " << m_swapchain_extent.width - << ", " << m_swapchain_extent.height; - - uint32_t image_count = capabilities.minImageCount + 1; - if(capabilities.maxImageCount != 0) - image_count = std::min(image_count, capabilities.maxImageCount); - logger.debug() << "swapchain image count: " << image_count; - - bool share_image = (m_graphic_queue_family != m_presentation_queue_family); - uint32_t queue_families[] = { - m_graphic_queue_family, - m_presentation_queue_family, - }; - - VkSwapchainCreateInfoKHR swapchain_info { - .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - .surface = m_surface, - .minImageCount = image_count, - .imageFormat = m_swapchain_format.format, - .imageColorSpace = m_swapchain_format.colorSpace, - .imageExtent = m_swapchain_extent, - .imageArrayLayers = 1, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, - .imageSharingMode = share_image? - VK_SHARING_MODE_CONCURRENT: - VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = share_image? 2u: 0u, - .pQueueFamilyIndices = share_image? queue_families: nullptr, - .preTransform = capabilities.currentTransform, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - .presentMode = m_present_mode, - .clipped = VK_TRUE, - .oldSwapchain = VK_NULL_HANDLE, - }; - - if(vkCreateSwapchainKHR(m_device, &swapchain_info, nullptr, &m_swapchain) != VK_SUCCESS) - throw std::runtime_error("failed to create swapchain"); - - vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, nullptr); - m_swapchain_images.resize(image_count); - vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, m_swapchain_images.data()); - - m_swapchain_image_views.resize(image_count); - for(size_t index = 0; index < image_count; index += 1) { - VkImageViewCreateInfo view_info { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = m_swapchain_images[index], - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = m_swapchain_format.format, - .components = { - .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY, - }, - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; - - if(vkCreateImageView(m_device, &view_info, nullptr, &m_swapchain_image_views[index]) != VK_SUCCESS) - throw std::runtime_error("failed to create swapchain image view"); - } + for(VkFramebuffer framebuffer: m_framebuffers) + m_context.destroy_framebuffer(framebuffer); + m_framebuffers.clear(); - m_invalid_swapchain = false; + m_context.destroy_pipeline(m_pipeline); + m_context.destroy_pipeline_layout(m_pipeline_layout); + m_context.destroy_render_pass(m_render_pass); } void VulkanTutorial::create_render_pass() { VkAttachmentDescription color_attachment { - .format = m_swapchain_format.format, + .format = m_context.surface_format().format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, @@ -614,19 +211,24 @@ void VulkanTutorial::create_render_pass() { .pDependencies = &subpass_dep, }; - if(vkCreateRenderPass(m_device, &render_pass_info, nullptr, &m_render_pass) != VK_SUCCESS) + if(vkCreateRenderPass( + m_context.device(), + &render_pass_info, + nullptr, + &m_render_pass + ) != VK_SUCCESS) throw std::runtime_error("failed to create render pass"); } void VulkanTutorial::create_graphic_pipeline() { auto const vertex_shader_module = create_shader_module_from_file("shaders/shader.vert.spv"); auto const vertex_shader_guard = make_guard([&]{ - vkDestroyShaderModule(m_device, vertex_shader_module, nullptr); + vkDestroyShaderModule(m_context.device(), vertex_shader_module, nullptr); }); auto const fragment_shader_module = create_shader_module_from_file("shaders/shader.frag.spv"); auto const fragment_shader_guard = make_guard([&]{ - vkDestroyShaderModule(m_device, fragment_shader_module, nullptr); + vkDestroyShaderModule(m_context.device(), fragment_shader_module, nullptr); }); VkPipelineShaderStageCreateInfo shader_stage_infos[] = { @@ -666,15 +268,15 @@ void VulkanTutorial::create_graphic_pipeline() { VkViewport viewport { .x = 0.0f, .y = 0.0f, - .width = float(m_swapchain_extent.width), - .height = float(m_swapchain_extent.height), + .width = float(m_context.swapchain_extent().width), + .height = float(m_context.swapchain_extent().height), .minDepth = 0.0f, .maxDepth = 1.0f, }; VkRect2D scissor = { .offset = { 0, 0 }, - .extent = m_swapchain_extent, + .extent = m_context.swapchain_extent(), }; VkPipelineViewportStateCreateInfo viewport_info { @@ -741,7 +343,12 @@ void VulkanTutorial::create_graphic_pipeline() { .pPushConstantRanges = nullptr, }; - if(vkCreatePipelineLayout(m_device, &layout_info, nullptr, &m_pipeline_layout) != VK_SUCCESS) + if(vkCreatePipelineLayout( + m_context.device(), + &layout_info, + nullptr, + &m_pipeline_layout + ) != VK_SUCCESS) throw std::runtime_error("failed to create pipeline layout"); VkGraphicsPipelineCreateInfo pipeline_info { @@ -763,41 +370,65 @@ void VulkanTutorial::create_graphic_pipeline() { .basePipelineIndex = -1, }; - if(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &m_pipeline) != VK_SUCCESS) + if(vkCreateGraphicsPipelines( + m_context.device(), + VK_NULL_HANDLE, + 1, &pipeline_info, + nullptr, + &m_pipeline + ) != VK_SUCCESS) throw std::runtime_error("failed to create graphic pipeline"); } void VulkanTutorial::create_framebuffers() { - m_framebuffers.resize(m_swapchain_image_views.size()); + m_framebuffers.resize(m_context.swapchain_image_count()); for(size_t index = 0; index < m_framebuffers.size(); index += 1) { + VkImageView view = m_context.swapchain_image_view(index); + VkFramebufferCreateInfo framebuffer_info { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = m_render_pass, .attachmentCount = 1, - .pAttachments = &m_swapchain_image_views[index], - .width = m_swapchain_extent.width, - .height = m_swapchain_extent.height, + .pAttachments = &view, + .width = m_context.swapchain_extent().width, + .height = m_context.swapchain_extent().height, .layers = 1, }; - if(vkCreateFramebuffer(m_device, &framebuffer_info, nullptr, &m_framebuffers[index]) != VK_SUCCESS) + if(vkCreateFramebuffer( + m_context.device(), + &framebuffer_info, + nullptr, + &m_framebuffers[index] + ) != VK_SUCCESS) throw std::runtime_error("failed to create framebuffer"); } } void VulkanTutorial::create_command_pool() { + if(m_command_pool != VK_NULL_HANDLE) + return; + VkCommandPoolCreateInfo pool_info { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = 0, - .queueFamilyIndex = m_graphic_queue_family, + .queueFamilyIndex = m_context.queue_family(GRAPHIC_QUEUE), }; - if(vkCreateCommandPool(m_device, &pool_info, nullptr, &m_command_pool) != VK_SUCCESS) + if(vkCreateCommandPool( + m_context.device(), + &pool_info, + nullptr, + &m_command_pool + ) != VK_SUCCESS) throw std::runtime_error("failed to create command pool"); } void VulkanTutorial::create_vertex_buffer() { + if(m_vertex_buffer != VK_NULL_HANDLE) + return; + VkBufferCreateInfo buffer_info { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = sizeof(vertices[0]) * vertices.size(), @@ -805,14 +436,26 @@ void VulkanTutorial::create_vertex_buffer() { .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; - if(vkCreateBuffer(m_device, &buffer_info, nullptr, &m_vertex_buffer) != VK_SUCCESS) + if(vkCreateBuffer( + m_context.device(), + &buffer_info, + nullptr, + &m_vertex_buffer + ) != VK_SUCCESS) throw std::runtime_error("failed to create vertex buffer"); VkMemoryRequirements memory_requirements; - vkGetBufferMemoryRequirements(m_device, m_vertex_buffer, &memory_requirements); + vkGetBufferMemoryRequirements( + m_context.device(), + m_vertex_buffer, + &memory_requirements + ); - const auto memory_type = find_memory(memory_requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + const auto memory_type = find_memory( + memory_requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + ); if(memory_type < 0) throw std::runtime_error("failed to find suitable memory type for vertex buffer"); @@ -822,18 +465,43 @@ void VulkanTutorial::create_vertex_buffer() { .memoryTypeIndex = uint32_t(memory_type), }; - if(vkAllocateMemory(m_device, &alloc_info, nullptr, &m_vertex_buffer_memory) != VK_SUCCESS) + if(vkAllocateMemory( + m_context.device(), + &alloc_info, + nullptr, + &m_vertex_buffer_memory + ) != VK_SUCCESS) throw std::runtime_error("failed to allocate vertex buffer memory"); - vkBindBufferMemory(m_device, m_vertex_buffer, m_vertex_buffer_memory, 0); + vkBindBufferMemory( + m_context.device(), + m_vertex_buffer, + m_vertex_buffer_memory, + 0 + ); void* buffer = nullptr; - vkMapMemory(m_device, m_vertex_buffer_memory, 0, buffer_info.size, 0, &buffer); + vkMapMemory( + m_context.device(), + m_vertex_buffer_memory, + 0, + buffer_info.size, + 0, + &buffer + ); + memcpy(buffer, vertices.data(), size_t(buffer_info.size)); - vkUnmapMemory(m_device, m_vertex_buffer_memory); + + vkUnmapMemory( + m_context.device(), + m_vertex_buffer_memory + ); } void VulkanTutorial::create_command_buffers() { + create_command_pool(); + create_vertex_buffer(); + m_command_buffers.resize(m_framebuffers.size()); VkCommandBufferAllocateInfo allocate_info { @@ -843,7 +511,11 @@ void VulkanTutorial::create_command_buffers() { .commandBufferCount = uint32_t(m_command_buffers.size()), }; - if(vkAllocateCommandBuffers(m_device, &allocate_info, m_command_buffers.data()) != VK_SUCCESS) + if(vkAllocateCommandBuffers( + m_context.device(), + &allocate_info, + m_command_buffers.data() + ) != VK_SUCCESS) throw std::runtime_error("failed to allocate command buffers"); for(size_t index = 0; index < m_command_buffers.size(); index += 1) { @@ -857,7 +529,10 @@ void VulkanTutorial::create_command_buffers() { logger.debug() << "record command buffer " << index << " (" << command_buffer << ")"; - if(vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) + if(vkBeginCommandBuffer( + command_buffer, + &begin_info + ) != VK_SUCCESS) throw std::runtime_error("failed to begin command buffer"); VkClearValue clear_color = { @@ -872,15 +547,23 @@ void VulkanTutorial::create_command_buffers() { .framebuffer = m_framebuffers[index], .renderArea = { .offset = { 0, 0 }, - .extent = m_swapchain_extent, + .extent = m_context.swapchain_extent(), }, .clearValueCount = 1, .pClearValues = &clear_color, }; - vkCmdBeginRenderPass(command_buffer, &pass_info, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass( + command_buffer, + &pass_info, + VK_SUBPASS_CONTENTS_INLINE + ); - vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline); + vkCmdBindPipeline( + command_buffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + m_pipeline + ); VkBuffer vertex_buffers[] = { m_vertex_buffer, @@ -888,9 +571,21 @@ void VulkanTutorial::create_command_buffers() { VkDeviceSize offsets[] = { 0, }; - vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, offsets); + vkCmdBindVertexBuffers( + command_buffer, + 0, + 1, + vertex_buffers, + offsets + ); - vkCmdDraw(command_buffer, uint32_t(vertices.size()), 1, 0, 0); + vkCmdDraw( + command_buffer, + uint32_t(vertices.size()), + 1, + 0, + 0 + ); vkCmdEndRenderPass(command_buffer); @@ -899,80 +594,6 @@ void VulkanTutorial::create_command_buffers() { } } -void VulkanTutorial::create_sync_objects() { - m_image_available_semaphores.resize(MAX_FRAMES_IN_FLIGHT); - m_render_finished_semaphores.resize(MAX_FRAMES_IN_FLIGHT); - m_in_flight_fence.resize(MAX_FRAMES_IN_FLIGHT); - m_images_in_flight.resize(m_swapchain_images.size(), VK_NULL_HANDLE); - - VkSemaphoreCreateInfo semaphore_info { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - - VkFenceCreateInfo fence_info { - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - .flags = VK_FENCE_CREATE_SIGNALED_BIT, - }; - - for(size_t index = 0; index < MAX_FRAMES_IN_FLIGHT; index += 1) { - if(vkCreateSemaphore(m_device, &semaphore_info, nullptr, &m_image_available_semaphores[index]) != VK_SUCCESS) - throw std::runtime_error("failed to create semaphore"); - if(vkCreateSemaphore(m_device, &semaphore_info, nullptr, &m_render_finished_semaphores[index]) != VK_SUCCESS) - throw std::runtime_error("failed to create semaphore"); - if(vkCreateFence(m_device, &fence_info, nullptr, &m_in_flight_fence[index]) != VK_SUCCESS) - throw std::runtime_error("failed to create fence"); - } -} - -void VulkanTutorial::cleanup_swap_chain() { - if (m_command_pool != VK_NULL_HANDLE && m_command_buffers.size() != 0) - vkFreeCommandBuffers(m_device, m_command_pool, uint32_t(m_command_buffers.size()), m_command_buffers.data()); - m_command_buffers.clear(); - - for(VkFramebuffer framebuffer: m_framebuffers) - vkDestroyFramebuffer(m_device, framebuffer, nullptr); - m_framebuffers.clear(); - - if(m_pipeline != VK_NULL_HANDLE) { - vkDestroyPipeline(m_device, m_pipeline, nullptr); - m_pipeline = VK_NULL_HANDLE; - } - - if(m_pipeline_layout != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr); - m_pipeline_layout = VK_NULL_HANDLE; - } - - if(m_render_pass != VK_NULL_HANDLE) { - vkDestroyRenderPass(m_device, m_render_pass, nullptr); - m_render_pass = VK_NULL_HANDLE; - } - - for(VkImageView view: m_swapchain_image_views) - vkDestroyImageView(m_device, view, nullptr); - m_swapchain_image_views.clear(); - - if(m_swapchain) { - vkDestroySwapchainKHR(m_device, m_swapchain, nullptr); - m_swapchain_images.clear(); - m_swapchain = nullptr; - } -} - -void VulkanTutorial::recreate_swap_chain() { - vkDeviceWaitIdle(m_device); - - logger.info() << "recreate swapchain"; - - cleanup_swap_chain(); - - create_swapchain(); - create_render_pass(); - create_graphic_pipeline(); - create_framebuffers(); - create_command_buffers(); -} - VkShaderModule VulkanTutorial::create_shader_module(const std::vector bytecode) { VkShaderModuleCreateInfo shader_info { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, @@ -981,7 +602,12 @@ VkShaderModule VulkanTutorial::create_shader_module(const std::vector byte }; VkShaderModule shader_module = VK_NULL_HANDLE; - if(vkCreateShaderModule(m_device, &shader_info, nullptr, &shader_module) != VK_SUCCESS) + if(vkCreateShaderModule( + m_context.device(), + &shader_info, + nullptr, + &shader_module + ) != VK_SUCCESS) throw std::runtime_error("failed to create shader module"); return shader_module; @@ -1000,7 +626,10 @@ VkShaderModule VulkanTutorial::create_shader_module_from_file(const char* path) int32_t VulkanTutorial::find_memory(uint32_t type_filter, VkMemoryPropertyFlags properties) { VkPhysicalDeviceMemoryProperties memory_properties; - vkGetPhysicalDeviceMemoryProperties(m_physical_device, &memory_properties); + vkGetPhysicalDeviceMemoryProperties( + m_context.physical_device(), + &memory_properties + ); for(uint32_t type_index = 0; type_index < memory_properties.memoryTypeCount; type_index += 1) { if(((1 << type_index) & type_filter) && @@ -1010,72 +639,3 @@ int32_t VulkanTutorial::find_memory(uint32_t type_filter, VkMemoryPropertyFlags return -1; } - -std::vector VulkanTutorial::get_physical_devices() const { - uint32_t devices_count = 0; - vkEnumeratePhysicalDevices(m_instance, &devices_count, nullptr); - - std::vector physical_devices(devices_count); - vkEnumeratePhysicalDevices(m_instance, &devices_count, physical_devices.data()); - - return physical_devices; -} - -std::vector VulkanTutorial::get_queue_families(VkPhysicalDevice physical_device) const { - uint32_t queue_families_count = 0; - vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, nullptr); - - std::vector queue_families(queue_families_count); - vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, queue_families.data()); - - return queue_families; -} - -std::vector VulkanTutorial::get_device_extensions(VkPhysicalDevice physical_device) const { - uint32_t extensions_count = 0; - vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, nullptr); - - std::vector extensions(extensions_count); - vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, extensions.data()); - - return extensions; -} - -std::vector VulkanTutorial::get_surface_formats(VkPhysicalDevice physical_device) const { - uint32_t surface_formats_count = 0; - vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, nullptr); - - std::vector surface_formats(surface_formats_count); - vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, surface_formats.data()); - - return surface_formats; -} - -std::vector VulkanTutorial::get_present_modes(VkPhysicalDevice physical_device) const { - uint32_t present_modes_count = 0; - vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, nullptr); - - std::vector present_modes(present_modes_count); - vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, present_modes.data()); - - return present_modes; -} - -void VulkanTutorial::initialize_extension_functions() { - uint32_t errors_count = 0; - - #define GET_PROC_ADDR(func_name) \ - vke ## func_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \ - m_instance, "vk" #func_name); \ - if(vke ## func_name == nullptr) \ - { \ - logger.error() << "failed to load extension function 'vk" #func_name "'"; \ - errors_count += 1; \ - } - - GET_PROC_ADDR(CreateDebugUtilsMessengerEXT) - GET_PROC_ADDR(DestroyDebugUtilsMessengerEXT) - - if(errors_count != 0) - throw std::runtime_error("failed to load extensions"); -} diff --git a/src/VulkanTutorial.h b/src/VulkanTutorial.h index f23603f..e033555 100644 --- a/src/VulkanTutorial.h +++ b/src/VulkanTutorial.h @@ -1,21 +1,13 @@ #pragma once +#include "VulkanContext.h" + #include #include #include #include -#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG) -#define VKEXPE_VALIDATION true -#else -#define VKEXPE_VALIDATION false -#endif - -extern PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT; -extern PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT; - - struct Vertex { Eigen::Vector2f position; Eigen::Vector3f color; @@ -26,6 +18,11 @@ struct Vertex { class VulkanTutorial { +public: + enum QueueIndex { + GRAPHIC_QUEUE, + }; + public: VulkanTutorial(); VulkanTutorial(const VulkanTutorial&) = delete; @@ -39,59 +36,27 @@ public: void draw_frame(); void invalidate_swapchain(); - static std::vector available_extensions(); - static std::vector available_layers(); - - static VKAPI_ATTR VkBool32 VKAPI_CALL print_debug_message( - VkDebugUtilsMessageSeverityFlagBitsEXT severity, - VkDebugUtilsMessageTypeFlagsEXT type, - const VkDebugUtilsMessengerCallbackDataEXT* data, - void* user_data - ); - private: - void create_instance(); - void create_surface(); - void find_physical_device(); - void create_device(); - void create_swapchain(); + void create_swapchain_objects(uint32_t image_count); + void destroy_swapchain_objects(); + void create_render_pass(); void create_graphic_pipeline(); void create_framebuffers(); void create_command_pool(); void create_vertex_buffer(); void create_command_buffers(); - void create_sync_objects(); - - void cleanup_swap_chain(); - void recreate_swap_chain(); VkShaderModule create_shader_module(const std::vector bytecode); VkShaderModule create_shader_module_from_file(const char* path); int32_t find_memory(uint32_t type_filter, VkMemoryPropertyFlags properties); - std::vector get_physical_devices() const; - std::vector get_queue_families(VkPhysicalDevice physical_device) const; - std::vector get_device_extensions(VkPhysicalDevice physical_device) const; - std::vector get_surface_formats(VkPhysicalDevice physical_device) const; - std::vector get_present_modes(VkPhysicalDevice physical_device) const; - - void initialize_extension_functions(); - private: - SDL_Window* m_window = nullptr; - - VkInstance m_instance = nullptr; - VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE; - VkSurfaceKHR m_surface; - VkPhysicalDevice m_physical_device = nullptr; - VkDevice m_device = nullptr; + VulkanContext m_context; VkQueue m_graphic_queue = nullptr; VkQueue m_presentation_queue = nullptr; - VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; - std::vector m_swapchain_images; - std::vector m_swapchain_image_views; + VkRenderPass m_render_pass = VK_NULL_HANDLE; VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE; VkPipeline m_pipeline = VK_NULL_HANDLE; @@ -100,22 +65,5 @@ private: VkBuffer m_vertex_buffer = VK_NULL_HANDLE; VkDeviceMemory m_vertex_buffer_memory = VK_NULL_HANDLE; std::vector m_command_buffers; - std::vector m_image_available_semaphores; - std::vector m_render_finished_semaphores; - std::vector m_in_flight_fence; - std::vector m_images_in_flight; - - uint32_t m_graphic_queue_family = -1; - uint32_t m_presentation_queue_family = -1; - VkSurfaceFormatKHR m_swapchain_format; - VkPresentModeKHR m_present_mode; - VkExtent2D m_swapchain_extent; - - size_t m_frame_index = 0; - bool m_invalid_swapchain = true; - - VkDebugUtilsMessengerCreateInfoEXT m_debug_info; - bool m_enableValidation = VKEXPE_VALIDATION; - - static constexpr size_t MAX_FRAMES_IN_FLIGHT = 2; + std::vector m_render_done; }; diff --git a/src/utils.cpp b/src/utils.cpp index 1f50ecd..587ec9f 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,8 +1,15 @@ #include "utils.h" +#include #include +Uuid make_uuid(uint8_t const* bytes) { + Uuid uuid; + std::memcpy(uuid.data(), bytes, UUID_SIZE); + return uuid; +} + std::vector read_binary_file(const char* path) { std::ifstream istream(path, std::ios_base::ate | std::ios_base::binary); if(!istream.is_open()) diff --git a/src/utils.h b/src/utils.h index 0141bdb..04d6b5c 100644 --- a/src/utils.h +++ b/src/utils.h @@ -3,6 +3,12 @@ #include #include #include +#include + + +constexpr size_t UUID_SIZE = 16; +using Uuid = std::array; +Uuid make_uuid(uint8_t const* bytes); template