// Copyright 2025 Steven Le Rouzic // // SPDX-License-Identifier: BSD-3-Clause #include "hk21/game/gpu.hpp" #include #include #include #include #include #include #include #include "hk21/vulkan_loader/api.hpp" #define VK_ALLOCATOR nullptr [[maybe_unused]] static void AslFormat(asl::Formatter& formatter, VkResult res) { switch (res) // NOLINT { case VK_SUCCESS: formatter.write("VK_SUCCESS"); break; case VK_NOT_READY: formatter.write("VK_NOT_READY"); break; case VK_TIMEOUT: formatter.write("VK_TIMEOUT"); break; case VK_EVENT_SET: formatter.write("VK_EVENT_SET"); break; case VK_EVENT_RESET: formatter.write("VK_EVENT_RESET"); break; case VK_INCOMPLETE: formatter.write("VK_INCOMPLETE"); break; case VK_ERROR_OUT_OF_HOST_MEMORY: formatter.write("VK_ERROR_OUT_OF_HOST_MEMORY"); break; case VK_ERROR_OUT_OF_DEVICE_MEMORY: formatter.write("VK_ERROR_OUT_OF_DEVICE_MEMORY"); break; case VK_ERROR_INITIALIZATION_FAILED: formatter.write("VK_ERROR_INITIALIZATION_FAILED"); break; case VK_ERROR_DEVICE_LOST: formatter.write("VK_ERROR_DEVICE_LOST"); break; case VK_ERROR_MEMORY_MAP_FAILED: formatter.write("VK_ERROR_MEMORY_MAP_FAILED"); break; case VK_ERROR_LAYER_NOT_PRESENT: formatter.write("VK_ERROR_LAYER_NOT_PRESENT"); break; case VK_ERROR_EXTENSION_NOT_PRESENT: formatter.write("VK_ERROR_EXTENSION_NOT_PRESENT"); break; case VK_ERROR_FEATURE_NOT_PRESENT: formatter.write("VK_ERROR_FEATURE_NOT_PRESENT"); break; case VK_ERROR_INCOMPATIBLE_DRIVER: formatter.write("VK_ERROR_INCOMPATIBLE_DRIVER"); break; case VK_ERROR_TOO_MANY_OBJECTS: formatter.write("VK_ERROR_TOO_MANY_OBJECTS"); break; case VK_ERROR_FORMAT_NOT_SUPPORTED: formatter.write("VK_ERROR_FORMAT_NOT_SUPPORTED"); break; case VK_ERROR_FRAGMENTED_POOL: formatter.write("VK_ERROR_FRAGMENTED_POOL"); break; case VK_ERROR_UNKNOWN: formatter.write("VK_ERROR_UNKNOWN"); break; case VK_ERROR_OUT_OF_POOL_MEMORY: formatter.write("VK_ERROR_OUT_OF_POOL_MEMORY"); break; case VK_ERROR_INVALID_EXTERNAL_HANDLE: formatter.write("VK_ERROR_INVALID_EXTERNAL_HANDLE"); break; case VK_ERROR_FRAGMENTATION: formatter.write("VK_ERROR_FRAGMENTATION"); break; case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: formatter.write("VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"); break; case VK_PIPELINE_COMPILE_REQUIRED: formatter.write("VK_PIPELINE_COMPILE_REQUIRED"); break; case VK_ERROR_NOT_PERMITTED: formatter.write("VK_ERROR_NOT_PERMITTED"); break; case VK_ERROR_SURFACE_LOST_KHR: formatter.write("VK_ERROR_SURFACE_LOST_KHR"); break; case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: formatter.write("VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"); break; case VK_SUBOPTIMAL_KHR: formatter.write("VK_SUBOPTIMAL_KHR"); break; case VK_ERROR_OUT_OF_DATE_KHR: formatter.write("VK_ERROR_OUT_OF_DATE_KHR"); break; case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: formatter.write("VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"); break; case VK_ERROR_VALIDATION_FAILED_EXT: formatter.write("VK_ERROR_VALIDATION_FAILED_EXT"); break; case VK_ERROR_INVALID_SHADER_NV: formatter.write("VK_ERROR_INVALID_SHADER_NV"); break; case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: formatter.write("VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"); break; case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: formatter.write("VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"); break; case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: formatter.write("VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"); break; case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: formatter.write("VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"); break; case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: formatter.write("VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"); break; case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: formatter.write("VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"); break; case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: formatter.write("VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"); break; case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: formatter.write("VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"); break; case VK_THREAD_IDLE_KHR: formatter.write("VK_THREAD_IDLE_KHR"); break; case VK_THREAD_DONE_KHR: formatter.write("VK_THREAD_DONE_KHR"); break; case VK_OPERATION_DEFERRED_KHR: formatter.write("VK_OPERATION_DEFERRED_KHR"); break; case VK_OPERATION_NOT_DEFERRED_KHR: formatter.write("VK_OPERATION_NOT_DEFERRED_KHR"); break; case VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR: formatter.write("VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR"); break; case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: formatter.write("VK_ERROR_COMPRESSION_EXHAUSTED_EXT"); break; case VK_INCOMPATIBLE_SHADER_BINARY_EXT: formatter.write("VK_INCOMPATIBLE_SHADER_BINARY_EXT"); break; case VK_PIPELINE_BINARY_MISSING_KHR: formatter.write("VK_PIPELINE_BINARY_MISSING_KHR"); break; case VK_ERROR_NOT_ENOUGH_SPACE_KHR: formatter.write("VK_ERROR_NOT_ENOUGH_SPACE_KHR"); break; default: formatter.write("Unknown Vulkan error"); break; } } namespace gpu { static uint32_t kTargetVersionMajor = 1; static uint32_t kTargetVersionMinor = 3; static uint32_t kTargetVersion = VK_MAKE_API_VERSION(0, kTargetVersionMajor, kTargetVersionMinor, 0); // NOLINT static asl::status_or create_instance() { asl::buffer instance_extensions; asl::buffer layers; { uint32_t count = 0; const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&count); for (uint32_t i = 0; i < count; ++i) { instance_extensions.push(extensions[i]); // NOLINT(*-pointer-arithmetic) } } layers.push("VK_LAYER_KHRONOS_validation"); layers.push("VK_LAYER_LUNARG_monitor"); instance_extensions.push(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); auto vkGetInstanceProcAddr = asl::bit_cast(SDL_Vulkan_GetVkGetInstanceProcAddr()); auto status = vulkan_loader::load_global(vkGetInstanceProcAddr); ASL_TRY(status); uint32_t version{}; vkEnumerateInstanceVersion(&version); uint32_t vk_major = VK_API_VERSION_MAJOR(version); // NOLINT uint32_t vk_minor = VK_API_VERSION_MINOR(version); // NOLINT if (vk_major != kTargetVersionMajor || vk_minor < kTargetVersionMinor) { return asl::runtime_error("Incompatible Vulkan version: {}.{}", vk_major, vk_minor); } VkApplicationInfo app_info{ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = "HK-21", .applicationVersion = 0, .pEngineName = "Custom", .engineVersion = 0, .apiVersion = kTargetVersion, }; VkInstanceCreateInfo instance_create_info{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, .flags = 0, .pApplicationInfo = &app_info, .enabledLayerCount = static_cast(layers.size()), .ppEnabledLayerNames = layers.data(), .enabledExtensionCount = static_cast(instance_extensions.size()), .ppEnabledExtensionNames = instance_extensions.data(), }; VkInstance instance{}; VkResult res = vkCreateInstance(&instance_create_info, VK_ALLOCATOR, &instance); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't create Vulkan instance: {}", res); } status = vulkan_loader::load_instance(vkGetInstanceProcAddr, instance); ASL_TRY(status); ASL_LOG_INFO("Vulkan instance created"); return instance; } static asl::status_or create_surface(SDL_Window* window, VkInstance instance) { VkSurfaceKHR surface{}; if (!SDL_Vulkan_CreateSurface(window, instance, VK_ALLOCATOR, &surface)) { return asl::runtime_error("Couldn't create Vulkan surface"); } return surface; } struct PhysicalDeviceInfo { VkPhysicalDevice physical_device; uint32_t queue_family_index; }; static asl::status_or find_physical_device(VkInstance instance) { uint32_t count{}; asl::buffer physical_devices; asl::buffer queue_family_prps; vkEnumeratePhysicalDevices(instance, &count, nullptr); physical_devices.resize_zero(count); VkResult res = vkEnumeratePhysicalDevices(instance, &count, physical_devices.data()); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't enumerate Vulkan physical devices: {}", res); } for (auto* physical_device: physical_devices) { VkPhysicalDeviceProperties prps; vkGetPhysicalDeviceProperties(physical_device, &prps); auto name = asl::string_view::from_zstr(prps.deviceName); if (prps.apiVersion < kTargetVersion) { ASL_LOG_INFO("Device {}: invalid supported Vulkan API version", name); continue; } uint32_t queue_family_count{}; vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); queue_family_prps.resize_zero(queue_family_count); vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_family_prps.data()); static constexpr VkQueueFlags kWantedQueueFlags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT; for (uint32_t queue_family_index = 0; queue_family_index < queue_family_count; ++queue_family_index) { if ((queue_family_prps[queue_family_index].queueFlags & kWantedQueueFlags) != kWantedQueueFlags) { continue; } if (!SDL_Vulkan_GetPresentationSupport(instance, physical_device, queue_family_index)) { continue; } ASL_LOG_INFO("Chosing device \"{}\" with queue family {}", name, queue_family_index); return PhysicalDeviceInfo{ physical_device, queue_family_index }; } ASL_LOG_INFO("Device {}: no valid queue found", name); } return asl::runtime_error("Couldn't find a valid Vulkan physical device"); } static asl::status_or create_device(VkPhysicalDevice physical_device, uint32_t queue_family_index) { asl::buffer device_extensions; device_extensions.push(VK_KHR_SWAPCHAIN_EXTENSION_NAME); const float queue_priority = 1.0F; VkDeviceQueueCreateInfo queue_create_info{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .flags = 0, .queueFamilyIndex = queue_family_index, .queueCount = 1, .pQueuePriorities = &queue_priority, }; VkDeviceCreateInfo create_info{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = nullptr, .flags = 0, .queueCreateInfoCount = 1, .pQueueCreateInfos = &queue_create_info, .enabledLayerCount = 0, .ppEnabledLayerNames = nullptr, .enabledExtensionCount = static_cast(device_extensions.size()), .ppEnabledExtensionNames = device_extensions.data(), .pEnabledFeatures = nullptr, }; VkDevice device{}; VkResult res = vkCreateDevice(physical_device, &create_info, VK_ALLOCATOR, &device); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't create Vulkan device: {}", res); } ASL_LOG_INFO("Vulkan device created"); auto status = vulkan_loader::load_device(device); ASL_TRY(status); return device; } struct FrameResources : asl::intrusive_list_node { VkFence complete_fence = VK_NULL_HANDLE; VkCommandPool command_pool = VK_NULL_HANDLE; }; class GpuImpl : public Gpu { bool m_destroyed{}; VkInstance m_instance; VkPhysicalDevice m_physical_device; VkSurfaceKHR m_surface; uint32_t m_queue_family_index; VkDevice m_device; VkQueue m_queue; asl::option m_swapchain; asl::buffer m_swapchain_images; VkSemaphore m_swapchain_image_acquire_semaphore = VK_NULL_HANDLE; VkSemaphore m_queue_complete_semaphore = VK_NULL_HANDLE; asl::GlobalHeap m_allocator; // @Todo Make this configurable asl::IntrusiveList m_in_flight_frames; public: GpuImpl( VkInstance instance, VkPhysicalDevice physical_device, VkSurfaceKHR surface, uint32_t queue_family_index, VkDevice device, VkQueue queue) : m_instance{instance} , m_physical_device{physical_device} , m_surface{surface} , m_queue_family_index{queue_family_index} , m_device{device} , m_queue{queue} , m_swapchain_image_acquire_semaphore{create_semaphore()} , m_queue_complete_semaphore{create_semaphore()} { } ASL_DELETE_COPY_MOVE(GpuImpl); ~GpuImpl() override { ASL_ASSERT(m_destroyed); } void destroy() override { ASL_ASSERT(!m_destroyed); m_destroyed = true; vkDeviceWaitIdle(m_device); recycle_resources(); destroy_semaphore(m_swapchain_image_acquire_semaphore); destroy_semaphore(m_queue_complete_semaphore); if (m_swapchain.has_value()) { vkDestroySwapchainKHR(m_device, m_swapchain.value(), VK_ALLOCATOR); } vkDestroyDevice(m_device, VK_ALLOCATOR); vkDestroySurfaceKHR(m_instance, m_surface, VK_ALLOCATOR); vkDestroyInstance(m_instance, VK_ALLOCATOR); } asl::status create_swapchain(uint32_t width, uint32_t height) { uint32_t count{}; VkSurfaceCapabilitiesKHR caps{}; VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &caps); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't retrieve Vulkan surface capabitilies: {}", res); } asl::buffer formats; vkGetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, m_surface, &count, nullptr); formats.resize_zero(count); res = vkGetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, m_surface, &count, formats.data()); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't retrieve Vulkan surface formats: {}", res); } asl::buffer present_modes; vkGetPhysicalDeviceSurfacePresentModesKHR(m_physical_device, m_surface, &count, nullptr); present_modes.resize_zero(count); res = vkGetPhysicalDeviceSurfacePresentModesKHR(m_physical_device, m_surface, &count, present_modes.data()); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't retrieve Vulkan surface present modes: {}", res); } asl::option format; for (const auto& f: formats) { if (f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR && (f.format == VK_FORMAT_B8G8R8_UNORM || f.format == VK_FORMAT_B8G8R8A8_UNORM || f.format == VK_FORMAT_R8G8B8_UNORM || f.format == VK_FORMAT_R8G8B8A8_UNORM)) { format = f; break; } } if (!format.has_value()) { return asl::runtime_error("Couldn't find suitable Vulkan surface format"); } VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; for (const auto& p: present_modes) { if (p == VK_PRESENT_MODE_MAILBOX_KHR) { present_mode = p; break; } } VkSwapchainCreateInfoKHR create_info{ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, .flags = 0, .surface = m_surface, .minImageCount = asl::min(asl::max(2U, caps.minImageCount), caps.maxImageCount), .imageFormat = format.value().format, .imageColorSpace = format.value().colorSpace, .imageExtent = { .width = asl::min(asl::max(width, caps.minImageExtent.width), caps.maxImageExtent.width), .height = asl::min(asl::max(height, caps.minImageExtent.height), caps.maxImageExtent.height), }, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 1, .pQueueFamilyIndices = &m_queue_family_index, .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = present_mode, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; VkSwapchainKHR swapchain{}; res = vkCreateSwapchainKHR(m_device, &create_info, VK_ALLOCATOR, &swapchain); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't create Vulkan swapchain: {}", res); } m_swapchain = swapchain; vkGetSwapchainImagesKHR(m_device, m_swapchain.value(), &count, nullptr); m_swapchain_images.resize_zero(count); res = vkGetSwapchainImagesKHR(m_device, m_swapchain.value(), &count, m_swapchain_images.data()); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't retrieve Vulkan swapchain images: {}", res); } ASL_LOG_INFO("Vulkan swapchain created ({}x{} with {} images)", create_info.imageExtent.width, create_info.imageExtent.height, m_swapchain_images.size()); return asl::ok(); } VkSemaphore create_semaphore() { static constexpr VkSemaphoreCreateInfo semaphore_create_info{ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = nullptr, .flags = 0, }; VkSemaphore semaphore{}; vkCreateSemaphore(m_device, &semaphore_create_info, VK_ALLOCATOR, &semaphore); return semaphore; } void destroy_semaphore(VkSemaphore semaphore) { vkDestroySemaphore(m_device, semaphore, VK_ALLOCATOR); } VkFence create_fence() { static constexpr VkFenceCreateInfo fence_create_info{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0, }; VkFence fence{}; vkCreateFence(m_device, &fence_create_info, VK_ALLOCATOR, &fence); return fence; } void destroy_fence(VkFence fence) { vkDestroyFence(m_device, fence, VK_ALLOCATOR); } asl::status frame_opt() { uint32_t image_index{}; VkResult res = vkAcquireNextImageKHR( m_device, m_swapchain.value(), 0xffff'ffff'ffff'ffffLLU, m_swapchain_image_acquire_semaphore, VK_NULL_HANDLE, &image_index); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't acquire swapchain image: {}", res); } VkCommandPoolCreateInfo command_pool_create_info{ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, .flags = 0, .queueFamilyIndex = m_queue_family_index, }; VkCommandPool command_pool{}; res = vkCreateCommandPool(m_device, &command_pool_create_info, VK_ALLOCATOR, &command_pool); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't create command pool: {}", res); } VkCommandBufferAllocateInfo alloc_info{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, .commandPool = command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VkCommandBuffer command_buffer{}; res = vkAllocateCommandBuffers(m_device, &alloc_info, &command_buffer); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't allocate command buffer: {}", res); } VkCommandBufferBeginInfo begin_info{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = nullptr, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, .pInheritanceInfo = nullptr, }; res = vkBeginCommandBuffer(command_buffer, &begin_info); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't begin command buffer: {}", res); } VkImageMemoryBarrier barrier1{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = m_swapchain_images[image_index], .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier1); VkClearColorValue clear_color{ .float32 = { 0.0F, 0.137F, 0.4F, 1.0F }, }; VkImageSubresourceRange range{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }; vkCmdClearColorImage(command_buffer, m_swapchain_images[image_index], VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range); VkImageMemoryBarrier barrier2{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = m_swapchain_images[image_index], .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier2); res = vkEndCommandBuffer(command_buffer); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't end command buffer: {}", res); } VkPipelineStageFlags wait_dst_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkFence fence = create_fence(); VkSubmitInfo submit_info{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = nullptr, .waitSemaphoreCount = 1, .pWaitSemaphores = &m_swapchain_image_acquire_semaphore, .pWaitDstStageMask = &wait_dst_stage_mask, .commandBufferCount = 1, .pCommandBuffers = &command_buffer, .signalSemaphoreCount = 1, .pSignalSemaphores = &m_queue_complete_semaphore, }; res = vkQueueSubmit(m_queue, 1, &submit_info, fence); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't submit queue: {}", res); } VkPresentInfoKHR present_info{ .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = nullptr, .waitSemaphoreCount = 1, .pWaitSemaphores = &m_queue_complete_semaphore, .swapchainCount = 1, .pSwapchains = &m_swapchain.value(), .pImageIndices = &image_index, .pResults = nullptr, }; res = vkQueuePresentKHR(m_queue, &present_info); if (res != VK_SUCCESS) { return asl::runtime_error("Couldn't present queue: {}", res); } auto* frame_resources = asl::alloc_new(m_allocator); frame_resources->command_pool = command_pool; frame_resources->complete_fence = fence; m_in_flight_frames.push_front(frame_resources); return asl::ok(); } // @Todo Make fences recyclable // @Todo Make frame structure recyclable void recycle_resources() { while (!m_in_flight_frames.is_empty()) { auto* frame = m_in_flight_frames.back(); auto status = vkGetFenceStatus(m_device, frame->complete_fence); if (status == VK_NOT_READY) { return; } if (status != VK_SUCCESS) { ASL_LOG_ERROR("Error on frame fence query: {}", status); return; } destroy_fence(frame->complete_fence); vkDestroyCommandPool(m_device, frame->command_pool, VK_ALLOCATOR); m_in_flight_frames.pop_back(); asl::alloc_delete(m_allocator, frame); } } void frame() override { recycle_resources(); auto s = frame_opt(); if (!s.ok()) { ASL_LOG_ERROR("Frame error: {}", s); } } }; asl::status_or> init(SDL_Window* window) { auto instance = create_instance(); ASL_TRY(instance); auto surface = create_surface(window, instance.value()); ASL_TRY(surface); auto physical_device_info = find_physical_device(instance.value()); ASL_TRY(physical_device_info); auto device = create_device( physical_device_info.value().physical_device, physical_device_info.value().queue_family_index); ASL_TRY(device); VkQueue queue{}; vkGetDeviceQueue(device.value(), physical_device_info.value().queue_family_index, 0, &queue); auto gpu = asl::make_box( instance.value(), physical_device_info.value().physical_device, surface.value(), physical_device_info.value().queue_family_index, device.value(), queue); int window_width{}; int window_height{}; SDL_GetWindowSizeInPixels(window, &window_width, &window_height); auto s = gpu->create_swapchain(static_cast(window_width), static_cast(window_height)); ASL_TRY(s); return gpu; }; } // namespace gpu