From 203bf64d8893259d0ffdb1740a0e36daf6992687 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 13 Sep 2020 17:51:00 +0300 Subject: [PATCH] [Vulkan] Context, remove Volk --- premake5.lua | 1 - src/xenia/app/premake5.lua | 1 - src/xenia/hid/premake5.lua | 1 - src/xenia/ui/d3d12/d3d12_context.cc | 215 +++-- src/xenia/ui/d3d12/d3d12_context.h | 4 +- src/xenia/ui/graphics_context.cc | 18 + src/xenia/ui/graphics_context.h | 5 +- src/xenia/ui/vulkan/vulkan_context.cc | 843 +++++++++++++++++- src/xenia/ui/vulkan/vulkan_context.h | 77 +- .../ui/vulkan/vulkan_immediate_drawer.cc | 14 +- src/xenia/ui/vulkan/vulkan_immediate_drawer.h | 7 + src/xenia/ui/vulkan/vulkan_provider.cc | 229 ++--- src/xenia/ui/vulkan/vulkan_provider.h | 84 +- src/xenia/ui/window.cc | 6 +- third_party/volk | 1 - third_party/volk.lua | 30 - 16 files changed, 1241 insertions(+), 295 deletions(-) delete mode 160000 third_party/volk delete mode 100644 third_party/volk.lua diff --git a/premake5.lua b/premake5.lua index 622a4da2c..2b8042334 100644 --- a/premake5.lua +++ b/premake5.lua @@ -226,7 +226,6 @@ solution("xenia") include("third_party/SDL2.lua") include("third_party/snappy.lua") include("third_party/spirv-tools.lua") - include("third_party/volk.lua") include("third_party/xxhash.lua") include("src/xenia") diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 520da24e4..3a0f6bb2a 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -20,7 +20,6 @@ project("xenia-app") "mspack", "snappy", "spirv-tools", - "volk", "xenia-app-discord", "xenia-apu", "xenia-apu-nop", diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index 152887e2b..b137f5178 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -22,7 +22,6 @@ project("xenia-hid-demo") links({ "fmt", "imgui", - "volk", "xenia-base", "xenia-helper-sdl", "xenia-hid", diff --git a/src/xenia/ui/d3d12/d3d12_context.cc b/src/xenia/ui/d3d12/d3d12_context.cc index f897a5516..7764afa44 100644 --- a/src/xenia/ui/d3d12/d3d12_context.cc +++ b/src/xenia/ui/d3d12/d3d12_context.cc @@ -9,9 +9,6 @@ #include "xenia/ui/d3d12/d3d12_context.h" -#include - -#include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/ui/d3d12/d3d12_immediate_drawer.h" @@ -19,9 +16,6 @@ #include "xenia/ui/d3d12/d3d12_util.h" #include "xenia/ui/window.h" -DEFINE_bool(d3d12_random_clear_color, false, - "Randomize presentation back buffer clear color.", "D3D12"); - namespace xe { namespace ui { namespace d3d12 { @@ -32,110 +26,112 @@ D3D12Context::D3D12Context(D3D12Provider* provider, Window* target_window) D3D12Context::~D3D12Context() { Shutdown(); } bool D3D12Context::Initialize() { + context_lost_ = false; + + if (!target_window_) { + return true; + } + auto& provider = GetD3D12Provider(); auto dxgi_factory = provider.GetDXGIFactory(); auto device = provider.GetDevice(); auto direct_queue = provider.GetDirectQueue(); - context_lost_ = false; + swap_fence_current_value_ = 1; + swap_fence_completed_value_ = 0; + swap_fence_completion_event_ = CreateEvent(nullptr, false, false, nullptr); + if (swap_fence_completion_event_ == nullptr) { + XELOGE("Failed to create the composition fence completion event"); + Shutdown(); + return false; + } + // Create a fence for transient resources of compositing. + if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, + IID_PPV_ARGS(&swap_fence_)))) { + XELOGE("Failed to create the composition fence"); + Shutdown(); + return false; + } - if (target_window_) { - swap_fence_current_value_ = 1; - swap_fence_completed_value_ = 0; - swap_fence_completion_event_ = CreateEvent(nullptr, false, false, nullptr); - if (swap_fence_completion_event_ == nullptr) { - XELOGE("Failed to create the composition fence completion event"); - Shutdown(); - return false; - } - // Create a fence for transient resources of compositing. - if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, - IID_PPV_ARGS(&swap_fence_)))) { - XELOGE("Failed to create the composition fence"); - Shutdown(); - return false; - } - - // Create the swap chain. - swap_chain_width_ = target_window_->scaled_width(); - swap_chain_height_ = target_window_->scaled_height(); - DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; - swap_chain_desc.Width = swap_chain_width_; - swap_chain_desc.Height = swap_chain_height_; - swap_chain_desc.Format = kSwapChainFormat; - swap_chain_desc.Stereo = FALSE; - swap_chain_desc.SampleDesc.Count = 1; - swap_chain_desc.SampleDesc.Quality = 0; - swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.BufferCount = kSwapChainBufferCount; - swap_chain_desc.Scaling = DXGI_SCALING_STRETCH; - swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; - swap_chain_desc.Flags = 0; - IDXGISwapChain1* swap_chain_1; - if (FAILED(dxgi_factory->CreateSwapChainForHwnd( - provider.GetDirectQueue(), - static_cast(target_window_->native_handle()), - &swap_chain_desc, nullptr, nullptr, &swap_chain_1))) { - XELOGE("Failed to create a DXGI swap chain"); - Shutdown(); - return false; - } - if (FAILED(swap_chain_1->QueryInterface(IID_PPV_ARGS(&swap_chain_)))) { - XELOGE("Failed to get version 3 of the DXGI swap chain interface"); - swap_chain_1->Release(); - Shutdown(); - return false; - } + // Create the swap chain. + swap_chain_width_ = target_window_->scaled_width(); + swap_chain_height_ = target_window_->scaled_height(); + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; + swap_chain_desc.Width = swap_chain_width_; + swap_chain_desc.Height = swap_chain_height_; + swap_chain_desc.Format = kSwapChainFormat; + swap_chain_desc.Stereo = FALSE; + swap_chain_desc.SampleDesc.Count = 1; + swap_chain_desc.SampleDesc.Quality = 0; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = kSwapChainBufferCount; + swap_chain_desc.Scaling = DXGI_SCALING_STRETCH; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + swap_chain_desc.Flags = 0; + IDXGISwapChain1* swap_chain_1; + if (FAILED(dxgi_factory->CreateSwapChainForHwnd( + provider.GetDirectQueue(), + reinterpret_cast(target_window_->native_handle()), + &swap_chain_desc, nullptr, nullptr, &swap_chain_1))) { + XELOGE("Failed to create a DXGI swap chain"); + Shutdown(); + return false; + } + if (FAILED(swap_chain_1->QueryInterface(IID_PPV_ARGS(&swap_chain_)))) { + XELOGE("Failed to get version 3 of the DXGI swap chain interface"); swap_chain_1->Release(); + Shutdown(); + return false; + } + swap_chain_1->Release(); - // Create a heap for RTV descriptors of swap chain buffers. - D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc; - rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; - rtv_heap_desc.NumDescriptors = kSwapChainBufferCount; - rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; - rtv_heap_desc.NodeMask = 0; - if (FAILED(device->CreateDescriptorHeap( - &rtv_heap_desc, IID_PPV_ARGS(&swap_chain_rtv_heap_)))) { - XELOGE("Failed to create swap chain RTV descriptor heap"); + // Create a heap for RTV descriptors of swap chain buffers. + D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc; + rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + rtv_heap_desc.NumDescriptors = kSwapChainBufferCount; + rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + rtv_heap_desc.NodeMask = 0; + if (FAILED(device->CreateDescriptorHeap( + &rtv_heap_desc, IID_PPV_ARGS(&swap_chain_rtv_heap_)))) { + XELOGE("Failed to create swap chain RTV descriptor heap"); + Shutdown(); + return false; + } + swap_chain_rtv_heap_start_ = + swap_chain_rtv_heap_->GetCPUDescriptorHandleForHeapStart(); + + // Get the buffers and create their RTV descriptors. + if (!InitializeSwapChainBuffers()) { + Shutdown(); + return false; + } + + // Create the command list for compositing. + for (uint32_t i = 0; i < kSwapCommandAllocatorCount; ++i) { + if (FAILED(device->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&swap_command_allocators_[i])))) { + XELOGE("Failed to create a composition command allocator"); Shutdown(); return false; } - swap_chain_rtv_heap_start_ = - swap_chain_rtv_heap_->GetCPUDescriptorHandleForHeapStart(); + } + if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + swap_command_allocators_[0], nullptr, + IID_PPV_ARGS(&swap_command_list_)))) { + XELOGE("Failed to create the composition graphics command list"); + Shutdown(); + return false; + } + // Initially in open state, wait until BeginSwap. + swap_command_list_->Close(); - // Get the buffers and create their RTV descriptors. - if (!InitializeSwapChainBuffers()) { - Shutdown(); - return false; - } - - // Create the command list for compositing. - for (uint32_t i = 0; i < kSwapCommandAllocatorCount; ++i) { - if (FAILED(device->CreateCommandAllocator( - D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&swap_command_allocators_[i])))) { - XELOGE("Failed to create a composition command allocator"); - Shutdown(); - return false; - } - } - if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - swap_command_allocators_[0], nullptr, - IID_PPV_ARGS(&swap_command_list_)))) { - XELOGE("Failed to create the composition graphics command list"); - Shutdown(); - return false; - } - // Initially in open state, wait until BeginSwap. - swap_command_list_->Close(); - - // Initialize the immediate mode drawer if not offscreen. - immediate_drawer_ = std::make_unique(*this); - if (!immediate_drawer_->Initialize()) { - Shutdown(); - return false; - } + // Initialize the immediate mode drawer if not offscreen. + immediate_drawer_ = std::make_unique(*this); + if (!immediate_drawer_->Initialize()) { + Shutdown(); + return false; } return true; @@ -223,9 +219,11 @@ ImmediateDrawer* D3D12Context::immediate_drawer() { return immediate_drawer_.get(); } -void D3D12Context::BeginSwap() { +bool D3D12Context::WasLost() { return context_lost_; } + +bool D3D12Context::BeginSwap() { if (!target_window_ || context_lost_) { - return; + return false; } // Resize the swap chain if the window is resized. @@ -252,13 +250,13 @@ void D3D12Context::BeginSwap() { kSwapChainBufferCount, target_window_width, target_window_height, kSwapChainFormat, 0))) { context_lost_ = true; - return; + return false; } swap_chain_width_ = target_window_width; swap_chain_height_ = target_window_height; if (!InitializeSwapChainBuffers()) { context_lost_ = true; - return; + return false; } } @@ -295,18 +293,11 @@ void D3D12Context::BeginSwap() { D3D12_CPU_DESCRIPTOR_HANDLE back_buffer_rtv = GetSwapChainBackBufferRTV(); swap_command_list_->OMSetRenderTargets(1, &back_buffer_rtv, TRUE, nullptr); float clear_color[4]; - if (cvars::d3d12_random_clear_color) { - clear_color[0] = rand() / float(RAND_MAX); // NOLINT(runtime/threadsafe_fn) - clear_color[1] = 1.0f; - clear_color[2] = 0.0f; - } else { - clear_color[0] = 0.0f; - clear_color[1] = 0.0f; - clear_color[2] = 0.0f; - } - clear_color[3] = 1.0f; + GetClearColor(clear_color); swap_command_list_->ClearRenderTargetView(back_buffer_rtv, clear_color, 0, nullptr); + + return true; } void D3D12Context::EndSwap() { diff --git a/src/xenia/ui/d3d12/d3d12_context.h b/src/xenia/ui/d3d12/d3d12_context.h index 2651adae9..c9f235b97 100644 --- a/src/xenia/ui/d3d12/d3d12_context.h +++ b/src/xenia/ui/d3d12/d3d12_context.h @@ -28,9 +28,9 @@ class D3D12Context : public GraphicsContext { ImmediateDrawer* immediate_drawer() override; - bool WasLost() override { return context_lost_; } + bool WasLost() override; - void BeginSwap() override; + bool BeginSwap() override; void EndSwap() override; std::unique_ptr Capture() override; diff --git a/src/xenia/ui/graphics_context.cc b/src/xenia/ui/graphics_context.cc index 73980cd37..7f5ab07b6 100644 --- a/src/xenia/ui/graphics_context.cc +++ b/src/xenia/ui/graphics_context.cc @@ -9,8 +9,13 @@ #include "xenia/ui/graphics_context.h" +#include + +#include "xenia/base/cvar.h" #include "xenia/ui/graphics_provider.h" +DEFINE_bool(random_clear_color, false, "Randomize window clear color.", "UI"); + namespace xe { namespace ui { @@ -26,5 +31,18 @@ bool GraphicsContext::MakeCurrent() { return true; } void GraphicsContext::ClearCurrent() {} +void GraphicsContext::GetClearColor(float* rgba) { + if (cvars::random_clear_color) { + rgba[0] = rand() / float(RAND_MAX); // NOLINT(runtime/threadsafe_fn) + rgba[1] = 1.0f; + rgba[2] = 0.0f; + } else { + rgba[0] = 0.0f; + rgba[1] = 0.0f; + rgba[2] = 0.0f; + } + rgba[3] = 1.0f; +} + } // namespace ui } // namespace xe diff --git a/src/xenia/ui/graphics_context.h b/src/xenia/ui/graphics_context.h index 383338770..0ed5bd881 100644 --- a/src/xenia/ui/graphics_context.h +++ b/src/xenia/ui/graphics_context.h @@ -51,7 +51,8 @@ class GraphicsContext { // This context must be made current in order for this call to work properly. virtual bool WasLost() = 0; - virtual void BeginSwap() = 0; + // Returns true if able to draw now (the target surface is available). + virtual bool BeginSwap() = 0; virtual void EndSwap() = 0; virtual std::unique_ptr Capture() = 0; @@ -59,6 +60,8 @@ class GraphicsContext { protected: explicit GraphicsContext(GraphicsProvider* provider, Window* target_window); + static void GetClearColor(float* rgba); + GraphicsProvider* provider_ = nullptr; Window* target_window_ = nullptr; }; diff --git a/src/xenia/ui/vulkan/vulkan_context.cc b/src/xenia/ui/vulkan/vulkan_context.cc index 2503c105d..28c68bcd5 100644 --- a/src/xenia/ui/vulkan/vulkan_context.cc +++ b/src/xenia/ui/vulkan/vulkan_context.cc @@ -9,8 +9,24 @@ #include "xenia/ui/vulkan/vulkan_context.h" +#include +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" +#include "xenia/base/platform.h" #include "xenia/ui/vulkan/vulkan_immediate_drawer.h" #include "xenia/ui/vulkan/vulkan_provider.h" +#include "xenia/ui/vulkan/vulkan_util.h" +#include "xenia/ui/window.h" + +#if XE_PLATFORM_ANDROID +#include +#elif XE_PLATFORM_WIN32 +#include "xenia/base/platform_win.h" +#endif namespace xe { namespace ui { @@ -19,21 +35,842 @@ namespace vulkan { VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window) : GraphicsContext(provider, target_window) {} -bool VulkanContext::Initialize() { return false; } +bool VulkanContext::Initialize() { + context_lost_ = false; + + if (!target_window_) { + return true; + } + + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + + VkFenceCreateInfo fence_create_info; + fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_create_info.pNext = nullptr; + fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + VkCommandPoolCreateInfo command_pool_create_info; + command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + command_pool_create_info.pNext = nullptr; + command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + command_pool_create_info.queueFamilyIndex = + provider.queue_family_graphics_compute(); + + VkCommandBufferAllocateInfo command_buffer_allocate_info; + command_buffer_allocate_info.sType = + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + command_buffer_allocate_info.pNext = nullptr; + command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + command_buffer_allocate_info.commandBufferCount = 1; + + for (uint32_t i = 0; i < kSwapchainMaxImageCount; ++i) { + SwapSubmission& submission = swap_submissions_[i]; + if (dfn.vkCreateFence(device, &fence_create_info, nullptr, + &submission.fence) != VK_SUCCESS) { + XELOGE("Failed to create the Vulkan composition fences"); + Shutdown(); + return false; + } + if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr, + &submission.command_pool) != VK_SUCCESS) { + XELOGE("Failed to create the Vulkan composition command pools"); + Shutdown(); + return false; + } + command_buffer_allocate_info.commandPool = submission.command_pool; + if (dfn.vkAllocateCommandBuffers(device, &command_buffer_allocate_info, + &submission.command_buffer) != + VK_SUCCESS) { + XELOGE("Failed to allocate the Vulkan composition command buffers"); + Shutdown(); + return false; + } + } + + VkSemaphoreCreateInfo semaphore_create_info; + semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphore_create_info.pNext = nullptr; + semaphore_create_info.flags = 0; + if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr, + &swap_image_acquisition_semaphore_) != VK_SUCCESS) { + XELOGE( + "Failed to create the Vulkan swap chain image acquisition semaphore"); + Shutdown(); + return false; + } + if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr, + &swap_render_completion_semaphore_) != VK_SUCCESS) { + XELOGE( + "Failed to create the Vulkan swap chain rendering completion " + "semaphore"); + Shutdown(); + return false; + } + + immediate_drawer_ = std::make_unique(*this); + // TODO(Triang3l): Initialize the immediate drawer. + + swap_swapchain_or_surface_recreation_needed_ = true; + + return true; +} + +void VulkanContext::Shutdown() { + if (!target_window_) { + return; + } + + AwaitAllSwapSubmissionsCompletion(); + + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::InstanceFunctions& ifn = provider.ifn(); + VkInstance instance = provider.instance(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + + swap_swapchain_image_current_ = UINT32_MAX; + DestroySwapchainFramebuffers(); + util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device, + swap_swapchain_); + util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device, + swap_render_pass_); + util::DestroyAndNullHandle(ifn.vkDestroySurfaceKHR, instance, swap_surface_); + swap_swapchain_or_surface_recreation_needed_ = false; + + util::DestroyAndNullHandle(dfn.vkDestroySemaphore, device, + swap_render_completion_semaphore_); + util::DestroyAndNullHandle(dfn.vkDestroySemaphore, device, + swap_image_acquisition_semaphore_); + + for (uint32_t i = 0; i < kSwapchainMaxImageCount; ++i) { + SwapSubmission& submission = swap_submissions_[i]; + util::DestroyAndNullHandle(dfn.vkDestroyCommandPool, device, + submission.command_pool); + util::DestroyAndNullHandle(dfn.vkDestroyFence, device, submission.fence); + } + swap_submission_current_ = 1; + swap_submission_completed_ = 0; +} ImmediateDrawer* VulkanContext::immediate_drawer() { return immediate_drawer_.get(); } -void VulkanContext::BeginSwap() {} +bool VulkanContext::WasLost() { return context_lost_; } -void VulkanContext::EndSwap() {} +bool VulkanContext::BeginSwap() { + if (!target_window_ || context_lost_) { + return false; + } + + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::InstanceFunctions& ifn = provider.ifn(); + VkPhysicalDevice physical_device = provider.physical_device(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + + uint32_t window_width = uint32_t(target_window_->scaled_width()); + uint32_t window_height = uint32_t(target_window_->scaled_height()); + if (swap_swapchain_ != VK_NULL_HANDLE) { + // Check if need to resize. + assert_true(swap_surface_ != VK_NULL_HANDLE); + // Win32 has minImageExtent == maxImageExtent == currentExtent, so the + // capabilities need to be requested every time they are needed. + VkSurfaceCapabilitiesKHR surface_capabilities; + if (ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physical_device, swap_surface_, &surface_capabilities) == + VK_SUCCESS) { + if (swap_swapchain_extent_.width != + xe::clamp(window_width, surface_capabilities.minImageExtent.width, + surface_capabilities.maxImageExtent.width) || + swap_swapchain_extent_.height != + xe::clamp(window_height, + surface_capabilities.minImageExtent.height, + surface_capabilities.maxImageExtent.height)) { + swap_swapchain_or_surface_recreation_needed_ = true; + } + } + } + + // If the swap chain turns out to be out of date, try to recreate it on the + // second attempt (to avoid skipping the frame entirely in this case). + for (uint32_t attempt = 0; attempt < 2; ++attempt) { + if (swap_swapchain_or_surface_recreation_needed_) { + // If recreation fails, don't retry until some change happens. + swap_swapchain_or_surface_recreation_needed_ = false; + + AwaitAllSwapSubmissionsCompletion(); + + uint32_t queue_family_graphics_compute = + provider.queue_family_graphics_compute(); + + if (swap_surface_ == VK_NULL_HANDLE) { + assert_true(swap_swapchain_ == VK_NULL_HANDLE); + assert_true(swap_swapchain_image_views_.empty()); + assert_true(swap_swapchain_framebuffers_.empty()); + + VkInstance instance = provider.instance(); + + VkResult surface_create_result; +#if XE_PLATFORM_ANDROID + VkAndroidSurfaceCreateInfoKHR surface_create_info; + surface_create_info.window = + reinterpret_cast(target_window_->native_handle()); + if (!surface_create_info.window) { + // The activity is in background - try again when the window is + // created. + return false; + } + surface_create_info.sType = + VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + surface_create_info.pNext = nullptr; + surface_create_info.flags = 0; + surface_create_result = ifn.vkCreateAndroidSurfaceKHR( + instance, &surface_create_info, nullptr, &swap_surface_); +#elif XE_PLATFORM_WIN32 + VkWin32SurfaceCreateInfoKHR surface_create_info; + surface_create_info.sType = + VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + surface_create_info.pNext = nullptr; + surface_create_info.flags = 0; + surface_create_info.hinstance = reinterpret_cast( + target_window_->native_platform_handle()); + surface_create_info.hwnd = + reinterpret_cast(target_window_->native_handle()); + surface_create_result = ifn.vkCreateWin32SurfaceKHR( + instance, &surface_create_info, nullptr, &swap_surface_); +#else +#error No Vulkan surface creation for the target platform. +#endif + if (surface_create_result != VK_SUCCESS) { + XELOGE("Failed to create a Vulkan surface"); + return false; + } + + // FIXME(Triang3l): Allow a separate queue for present - see + // vulkan_provider.cc for details. + VkBool32 surface_supported; + if (ifn.vkGetPhysicalDeviceSurfaceSupportKHR( + physical_device, queue_family_graphics_compute, swap_surface_, + &surface_supported) != VK_SUCCESS || + !surface_supported) { + XELOGE( + "The Vulkan graphics and compute queue doesn't support " + "presentation"); + ifn.vkDestroySurfaceKHR(instance, swap_surface_, nullptr); + swap_surface_ = VK_NULL_HANDLE; + return false; + } + + // Choose an SDR format, 8.8.8.8 preferred, or if not available, any + // supported. Windows and GNU/Linux use B8G8R8A8, Android uses R8G8B8A8. + std::vector surface_formats; + VkResult surface_formats_get_result; + for (;;) { + uint32_t surface_format_count = uint32_t(surface_formats.size()); + bool surface_formats_was_empty = !surface_format_count; + surface_formats_get_result = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR( + physical_device, swap_surface_, &surface_format_count, + surface_formats_was_empty ? nullptr : surface_formats.data()); + // If the original surface format count was 0 (first call), SUCCESS is + // returned, not INCOMPLETE. + if (surface_formats_get_result == VK_SUCCESS || + surface_formats_get_result == VK_INCOMPLETE) { + surface_formats.resize(surface_format_count); + if (surface_formats_get_result == VK_SUCCESS && + (!surface_formats_was_empty || !surface_format_count)) { + break; + } + } else { + break; + } + } + if (surface_formats_get_result != VK_SUCCESS || + surface_formats.empty()) { + XELOGE("Failed to get Vulkan surface formats"); + ifn.vkDestroySurfaceKHR(instance, swap_surface_, nullptr); + swap_surface_ = VK_NULL_HANDLE; + return false; + } + VkSurfaceFormatKHR surface_format; + if (surface_formats.size() == 1 && + surface_formats[0].format == VK_FORMAT_UNDEFINED) { +#if XE_PLATFORM_ANDROID + surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; +#else + surface_format.format = VK_FORMAT_B8G8R8A8_UNORM; +#endif + surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } else { + surface_format = surface_formats.front(); + for (const VkSurfaceFormatKHR& surface_format_current : + surface_formats) { + if (surface_format_current.format == VK_FORMAT_B8G8R8A8_UNORM || + surface_format_current.format == VK_FORMAT_R8G8B8A8_UNORM || + surface_format_current.format == + VK_FORMAT_A8B8G8R8_UNORM_PACK32) { + surface_format = surface_format_current; + break; + } + } + } + if (swap_surface_format_.format != surface_format.format) { + util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device, + swap_render_pass_); + } + swap_surface_format_ = surface_format; + + // Prefer a low-latency present mode because emulation is done on the + // same queue, ordered by the decreasing amount of tearing, fall back to + // FIFO if no other options. + swap_surface_present_mode_ = VK_PRESENT_MODE_FIFO_KHR; + std::vector surface_present_modes; + VkResult surface_present_modes_get_result; + for (;;) { + uint32_t surface_present_mode_count = + uint32_t(surface_present_modes.size()); + bool surface_present_modes_was_empty = !surface_present_mode_count; + surface_present_modes_get_result = + ifn.vkGetPhysicalDeviceSurfacePresentModesKHR( + physical_device, swap_surface_, &surface_present_mode_count, + surface_present_modes_was_empty + ? nullptr + : surface_present_modes.data()); + // If the original surface present mode count was 0 (first call), + // SUCCESS is returned, not INCOMPLETE. + if (surface_present_modes_get_result == VK_SUCCESS || + surface_present_modes_get_result == VK_INCOMPLETE) { + surface_present_modes.resize(surface_present_mode_count); + if (surface_present_modes_get_result == VK_SUCCESS && + (!surface_present_modes_was_empty || + !surface_present_mode_count)) { + break; + } + } else { + break; + } + } + if (surface_present_modes_get_result == VK_SUCCESS) { + static const VkPresentModeKHR present_modes_preferred[] = { + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR, + }; + for (size_t i = 0; i < xe::countof(present_modes_preferred); ++i) { + VkPresentModeKHR present_mode_preferred = + present_modes_preferred[i]; + if (std::find(surface_present_modes.cbegin(), + surface_present_modes.cend(), + present_mode_preferred) != + surface_present_modes.cend()) { + swap_surface_present_mode_ = present_mode_preferred; + break; + } + } + } + } + + // Recreate the swap chain unconditionally because a request was made. + // The old swapchain will be retired even if vkCreateSwapchainKHR fails, + // so destroy the framebuffers and the image views unconditionally. + // If anything fails before the vkCreateSwapchainKHR call, also destroy + // the swapchain to fulfill the request. + // It was safe to handle errors while creating the surface without caring + // about destroying the swapchain, because there can't be swapchain when + // there is no surface. + DestroySwapchainFramebuffers(); + // Win32 has minImageExtent == maxImageExtent == currentExtent, so the + // capabilities need to be requested every time they are needed. + VkSurfaceCapabilitiesKHR surface_capabilities; + if (ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physical_device, swap_surface_, &surface_capabilities) != + VK_SUCCESS) { + XELOGE("Failed to get Vulkan surface capabilities"); + util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device, + swap_swapchain_); + return false; + } + // TODO(Triang3l): Support rotated transforms. + if (!(surface_capabilities.supportedTransforms & + VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) { + XELOGE("The Vulkan surface doesn't support identity transform"); + util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device, + swap_swapchain_); + return false; + } + VkSwapchainCreateInfoKHR swapchain_create_info; + swapchain_create_info.imageExtent.width = + xe::clamp(window_width, surface_capabilities.minImageExtent.width, + surface_capabilities.maxImageExtent.width); + swapchain_create_info.imageExtent.height = + xe::clamp(window_height, surface_capabilities.minImageExtent.height, + surface_capabilities.maxImageExtent.height); + if (!swapchain_create_info.imageExtent.width || + !swapchain_create_info.imageExtent.height) { + // Everything else is fine with the surface, but the window is too + // small, try again when the window may be resized (won't try to do some + // vkCreate* every BeginSwap, will reach this part again, so okay to set + // swap_swapchain_or_surface_recreation_needed_ back to true). + swap_swapchain_or_surface_recreation_needed_ = true; + util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device, + swap_swapchain_); + return false; + } + swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_create_info.pNext = nullptr; + swapchain_create_info.flags = 0; + swapchain_create_info.surface = swap_surface_; + swapchain_create_info.minImageCount = kSwapchainMaxImageCount; + if (surface_capabilities.maxImageCount) { + swapchain_create_info.minImageCount = + std::min(swapchain_create_info.minImageCount, + surface_capabilities.maxImageCount); + } + swapchain_create_info.imageFormat = swap_surface_format_.format; + swapchain_create_info.imageColorSpace = swap_surface_format_.colorSpace; + swapchain_create_info.imageArrayLayers = 1; + swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + // FIXME(Triang3l): Allow a separate queue for present - see + // vulkan_provider.cc for details. + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchain_create_info.queueFamilyIndexCount = 1; + swapchain_create_info.pQueueFamilyIndices = + &queue_family_graphics_compute; + // TODO(Triang3l): Support rotated transforms. + swapchain_create_info.preTransform = + VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + if (!(surface_capabilities.supportedCompositeAlpha & + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) { + if (surface_capabilities.supportedCompositeAlpha & + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + swapchain_create_info.compositeAlpha = + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } else { + // Whatever. supportedCompositeAlpha must have at least one bit set, + // but if it somehow doesn't (impossible situation according to the + // specification, but who knows), just assume opaque. + uint32_t composite_alpha_bit_index; + if (xe::bit_scan_forward( + uint32_t(surface_capabilities.supportedCompositeAlpha), + &composite_alpha_bit_index)) { + swapchain_create_info.compositeAlpha = VkCompositeAlphaFlagBitsKHR( + uint32_t(1) << composite_alpha_bit_index); + } + } + } + swapchain_create_info.presentMode = swap_surface_present_mode_; + swapchain_create_info.clipped = VK_TRUE; + swapchain_create_info.oldSwapchain = swap_swapchain_; + VkResult swapchain_create_result = dfn.vkCreateSwapchainKHR( + device, &swapchain_create_info, nullptr, &swap_swapchain_); + // The old swapchain is retired even if vkCreateSwapchainKHR has failed. + if (swapchain_create_info.oldSwapchain != VK_NULL_HANDLE) { + dfn.vkDestroySwapchainKHR(device, swapchain_create_info.oldSwapchain, + nullptr); + } + if (swapchain_create_result != VK_SUCCESS) { + XELOGE("Failed to create a Vulkan swapchain"); + swap_swapchain_ = VK_NULL_HANDLE; + return false; + } + swap_swapchain_extent_ = swapchain_create_info.imageExtent; + + // The render pass is needed to create framebuffers for swapchain images. + // It depends on the surface format, and thus can be reused with different + // surfaces by different swapchains, so it has separate lifetime tracking. + // It's safe to fail now (though destroying the new swapchain), because + // the request to destroy the old VkSwapchain somehow (after retiring, or + // directly) has been fulfilled. + if (swap_render_pass_ == VK_NULL_HANDLE) { + VkAttachmentDescription render_pass_color_attachment; + render_pass_color_attachment.flags = 0; + render_pass_color_attachment.format = swap_surface_format_.format; + render_pass_color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + render_pass_color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + render_pass_color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + render_pass_color_attachment.stencilLoadOp = + VK_ATTACHMENT_LOAD_OP_DONT_CARE; + render_pass_color_attachment.stencilStoreOp = + VK_ATTACHMENT_STORE_OP_DONT_CARE; + render_pass_color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + render_pass_color_attachment.finalLayout = + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkAttachmentReference render_pass_color_attachment_reference; + render_pass_color_attachment_reference.attachment = 0; + render_pass_color_attachment_reference.layout = + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription render_pass_subpass; + render_pass_subpass.flags = 0; + render_pass_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + render_pass_subpass.inputAttachmentCount = 0; + render_pass_subpass.pInputAttachments = nullptr; + render_pass_subpass.colorAttachmentCount = 1; + render_pass_subpass.pColorAttachments = + &render_pass_color_attachment_reference; + render_pass_subpass.pResolveAttachments = nullptr; + render_pass_subpass.pDepthStencilAttachment = nullptr; + render_pass_subpass.preserveAttachmentCount = 0; + render_pass_subpass.pPreserveAttachments = nullptr; + VkRenderPassCreateInfo render_pass_create_info; + render_pass_create_info.sType = + VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + render_pass_create_info.pNext = nullptr; + render_pass_create_info.flags = 0; + render_pass_create_info.attachmentCount = 1; + render_pass_create_info.pAttachments = &render_pass_color_attachment; + render_pass_create_info.subpassCount = 1; + render_pass_create_info.pSubpasses = &render_pass_subpass; + render_pass_create_info.dependencyCount = 0; + render_pass_create_info.pDependencies = nullptr; + if (dfn.vkCreateRenderPass(device, &render_pass_create_info, nullptr, + &swap_render_pass_) != VK_SUCCESS) { + XELOGE("Failed to create the Vulkan presentation render pass."); + dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr); + swap_swapchain_ = VK_NULL_HANDLE; + return false; + } + } + + std::vector swapchain_images; + uint32_t swapchain_image_count; + VkResult swapchain_images_get_result; + for (;;) { + swapchain_image_count = uint32_t(swapchain_images.size()); + bool swapchain_images_was_empty = !swapchain_image_count; + swapchain_images_get_result = dfn.vkGetSwapchainImagesKHR( + device, swap_swapchain_, &swapchain_image_count, + swapchain_images_was_empty ? nullptr : swapchain_images.data()); + // If the original swapchain image count was 0 (first call), SUCCESS is + // returned, not INCOMPLETE. + if (swapchain_images_get_result == VK_SUCCESS || + swapchain_images_get_result == VK_INCOMPLETE) { + swapchain_images.resize(swapchain_image_count); + if (swapchain_images_get_result == VK_SUCCESS && + (!swapchain_images_was_empty || !swapchain_image_count)) { + break; + } + } else { + break; + } + } + if (swapchain_images_get_result != VK_SUCCESS || + swapchain_images.empty()) { + XELOGE("Failed to get Vulkan swapchain images"); + dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr); + swap_swapchain_ = VK_NULL_HANDLE; + return false; + } + assert_true(swap_swapchain_image_views_.empty()); + swap_swapchain_image_views_.reserve(swapchain_image_count); + assert_true(swap_swapchain_framebuffers_.empty()); + swap_swapchain_framebuffers_.reserve(swapchain_image_count); + VkImageViewCreateInfo swapchain_image_view_create_info; + swapchain_image_view_create_info.sType = + VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + swapchain_image_view_create_info.pNext = nullptr; + swapchain_image_view_create_info.flags = 0; + swapchain_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + swapchain_image_view_create_info.format = swap_surface_format_.format; + swapchain_image_view_create_info.components.r = + VK_COMPONENT_SWIZZLE_IDENTITY; + swapchain_image_view_create_info.components.g = + VK_COMPONENT_SWIZZLE_IDENTITY; + swapchain_image_view_create_info.components.b = + VK_COMPONENT_SWIZZLE_IDENTITY; + swapchain_image_view_create_info.components.a = + VK_COMPONENT_SWIZZLE_IDENTITY; + swapchain_image_view_create_info.subresourceRange.aspectMask = + VK_IMAGE_ASPECT_COLOR_BIT; + swapchain_image_view_create_info.subresourceRange.baseMipLevel = 0; + swapchain_image_view_create_info.subresourceRange.levelCount = 1; + swapchain_image_view_create_info.subresourceRange.baseArrayLayer = 0; + swapchain_image_view_create_info.subresourceRange.layerCount = 1; + VkFramebufferCreateInfo swapchain_framebuffer_create_info; + swapchain_framebuffer_create_info.sType = + VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + swapchain_framebuffer_create_info.pNext = nullptr; + swapchain_framebuffer_create_info.flags = 0; + swapchain_framebuffer_create_info.renderPass = swap_render_pass_; + swapchain_framebuffer_create_info.attachmentCount = 1; + swapchain_framebuffer_create_info.width = swap_swapchain_extent_.width; + swapchain_framebuffer_create_info.height = swap_swapchain_extent_.height; + swapchain_framebuffer_create_info.layers = 1; + for (uint32_t i = 0; i < swapchain_image_count; ++i) { + VkImage swapchain_image = swapchain_images[i]; + swapchain_image_view_create_info.image = swapchain_image; + VkImageView swapchain_image_view; + if (dfn.vkCreateImageView(device, &swapchain_image_view_create_info, + nullptr, + &swapchain_image_view) != VK_SUCCESS) { + XELOGE("Failed to create Vulkan swapchain image views"); + DestroySwapchainFramebuffers(); + dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr); + swap_swapchain_ = VK_NULL_HANDLE; + return false; + } + swap_swapchain_image_views_.push_back(swapchain_image_view); + swapchain_framebuffer_create_info.pAttachments = &swapchain_image_view; + VkFramebuffer swapchain_framebuffer; + if (dfn.vkCreateFramebuffer(device, &swapchain_framebuffer_create_info, + nullptr, + &swapchain_framebuffer) != VK_SUCCESS) { + XELOGE("Failed to create Vulkan swapchain framebuffers"); + DestroySwapchainFramebuffers(); + dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr); + swap_swapchain_ = VK_NULL_HANDLE; + return false; + } + swap_swapchain_framebuffers_.push_back(swapchain_framebuffer); + } + } + + if (swap_swapchain_ == VK_NULL_HANDLE) { + return false; + } + assert_true(swap_surface_ != VK_NULL_HANDLE); + assert_true(swap_render_pass_ != VK_NULL_HANDLE); + assert_false(swap_swapchain_image_views_.empty()); + assert_false(swap_swapchain_framebuffers_.empty()); + + // Await the frame data to be available before doing anything else. + if (swap_submission_completed_ + kSwapchainMaxImageCount < + swap_submission_current_) { + uint64_t submission_awaited = + swap_submission_current_ - kSwapchainMaxImageCount; + VkFence submission_fences[kSwapchainMaxImageCount]; + uint32_t submission_fence_count = 0; + while (swap_submission_completed_ + 1 + submission_fence_count <= + submission_awaited) { + assert_true(submission_fence_count < kSwapchainMaxImageCount); + uint32_t submission_index = + (swap_submission_completed_ + 1 + submission_fence_count) % + kSwapchainMaxImageCount; + submission_fences[submission_fence_count++] = + swap_submissions_[submission_index].fence; + } + if (submission_fence_count) { + if (dfn.vkWaitForFences(device, submission_fence_count, + submission_fences, VK_TRUE, + UINT64_MAX) != VK_SUCCESS) { + XELOGE("Failed to await the Vulkan presentation submission fences"); + return false; + } + swap_submission_completed_ += submission_fence_count; + } + } + + const SwapSubmission& submission = + swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount]; + if (dfn.vkResetCommandPool(device, submission.command_pool, 0) != + VK_SUCCESS) { + XELOGE("Failed to reset the Vulkan presentation command pool"); + return false; + } + + // After the image is acquired, this function must not fail before the + // semaphore has been signaled, and the image also must be returned to the + // swapchain. + uint32_t acquired_image_index; + switch (dfn.vkAcquireNextImageKHR(device, swap_swapchain_, UINT64_MAX, + swap_image_acquisition_semaphore_, + nullptr, &acquired_image_index)) { + case VK_SUCCESS: + case VK_SUBOPTIMAL_KHR: + // Not recreating in case of suboptimal, just to prevent a recreation + // loop in case the newly created swapchain is suboptimal too. + break; + case VK_ERROR_DEVICE_LOST: + context_lost_ = true; + return false; + case VK_ERROR_OUT_OF_DATE_KHR: + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: + swap_swapchain_or_surface_recreation_needed_ = true; + continue; + case VK_ERROR_SURFACE_LOST_KHR: + RequestSurfaceRecreation(); + continue; + default: + return false; + } + swap_swapchain_image_current_ = acquired_image_index; + + VkCommandBufferBeginInfo command_buffer_begin_info; + command_buffer_begin_info.sType = + VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + command_buffer_begin_info.pNext = nullptr; + command_buffer_begin_info.flags = + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + command_buffer_begin_info.pInheritanceInfo = nullptr; + dfn.vkBeginCommandBuffer(submission.command_buffer, + &command_buffer_begin_info); + VkClearValue clear_value; + GetClearColor(clear_value.color.float32); + VkRenderPassBeginInfo render_pass_begin_info; + render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_begin_info.pNext = nullptr; + render_pass_begin_info.renderPass = swap_render_pass_; + render_pass_begin_info.framebuffer = + swap_swapchain_framebuffers_[acquired_image_index]; + render_pass_begin_info.renderArea.offset.x = 0; + render_pass_begin_info.renderArea.offset.y = 0; + render_pass_begin_info.renderArea.extent = swap_swapchain_extent_; + render_pass_begin_info.clearValueCount = 1; + render_pass_begin_info.pClearValues = &clear_value; + dfn.vkCmdBeginRenderPass(submission.command_buffer, &render_pass_begin_info, + VK_SUBPASS_CONTENTS_INLINE); + + return true; + } + + // vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR even after + // recreation. + return false; +} + +void VulkanContext::EndSwap() { + if (!target_window_ || context_lost_) { + return; + } + assert_true(swap_swapchain_image_current_ != UINT32_MAX); + if (swap_swapchain_image_current_ == UINT32_MAX) { + return; + } + + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + VkQueue queue_graphics_compute = provider.queue_graphics_compute(); + + const SwapSubmission& submission = + swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount]; + dfn.vkCmdEndRenderPass(submission.command_buffer); + dfn.vkEndCommandBuffer(submission.command_buffer); + dfn.vkResetFences(device, 1, &submission.fence); + VkSubmitInfo submit_info; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = nullptr; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &swap_image_acquisition_semaphore_; + VkPipelineStageFlags image_acquisition_semaphore_wait_stage = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.pWaitDstStageMask = &image_acquisition_semaphore_wait_stage; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &submission.command_buffer; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &swap_render_completion_semaphore_; + VkResult submit_result = dfn.vkQueueSubmit(queue_graphics_compute, 1, + &submit_info, submission.fence); + if (submit_result != VK_SUCCESS) { + // If failed, can't even return the swapchain image - so treat all errors as + // context loss. + context_lost_ = true; + return; + } + ++swap_submission_current_; + + VkPresentInfoKHR present_info; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.pNext = nullptr; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &swap_render_completion_semaphore_; + present_info.swapchainCount = 1; + present_info.pSwapchains = &swap_swapchain_; + present_info.pImageIndices = &swap_swapchain_image_current_; + present_info.pResults = nullptr; + // FIXME(Triang3l): Allow a separate queue for present - see + // vulkan_provider.cc for details. + VkResult present_result = + dfn.vkQueuePresentKHR(queue_graphics_compute, &present_info); + swap_swapchain_image_current_ = UINT32_MAX; + switch (present_result) { + case VK_SUCCESS: + case VK_SUBOPTIMAL_KHR: + // Not recreating in case of suboptimal, just to prevent a recreation + // loop in case the newly created swapchain is suboptimal too. + break; + case VK_ERROR_OUT_OF_DATE_KHR: + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: + swap_swapchain_or_surface_recreation_needed_ = true; + return; + case VK_ERROR_SURFACE_LOST_KHR: + // Safe to await submission completion now - swap_submission_current_ has + // already been incremented to the next frame. + RequestSurfaceRecreation(); + return; + default: + // Treat any error as device loss since it would leave the semaphore + // forever signaled anyway, and the image won't be returned to the + // swapchain. + context_lost_ = true; + return; + } +} std::unique_ptr VulkanContext::Capture() { // TODO(Triang3l): Read back swap chain front buffer. return nullptr; } +void VulkanContext::RequestSurfaceRecreation() { +#if XE_PLATFORM_ANDROID + // The surface doesn't exist when the activity is in background. + swap_swapchain_or_surface_recreation_needed_ = + target_window_->native_handle() != nullptr; +#else + swap_swapchain_or_surface_recreation_needed_ = true; +#endif + if (swap_surface_ == VK_NULL_HANDLE) { + return; + } + AwaitAllSwapSubmissionsCompletion(); + DestroySwapchainFramebuffers(); + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::InstanceFunctions& ifn = provider.ifn(); + VkInstance instance = provider.instance(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device, + swap_swapchain_); + ifn.vkDestroySurfaceKHR(instance, swap_surface_, nullptr); + swap_surface_ = VK_NULL_HANDLE; +} + +void VulkanContext::AwaitAllSwapSubmissionsCompletion() { + assert_not_null(target_window_); + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + VkFence fences[kSwapchainMaxImageCount]; + uint32_t fence_count = 0; + while (swap_submission_completed_ + 1 < swap_submission_current_) { + assert_true(fence_count < kSwapchainMaxImageCount); + uint32_t submission_index = + ++swap_submission_completed_ % kSwapchainMaxImageCount; + fences[fence_count++] = swap_submissions_[submission_index].fence; + } + if (fence_count && !context_lost_) { + dfn.vkWaitForFences(device, fence_count, fences, VK_TRUE, UINT64_MAX); + } +} + +void VulkanContext::DestroySwapchainFramebuffers() { + assert_not_null(target_window_); + const VulkanProvider& provider = GetVulkanProvider(); + const VulkanProvider::DeviceFunctions& dfn = provider.dfn(); + VkDevice device = provider.device(); + for (VkFramebuffer framebuffer : swap_swapchain_framebuffers_) { + dfn.vkDestroyFramebuffer(device, framebuffer, nullptr); + } + swap_swapchain_framebuffers_.clear(); + for (VkImageView image_view : swap_swapchain_image_views_) { + dfn.vkDestroyImageView(device, image_view, nullptr); + } + swap_swapchain_image_views_.clear(); +} + } // namespace vulkan } // namespace ui } // namespace xe diff --git a/src/xenia/ui/vulkan/vulkan_context.h b/src/xenia/ui/vulkan/vulkan_context.h index 880e99561..477d4de17 100644 --- a/src/xenia/ui/vulkan/vulkan_context.h +++ b/src/xenia/ui/vulkan/vulkan_context.h @@ -10,7 +10,9 @@ #ifndef XENIA_UI_VULKAN_VULKAN_CONTEXT_H_ #define XENIA_UI_VULKAN_VULKAN_CONTEXT_H_ +#include #include +#include #include "xenia/ui/graphics_context.h" #include "xenia/ui/vulkan/vulkan_immediate_drawer.h" @@ -24,19 +26,22 @@ class VulkanContext : public GraphicsContext { public: ImmediateDrawer* immediate_drawer() override; - // Returns true if the OS took away our context because we caused a TDR or - // some other outstanding error. When this happens, this context, as well as - // any other shared contexts are junk. - // This context must be made current in order for this call to work properly. - bool WasLost() override { return false; } + bool WasLost() override; - void BeginSwap() override; + bool BeginSwap() override; void EndSwap() override; std::unique_ptr Capture() override; - VulkanProvider* GetVulkanProvider() const { - return static_cast(provider_); + VulkanProvider& GetVulkanProvider() const { + return static_cast(*provider_); + } + + void RequestSurfaceRecreation(); + + VkCommandBuffer GetSwapCommandBuffer() const { + return swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount] + .command_buffer; } private: @@ -45,6 +50,62 @@ class VulkanContext : public GraphicsContext { bool Initialize(); private: + void Shutdown(); + + void AwaitAllSwapSubmissionsCompletion(); + + // AwaitAllSwapSubmissionsCompletion must be called before. As this can be + // used in swapchain creation or in shutdown, + // swap_swapchain_or_surface_recreation_needed_ won't be set by this. + void DestroySwapchainFramebuffers(); + + bool context_lost_ = false; + + // Actual image count may be less, depending on what the surface can provide. + static constexpr uint32_t kSwapchainMaxImageCount = 3; + + // Because of the nature of Vulkan fences (that they belong only to their + // specific submission, not the submission and all prior submissions), ALL + // fences since the last completed submission to the needed submission should + // individually be checked, not just the last one. However, this submission + // number abstraction hides the loosely ordered design of Vulkan submissions + // (it's okay to wait first for completion of A, then of B, no matter if they + // are actually completed in AB or in BA order). + + struct SwapSubmission { + // One pool per frame, with resetting the pool itself rather than individual + // command buffers (resetting command buffers themselves is not recommended + // by Arm since it makes the pool unable to use a single big allocation), as + // recommended by Nvidia (Direct3D 12-like way): + // https://developer.nvidia.com/sites/default/files/akamai/gameworks/blog/munich/mschott_vulkan_multi_threading.pdf + VkFence fence = VK_NULL_HANDLE; + VkCommandPool command_pool = VK_NULL_HANDLE; + VkCommandBuffer command_buffer; + }; + SwapSubmission swap_submissions_[kSwapchainMaxImageCount]; + uint64_t swap_submission_current_ = 1; + uint64_t swap_submission_completed_ = 0; + + VkSemaphore swap_image_acquisition_semaphore_ = VK_NULL_HANDLE; + VkSemaphore swap_render_completion_semaphore_ = VK_NULL_HANDLE; + + VkSurfaceKHR swap_surface_ = VK_NULL_HANDLE; + VkSurfaceFormatKHR swap_surface_format_ = {VK_FORMAT_UNDEFINED, + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; + VkPresentModeKHR swap_surface_present_mode_; + VkRenderPass swap_render_pass_ = VK_NULL_HANDLE; + VkSwapchainKHR swap_swapchain_ = VK_NULL_HANDLE; + VkExtent2D swap_swapchain_extent_; + std::vector swap_swapchain_image_views_; + std::vector swap_swapchain_framebuffers_; + + uint32_t swap_swapchain_image_current_ = UINT32_MAX; + + // Attempts to recreate the swapchain will only be made in BeginSwap if this + // is true (set when something relevant is changed), so if creation fails, + // there won't be attempts every frame again and again. + bool swap_swapchain_or_surface_recreation_needed_ = false; + std::unique_ptr immediate_drawer_ = nullptr; }; diff --git a/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc b/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc index 3fc06ebd5..abd787f12 100644 --- a/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc +++ b/src/xenia/ui/vulkan/vulkan_immediate_drawer.cc @@ -9,14 +9,26 @@ #include "xenia/ui/vulkan/vulkan_immediate_drawer.h" +#include "xenia/ui/vulkan/vulkan_context.h" + namespace xe { namespace ui { namespace vulkan { +class VulkanImmediateTexture : public ImmediateTexture { + public: + VulkanImmediateTexture(uint32_t width, uint32_t height) + : ImmediateTexture(width, height) {} +}; + +VulkanImmediateDrawer::VulkanImmediateDrawer(VulkanContext& graphics_context) + : ImmediateDrawer(&graphics_context), context_(graphics_context) {} + std::unique_ptr VulkanImmediateDrawer::CreateTexture( uint32_t width, uint32_t height, ImmediateTextureFilter filter, bool repeat, const uint8_t* data) { - return nullptr; + auto texture = std::make_unique(width, height); + return std::unique_ptr(texture.release()); } void VulkanImmediateDrawer::UpdateTexture(ImmediateTexture* texture, diff --git a/src/xenia/ui/vulkan/vulkan_immediate_drawer.h b/src/xenia/ui/vulkan/vulkan_immediate_drawer.h index f51ffdd97..2e437ea25 100644 --- a/src/xenia/ui/vulkan/vulkan_immediate_drawer.h +++ b/src/xenia/ui/vulkan/vulkan_immediate_drawer.h @@ -16,8 +16,12 @@ namespace xe { namespace ui { namespace vulkan { +class VulkanContext; + class VulkanImmediateDrawer : public ImmediateDrawer { public: + VulkanImmediateDrawer(VulkanContext& graphics_context); + std::unique_ptr CreateTexture(uint32_t width, uint32_t height, ImmediateTextureFilter filter, @@ -30,6 +34,9 @@ class VulkanImmediateDrawer : public ImmediateDrawer { void Draw(const ImmediateDraw& draw) override; void EndDrawBatch() override; void End() override; + + private: + VulkanContext& context_; }; } // namespace vulkan diff --git a/src/xenia/ui/vulkan/vulkan_provider.cc b/src/xenia/ui/vulkan/vulkan_provider.cc index 0e8930eb2..1a9a94921 100644 --- a/src/xenia/ui/vulkan/vulkan_provider.cc +++ b/src/xenia/ui/vulkan/vulkan_provider.cc @@ -24,6 +24,12 @@ #include "xenia/base/platform_win.h" #endif +// TODO(Triang3l): Disable Vulkan validation before releasing a stable version. +DEFINE_bool( + vulkan_validation, true, + "Enable Vulkan validation (VK_LAYER_KHRONOS_validation). Messages will be " + "written to the OS debug log.", + "GPU"); DEFINE_int32( vulkan_device, -1, "Index of the physical device to use, or -1 for any compatible device.", @@ -55,10 +61,10 @@ VulkanProvider::VulkanProvider(Window* main_window) VulkanProvider::~VulkanProvider() { if (device_ != VK_NULL_HANDLE) { - ifn_.destroyDevice(device_, nullptr); + ifn_.vkDestroyDevice(device_, nullptr); } if (instance_ != VK_NULL_HANDLE) { - destroyInstance_(instance_, nullptr); + lfn_.vkDestroyInstance(instance_, nullptr); } #if XE_PLATFORM_LINUX @@ -74,6 +80,7 @@ VulkanProvider::~VulkanProvider() { bool VulkanProvider::Initialize() { // Load the library. + bool library_functions_loaded = true; #if XE_PLATFORM_LINUX #if XE_PLATFORM_ANDROID const char* libvulkan_name = "libvulkan.so"; @@ -86,61 +93,46 @@ bool VulkanProvider::Initialize() { XELOGE("Failed to load {}", libvulkan_name); return false; } - getInstanceProcAddr_ = - PFN_vkGetInstanceProcAddr(dlsym(library_, "vkGetInstanceProcAddr")); - destroyInstance_ = - PFN_vkDestroyInstance(dlsym(library_, "vkDestroyInstance")); - if (!getInstanceProcAddr_ || !destroyInstance_) { - XELOGE("Failed to get vkGetInstanceProcAddr and vkDestroyInstance from {}", - libvulkan_name); - return false; - } +#define XE_VULKAN_LOAD_MODULE_LFN(name) \ + library_functions_loaded &= \ + (lfn_.name = PFN_##name(dlsym(library_, #name))) != nullptr; #elif XE_PLATFORM_WIN32 library_ = LoadLibraryA("vulkan-1.dll"); if (!library_) { XELOGE("Failed to load vulkan-1.dll"); return false; } - getInstanceProcAddr_ = PFN_vkGetInstanceProcAddr( - GetProcAddress(library_, "vkGetInstanceProcAddr")); - destroyInstance_ = - PFN_vkDestroyInstance(GetProcAddress(library_, "vkDestroyInstance")); - if (!getInstanceProcAddr_ || !destroyInstance_) { - XELOGE( - "Failed to get vkGetInstanceProcAddr and vkDestroyInstance from " - "vulkan-1.dll"); - return false; - } +#define XE_VULKAN_LOAD_MODULE_LFN(name) \ + library_functions_loaded &= \ + (lfn_.name = PFN_##name(GetProcAddress(library_, #name))) != nullptr; #else #error No Vulkan library loading provided for the target platform. #endif - assert_not_null(getInstanceProcAddr_); - assert_not_null(destroyInstance_); - bool library_functions_loaded = true; - library_functions_loaded &= - (library_functions_.createInstance = PFN_vkCreateInstance( - getInstanceProcAddr_(VK_NULL_HANDLE, "vkCreateInstance"))) != - nullptr; - library_functions_loaded &= - (library_functions_.enumerateInstanceExtensionProperties = - PFN_vkEnumerateInstanceExtensionProperties(getInstanceProcAddr_( - VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"))) != - nullptr; + XE_VULKAN_LOAD_MODULE_LFN(vkGetInstanceProcAddr); + XE_VULKAN_LOAD_MODULE_LFN(vkDestroyInstance); +#undef XE_VULKAN_LOAD_MODULE_LFN if (!library_functions_loaded) { XELOGE("Failed to get Vulkan library function pointers"); return false; } - library_functions_.enumerateInstanceVersion_1_1 = - PFN_vkEnumerateInstanceVersion( - getInstanceProcAddr_(VK_NULL_HANDLE, "vkEnumerateInstanceVersion")); + library_functions_loaded &= + (lfn_.vkCreateInstance = PFN_vkCreateInstance(lfn_.vkGetInstanceProcAddr( + VK_NULL_HANDLE, "vkCreateInstance"))) != nullptr; + if (!library_functions_loaded) { + XELOGE( + "Failed to get Vulkan library function pointers via " + "vkGetInstanceProcAddr"); + return false; + } + lfn_.v_1_1.vkEnumerateInstanceVersion = PFN_vkEnumerateInstanceVersion( + lfn_.vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion")); // Get the API version. const uint32_t api_version_target = VK_MAKE_VERSION(1, 2, 148); static_assert(VK_HEADER_VERSION_COMPLETE >= api_version_target, "Vulkan header files must be up to date"); - if (!library_functions_.enumerateInstanceVersion_1_1 || - library_functions_.enumerateInstanceVersion_1_1(&api_version_) != - VK_SUCCESS) { + if (!lfn_.v_1_1.vkEnumerateInstanceVersion || + lfn_.v_1_1.vkEnumerateInstanceVersion(&api_version_) != VK_SUCCESS) { api_version_ = VK_API_VERSION_1_0; } XELOGVK("Vulkan instance version {}.{}.{}", VK_VERSION_MAJOR(api_version_), @@ -173,66 +165,59 @@ bool VulkanProvider::Initialize() { instance_create_info.pNext = nullptr; instance_create_info.flags = 0; instance_create_info.pApplicationInfo = &application_info; - // TODO(Triang3l): Enable the validation layer. - instance_create_info.enabledLayerCount = 0; - instance_create_info.ppEnabledLayerNames = nullptr; + static const char* validation_layer = "VK_LAYER_KHRONOS_validation"; + if (cvars::vulkan_validation) { + instance_create_info.enabledLayerCount = 1; + instance_create_info.ppEnabledLayerNames = &validation_layer; + } else { + instance_create_info.enabledLayerCount = 0; + instance_create_info.ppEnabledLayerNames = nullptr; + } instance_create_info.enabledExtensionCount = uint32_t(instance_extensions_enabled.size()); instance_create_info.ppEnabledExtensionNames = instance_extensions_enabled.data(); - if (library_functions_.createInstance(&instance_create_info, nullptr, - &instance_) != VK_SUCCESS) { - XELOGE("Failed to create a Vulkan instance with surface support"); - return false; + VkResult instance_create_result = + lfn_.vkCreateInstance(&instance_create_info, nullptr, &instance_); + if (instance_create_result != VK_SUCCESS) { + if (instance_create_result == VK_ERROR_LAYER_NOT_PRESENT) { + XELOGE("Failed to enable the Vulkan validation layer"); + instance_create_info.enabledLayerCount = 0; + instance_create_info.ppEnabledLayerNames = nullptr; + instance_create_result = + lfn_.vkCreateInstance(&instance_create_info, nullptr, &instance_); + } + if (instance_create_result != VK_SUCCESS) { + XELOGE("Failed to create a Vulkan instance with surface support"); + return false; + } } // Get instance functions. bool instance_functions_loaded = true; - instance_functions_loaded &= - (ifn_.createDevice = PFN_vkCreateDevice( - getInstanceProcAddr_(instance_, "vkCreateDevice"))) != nullptr; - instance_functions_loaded &= - (ifn_.destroyDevice = PFN_vkDestroyDevice( - getInstanceProcAddr_(instance_, "vkDestroyDevice"))) != nullptr; - instance_functions_loaded &= - (ifn_.enumerateDeviceExtensionProperties = - PFN_vkEnumerateDeviceExtensionProperties(getInstanceProcAddr_( - instance_, "vkEnumerateDeviceExtensionProperties"))) != nullptr; - instance_functions_loaded &= - (ifn_.enumeratePhysicalDevices = PFN_vkEnumeratePhysicalDevices( - getInstanceProcAddr_(instance_, "vkEnumeratePhysicalDevices"))) != - nullptr; - instance_functions_loaded &= - (ifn_.getDeviceProcAddr = PFN_vkGetDeviceProcAddr( - getInstanceProcAddr_(instance_, "vkGetDeviceProcAddr"))) != nullptr; - instance_functions_loaded &= - (ifn_.getPhysicalDeviceFeatures = PFN_vkGetPhysicalDeviceFeatures( - getInstanceProcAddr_(instance_, "vkGetPhysicalDeviceFeatures"))) != - nullptr; - instance_functions_loaded &= - (ifn_.getPhysicalDeviceProperties = PFN_vkGetPhysicalDeviceProperties( - getInstanceProcAddr_(instance_, "vkGetPhysicalDeviceProperties"))) != - nullptr; - instance_functions_loaded &= - (ifn_.getPhysicalDeviceQueueFamilyProperties = - PFN_vkGetPhysicalDeviceQueueFamilyProperties(getInstanceProcAddr_( - instance_, "vkGetPhysicalDeviceQueueFamilyProperties"))) != - nullptr; - instance_functions_loaded &= - (ifn_.getPhysicalDeviceSurfaceSupportKHR = - PFN_vkGetPhysicalDeviceSurfaceSupportKHR(getInstanceProcAddr_( - instance_, "vkGetPhysicalDeviceSurfaceSupportKHR"))) != nullptr; +#define XE_VULKAN_LOAD_IFN(name) \ + instance_functions_loaded &= \ + (ifn_.name = PFN_##name( \ + lfn_.vkGetInstanceProcAddr(instance_, #name))) != nullptr; + XE_VULKAN_LOAD_IFN(vkCreateDevice); + XE_VULKAN_LOAD_IFN(vkDestroyDevice); + XE_VULKAN_LOAD_IFN(vkDestroySurfaceKHR); + XE_VULKAN_LOAD_IFN(vkEnumerateDeviceExtensionProperties); + XE_VULKAN_LOAD_IFN(vkEnumeratePhysicalDevices); + XE_VULKAN_LOAD_IFN(vkGetDeviceProcAddr); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceFeatures); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceProperties); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceQueueFamilyProperties); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfaceFormatsKHR); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfacePresentModesKHR); + XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfaceSupportKHR); #if XE_PLATFORM_ANDROID - instance_functions_loaded &= - (ifn_.createAndroidSurfaceKHR = PFN_vkCreateAndroidSurfaceKHR( - getInstanceProcAddr_(instance_, "vkCreateAndroidSurfaceKHR"))) != - nullptr; + XE_VULKAN_LOAD_IFN(vkCreateAndroidSurfaceKHR); #elif XE_PLATFORM_WIN32 - instance_functions_loaded &= - (ifn_.createWin32SurfaceKHR = PFN_vkCreateWin32SurfaceKHR( - getInstanceProcAddr_(instance_, "vkCreateWin32SurfaceKHR"))) != - nullptr; + XE_VULKAN_LOAD_IFN(vkCreateWin32SurfaceKHR); #endif +#undef XE_VULKAN_LOAD_IFN if (!instance_functions_loaded) { XELOGE("Failed to get Vulkan instance function pointers"); return false; @@ -242,8 +227,8 @@ bool VulkanProvider::Initialize() { std::vector physical_devices; for (;;) { uint32_t physical_device_count = uint32_t(physical_devices.size()); - bool physical_devices_was_empty = physical_devices.empty(); - VkResult physical_device_enumerate_result = ifn_.enumeratePhysicalDevices( + bool physical_devices_was_empty = !physical_device_count; + VkResult physical_device_enumerate_result = ifn_.vkEnumeratePhysicalDevices( instance_, &physical_device_count, physical_devices_was_empty ? nullptr : physical_devices.data()); // If the original device count was 0 (first call), SUCCESS is returned, not @@ -288,7 +273,8 @@ bool VulkanProvider::Initialize() { VkPhysicalDevice physical_device_current = physical_devices[i]; // Get physical device features and check if the needed ones are supported. - ifn_.getPhysicalDeviceFeatures(physical_device_current, &device_features_); + ifn_.vkGetPhysicalDeviceFeatures(physical_device_current, + &device_features_); // TODO(Triang3l): Make geometry shaders optional by providing compute // shader fallback (though that would require vertex shader stores). if (!device_features_.geometryShader) { @@ -299,10 +285,10 @@ bool VulkanProvider::Initialize() { // (preferably the same for the least latency between the two, as Xenia // submits sparse binding commands right before graphics commands anyway). uint32_t queue_family_count = 0; - ifn_.getPhysicalDeviceQueueFamilyProperties(physical_device_current, - &queue_family_count, nullptr); + ifn_.vkGetPhysicalDeviceQueueFamilyProperties(physical_device_current, + &queue_family_count, nullptr); queue_families.resize(queue_family_count); - ifn_.getPhysicalDeviceQueueFamilyProperties( + ifn_.vkGetPhysicalDeviceQueueFamilyProperties( physical_device_current, &queue_family_count, queue_families.data()); assert_true(queue_family_count == queue_families.size()); queue_family_graphics_compute_ = UINT32_MAX; @@ -364,9 +350,9 @@ bool VulkanProvider::Initialize() { for (;;) { uint32_t device_extension_count = uint32_t(device_extension_properties.size()); - bool device_extensions_was_empty = device_extension_properties.empty(); + bool device_extensions_was_empty = !device_extension_count; device_extensions_enumerate_result = - ifn_.enumerateDeviceExtensionProperties( + ifn_.vkEnumerateDeviceExtensionProperties( physical_device_current, nullptr, &device_extension_count, device_extensions_was_empty ? nullptr : device_extension_properties.data()); @@ -411,7 +397,7 @@ bool VulkanProvider::Initialize() { "support"); return false; } - ifn_.getPhysicalDeviceProperties(physical_device_, &device_properties_); + ifn_.vkGetPhysicalDeviceProperties(physical_device_, &device_properties_); XELOGVK( "Vulkan device: {} (vendor {:04X}, device {:04X}, driver {:08X}, API " "{}.{}.{})", @@ -454,7 +440,7 @@ bool VulkanProvider::Initialize() { device_create_info.queueCreateInfoCount = separate_sparse_binding_queue ? 2 : 1; device_create_info.pQueueCreateInfos = queue_create_infos; - // TODO(Triang3l): Enable the validation layer. + // Device layers are deprecated - using validation layer on the instance. device_create_info.enabledLayerCount = 0; device_create_info.ppEnabledLayerNames = nullptr; device_create_info.enabledExtensionCount = @@ -462,29 +448,58 @@ bool VulkanProvider::Initialize() { device_create_info.ppEnabledExtensionNames = device_extensions_enabled.data(); // TODO(Triang3l): Enable only needed features. device_create_info.pEnabledFeatures = &device_features_; - if (ifn_.createDevice(physical_device_, &device_create_info, nullptr, - &device_) != VK_SUCCESS) { + if (ifn_.vkCreateDevice(physical_device_, &device_create_info, nullptr, + &device_) != VK_SUCCESS) { XELOGE("Failed to create a Vulkan device"); return false; } // Get device functions. bool device_functions_loaded = true; - device_functions_loaded &= - (dfn_.getDeviceQueue = PFN_vkGetDeviceQueue( - ifn_.getDeviceProcAddr(device_, "vkGetDeviceQueue"))) != nullptr; +#define XE_VULKAN_LOAD_DFN(name) \ + device_functions_loaded &= \ + (dfn_.name = PFN_##name(ifn_.vkGetDeviceProcAddr(device_, #name))) != \ + nullptr; + XE_VULKAN_LOAD_DFN(vkAcquireNextImageKHR); + XE_VULKAN_LOAD_DFN(vkAllocateCommandBuffers); + XE_VULKAN_LOAD_DFN(vkBeginCommandBuffer); + XE_VULKAN_LOAD_DFN(vkCmdBeginRenderPass); + XE_VULKAN_LOAD_DFN(vkCmdEndRenderPass); + XE_VULKAN_LOAD_DFN(vkCreateCommandPool); + XE_VULKAN_LOAD_DFN(vkCreateFence); + XE_VULKAN_LOAD_DFN(vkCreateFramebuffer); + XE_VULKAN_LOAD_DFN(vkCreateImageView); + XE_VULKAN_LOAD_DFN(vkCreateRenderPass); + XE_VULKAN_LOAD_DFN(vkCreateSemaphore); + XE_VULKAN_LOAD_DFN(vkCreateSwapchainKHR); + XE_VULKAN_LOAD_DFN(vkDestroyCommandPool); + XE_VULKAN_LOAD_DFN(vkDestroyFence); + XE_VULKAN_LOAD_DFN(vkDestroyFramebuffer); + XE_VULKAN_LOAD_DFN(vkDestroyImageView); + XE_VULKAN_LOAD_DFN(vkDestroyRenderPass); + XE_VULKAN_LOAD_DFN(vkDestroySemaphore); + XE_VULKAN_LOAD_DFN(vkDestroySwapchainKHR); + XE_VULKAN_LOAD_DFN(vkEndCommandBuffer); + XE_VULKAN_LOAD_DFN(vkGetDeviceQueue); + XE_VULKAN_LOAD_DFN(vkGetSwapchainImagesKHR); + XE_VULKAN_LOAD_DFN(vkResetCommandPool); + XE_VULKAN_LOAD_DFN(vkResetFences); + XE_VULKAN_LOAD_DFN(vkQueuePresentKHR); + XE_VULKAN_LOAD_DFN(vkQueueSubmit); + XE_VULKAN_LOAD_DFN(vkWaitForFences); +#undef XE_VULKAN_LOAD_DFN if (!device_functions_loaded) { XELOGE("Failed to get Vulkan device function pointers"); return false; } // Get the queues. - dfn_.getDeviceQueue(device_, queue_family_graphics_compute_, 0, - &queue_graphics_compute_); + dfn_.vkGetDeviceQueue(device_, queue_family_graphics_compute_, 0, + &queue_graphics_compute_); if (queue_family_sparse_binding != UINT32_MAX) { if (separate_sparse_binding_queue) { - dfn_.getDeviceQueue(device_, queue_family_sparse_binding, 0, - &queue_sparse_binding_); + dfn_.vkGetDeviceQueue(device_, queue_family_sparse_binding, 0, + &queue_sparse_binding_); } else { queue_sparse_binding_ = queue_graphics_compute_; } diff --git a/src/xenia/ui/vulkan/vulkan_provider.h b/src/xenia/ui/vulkan/vulkan_provider.h index f03e6d390..da4d56b80 100644 --- a/src/xenia/ui/vulkan/vulkan_provider.h +++ b/src/xenia/ui/vulkan/vulkan_provider.h @@ -24,7 +24,7 @@ #ifndef VK_USE_PLATFORM_WIN32_KHR #define VK_USE_PLATFORM_WIN32_KHR 1 #endif -#endif // XE_PLATFORM_WIN32 +#endif #ifndef VK_NO_PROTOTYPES #define VK_NO_PROTOTYPES 1 @@ -47,37 +47,45 @@ class VulkanProvider : public GraphicsProvider { Window* target_window) override; std::unique_ptr CreateOffscreenContext() override; - // Functions with a version suffix (like _1_1) are null when api_version() is - // below this version. - struct LibraryFunctions { - PFN_vkCreateInstance createInstance; - PFN_vkEnumerateInstanceExtensionProperties - enumerateInstanceExtensionProperties; - PFN_vkEnumerateInstanceVersion enumerateInstanceVersion_1_1; + // From the module. + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkDestroyInstance vkDestroyInstance; + // From vkGetInstanceProcAddr. + PFN_vkCreateInstance vkCreateInstance; + struct { + PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion; + } v_1_1; }; - const LibraryFunctions& library_functions() const { - return library_functions_; - } + const LibraryFunctions& lfn() const { return lfn_; } uint32_t api_version() const { return api_version_; } VkInstance instance() const { return instance_; } struct InstanceFunctions { - PFN_vkCreateDevice createDevice; - PFN_vkDestroyDevice destroyDevice; - PFN_vkEnumerateDeviceExtensionProperties enumerateDeviceExtensionProperties; - PFN_vkEnumeratePhysicalDevices enumeratePhysicalDevices; - PFN_vkGetDeviceProcAddr getDeviceProcAddr; - PFN_vkGetPhysicalDeviceFeatures getPhysicalDeviceFeatures; - PFN_vkGetPhysicalDeviceProperties getPhysicalDeviceProperties; + PFN_vkCreateDevice vkCreateDevice; + PFN_vkDestroyDevice vkDestroyDevice; + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; + PFN_vkEnumerateDeviceExtensionProperties + vkEnumerateDeviceExtensionProperties; + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures; + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; PFN_vkGetPhysicalDeviceQueueFamilyProperties - getPhysicalDeviceQueueFamilyProperties; - PFN_vkGetPhysicalDeviceSurfaceSupportKHR getPhysicalDeviceSurfaceSupportKHR; + vkGetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR + vkGetPhysicalDeviceSurfaceCapabilitiesKHR; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR + vkGetPhysicalDeviceSurfaceFormatsKHR; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR + vkGetPhysicalDeviceSurfacePresentModesKHR; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR + vkGetPhysicalDeviceSurfaceSupportKHR; #if XE_PLATFORM_ANDROID - PFN_vkCreateAndroidSurfaceKHR createAndroidSurfaceKHR; + PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; #elif XE_PLATFORM_WIN32 - PFN_vkCreateWin32SurfaceKHR createWin32SurfaceKHR; + PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR; #endif }; const InstanceFunctions& ifn() const { return ifn_; } @@ -103,7 +111,33 @@ class VulkanProvider : public GraphicsProvider { VkDevice device() const { return device_; } struct DeviceFunctions { - PFN_vkGetDeviceQueue getDeviceQueue; + PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; + PFN_vkBeginCommandBuffer vkBeginCommandBuffer; + PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass; + PFN_vkCmdEndRenderPass vkCmdEndRenderPass; + PFN_vkCreateCommandPool vkCreateCommandPool; + PFN_vkCreateFence vkCreateFence; + PFN_vkCreateFramebuffer vkCreateFramebuffer; + PFN_vkCreateImageView vkCreateImageView; + PFN_vkCreateRenderPass vkCreateRenderPass; + PFN_vkCreateSemaphore vkCreateSemaphore; + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; + PFN_vkDestroyCommandPool vkDestroyCommandPool; + PFN_vkDestroyFence vkDestroyFence; + PFN_vkDestroyFramebuffer vkDestroyFramebuffer; + PFN_vkDestroyImageView vkDestroyImageView; + PFN_vkDestroyRenderPass vkDestroyRenderPass; + PFN_vkDestroySemaphore vkDestroySemaphore; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; + PFN_vkEndCommandBuffer vkEndCommandBuffer; + PFN_vkGetDeviceQueue vkGetDeviceQueue; + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; + PFN_vkResetCommandPool vkResetCommandPool; + PFN_vkResetFences vkResetFences; + PFN_vkQueuePresentKHR vkQueuePresentKHR; + PFN_vkQueueSubmit vkQueueSubmit; + PFN_vkWaitForFences vkWaitForFences; }; const DeviceFunctions& dfn() const { return dfn_; } @@ -122,9 +156,7 @@ class VulkanProvider : public GraphicsProvider { HMODULE library_ = nullptr; #endif - PFN_vkGetInstanceProcAddr getInstanceProcAddr_ = nullptr; - PFN_vkDestroyInstance destroyInstance_ = nullptr; - LibraryFunctions library_functions_ = {}; + LibraryFunctions lfn_ = {}; uint32_t api_version_ = VK_API_VERSION_1_0; diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index 8be8900c8..1273b61f0 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -200,11 +200,15 @@ void Window::OnPaint(UIEvent* e) { io.DisplaySize = ImVec2(static_cast(scaled_width()), static_cast(scaled_height())); - context_->BeginSwap(); + bool can_swap = context_->BeginSwap(); if (context_->WasLost()) { on_context_lost(e); return; } + if (!can_swap) { + // Surface not available. + return; + } ImGui::NewFrame(); diff --git a/third_party/volk b/third_party/volk deleted file mode 160000 index 30a851b67..000000000 --- a/third_party/volk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 30a851b67e129a3d91f191b2e9dcdad65ba98438 diff --git a/third_party/volk.lua b/third_party/volk.lua deleted file mode 100644 index 7ba0dd618..000000000 --- a/third_party/volk.lua +++ /dev/null @@ -1,30 +0,0 @@ -group("third_party") -project("volk") - uuid("C9781C93-2DF5-47A2-94EE-2C5EBED61239") - kind("StaticLib") - language("C") - - defines({ - "_LIB", - "API_NAME=\"vulkan\"", - }) - removedefines({ - "_UNICODE", - "UNICODE", - }) - includedirs({ - "volk", - }) - files({ - "volk/volk.c", - "volk/volk.h", - }) - - filter("platforms:Windows") - defines({ - "VK_USE_PLATFORM_WIN32_KHR", - }) - filter("platforms:Linux") - defines({ - "VK_USE_PLATFORM_XCB_KHR", - })