// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #pragma once #if GRAPHICS_API_VULKAN #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUResource.h" #include "DescriptorSetVulkan.h" #include "IncludeVulkanHeaders.h" #include "Config.h" enum class SpirvShaderResourceType; enum class GPUTextureFlags; class Engine; class GPUContextVulkan; class GPUAdapterVulkan; class GPUSwapChainVulkan; class CmdBufferVulkan; class QueueVulkan; class FenceVulkan; class GPUTextureVulkan; class GPUBufferVulkan; class GPUTimerQueryVulkan; class RenderPassVulkan; class FenceManagerVulkan; class GPUDeviceVulkan; class UniformBufferUploaderVulkan; class DescriptorPoolsManagerVulkan; class SemaphoreVulkan { private: GPUDeviceVulkan* _device; VkSemaphore _semaphoreHandle; public: /// /// Initializes a new instance of the class. /// /// The graphics device. SemaphoreVulkan(GPUDeviceVulkan* device); /// /// Finalizes an instance of the class. /// ~SemaphoreVulkan(); /// /// Gets the handle. /// /// The semaphore. inline VkSemaphore GetHandle() const { return _semaphoreHandle; } }; class FenceVulkan { friend FenceManagerVulkan; private: VkFence _handle; bool _signaled; FenceManagerVulkan* _owner; public: FenceVulkan(GPUDeviceVulkan* device, FenceManagerVulkan* owner, bool createSignaled); ~FenceVulkan(); public: inline VkFence GetHandle() const { return _handle; } inline bool IsSignaled() const { return _signaled; } FenceManagerVulkan* GetOwner() const { return _owner; } }; class FenceManagerVulkan { private: GPUDeviceVulkan* _device; Array _freeFences; Array _usedFences; public: FenceManagerVulkan() : _device(nullptr) { } ~FenceManagerVulkan(); public: /// /// Initializes the specified device. /// /// The graphics device. void Init(GPUDeviceVulkan* device) { _device = device; } void Dispose(); FenceVulkan* AllocateFence(bool createSignaled = false); inline bool IsFenceSignaled(FenceVulkan* fence) { if (fence->IsSignaled()) { return true; } return CheckFenceState(fence); } // Returns true if waiting timed out or failed, false otherwise. bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds); void ResetFence(FenceVulkan* fence); // Sets the fence handle to null void ReleaseFence(FenceVulkan*& fence); // Sets the fence handle to null void WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds); private: // Returns true if fence was signaled, otherwise false. bool CheckFenceState(FenceVulkan* fence); void DestroyFence(FenceVulkan* fence); }; class DeferredDeletionQueueVulkan { public: enum Type { RenderPass, Buffer, BufferView, Image, ImageView, Pipeline, PipelineLayout, Framebuffer, DescriptorSetLayout, Sampler, Semaphore, ShaderModule, Event, QueryPool, }; private: struct Entry { uint64 FenceCounter; uint64 Handle; uint64 FrameNumber; VmaAllocation AllocationHandle; Type StructureType; CmdBufferVulkan* CmdBuffer; }; GPUDeviceVulkan* _device; CriticalSection _locker; Array _entries; public: /// /// Initializes a new instance of the class. /// /// The graphics device. DeferredDeletionQueueVulkan(GPUDeviceVulkan* device); /// /// Finalizes an instance of the class. /// ~DeferredDeletionQueueVulkan(); public: template inline void EnqueueResource(Type type, T handle) { static_assert(sizeof(T) <= sizeof(uint64), "Invalid handle size."); EnqueueGenericResource(type, (uint64)handle, VK_NULL_HANDLE); } template inline void EnqueueResource(Type type, T handle, VmaAllocation allocation) { static_assert(sizeof(T) <= sizeof(uint64), "Invalid handle size."); EnqueueGenericResource(type, (uint64)handle, allocation); } auto ReleaseResources(bool deleteImmediately = false) -> void; private: void EnqueueGenericResource(Type type, uint64 handle, VmaAllocation allocation); }; class RenderTargetLayoutVulkan { public: int32 RTsCount; MSAALevel MSAA; bool ReadDepth; bool WriteDepth; PixelFormat DepthFormat; PixelFormat RTVsFormats[GPU_MAX_RT_BINDED]; VkExtent3D Extent; public: bool operator==(const RenderTargetLayoutVulkan& other) const { return Platform::MemoryCompare((void*)this, &other, sizeof(RenderTargetLayoutVulkan)) == 0; } }; uint32 GetHash(const RenderTargetLayoutVulkan& key); class FramebufferVulkan { public: struct Key { RenderPassVulkan* RenderPass; int32 AttachmentCount; VkImageView Attachments[GPU_MAX_RT_BINDED + 1]; public: bool operator==(const Key& other) const { return Platform::MemoryCompare((void*)this, &other, sizeof(Key)) == 0; } }; private: GPUDeviceVulkan* _device; VkFramebuffer _handle; public: FramebufferVulkan(GPUDeviceVulkan* device, Key& key, VkExtent3D& extent, uint32 layers); ~FramebufferVulkan(); public: VkImageView Attachments[GPU_MAX_RT_BINDED + 1]; VkExtent3D Extent; uint32 Layers; public: inline VkFramebuffer GetHandle() { return _handle; } bool HasReference(VkImageView imageView) const; }; uint32 GetHash(const FramebufferVulkan::Key& key); class RenderPassVulkan { private: GPUDeviceVulkan* _device; VkRenderPass _handle; public: RenderTargetLayoutVulkan Layout; public: RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLayoutVulkan& layout); ~RenderPassVulkan(); public: inline VkRenderPass GetHandle() const { return _handle; } }; class QueryPoolVulkan { protected: struct Range { uint32 Start; uint32 Count; }; GPUDeviceVulkan* _device; VkQueryPool _handle; volatile int32 _count; const uint32 _capacity; const VkQueryType _type; #if VULKAN_RESET_QUERY_POOLS Array _resetRanges; #endif public: QueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, VkQueryType type); ~QueryPoolVulkan(); public: inline VkQueryPool GetHandle() const { return _handle; } #if VULKAN_RESET_QUERY_POOLS void Reset(CmdBufferVulkan* cmdBuffer); #endif }; class BufferedQueryPoolVulkan : public QueryPoolVulkan { private: Array _queryOutput; Array _usedQueryBits; Array _startedQueryBits; Array _readResultsBits; // Last potentially free index in the pool int32 _lastBeginIndex; public: BufferedQueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, VkQueryType type); bool AcquireQuery(uint32& resultIndex); void ReleaseQuery(uint32 queryIndex); void MarkQueryAsStarted(uint32 queryIndex); bool GetResults(GPUContextVulkan* context, uint32 index, uint64& result); bool HasRoom() const; }; /// /// The dummy Vulkan resources manager. Helps when user need to pass null texture handle to the shader. /// class HelperResourcesVulkan { public: enum class StaticSamplers { SamplerLinearClamp =0, SamplerPointClamp = 1, SamplerLinearWrap = 2, SamplerPointWrap = 3, ShadowSampler = 4, ShadowSamplerPCF = 5, MAX }; private: GPUDeviceVulkan* _device; GPUTextureVulkan* _dummyTextures[6]; GPUBufferVulkan* _dummyBuffer; GPUBufferVulkan* _dummyVB; VkSampler _staticSamplers[static_cast(StaticSamplers::MAX)]; public: /// /// Initializes a new instance of the class. /// /// The graphics device. HelperResourcesVulkan(GPUDeviceVulkan* device); public: VkSampler GetStaticSampler(StaticSamplers type); GPUTextureVulkan* GetDummyTexture(SpirvShaderResourceType type); GPUBufferVulkan* GetDummyBuffer(); GPUBufferVulkan* GetDummyVertexBuffer(); void Dispose(); }; /// /// Vulkan staging buffers manager. /// class StagingManagerVulkan { private: struct PendingItems { uint64 FenceCounter; Array Resources; }; struct PendingItemsPerCmdBuffer { CmdBufferVulkan* CmdBuffer; Array Items; inline PendingItems* FindOrAdd(uint64 fence); }; struct FreeEntry { GPUBuffer* Buffer; uint64 FrameNumber; }; GPUDeviceVulkan* _device; CriticalSection _locker; Array _allBuffers; Array _freeBuffers; Array _pendingBuffers; #if !BUILD_RELEASE uint64 _allBuffersTotalSize = 0; uint64 _allBuffersPeekSize = 0; uint64 _allBuffersAllocSize = 0; uint64 _allBuffersFreeSize = 0; #endif public: StagingManagerVulkan(GPUDeviceVulkan* device); GPUBuffer* AcquireBuffer(uint32 size, GPUResourceUsage usage); void ReleaseBuffer(CmdBufferVulkan* cmdBuffer, GPUBuffer*& buffer); void ProcessPendingFree(); void Dispose(); private: PendingItemsPerCmdBuffer* FindOrAdd(CmdBufferVulkan* cmdBuffer); }; /// /// Implementation of Graphics Device for Vulkan backend. /// class GPUDeviceVulkan : public GPUDevice { friend GPUContextVulkan; friend GPUSwapChainVulkan; friend FenceManagerVulkan; private: CriticalSection _fenceLock; Dictionary _renderPasses; Dictionary _framebuffers; Dictionary _layouts; // TODO: use mutex to protect those collections BUT use 2 pools per cache: one lock-free with lookup only and second protected with mutex synced on frame end! public: // Create new graphics device (returns Vulkan if failed) // @returns Created device or Vulkan static GPUDevice* Create(); /// /// Initializes a new instance of the class. /// /// The shader profile. /// The GPU device adapter. GPUDeviceVulkan(ShaderProfile shaderProfile, GPUAdapterVulkan* adapter); /// /// Finalizes an instance of the class. /// ~GPUDeviceVulkan(); public: struct OptionalVulkanDeviceExtensions { uint32 HasKHRMaintenance1 : 1; uint32 HasKHRMaintenance2 : 1; uint32 HasMirrorClampToEdge : 1; uint32 HasKHRExternalMemoryCapabilities : 1; uint32 HasKHRGetPhysicalDeviceProperties2 : 1; uint32 HasEXTValidationCache : 1; }; static void GetInstanceLayersAndExtensions(Array& outInstanceExtensions, Array& outInstanceLayers, bool& outDebugUtils); static void GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array& outDeviceExtensions, Array& outDeviceLayers); void ParseOptionalDeviceExtensions(const Array& deviceExtensions); static OptionalVulkanDeviceExtensions OptionalDeviceExtensions; public: /// /// The Vulkan instance. /// static VkInstance Instance; /// /// The Vulkan instance extensions. /// static Array InstanceExtensions; /// /// The Vulkan instance layers. /// static Array InstanceLayers; public: /// /// The main Vulkan commands context. /// GPUContextVulkan* MainContext = nullptr; /// /// The Vulkan adapter. /// GPUAdapterVulkan* Adapter = nullptr; /// /// The Vulkan device. /// VkDevice Device = VK_NULL_HANDLE; /// /// The Vulkan device queues family properties. /// Array QueueFamilyProps; /// /// The Vulkan fence manager. /// FenceManagerVulkan FenceManager; /// /// The Vulkan resources deferred deletion queue. /// DeferredDeletionQueueVulkan DeferredDeletionQueue; /// /// The staging buffers manager. /// StagingManagerVulkan StagingManager; /// /// The helper device resources manager. /// HelperResourcesVulkan HelperResources; /// /// The graphics queue. /// QueueVulkan* GraphicsQueue = nullptr; /// /// The compute queue. /// QueueVulkan* ComputeQueue = nullptr; /// /// The transfer queue. /// QueueVulkan* TransferQueue = nullptr; /// /// The present queue. /// QueueVulkan* PresentQueue = nullptr; /// /// The Vulkan memory allocator. /// VmaAllocator Allocator = VK_NULL_HANDLE; /// /// The pipeline cache. /// VkPipelineCache PipelineCache = VK_NULL_HANDLE; #if VK_EXT_validation_cache /// /// The optional validation cache. /// VkValidationCacheEXT ValidationCache = VK_NULL_HANDLE; #endif /// /// The uniform buffers uploader. /// UniformBufferUploaderVulkan* UniformBufferUploader = nullptr; /// /// The descriptor pools manager. /// DescriptorPoolsManagerVulkan* DescriptorPoolsManager = nullptr; /// /// The physical device limits. /// VkPhysicalDeviceLimits PhysicalDeviceLimits; /// /// The physical device enabled features. /// VkPhysicalDeviceFeatures PhysicalDeviceFeatures; Array TimestampQueryPools; #if VULKAN_RESET_QUERY_POOLS Array QueriesToReset; #endif inline BufferedQueryPoolVulkan* FindAvailableQueryPool(Array& pools, VkQueryType queryType) { // Try to use pool with available space inside for (int32 i = 0; i < pools.Count(); i++) { auto pool = pools[i]; if (pool->HasRoom()) { return pool; } } // Create new pool enum { NUM_OCCLUSION_QUERIES_PER_POOL = 4096, NUM_TIMESTAMP_QUERIES_PER_POOL = 1024, }; const auto pool = New(this, queryType == VK_QUERY_TYPE_OCCLUSION ? NUM_OCCLUSION_QUERIES_PER_POOL : NUM_TIMESTAMP_QUERIES_PER_POOL, queryType); pools.Add(pool); return pool; } inline BufferedQueryPoolVulkan* FindAvailableTimestampQueryPool() { return FindAvailableQueryPool(TimestampQueryPools, VK_QUERY_TYPE_TIMESTAMP); } RenderPassVulkan* GetOrCreateRenderPass(RenderTargetLayoutVulkan& layout); FramebufferVulkan* GetOrCreateFramebuffer(FramebufferVulkan::Key& key, VkExtent3D& extent, uint32 layers); PipelineLayoutVulkan* GetOrCreateLayout(DescriptorSetLayoutInfoVulkan& key); void OnImageViewDestroy(VkImageView imageView); public: /// /// Setups the present queue to be ready for the given window surface. /// /// The surface. void SetupPresentQueue(VkSurfaceKHR surface); /// /// Finds the closest pixel format that a specific Vulkan device supports. /// /// The input format. /// The texture usage flags. /// If set to true the optimal tiling should be used, otherwise use linear tiling. /// The output format. PixelFormat GetClosestSupportedPixelFormat(PixelFormat format, GPUTextureFlags flags, bool optimalTiling); #if VK_EXT_validation_cache /// /// Loads the validation cache. /// void LoadValidationCache(); /// /// Saves the validation cache. /// bool SaveValidationCache(); #endif public: // [GPUDevice] GPUContext* GetMainContext() override; GPUAdapter* GetAdapter() const override; void* GetNativePtr() const override; bool Init() override; void DrawBegin() override; void Dispose() override; void WaitForGPU() override; GPUTexture* CreateTexture(const StringView& name) override; GPUShader* CreateShader(const StringView& name) override; GPUPipelineState* CreatePipelineState() override; GPUTimerQuery* CreateTimerQuery() override; GPUBuffer* CreateBuffer(const StringView& name) override; GPUSwapChain* CreateSwapChain(Window* window) override; }; /// /// GPU resource implementation for Vulkan backend. /// template class GPUResourceVulkan : public GPUResourceBase { public: /// /// Initializes a new instance of the class. /// /// The graphics device. /// The resource name. GPUResourceVulkan(GPUDeviceVulkan* device, const StringView& name) : GPUResourceBase(device, name) { } }; /// /// Represents a GPU resource that contain descriptor resource for binding to the pipeline (shader resource, sampler, buffer, etc.). /// class DescriptorOwnerResourceVulkan { public: /// /// Finalizes an instance of the class. /// virtual ~DescriptorOwnerResourceVulkan() { } public: /// /// Gets the sampler descriptor. /// /// The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The sampler. virtual void DescriptorAsSampler(GPUContextVulkan* context, VkSampler& sampler) { CRASH; } /// /// Gets the image descriptor. /// /// The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The image view. /// The image layout. virtual void DescriptorAsImage(GPUContextVulkan* context, VkImageView& imageView, VkImageLayout& layout) { CRASH; } /// /// Gets the storage image descriptor. /// /// The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The image view. /// The image layout. virtual void DescriptorAsStorageImage(GPUContextVulkan* context, VkImageView& imageView, VkImageLayout& layout) { CRASH; } /// /// Gets the uniform texel buffer descriptor. /// /// The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView) { CRASH; } /// /// Gets the storage buffer descriptor. /// /// The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer. /// The range offset (in bytes). /// The range size (in bytes). virtual void DescriptorAsStorageBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range) { CRASH; } /// /// Gets the dynamic uniform buffer descriptor. /// /// The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer. /// The range offset (in bytes). /// The range size (in bytes). /// The dynamic offset (in bytes). virtual void DescriptorAsDynamicUniformBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range, uint32& dynamicOffset) { CRASH; } }; extern GPUDevice* CreateGPUDeviceVulkan(); #endif