diff options
| author | Stenzek <stenzek@gmail.com> | 2016-08-13 22:57:50 +1000 |
|---|---|---|
| committer | Stenzek <stenzek@gmail.com> | 2016-10-01 02:40:01 +1000 |
| commit | 77a128ab8707659a531029a21a2e874108eedf38 (patch) | |
| tree | 7b4a52ee61208eec2bf44a7745f3dac3bed1bafb /Source/Core/VideoBackends/Vulkan/SwapChain.cpp | |
| parent | fdd954e7e757b4601eb6d2636e12623aef63c9b4 (diff) | |
Implement experimental Vulkan backend
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/SwapChain.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/Vulkan/SwapChain.cpp | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp new file mode 100644 index 0000000000..aab4f6c330 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp @@ -0,0 +1,496 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstdint> + +#include "Common/Assert.h" +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/SwapChain.h" +#include "VideoBackends/Vulkan/VulkanContext.h" + +#if defined(VK_USE_PLATFORM_XLIB_KHR) +#include <X11/Xlib.h> +#elif defined(VK_USE_PLATFORM_XCB_KHR) +#include <X11/Xlib-xcb.h> +#include <X11/Xlib.h> +#endif + +namespace Vulkan +{ +SwapChain::SwapChain(void* native_handle, VkSurfaceKHR surface) + : m_native_handle(native_handle), m_surface(surface) +{ +} + +SwapChain::~SwapChain() +{ + DestroySwapChainImages(); + DestroySwapChain(); + DestroyRenderPass(); + DestroySurface(); +} + +VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, void* hwnd) +{ +#if defined(VK_USE_PLATFORM_WIN32_KHR) + VkWin32SurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkWin32SurfaceCreateFlagsKHR flags + nullptr, // HINSTANCE hinstance + reinterpret_cast<HWND>(hwnd) // HWND hwnd + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + // Assuming the display handles are compatible, or shared. This matches what we do in the + // GL backend, but it's not ideal. + Display* display = XOpenDisplay(nullptr); + + VkXlibSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkXlibSurfaceCreateFlagsKHR flags + display, // Display* dpy + reinterpret_cast<Window>(hwnd) // Window window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + +#elif defined(VK_USE_PLATFORM_XCB_KHR) + // If we ever switch to using xcb, we should pass the display handle as well. + Display* display = XOpenDisplay(nullptr); + xcb_connection_t* connection = XGetXCBConnection(display); + + VkXcbSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkXcbSurfaceCreateFlagsKHR flags + connection, // xcb_connection_t* connection + static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(hwnd)) // xcb_window_t window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateXcbSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateXcbSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + VkAndroidSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkAndroidSurfaceCreateFlagsKHR flags + reinterpret_cast<ANativeWindow*>(hwnd) // ANativeWindow* window + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + +#else + return VK_NULL_HANDLE; +#endif +} + +std::unique_ptr<SwapChain> SwapChain::Create(void* native_handle, VkSurfaceKHR surface) +{ + std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(native_handle, surface); + + if (!swap_chain->CreateSwapChain() || !swap_chain->CreateRenderPass() || + !swap_chain->SetupSwapChainImages()) + { + return nullptr; + } + + return swap_chain; +} + +bool SwapChain::SelectSurfaceFormat() +{ + u32 format_count; + VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), + m_surface, &format_count, nullptr); + if (res != VK_SUCCESS || format_count == 0) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + return false; + } + + std::vector<VkSurfaceFormatKHR> surface_formats(format_count); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, + &format_count, surface_formats.data()); + _assert_(res == VK_SUCCESS); + + // If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA + if (surface_formats[0].format == VK_FORMAT_UNDEFINED) + { + m_surface_format.format = VK_FORMAT_R8G8B8A8_UNORM; + m_surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + return true; + } + + // Use the first surface format, just use what it prefers. + // Some drivers seem to return a SRGB format here (Intel Mesa). + // This results in gamma correction when presenting to the screen, which we don't want. + // Use a linear format instead, if this is the case. + m_surface_format.format = Util::GetLinearFormat(surface_formats[0].format); + m_surface_format.colorSpace = surface_formats[0].colorSpace; + return true; +} + +bool SwapChain::SelectPresentMode() +{ + VkResult res; + u32 mode_count; + res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, + &mode_count, nullptr); + if (res != VK_SUCCESS || mode_count == 0) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: "); + return false; + } + + std::vector<VkPresentModeKHR> present_modes(mode_count); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(g_vulkan_context->GetPhysicalDevice(), m_surface, + &mode_count, present_modes.data()); + _assert_(res == VK_SUCCESS); + + // Checks if a particular mode is supported, if it is, returns that mode. + auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) { + auto it = std::find_if(present_modes.begin(), present_modes.end(), + [check_mode](VkPresentModeKHR mode) { return check_mode == mode; }); + return it != present_modes.end(); + }; + + // If vsync is enabled, prefer VK_PRESENT_MODE_FIFO_KHR. + if (g_ActiveConfig.IsVSync()) + { + // Try for relaxed vsync first, since it's likely the VI won't line up with + // the refresh rate of the system exactly, so tearing once is better than + // waiting for the next vblank. + if (CheckForMode(VK_PRESENT_MODE_FIFO_RELAXED_KHR)) + { + m_present_mode = VK_PRESENT_MODE_FIFO_RELAXED_KHR; + return true; + } + + // Fall back to strict vsync. + if (CheckForMode(VK_PRESENT_MODE_FIFO_KHR)) + { + WARN_LOG(VIDEO, "Vulkan: FIFO_RELAXED not available, falling back to FIFO."); + m_present_mode = VK_PRESENT_MODE_FIFO_KHR; + return true; + } + } + + // Prefer screen-tearing, if possible, for lowest latency. + if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR)) + { + m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; + return true; + } + + // Use optimized-vsync above vsync. + if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR)) + { + m_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; + return true; + } + + // Fall back to whatever is available. + m_present_mode = present_modes[0]; + return true; +} + +bool SwapChain::CreateRenderPass() +{ + // render pass for rendering to the swap chain + VkAttachmentDescription present_render_pass_attachments[] = { + {0, m_surface_format.format, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_CLEAR, + VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; + + VkAttachmentReference present_render_pass_color_attachment_references[] = { + {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}}; + + VkSubpassDescription present_render_pass_subpass_descriptions[] = { + {0, VK_PIPELINE_BIND_POINT_GRAPHICS, 0, nullptr, 1, + present_render_pass_color_attachment_references, nullptr, nullptr, 0, nullptr}}; + + VkRenderPassCreateInfo present_render_pass_info = { + VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + nullptr, + 0, + static_cast<u32>(ArraySize(present_render_pass_attachments)), + present_render_pass_attachments, + static_cast<u32>(ArraySize(present_render_pass_subpass_descriptions)), + present_render_pass_subpass_descriptions, + 0, + nullptr}; + + VkResult res = vkCreateRenderPass(g_vulkan_context->GetDevice(), &present_render_pass_info, + nullptr, &m_render_pass); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateRenderPass (present) failed: "); + return false; + } + + return true; +} + +void SwapChain::DestroyRenderPass() +{ + if (!m_render_pass) + return; + + g_command_buffer_mgr->DeferResourceDestruction(m_render_pass); + m_render_pass = nullptr; +} + +bool SwapChain::CreateSwapChain() +{ + // Look up surface properties to determine image count and dimensions + VkSurfaceCapabilitiesKHR surface_capabilities; + VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkan_context->GetPhysicalDevice(), + m_surface, &surface_capabilities); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: "); + return false; + } + + // Select swap chain format and present mode + if (!SelectSurfaceFormat() || !SelectPresentMode()) + return false; + + // Select number of images in swap chain, we prefer one buffer in the background to work on + uint32_t image_count = + std::min(surface_capabilities.minImageCount + 1, surface_capabilities.maxImageCount); + + // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here + // determines window size? + VkExtent2D size = surface_capabilities.currentExtent; + if (size.width == UINT32_MAX) + { + size.width = std::min(std::max(surface_capabilities.minImageExtent.width, 640u), + surface_capabilities.maxImageExtent.width); + size.height = std::min(std::max(surface_capabilities.minImageExtent.height, 480u), + surface_capabilities.maxImageExtent.height); + } + + // Prefer identity transform if possible + VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) + transform = surface_capabilities.currentTransform; + + // Select swap chain flags, we only need a colour attachment + VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) + { + ERROR_LOG(VIDEO, "Vulkan: Swap chain does not support usage as color attachment"); + return false; + } + + // Store the old/current swap chain when recreating for resize + VkSwapchainKHR old_swap_chain = m_swap_chain; + + // Now we can actually create the swap chain + // TODO: Handle case where the present queue is not the graphics queue. + VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + nullptr, + 0, + m_surface, + image_count, + m_surface_format.format, + m_surface_format.colorSpace, + size, + 1, + image_usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, + nullptr, + transform, + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + m_present_mode, + VK_TRUE, + old_swap_chain}; + + res = + vkCreateSwapchainKHR(g_vulkan_context->GetDevice(), &swap_chain_info, nullptr, &m_swap_chain); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: "); + return false; + } + + // Now destroy the old swap chain, since it's been recreated. + // We can do this immediately since all work should have been completed before calling resize. + if (old_swap_chain != VK_NULL_HANDLE) + vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), old_swap_chain, nullptr); + + m_width = size.width; + m_height = size.height; + return true; +} + +bool SwapChain::SetupSwapChainImages() +{ + _assert_(m_swap_chain_images.empty()); + + uint32_t image_count; + VkResult res = + vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, nullptr); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: "); + return false; + } + + std::vector<VkImage> images(image_count); + res = vkGetSwapchainImagesKHR(g_vulkan_context->GetDevice(), m_swap_chain, &image_count, + images.data()); + _assert_(res == VK_SUCCESS); + + m_swap_chain_images.reserve(image_count); + for (uint32_t i = 0; i < image_count; i++) + { + SwapChainImage image; + image.image = images[i]; + + // Create texture object, which creates a view of the backbuffer + image.texture = Texture2D::CreateFromExistingImage( + m_width, m_height, 1, 1, m_surface_format.format, VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_VIEW_TYPE_2D, image.image); + + VkImageView view = image.texture->GetView(); + VkFramebufferCreateInfo framebuffer_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + nullptr, + 0, + m_render_pass, + 1, + &view, + m_width, + m_height, + 1}; + + res = vkCreateFramebuffer(g_vulkan_context->GetDevice(), &framebuffer_info, nullptr, + &image.framebuffer); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateFramebuffer failed: "); + return false; + } + + m_swap_chain_images.emplace_back(std::move(image)); + } + + return true; +} + +void SwapChain::DestroySwapChainImages() +{ + for (const auto& it : m_swap_chain_images) + { + // Images themselves are cleaned up by the swap chain object + vkDestroyFramebuffer(g_vulkan_context->GetDevice(), it.framebuffer, nullptr); + } + m_swap_chain_images.clear(); +} + +void SwapChain::DestroySwapChain() +{ + if (m_swap_chain == VK_NULL_HANDLE) + return; + + vkDestroySwapchainKHR(g_vulkan_context->GetDevice(), m_swap_chain, nullptr); + m_swap_chain = VK_NULL_HANDLE; +} + +VkResult SwapChain::AcquireNextImage(VkSemaphore available_semaphore) +{ + VkResult res = + vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, + available_semaphore, VK_NULL_HANDLE, &m_current_swap_chain_image_index); + if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) + LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: "); + + return res; +} + +bool SwapChain::ResizeSwapChain() +{ + if (!CreateSwapChain()) + return false; + + DestroySwapChainImages(); + if (!SetupSwapChainImages()) + { + PanicAlert("Failed to re-configure swap chain images, this is fatal (for now)"); + return false; + } + + return true; +} + +bool SwapChain::RecreateSurface(void* native_handle) +{ + // Destroy the old swap chain, images, and surface. + DestroyRenderPass(); + DestroySwapChainImages(); + DestroySwapChain(); + DestroySurface(); + + // Re-create the surface with the new native handle + m_native_handle = native_handle; + m_surface = CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), native_handle); + if (m_surface == VK_NULL_HANDLE) + return false; + + // Finally re-create the swap chain + if (!CreateSwapChain() || !SetupSwapChainImages() || !CreateRenderPass()) + return false; + + return true; +} + +void SwapChain::DestroySurface() +{ + vkDestroySurfaceKHR(g_vulkan_context->GetVulkanInstance(), m_surface, nullptr); + m_surface = VK_NULL_HANDLE; +} +} |
