// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include #include #include #include #include #include #include #include #define VK_NO_PROTOTYPES #include #include "common/common_types.h" namespace Vulkan::vk { /** * Span for Vulkan arrays. * Based on std::span but optimized for array access instead of iterators. * Size returns uint32_t instead of size_t to ease interaction with Vulkan functions. */ template class Span { public: using value_type = T; using size_type = u32; using difference_type = std::ptrdiff_t; using reference = const T&; using const_reference = const T&; using pointer = const T*; using const_pointer = const T*; using iterator = const T*; using const_iterator = const T*; /// Construct an empty span. constexpr Span() noexcept = default; /// Construct a span from a single element. constexpr Span(const T& value) noexcept : ptr{&value}, num{1} {} /// Construct a span from a range. template // requires std::data(const Range&) // requires std::size(const Range&) constexpr Span(const Range& range) : ptr{std::data(range)}, num{std::size(range)} {} /// Construct a span from a pointer and a size. /// This is inteded for subranges. constexpr Span(const T* ptr, std::size_t num) noexcept : ptr{ptr}, num{num} {} /// Returns the data pointer by the span. constexpr const T* data() const noexcept { return ptr; } /// Returns the number of elements in the span. /// @note Returns a 32 bits integer because most Vulkan functions expect this type. constexpr u32 size() const noexcept { return static_cast(num); } /// Returns true when the span is empty. constexpr bool empty() const noexcept { return num == 0; } /// Returns a reference to the element in the passed index. /// @pre: index < size() constexpr const T& operator[](std::size_t index) const noexcept { return ptr[index]; } /// Returns an iterator to the beginning of the span. constexpr const T* begin() const noexcept { return ptr; } /// Returns an iterator to the end of the span. constexpr const T* end() const noexcept { return ptr + num; } /// Returns an iterator to the beginning of the span. constexpr const T* cbegin() const noexcept { return ptr; } /// Returns an iterator to the end of the span. constexpr const T* cend() const noexcept { return ptr + num; } private: const T* ptr = nullptr; std::size_t num = 0; }; /// Vulkan exception generated from a VkResult. class Exception final : public std::exception { public: /// Construct the exception with a result. /// @pre result != VK_SUCCESS explicit Exception(VkResult result_) : result{result_} {} virtual ~Exception() = default; const char* what() const noexcept override; private: VkResult result; }; /// Converts a VkResult enum into a rodata string const char* ToString(VkResult) noexcept; /// Throws a Vulkan exception if result is not success. inline void Check(VkResult result) { if (result != VK_SUCCESS) { throw Exception(result); } } /// Throws a Vulkan exception if result is an error. /// @return result inline VkResult Filter(VkResult result) { if (result < 0) { throw Exception(result); } return result; } /// Table holding Vulkan instance function pointers. struct InstanceDispatch { PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; PFN_vkCreateInstance vkCreateInstance; PFN_vkDestroyInstance vkDestroyInstance; PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties; PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT; PFN_vkCreateDevice vkCreateDevice; PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT; PFN_vkDestroyDevice vkDestroyDevice; PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR; PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR; PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties; PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR; PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR; PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR; PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR; PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR; PFN_vkQueuePresentKHR vkQueuePresentKHR; }; /// Table holding Vulkan device function pointers. struct DeviceDispatch : public InstanceDispatch { PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR; PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers; PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets; PFN_vkAllocateMemory vkAllocateMemory; PFN_vkBeginCommandBuffer vkBeginCommandBuffer; PFN_vkBindBufferMemory vkBindBufferMemory; PFN_vkBindImageMemory vkBindImageMemory; PFN_vkCmdBeginQuery vkCmdBeginQuery; PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass; PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT; PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets; PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer; PFN_vkCmdBindPipeline vkCmdBindPipeline; PFN_vkCmdBindTransformFeedbackBuffersEXT vkCmdBindTransformFeedbackBuffersEXT; PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers; PFN_vkCmdBlitImage vkCmdBlitImage; PFN_vkCmdClearAttachments vkCmdClearAttachments; PFN_vkCmdCopyBuffer vkCmdCopyBuffer; PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage; PFN_vkCmdCopyImage vkCmdCopyImage; PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer; PFN_vkCmdDispatch vkCmdDispatch; PFN_vkCmdDraw vkCmdDraw; PFN_vkCmdDrawIndexed vkCmdDrawIndexed; PFN_vkCmdEndQuery vkCmdEndQuery; PFN_vkCmdEndRenderPass vkCmdEndRenderPass; PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT; PFN_vkCmdFillBuffer vkCmdFillBuffer; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier; PFN_vkCmdPushConstants vkCmdPushConstants; PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants; PFN_vkCmdSetCheckpointNV vkCmdSetCheckpointNV; PFN_vkCmdSetDepthBias vkCmdSetDepthBias; PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds; PFN_vkCmdSetScissor vkCmdSetScissor; PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask; PFN_vkCmdSetStencilReference vkCmdSetStencilReference; PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask; PFN_vkCmdSetViewport vkCmdSetViewport; PFN_vkCreateBuffer vkCreateBuffer; PFN_vkCreateBufferView vkCreateBufferView; PFN_vkCreateCommandPool vkCreateCommandPool; PFN_vkCreateComputePipelines vkCreateComputePipelines; PFN_vkCreateDescriptorPool vkCreateDescriptorPool; PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout; PFN_vkCreateDescriptorUpdateTemplateKHR vkCreateDescriptorUpdateTemplateKHR; PFN_vkCreateFence vkCreateFence; PFN_vkCreateFramebuffer vkCreateFramebuffer; PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines; PFN_vkCreateImage vkCreateImage; PFN_vkCreateImageView vkCreateImageView; PFN_vkCreatePipelineLayout vkCreatePipelineLayout; PFN_vkCreateQueryPool vkCreateQueryPool; PFN_vkCreateRenderPass vkCreateRenderPass; PFN_vkCreateSampler vkCreateSampler; PFN_vkCreateSemaphore vkCreateSemaphore; PFN_vkCreateShaderModule vkCreateShaderModule; PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR; PFN_vkDestroyBuffer vkDestroyBuffer; PFN_vkDestroyBufferView vkDestroyBufferView; PFN_vkDestroyCommandPool vkDestroyCommandPool; PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool; PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout; PFN_vkDestroyDescriptorUpdateTemplateKHR vkDestroyDescriptorUpdateTemplateKHR; PFN_vkDestroyFence vkDestroyFence; PFN_vkDestroyFramebuffer vkDestroyFramebuffer; PFN_vkDestroyImage vkDestroyImage; PFN_vkDestroyImageView vkDestroyImageView; PFN_vkDestroyPipeline vkDestroyPipeline; PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout; PFN_vkDestroyQueryPool vkDestroyQueryPool; PFN_vkDestroyRenderPass vkDestroyRenderPass; PFN_vkDestroySampler vkDestroySampler; PFN_vkDestroySemaphore vkDestroySemaphore; PFN_vkDestroyShaderModule vkDestroyShaderModule; PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR; PFN_vkDeviceWaitIdle vkDeviceWaitIdle; PFN_vkEndCommandBuffer vkEndCommandBuffer; PFN_vkFreeCommandBuffers vkFreeCommandBuffers; PFN_vkFreeDescriptorSets vkFreeDescriptorSets; PFN_vkFreeMemory vkFreeMemory; PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; PFN_vkGetDeviceQueue vkGetDeviceQueue; PFN_vkGetFenceStatus vkGetFenceStatus; PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; PFN_vkGetQueryPoolResults vkGetQueryPoolResults; PFN_vkGetQueueCheckpointDataNV vkGetQueueCheckpointDataNV; PFN_vkMapMemory vkMapMemory; PFN_vkQueueSubmit vkQueueSubmit; PFN_vkResetFences vkResetFences; PFN_vkResetQueryPoolEXT vkResetQueryPoolEXT; PFN_vkUnmapMemory vkUnmapMemory; PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR; PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets; PFN_vkWaitForFences vkWaitForFences; }; /// Loads instance agnostic function pointers. /// @return True on success, false on error. bool Load(InstanceDispatch&) noexcept; /// Loads instance function pointers. /// @return True on success, false on error. bool Load(VkInstance, InstanceDispatch&) noexcept; void Destroy(VkInstance, const InstanceDispatch&) noexcept; void Destroy(VkDevice, const InstanceDispatch&) noexcept; void Destroy(VkDevice, VkBuffer, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkBufferView, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkCommandPool, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkDescriptorPool, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkDescriptorSetLayout, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkDescriptorUpdateTemplateKHR, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkDeviceMemory, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkFence, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkFramebuffer, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkImage, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkImageView, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkPipeline, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkPipelineLayout, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkQueryPool, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkRenderPass, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkSampler, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkSwapchainKHR, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkSemaphore, const DeviceDispatch&) noexcept; void Destroy(VkDevice, VkShaderModule, const DeviceDispatch&) noexcept; void Destroy(VkInstance, VkDebugUtilsMessengerEXT, const InstanceDispatch&) noexcept; void Destroy(VkInstance, VkSurfaceKHR, const InstanceDispatch&) noexcept; VkResult Free(VkDevice, VkDescriptorPool, Span, const DeviceDispatch&) noexcept; VkResult Free(VkDevice, VkCommandPool, Span, const DeviceDispatch&) noexcept; template class Handle; /// Handle with an owning type. /// Analogue to std::unique_ptr. template class Handle { public: /// Construct a handle and hold it's ownership. explicit Handle(Type handle_, OwnerType owner_, const Dispatch& dld_) noexcept : handle{handle_}, owner{owner_}, dld{&dld_} {} /// Construct an empty handle. Handle() = default; /// Copying Vulkan objects is not supported and will never be. Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; /// Construct a handle transfering the ownership from another handle. Handle(Handle&& rhs) noexcept : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {} /// Assign the current handle transfering the ownership from another handle. /// Destroys any previously held object. Handle& operator=(Handle&& rhs) noexcept { Release(); handle = std::exchange(rhs.handle, nullptr); owner = rhs.owner; dld = rhs.dld; return *this; } /// Destroys the current handle if it existed. ~Handle() noexcept { Release(); } /// Destroys any held object. void reset() noexcept { Release(); handle = nullptr; } /// Returns the address of the held object. /// Intended for Vulkan structures that expect a pointer to an array. const Type* address() const noexcept { return std::addressof(handle); } /// Returns the held Vulkan handle. Type operator*() const noexcept { return handle; } /// Returns true when there's a held object. explicit operator bool() const noexcept { return handle != nullptr; } protected: Type handle = nullptr; OwnerType owner = nullptr; const Dispatch* dld = nullptr; private: /// Destroys the held object if it exists. void Release() noexcept { if (handle) { Destroy(owner, handle, *dld); } } }; /// Dummy type used to specify a handle has no owner. struct NoOwner {}; /// Handle without an owning type. /// Analogue to std::unique_ptr template class Handle { public: /// Construct a handle and hold it's ownership. explicit Handle(Type handle_, const Dispatch& dld_) noexcept : handle{handle_}, dld{&dld_} {} /// Construct an empty handle. Handle() noexcept = default; /// Copying Vulkan objects is not supported and will never be. Handle(const Handle&) = delete; Handle& operator=(const Handle&) = delete; /// Construct a handle transfering ownership from another handle. Handle(Handle&& rhs) noexcept : handle{std::exchange(rhs.handle, nullptr)}, dld{rhs.dld} {} /// Assign the current handle transfering the ownership from another handle. /// Destroys any previously held object. Handle& operator=(Handle&& rhs) noexcept { Release(); handle = std::exchange(rhs.handle, nullptr); dld = rhs.dld; return *this; } /// Destroys the current handle if it existed. ~Handle() noexcept { Release(); } /// Destroys any held object. void reset() noexcept { Release(); handle = nullptr; } /// Returns the address of the held object. /// Intended for Vulkan structures that expect a pointer to an array. const Type* address() const noexcept { return std::addressof(handle); } /// Returns the held Vulkan handle. Type operator*() const noexcept { return handle; } /// Returns true when there's a held object. operator bool() const noexcept { return handle != nullptr; } protected: Type handle = nullptr; const Dispatch* dld = nullptr; private: /// Destroys the held object if it exists. void Release() noexcept { if (handle) { Destroy(handle, *dld); } } }; /// Array of a pool allocation. /// Analogue to std::vector template class PoolAllocations { public: /// Construct an empty allocation. PoolAllocations() = default; /// Construct an allocation. Errors are reported through IsOutOfPoolMemory(). explicit PoolAllocations(std::unique_ptr allocations, std::size_t num, VkDevice device, PoolType pool, const DeviceDispatch& dld) noexcept : allocations{std::move(allocations)}, num{num}, device{device}, pool{pool}, dld{&dld} {} /// Copying Vulkan allocations is not supported and will never be. PoolAllocations(const PoolAllocations&) = delete; PoolAllocations& operator=(const PoolAllocations&) = delete; /// Construct an allocation transfering ownership from another allocation. PoolAllocations(PoolAllocations&& rhs) noexcept : allocations{std::move(rhs.allocations)}, num{rhs.num}, device{rhs.device}, pool{rhs.pool}, dld{rhs.dld} {} /// Assign an allocation transfering ownership from another allocation. /// Releases any previously held allocation. PoolAllocations& operator=(PoolAllocations&& rhs) noexcept { Release(); allocations = std::move(rhs.allocations); num = rhs.num; device = rhs.device; pool = rhs.pool; dld = rhs.dld; return *this; } /// Destroys any held allocation. ~PoolAllocations() { Release(); } /// Returns the number of allocations. std::size_t size() const noexcept { return num; } /// Returns a pointer to the array of allocations. AllocationType const* data() const noexcept { return allocations.get(); } /// Returns the allocation in the specified index. /// @pre index < size() AllocationType operator[](std::size_t index) const noexcept { return allocations[index]; } /// True when a pool fails to construct. bool IsOutOfPoolMemory() const noexcept { return !device; } private: /// Destroys the held allocations if they exist. void Release() noexcept { if (!allocations) { return; } const Span span(allocations.get(), num); const VkResult result = Free(device, pool, span, *dld); // There's no way to report errors from a destructor. if (result != VK_SUCCESS) { std::terminate(); } } std::unique_ptr allocations; std::size_t num = 0; VkDevice device = nullptr; PoolType pool = nullptr; const DeviceDispatch* dld = nullptr; }; using BufferView = Handle; using DebugCallback = Handle; using DescriptorSetLayout = Handle; using DescriptorUpdateTemplateKHR = Handle; using Framebuffer = Handle; using ImageView = Handle; using Pipeline = Handle; using PipelineLayout = Handle; using QueryPool = Handle; using RenderPass = Handle; using Sampler = Handle; using Semaphore = Handle; using ShaderModule = Handle; using SurfaceKHR = Handle; using DescriptorSets = PoolAllocations; using CommandBuffers = PoolAllocations; /// Vulkan instance owning handle. class Instance : public Handle { using Handle::Handle; public: /// Creates a Vulkan instance. Use "operator bool" for error handling. static Instance Create(Span layers, Span extensions, InstanceDispatch& dld) noexcept; /// Enumerates physical devices. /// @return Physical devices and an empty handle on failure. std::optional> EnumeratePhysicalDevices(); /// Tries to create a debug callback messenger. Returns an empty handle on failure. DebugCallback TryCreateDebugCallback(PFN_vkDebugUtilsMessengerCallbackEXT callback) noexcept; }; class Queue { public: /// Construct an empty queue handle. constexpr Queue() noexcept = default; /// Construct a queue handle. constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {} /// Returns the checkpoint data. /// @note Returns an empty vector when the function pointer is not present. std::vector GetCheckpointDataNV(const DeviceDispatch& dld) const; void Submit(Span submit_infos, VkFence fence) const { Check(dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence)); } VkResult Present(const VkPresentInfoKHR& present_info) const noexcept { return dld->vkQueuePresentKHR(queue, &present_info); } private: VkQueue queue = nullptr; const DeviceDispatch* dld = nullptr; }; class Buffer : public Handle { using Handle::Handle; public: /// Attaches a memory allocation. void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const; }; class Image : public Handle { using Handle::Handle; public: /// Attaches a memory allocation. void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const; }; } // namespace Vulkan::vk