diff --git a/src/xenia/ui/vk/vulkan_context.cc b/src/xenia/ui/vk/vulkan_context.cc index a9e677e80..bfb41d6e1 100644 --- a/src/xenia/ui/vk/vulkan_context.cc +++ b/src/xenia/ui/vk/vulkan_context.cc @@ -9,7 +9,9 @@ #include "xenia/ui/vk/vulkan_context.h" +#include "xenia/base/logging.h" #include "xenia/ui/vk/vulkan_immediate_drawer.h" +#include "xenia/ui/vk/vulkan_util.h" namespace xe { namespace ui { @@ -21,8 +23,31 @@ VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window) VulkanContext::~VulkanContext() { Shutdown(); } bool VulkanContext::Initialize() { + auto device = GetVulkanProvider()->GetDevice(); + context_lost_ = false; + current_frame_ = 1; + // No frames have been completed yet. + last_completed_frame_ = 0; + // Keep in sync with the modulo because why not. + current_queue_frame_ = 1; + + // Create fences for synchronization of reuse and destruction of transient + // objects (like command buffers) and for global shutdown. + 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; + for (uint32_t i = 0; i < kQueuedFrames; ++i) { + if (vkCreateFence(device, &fence_create_info, nullptr, &fences_[i]) != + VK_SUCCESS) { + XELOGE("Failed to create a Vulkan fence"); + Shutdown(); + return false; + } + } + if (target_window_) { // Initialize the immediate mode drawer if not offscreen. immediate_drawer_ = std::make_unique(this); @@ -36,9 +61,19 @@ bool VulkanContext::Initialize() { } void VulkanContext::Shutdown() { + auto device = GetVulkanProvider()->GetDevice(); + + if (initialized_fully_ && !context_lost_) { + AwaitAllFramesCompletion(); + } + initialized_fully_ = false; immediate_drawer_.reset(); + + for (uint32_t i = 0; i < kQueuedFrames; ++i) { + util::DestroyAndNullHandle(vkDestroyFence, device, fences_[i]); + } } ImmediateDrawer* VulkanContext::immediate_drawer() { @@ -51,15 +86,70 @@ bool VulkanContext::MakeCurrent() { return true; } void VulkanContext::ClearCurrent() {} -void VulkanContext::BeginSwap() {} +void VulkanContext::BeginSwap() { + if (context_lost_) { + return; + } -void VulkanContext::EndSwap() {} + auto device = GetVulkanProvider()->GetDevice(); + + // Await the availability of transient objects for the new frame. + // The frame number is incremented in EndSwap so it can be treated the same + // way both when inside a frame and when outside of it (it's tied to actual + // submissions). + if (vkWaitForFences(device, 1, &fences_[current_queue_frame_], VK_TRUE, + UINT64_MAX) != VK_SUCCESS) { + context_lost_ = true; + return; + } + // Update the completed frame if didn't explicitly await all queued frames. + if (last_completed_frame_ + kQueuedFrames < current_frame_) { + last_completed_frame_ = current_frame_ - kQueuedFrames; + } +} + +void VulkanContext::EndSwap() { + if (context_lost_) { + return; + } + + // Go to the next transient object frame. + auto queue = GetVulkanProvider()->GetGraphicsQueue(); + if (vkQueueSubmit(queue, 0, nullptr, fences_[current_queue_frame_]) != + VK_SUCCESS) { + context_lost_ = true; + return; + } + ++current_queue_frame_; + if (current_queue_frame_ >= kQueuedFrames) { + current_queue_frame_ -= kQueuedFrames; + } + ++current_frame_; +} std::unique_ptr VulkanContext::Capture() { // TODO(Triang3l): Read back swap chain front buffer. return nullptr; } +void VulkanContext::AwaitAllFramesCompletion() { + // Await the last frame since previous frames must be completed before it. + if (context_lost_) { + return; + } + auto device = GetVulkanProvider()->GetDevice(); + uint32_t await_frame = current_queue_frame_ + (kQueuedFrames - 1); + if (await_frame >= kQueuedFrames) { + await_frame -= kQueuedFrames; + } + if (vkWaitForFences(device, 1, &fences_[await_frame], VK_TRUE, UINT64_MAX) != + VK_SUCCESS) { + context_lost_ = true; + return; + } + last_completed_frame_ = current_frame_ - 1; +} + } // namespace vk } // namespace ui } // namespace xe diff --git a/src/xenia/ui/vk/vulkan_context.h b/src/xenia/ui/vk/vulkan_context.h index 1c396c176..c3d960517 100644 --- a/src/xenia/ui/vk/vulkan_context.h +++ b/src/xenia/ui/vk/vulkan_context.h @@ -39,6 +39,20 @@ class VulkanContext : public GraphicsContext { std::unique_ptr Capture() override; + VulkanProvider* GetVulkanProvider() const { + return static_cast(provider_); + } + + // The count of copies of transient objects (like command buffers, dynamic + // descriptor pools) that must be kept when rendering with this context. + static constexpr uint32_t kQueuedFrames = 3; + // The current absolute frame number. + uint64_t GetCurrentFrame() { return current_frame_; } + // The last completed frame - it's fine to destroy objects used in it. + uint64_t GetLastCompletedFrame() { return last_completed_frame_; } + uint32_t GetCurrentQueueFrame() { return current_queue_frame_; } + void AwaitAllFramesCompletion(); + private: friend class VulkanProvider; @@ -52,6 +66,11 @@ class VulkanContext : public GraphicsContext { bool context_lost_ = false; + uint64_t current_frame_ = 1; + uint64_t last_completed_frame_ = 0; + uint32_t current_queue_frame_ = 1; + VkFence fences_[kQueuedFrames] = {}; + std::unique_ptr immediate_drawer_ = nullptr; }; diff --git a/src/xenia/ui/vk/vulkan_provider.cc b/src/xenia/ui/vk/vulkan_provider.cc index 1b3985e3a..b3d4a799b 100644 --- a/src/xenia/ui/vk/vulkan_provider.cc +++ b/src/xenia/ui/vk/vulkan_provider.cc @@ -59,7 +59,7 @@ VulkanProvider::~VulkanProvider() { bool VulkanProvider::Initialize() { if (volkInitialize() != VK_SUCCESS) { - XELOGE("Failed to initialize the Vulkan loader volk."); + XELOGE("Failed to initialize the Vulkan loader volk"); return false; } @@ -101,7 +101,7 @@ bool VulkanProvider::Initialize() { instance_create_info.ppEnabledExtensionNames = instance_extensions; if (vkCreateInstance(&instance_create_info, nullptr, &instance_) != VK_SUCCESS) { - XELOGE("Failed to create a Vulkan instance."); + XELOGE("Failed to create a Vulkan instance"); return false; } volkLoadInstance(instance_); @@ -112,13 +112,13 @@ bool VulkanProvider::Initialize() { uint32_t physical_device_count; if (vkEnumeratePhysicalDevices(instance_, &physical_device_count, nullptr) != VK_SUCCESS) { - XELOGE("Failed to get Vulkan physical device count."); + XELOGE("Failed to get Vulkan physical device count"); return false; } physical_devices.resize(physical_device_count); if (vkEnumeratePhysicalDevices(instance_, &physical_device_count, physical_devices.data()) != VK_SUCCESS) { - XELOGE("Failed to get Vulkan physical devices."); + XELOGE("Failed to get Vulkan physical devices"); return false; } physical_devices.resize(physical_device_count); @@ -215,7 +215,7 @@ bool VulkanProvider::Initialize() { break; } if (physical_device_ == VK_NULL_HANDLE) { - XELOGE("Failed to get a supported Vulkan physical device."); + XELOGE("Failed to get a supported Vulkan physical device"); return false; } // TODO(Triang3l): Check if VK_EXT_fragment_shader_interlock and @@ -254,9 +254,10 @@ bool VulkanProvider::Initialize() { device_create_info.pEnabledFeatures = nullptr; if (vkCreateDevice(physical_device_, &device_create_info, nullptr, &device_) != VK_SUCCESS) { - XELOGE("Failed to create a Vulkan device."); + XELOGE("Failed to create a Vulkan device"); return false; } + volkLoadDevice(device_); vkGetDeviceQueue(device_, queue_family, 0, &graphics_queue_); return true; diff --git a/src/xenia/ui/vk/vulkan_provider.h b/src/xenia/ui/vk/vulkan_provider.h index 33d459bef..c4723db3a 100644 --- a/src/xenia/ui/vk/vulkan_provider.h +++ b/src/xenia/ui/vk/vulkan_provider.h @@ -34,6 +34,12 @@ class VulkanProvider : public GraphicsProvider { Window* target_window) override; std::unique_ptr CreateOffscreenContext() override; + const VkPhysicalDeviceFeatures& GetPhysicalDeviceFeatures() const { + return physical_device_features_; + } + VkDevice GetDevice() const { return device_; } + VkQueue GetGraphicsQueue() const { return graphics_queue_; } + private: explicit VulkanProvider(Window* main_window); diff --git a/src/xenia/ui/vk/vulkan_util.h b/src/xenia/ui/vk/vulkan_util.h index 37e74c560..f334b6619 100644 --- a/src/xenia/ui/vk/vulkan_util.h +++ b/src/xenia/ui/vk/vulkan_util.h @@ -27,6 +27,17 @@ inline bool DestroyAndNullHandle(F* destroy_function, T& handle) { return false; } +template +inline bool DestroyAndNullHandle(F* destroy_function, VkDevice device, + T& handle) { + if (handle != VK_NULL_HANDLE) { + destroy_function(device, handle, nullptr); + handle = VK_NULL_HANDLE; + return true; + } + return false; +} + } // namespace util } // namespace vk } // namespace ui