#include #include #include #include #include #include #include #include #include namespace Vulkan { ContextSettings::ContextSettings() { } ContextSettings::~ContextSettings() { } bool ContextSettings::debug() const { return m_debug; } ContextSettings& ContextSettings::with_debug(bool enabled) { m_debug = enabled; return *this; } const std::optional& ContextSettings::physical_device() const { return m_physical_device; } ContextSettings& ContextSettings::with_physical_device(Uuid uuid) { m_physical_device = uuid; return *this; } const std::vector& ContextSettings::queues() const { return m_queues; } ContextSettings& ContextSettings::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* ContextSettings::window() const { return m_window; } ContextSettings& ContextSettings::with_window(SDL_Window* window) { m_window = window; return *this; } ////////////////////////////////////////////////////////////////////////////// // Context Context::Context() { } Context::~Context() { shutdown(); } VkInstance Context::instance() { return m_instance; } VkPhysicalDevice Context::physical_device() { return m_physical_device; } VkDevice Context::device() { return m_device; } uint32_t Context::queue_family(size_t queue_index) const { return m_queue_families[queue_index]; } uint32_t Context::presentation_queue_family() const { return m_presentation_queue_family; } VkQueue Context::queue(size_t queue_index) { return m_queues[queue_index]; } VkQueue Context::presentation_queue() { return m_presentation_queue; } SDL_Window* Context::window() { return m_window; } VkSurfaceKHR Context::surface() { return m_surface; } VkSurfaceFormatKHR Context::surface_format() const { return m_surface_format; } VkPresentModeKHR Context::present_mode() const { return m_present_mode; } void Context::initialize(const ContextSettings& settings) { m_window = settings.window(); create_instance(settings); create_surface(settings); choose_physical_device(settings); create_device(settings); create_internal_objects(); m_allocator.reset(new Allocator(this)); } void Context::shutdown() { if(!m_instance) return; vkDeviceWaitIdle(m_device); for(auto& callback: m_context_destruction_callbacks) callback(); m_allocator->free_all_pages(); m_allocator.reset(); destroy_fence(m_transfer_fence); destroy_command_pool(m_transfer_command_pool); destroy_device(m_device); destroy_surface(m_surface); destroy_debug_messenger(m_debug_messenger); destroy_instance(m_instance); } void Context::register_context_destruction_callback(ContextDestructionCallback callback) { m_context_destruction_callbacks.emplace_back(std::move(callback)); } std::vector Context::available_extensions() { uint32_t count; vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); std::vector extensions(count); vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data()); return extensions; } std::vector Context::available_layers() { uint32_t count; vkEnumerateInstanceLayerProperties(&count, nullptr); std::vector layers(count); vkEnumerateInstanceLayerProperties(&count, layers.data()); return layers; } std::vector Context::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 Context::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 Context::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 Context::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 Context::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 Context::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 Context::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(setDebugUtilsObjectName(m_device, &name_info) != VK_SUCCESS) throw std::runtime_error("failed to set debug name"); } void Context::set_object_name(VkObjectType type, uint64_t object, const std::string& name) { set_object_name(type, object, name.c_str()); } VkShaderModule Context::create_shader_module(const std::vector bytecode) { VkShaderModuleCreateInfo shader_info { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = bytecode.size(), .pCode = reinterpret_cast(bytecode.data()), }; VkShaderModule shader_module = VK_NULL_HANDLE; if(vkCreateShaderModule( m_device, &shader_info, nullptr, &shader_module ) != VK_SUCCESS) throw std::runtime_error("failed to create shader module"); return shader_module; } VkShaderModule Context::create_shader_module_from_file(const char* path) { auto const bytecode = read_binary_file(path); try { return create_shader_module(bytecode); } catch(std::exception err) { throw std::runtime_error(cat("failed to create shader '", path, "':\n ", err.what())); } return VK_NULL_HANDLE; } void Context::copy_buffer(VkBuffer dst, VkBuffer src, uint32_t dst_queue_family, VkDeviceSize size) { VkCommandBufferAllocateInfo alloc_info { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = m_transfer_command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VkCommandBuffer command_buffer; vkAllocateCommandBuffers(m_device, &alloc_info, &command_buffer); auto const command_buffer_guard = make_guard([this, command_buffer]() { vkFreeCommandBuffers(m_device, m_transfer_command_pool, 1, &command_buffer); }); VkCommandBufferBeginInfo begin_info { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(command_buffer, &begin_info); VkBufferCopy region { .srcOffset = 0, .dstOffset = 0, .size = size, }; vkCmdCopyBuffer(command_buffer, src, dst, 1, ®ion); VkBufferMemoryBarrier buffer_barrier { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .srcQueueFamilyIndex = queue_family(VK_QUEUE_TRANSFER_BIT), .dstQueueFamilyIndex = dst_queue_family, .buffer = dst, .offset = 0, .size = size, }; vkCmdPipelineBarrier( command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 1, &buffer_barrier, 0, nullptr ); vkEndCommandBuffer(command_buffer); vkResetFences(m_device, 1, &m_transfer_fence); VkSubmitInfo submit_info { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &command_buffer, }; vkQueueSubmit( queue(m_transfer_queue_index), 1, &submit_info, m_transfer_fence ); vkWaitForFences(m_device, 1, &m_transfer_fence, VK_TRUE, UINT64_MAX); } void Context::destroy_instance(VkInstance& instance) { if(instance == nullptr) return; vkDestroyInstance(instance, nullptr); instance = nullptr; } void Context::destroy_debug_messenger(VkDebugUtilsMessengerEXT& debug_messenger) { if(debug_messenger == VK_NULL_HANDLE) return; destroyDebugUtilsMessenger(m_instance, debug_messenger, nullptr); debug_messenger = VK_NULL_HANDLE; } void Context::destroy_surface(VkSurfaceKHR& surface) { if(surface == VK_NULL_HANDLE) return; vkDestroySurfaceKHR(m_instance, surface, nullptr); surface = VK_NULL_HANDLE; } void Context::destroy_device(VkDevice& device) { if(device == nullptr) return; vkDestroyDevice(device, nullptr); device = nullptr; } void Context::destroy_swapchain(VkSwapchainKHR& swapchain) { if(swapchain == VK_NULL_HANDLE) return; vkDestroySwapchainKHR(m_device, swapchain, nullptr); swapchain = VK_NULL_HANDLE; } void Context::destroy_image(VkImage& image) { if(image == VK_NULL_HANDLE) return; vkDestroyImage(m_device, image, nullptr); image = VK_NULL_HANDLE; } void Context::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 Context::destroy_framebuffer(VkFramebuffer& framebuffer) { if(framebuffer == VK_NULL_HANDLE) return; vkDestroyFramebuffer(m_device, framebuffer, nullptr); framebuffer = VK_NULL_HANDLE; } void Context::destroy_buffer(VkBuffer& buffer) { if(buffer == VK_NULL_HANDLE) return; vkDestroyBuffer(m_device, buffer, nullptr); buffer = VK_NULL_HANDLE; } void Context::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 Context::destroy_descriptor_pool(VkDescriptorPool& descriptor_pool) { if(descriptor_pool == VK_NULL_HANDLE) return; vkDestroyDescriptorPool(m_device, descriptor_pool, nullptr); descriptor_pool = VK_NULL_HANDLE; } void Context::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 Context::destroy_descriptor_set_layout(VkDescriptorSetLayout& descriptor_set_layout) { if(descriptor_set_layout == VK_NULL_HANDLE) return; vkDestroyDescriptorSetLayout(m_device, descriptor_set_layout, nullptr); descriptor_set_layout = VK_NULL_HANDLE; } void Context::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 Context::destroy_pipeline(VkPipeline& pipeline) { if(pipeline == VK_NULL_HANDLE) return; vkDestroyPipeline(m_device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; } void Context::destroy_semaphore(VkSemaphore& semaphore) { if(semaphore == VK_NULL_HANDLE) return; vkDestroySemaphore(m_device, semaphore, nullptr); semaphore = VK_NULL_HANDLE; } void Context::destroy_fence(VkFence& fence) { if(fence == VK_NULL_HANDLE) return; vkDestroyFence(m_device, fence, nullptr); fence = VK_NULL_HANDLE; } void Context::free_memory(VkDeviceMemory& memory) { if(memory == VK_NULL_HANDLE) return; vkFreeMemory(m_device, memory, nullptr); memory = VK_NULL_HANDLE; } void Context::create_instance(const ContextSettings& 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 = Context::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()) { createDebugUtilsMessenger( m_instance, &debug_info, nullptr, &m_debug_messenger); } } void Context::create_surface(const ContextSettings& settings) { if(!SDL_Vulkan_CreateSurface(m_window, m_instance, &m_surface)) throw std::runtime_error("failed to create surface"); } void Context::choose_physical_device(const ContextSettings& 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) { auto maybe_properties = select_physical_device(physical_device, settings); if(maybe_properties) { m_physical_device = physical_device; m_physical_device_properties = *maybe_properties; vkGetPhysicalDeviceMemoryProperties(m_physical_device, &m_memory_properties); break; } } if(!m_physical_device) throw std::runtime_error("failed to find suitable physical device"); logger.info() << "use suitable device: " << m_physical_device_properties.deviceName; logger.debug() << "swapchain format: " << m_surface_format.format; logger.debug() << "present mode: " << m_present_mode; } std::optional Context::select_physical_device( VkPhysicalDevice physical_device, const ContextSettings& settings ) { VkPhysicalDeviceProperties device_properties; vkGetPhysicalDeviceProperties(physical_device, &device_properties); VkPhysicalDeviceFeatures available_features; vkGetPhysicalDeviceFeatures(physical_device, &available_features); if (!available_features.geometryShader) return std::nullopt; m_transfer_queue_index = settings.queues().size(); m_queue_families.assign(settings.queues().size() + 1, 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; } if(m_queue_families[m_transfer_queue_index] == INVALID_QUEUE_FAMILY && queue_family.queueFlags == VK_QUEUE_TRANSFER_BIT) m_queue_families[m_transfer_queue_index] = queue_family_index; queue_family_index += 1; } for(auto const queue_family: m_queue_families) { if(queue_family == INVALID_QUEUE_FAMILY) return std::nullopt; } 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 std::nullopt; auto const surface_formats = this->surface_formats(physical_device); if(surface_formats.empty()) return std::nullopt; auto const present_modes = this->present_modes(physical_device); if(present_modes.empty()) return std::nullopt; 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; // m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; } return device_properties; } void Context::create_device(const ContextSettings& 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 { .geometryShader = true, }; 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 Context::create_internal_objects() { VkCommandPoolCreateInfo pool_info { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = 0, .queueFamilyIndex = queue_family(m_transfer_queue_index), }; if(vkCreateCommandPool( m_device, &pool_info, nullptr, &m_transfer_command_pool ) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool"); } VkFenceCreateInfo fence_info { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, }; if(vkCreateFence(m_device, &fence_info, nullptr, &m_transfer_fence) != VK_SUCCESS) throw std::runtime_error("failed to create fence"); } void Context::initialize_extension_functions() { uint32_t errors_count = 0; #define GET_PROC_ADDR(ptr_name, func_name) \ ptr_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \ m_instance, "vk" #func_name); \ if(ptr_name == nullptr) \ { \ logger.error() << "failed to load extension function 'vk" #func_name "'"; \ errors_count += 1; \ } GET_PROC_ADDR(createDebugUtilsMessenger, CreateDebugUtilsMessengerEXT) GET_PROC_ADDR(destroyDebugUtilsMessenger, DestroyDebugUtilsMessengerEXT) GET_PROC_ADDR(setDebugUtilsObjectName, SetDebugUtilsObjectNameEXT) if(errors_count != 0) throw std::runtime_error("failed to load extensions"); } VKAPI_ATTR VkBool32 VKAPI_CALL Context::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 && data->messageIdNumber != 2094043421 // wrong swapchain extent ) abort(); return VK_FALSE; } }