diff --git a/src/vulkan/meson.build b/src/vulkan/meson.build index 0558c868..82635523 100644 --- a/src/vulkan/meson.build +++ b/src/vulkan/meson.build @@ -1,6 +1,7 @@ vkcommon_src = files([ 'vulkan_loader.cpp', 'vulkan_names.cpp', + 'vulkan_presenter.cpp', ]) thread_dep = dependency('threads') diff --git a/src/vulkan/vulkan_presenter.cpp b/src/vulkan/vulkan_presenter.cpp new file mode 100644 index 00000000..e5b37f8b --- /dev/null +++ b/src/vulkan/vulkan_presenter.cpp @@ -0,0 +1,389 @@ +#include "vulkan_presenter.h" + +#include "../dxvk/dxvk_format.h" + +namespace dxvk::vk { + + Presenter::Presenter( + HWND window, + const Rc& vki, + const Rc& vkd, + PresenterDevice device, + const PresenterDesc& desc) + : m_vki(vki), m_vkd(vkd), m_device(device) { + if (createSurface(window) != VK_SUCCESS) + throw DxvkError("Failed to create surface"); + + if (recreateSwapChain(desc) != VK_SUCCESS) + throw DxvkError("Failed to create swap chain"); + } + + + Presenter::~Presenter() { + destroySwapchain(); + destroySurface(); + } + + + PresenterInfo Presenter::info() const { + return m_info; + } + + + PresenterSync Presenter::getSyncSemaphores() const { + return m_semaphores.at(m_frameIndex); + } + + + PresenterImage Presenter::getImage(uint32_t index) const { + return m_images.at(index); + } + + + VkResult Presenter::acquireNextImage( + VkSemaphore signal, + uint32_t& index) { + VkResult status = m_vkd->vkAcquireNextImageKHR( + m_vkd->device(), m_swapchain, + std::numeric_limits::max(), + signal, VK_NULL_HANDLE, &m_imageIndex); + + if (status != VK_SUCCESS + && status != VK_SUBOPTIMAL_KHR) + return status; + + m_frameIndex += 1; + m_frameIndex %= m_semaphores.size(); + + index = m_imageIndex; + return status; + } + + + VkResult Presenter::presentImage(VkSemaphore wait) { + VkPresentInfoKHR info; + info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + info.pNext = nullptr; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &wait; + info.swapchainCount = 1; + info.pSwapchains = &m_swapchain; + info.pImageIndices = &m_imageIndex; + info.pResults = nullptr; + + return m_vkd->vkQueuePresentKHR(m_device.queue, &info); + } + + + VkResult Presenter::recreateSwapChain(const PresenterDesc& desc) { + if (m_swapchain) + destroySwapchain(); + + // Query surface capabilities. Some properties might + // have changed, including the size limits and supported + // present modes, so we'll just query everything again. + VkSurfaceCapabilitiesKHR caps; + std::vector formats; + std::vector modes; + + VkResult status; + + if ((status = m_vki->vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + m_device.adapter, m_surface, &caps)) != VK_SUCCESS) + return status; + + if ((status = getSupportedFormats(formats)) != VK_SUCCESS) + return status; + + if ((status = getSupportedPresentModes(modes)) != VK_SUCCESS) + return status; + + // Select actual swap chain properties and create swap chain + m_info.format = pickFormat(formats.size(), formats.data(), desc.numFormats, desc.formats); + m_info.presentMode = pickPresentMode(modes.size(), modes.data(), desc.numPresentModes, desc.presentModes); + m_info.imageExtent = pickImageExtent(caps, desc.imageExtent); + m_info.imageCount = pickImageCount(caps, m_info.presentMode, desc.imageCount); + + VkSwapchainCreateInfoKHR swapInfo; + swapInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapInfo.pNext = nullptr; + swapInfo.flags = 0; + swapInfo.surface = m_surface; + swapInfo.minImageCount = m_info.imageCount; + swapInfo.imageFormat = m_info.format.format; + swapInfo.imageColorSpace = m_info.format.colorSpace; + swapInfo.imageExtent = m_info.imageExtent; + swapInfo.imageArrayLayers = 1; + swapInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT + | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + swapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapInfo.queueFamilyIndexCount = 0; + swapInfo.pQueueFamilyIndices = nullptr; + swapInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + swapInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapInfo.presentMode = m_info.presentMode; + swapInfo.clipped = VK_TRUE; + swapInfo.oldSwapchain = VK_NULL_HANDLE; + + Logger::info(str::format( + "Presenter: Actual swap chain properties:" + "\n Format: ", m_info.format.format, + "\n Present mode: ", m_info.presentMode, + "\n Buffer size: ", m_info.imageExtent.width, "x", m_info.imageExtent.height, + "\n Image count: ", m_info.imageCount)); + + if ((status = m_vkd->vkCreateSwapchainKHR(m_vkd->device(), + &swapInfo, nullptr, &m_swapchain)) != VK_SUCCESS) + return status; + + // Acquire images and create views + std::vector images; + + if ((status = getSwapImages(images)) != VK_SUCCESS) + return status; + + // Update actual image count + m_info.imageCount = images.size(); + m_images.resize(m_info.imageCount); + + for (uint32_t i = 0; i < m_info.imageCount; i++) { + m_images[i].image = images[i]; + + VkImageViewCreateInfo viewInfo; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.pNext = nullptr; + viewInfo.flags = 0; + viewInfo.image = images[i]; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = m_info.format.format; + viewInfo.components = VkComponentMapping { + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; + viewInfo.subresourceRange = { + VK_IMAGE_ASPECT_COLOR_BIT, + 0, 1, 0, 1 }; + + if ((status = m_vkd->vkCreateImageView(m_vkd->device(), + &viewInfo, nullptr, &m_images[i].view)) != VK_SUCCESS) + return status; + } + + // Create one set of semaphores per swap image + m_semaphores.resize(m_info.imageCount); + + for (uint32_t i = 0; i < m_info.imageCount; i++) { + VkSemaphoreCreateInfo info; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + info.pNext = nullptr; + info.flags = 0; + + if ((status = m_vkd->vkCreateSemaphore(m_vkd->device(), + &info, nullptr, &m_semaphores[i].acquire)) != VK_SUCCESS) + return status; + + if ((status = m_vkd->vkCreateSemaphore(m_vkd->device(), + &info, nullptr, &m_semaphores[i].present)) != VK_SUCCESS) + return status; + } + + // Invalidate indices + m_imageIndex = 0; + m_frameIndex = 0; + return VK_SUCCESS; + } + + + VkResult Presenter::getSupportedFormats(std::vector& formats) { + uint32_t numFormats = 0; + + VkResult status = m_vki->vkGetPhysicalDeviceSurfaceFormatsKHR( + m_device.adapter, m_surface, &numFormats, nullptr); + + if (status != VK_SUCCESS) + return status; + + formats.resize(numFormats); + + return m_vki->vkGetPhysicalDeviceSurfaceFormatsKHR( + m_device.adapter, m_surface, &numFormats, formats.data()); + } + + + VkResult Presenter::getSupportedPresentModes(std::vector& modes) { + uint32_t numModes = 0; + + VkResult status = m_vki->vkGetPhysicalDeviceSurfacePresentModesKHR( + m_device.adapter, m_surface, &numModes, nullptr); + + if (status != VK_SUCCESS) + return status; + + modes.resize(numModes); + + return m_vki->vkGetPhysicalDeviceSurfacePresentModesKHR( + m_device.adapter, m_surface, &numModes, modes.data()); + } + + + VkResult Presenter::getSwapImages(std::vector& images) { + uint32_t imageCount = 0; + + VkResult status = m_vkd->vkGetSwapchainImagesKHR( + m_vkd->device(), m_swapchain, &imageCount, nullptr); + + if (status != VK_SUCCESS) + return status; + + images.resize(imageCount); + + return m_vkd->vkGetSwapchainImagesKHR( + m_vkd->device(), m_swapchain, &imageCount, images.data()); + } + + + VkSurfaceFormatKHR Presenter::pickFormat( + uint32_t numSupported, + const VkSurfaceFormatKHR* pSupported, + uint32_t numDesired, + const VkSurfaceFormatKHR* pDesired) { + if (numDesired > 0) { + // If the implementation allows us to freely choose + // the format, we'll just use the preferred format. + if (numSupported == 1 && pSupported[0].format == VK_FORMAT_UNDEFINED) + return pDesired[0]; + + // If the preferred format is explicitly listed in + // the array of supported surface formats, use it + for (uint32_t i = 0; i < numDesired; i++) { + for (uint32_t j = 0; j < numSupported; j++) { + if (pSupported[j].format == pDesired[i].format + && pSupported[j].colorSpace == pDesired[i].colorSpace) + return pSupported[j]; + } + } + + // If that didn't work, we'll fall back to a format + // which has similar properties to the preferred one + DxvkFormatFlags prefFlags = imageFormatInfo(pDesired[0].format)->flags; + + for (uint32_t j = 0; j < numSupported; j++) { + auto currFlags = imageFormatInfo(pSupported[j].format)->flags; + + if ((currFlags & DxvkFormatFlag::ColorSpaceSrgb) + == (prefFlags & DxvkFormatFlag::ColorSpaceSrgb)) + return pSupported[j]; + } + } + + // Otherwise, fall back to the first supported format + return pSupported[0]; + } + + + VkPresentModeKHR Presenter::pickPresentMode( + uint32_t numSupported, + const VkPresentModeKHR* pSupported, + uint32_t numDesired, + const VkPresentModeKHR* pDesired) { + // Just pick the first desired and supported mode + for (uint32_t i = 0; i < numDesired; i++) { + for (uint32_t j = 0; j < numSupported; j++) { + if (pSupported[j] == pDesired[i]) + return pSupported[j]; + } + } + + // Guaranteed to be available + return VK_PRESENT_MODE_FIFO_KHR; + } + + + VkExtent2D Presenter::pickImageExtent( + const VkSurfaceCapabilitiesKHR& caps, + VkExtent2D desired) { + if (caps.currentExtent.width != std::numeric_limits::max()) + return caps.currentExtent; + + VkExtent2D actual; + actual.width = clamp(desired.width, caps.minImageExtent.width, caps.maxImageExtent.width); + actual.height = clamp(desired.height, caps.minImageExtent.height, caps.maxImageExtent.height); + return actual; + } + + + uint32_t Presenter::pickImageCount( + const VkSurfaceCapabilitiesKHR& caps, + VkPresentModeKHR presentMode, + uint32_t desired) { + uint32_t count = caps.minImageCount; + + if (presentMode != VK_PRESENT_MODE_IMMEDIATE_KHR) + count = caps.minImageCount + 1; + + if (count < desired) + count = desired; + + if (count > caps.maxImageCount && caps.maxImageCount != 0) + count = caps.maxImageCount; + + return count; + } + + + VkResult Presenter::createSurface(HWND window) { + HINSTANCE instance = reinterpret_cast( + GetWindowLongPtr(window, GWLP_HINSTANCE)); + + VkWin32SurfaceCreateInfoKHR info; + info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + info.pNext = nullptr; + info.flags = 0; + info.hinstance = instance; + info.hwnd = window; + + VkResult status = m_vki->vkCreateWin32SurfaceKHR( + m_vki->instance(), &info, nullptr, &m_surface); + + if (status != VK_SUCCESS) + return status; + + VkBool32 supportStatus = VK_FALSE; + + if ((status = m_vki->vkGetPhysicalDeviceSurfaceSupportKHR(m_device.adapter, + m_device.queueFamily, m_surface, &supportStatus)) != VK_SUCCESS) + return status; + + if (!supportStatus) { + m_vki->vkDestroySurfaceKHR(m_vki->instance(), m_surface, nullptr); + return VK_ERROR_OUT_OF_HOST_MEMORY; // just abuse this + } + + return VK_SUCCESS; + } + + + void Presenter::destroySwapchain() { + m_vkd->vkDeviceWaitIdle(m_vkd->device()); + + for (const auto& img : m_images) + m_vkd->vkDestroyImageView(m_vkd->device(), img.view, nullptr); + + for (const auto& sem : m_semaphores) { + m_vkd->vkDestroySemaphore(m_vkd->device(), sem.acquire, nullptr); + m_vkd->vkDestroySemaphore(m_vkd->device(), sem.present, nullptr); + } + + m_vkd->vkDestroySwapchainKHR(m_vkd->device(), m_swapchain, nullptr); + + m_images.clear(); + m_semaphores.clear(); + + m_swapchain = VK_NULL_HANDLE; + } + + + void Presenter::destroySurface() { + m_vki->vkDestroySurfaceKHR(m_vki->instance(), m_surface, nullptr); + } + +} \ No newline at end of file diff --git a/src/vulkan/vulkan_presenter.h b/src/vulkan/vulkan_presenter.h new file mode 100644 index 00000000..19a70659 --- /dev/null +++ b/src/vulkan/vulkan_presenter.h @@ -0,0 +1,209 @@ +#pragma once + +#include + +#include "../util/log/log.h" + +#include "../util/util_error.h" +#include "../util/util_math.h" +#include "../util/util_string.h" + +#include "vulkan_loader.h" + +namespace dxvk::vk { + + /** + * \brief Presenter description + * + * Contains the desired properties of + * the swap chain. This is passed as + * an input during swap chain creation. + */ + struct PresenterDesc { + VkExtent2D imageExtent; + uint32_t imageCount; + uint32_t numFormats; + VkSurfaceFormatKHR formats[4]; + uint32_t numPresentModes; + VkPresentModeKHR presentModes[4]; + }; + + /** + * \brief Presenter properties + * + * Contains the actual properties + * of the underlying swap chain. + */ + struct PresenterInfo { + VkSurfaceFormatKHR format; + VkPresentModeKHR presentMode; + VkExtent2D imageExtent; + uint32_t imageCount; + }; + + /** + * \brief Adapter and queue + */ + struct PresenterDevice { + uint32_t queueFamily = 0; + VkQueue queue = VK_NULL_HANDLE; + VkPhysicalDevice adapter = VK_NULL_HANDLE; + }; + + /** + * \brief Swap image and view + */ + struct PresenterImage { + VkImage image = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + }; + + /** + * \brief Presenter semaphores + * + * Pair of semaphores used for acquire + * and present operations, including + * the command buffers used in between. + */ + struct PresenterSync { + VkSemaphore acquire; + VkSemaphore present; + }; + + /** + * \brief Vulkan presenter + * + * Provides abstractions for some of the + * more complicated aspects of Vulkan's + * window system integration. + */ + class Presenter : public RcObject { + + public: + + Presenter( + HWND window, + const Rc& vki, + const Rc& vkd, + PresenterDevice device, + const PresenterDesc& desc); + + ~Presenter(); + + /** + * \brief Actual presenter info + * \returns Swap chain properties + */ + PresenterInfo info() const; + + /** + * \breif Retrieves a pair of semaphores + * + * These semaphores are meant to be used + * for acquire and present operations. + * \returns Pair of semaphores + */ + PresenterSync getSyncSemaphores() const; + + /** + * \brief Retrieves image by index + * + * Can be used to create per-image objects. + * \param [in] index Image index + * \returns Image handle + */ + PresenterImage getImage( + uint32_t index) const; + + /** + * \brief Acquires next image + * + * Potentially blocks the calling thread. + * If this returns an error, the swap chain + * must be recreated and a new image must + * be acquired before proceeding. + * \param [in] signal Semaphore to signal + * \param [out] index Acquired image index + * \returns Status of the operation + */ + VkResult acquireNextImage( + VkSemaphore signal, + uint32_t& index); + + /** + * \brief Presents current image + * + * Presents the current image. If this returns + * an error, the swap chain must be recreated, + * but do not present before acquiring an image. + * \param [in] wait Semaphore to wait on + * \returns Status of the operation + */ + VkResult presentImage( + VkSemaphore wait); + + /** + * \brief Changes presenter properties + * + * Recreates the swap chain immediately. + * \param [in] desc Swap chain description + */ + VkResult recreateSwapChain( + const PresenterDesc& desc); + + private: + + Rc m_vki; + Rc m_vkd; + + PresenterDevice m_device; + PresenterInfo m_info; + + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; + + std::vector m_images; + std::vector m_semaphores; + + uint32_t m_imageIndex = 0; + uint32_t m_frameIndex = 0; + + VkResult getSupportedFormats( + std::vector& formats); + + VkResult getSupportedPresentModes( + std::vector& modes); + + VkResult getSwapImages( + std::vector& images); + + VkSurfaceFormatKHR pickFormat( + uint32_t numSupported, + const VkSurfaceFormatKHR* pSupported, + uint32_t numDesired, + const VkSurfaceFormatKHR* pDesired); + + VkPresentModeKHR pickPresentMode( + uint32_t numSupported, + const VkPresentModeKHR* pSupported, + uint32_t numDesired, + const VkPresentModeKHR* pDesired); + + VkExtent2D pickImageExtent( + const VkSurfaceCapabilitiesKHR& caps, + VkExtent2D desired); + + uint32_t pickImageCount( + const VkSurfaceCapabilitiesKHR& caps, + VkPresentModeKHR presentMode, + uint32_t desired); + + VkResult createSurface(HWND window); + + void destroySwapchain(); + + void destroySurface(); + + }; + +} \ No newline at end of file