// Copyright (c) 2012-2024 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 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; } }; struct FenceVulkan { VkFence Handle; bool IsSignaled; }; class FenceManagerVulkan { private: GPUDeviceVulkan* _device = nullptr; Array _freeFences; Array _usedFences; public: ~FenceManagerVulkan(); public: void Init(GPUDeviceVulkan* device) { _device = device; } void Dispose(); FenceVulkan* AllocateFence(bool createSignaled = false); FORCE_INLINE bool IsFenceSignaled(FenceVulkan* fence) const { return fence->IsSignaled || CheckFenceState(fence); } // Returns true if waiting timed out or failed, false otherwise. bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds) const; void ResetFence(FenceVulkan* fence) const; // 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) const; void DestroyFence(FenceVulkan* fence) const; }; 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); } void ReleaseResources(bool immediately = false); private: void EnqueueGenericResource(Type type, uint64 handle, VmaAllocation allocation); }; struct RenderTargetLayoutVulkan { union { struct { uint32 Layers : 10; // Limited by GPU_MAX_TEXTURE_ARRAY_SIZE uint32 RTsCount : 3; // Limited by GPU_MAX_RT_BINDED uint32 ReadDepth : 1; uint32 WriteDepth : 1; uint32 ReadStencil : 1; uint32 WriteStencil : 1; uint32 BlendEnable : 1; }; uint32 Flags; }; MSAALevel MSAA; PixelFormat DepthFormat; PixelFormat RTVsFormats[GPU_MAX_RT_BINDED]; VkExtent2D Extent; FORCE_INLINE bool operator==(const RenderTargetLayoutVulkan& other) const { return Platform::MemoryCompare(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]; FORCE_INLINE bool operator==(const Key& other) const { return Platform::MemoryCompare(this, &other, sizeof(Key)) == 0; } }; FramebufferVulkan(GPUDeviceVulkan* device, const Key& key, const VkExtent2D& extent, uint32 layers); ~FramebufferVulkan(); GPUDeviceVulkan* Device; VkFramebuffer Handle; VkImageView Attachments[GPU_MAX_RT_BINDED + 1]; VkExtent2D Extent; uint32 Layers; bool HasReference(VkImageView imageView) const; }; uint32 GetHash(const FramebufferVulkan::Key& key); class RenderPassVulkan { public: GPUDeviceVulkan* Device; VkRenderPass Handle; RenderTargetLayoutVulkan Layout; #if VULKAN_USE_DEBUG_DATA VkRenderPassCreateInfo DebugCreateInfo; #endif RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLayoutVulkan& layout); ~RenderPassVulkan(); }; class QueryPoolVulkan { protected: struct Range { uint32 Start; uint32 Count; }; GPUDeviceVulkan* _device; VkQueryPool _handle; 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 bool ResetBeforeUse; 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(CmdBufferVulkan* cmdBuffer, 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 { private: GPUDeviceVulkan* _device; GPUTextureVulkan* _dummyTextures[6]; GPUBufferVulkan* _dummyBuffer; GPUBufferVulkan* _dummyVB; VkSampler _staticSamplers[GPU_STATIC_SAMPLERS_COUNT]; public: HelperResourcesVulkan(GPUDeviceVulkan* device); public: VkSampler* GetStaticSamplers(); GPUTextureVulkan* GetDummyTexture(SpirvShaderResourceType type); GPUBufferVulkan* GetDummyBuffer(); GPUBufferVulkan* GetDummyVertexBuffer(); void Dispose(); }; /// /// Vulkan staging buffers manager. /// class StagingManagerVulkan { private: struct PendingEntry { GPUBuffer* Buffer; CmdBufferVulkan* CmdBuffer; uint64 FenceCounter; }; 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(); }; /// /// Implementation of Graphics Device for Vulkan backend. /// class GPUDeviceVulkan : public GPUDevice { friend GPUContextVulkan; friend GPUSwapChainVulkan; friend FenceManagerVulkan; private: CriticalSection _fenceLock; mutable void* _nativePtr[2]; 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: 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; #if VULKAN_USE_VALIDATION_CACHE uint32 HasEXTValidationCache : 1; #endif }; static OptionalVulkanDeviceExtensions OptionalDeviceExtensions; private: static void GetInstanceLayersAndExtensions(Array& outInstanceExtensions, Array& outInstanceLayers, bool& outDebugUtils); void GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array& outDeviceExtensions, Array& outDeviceLayers); static void ParseOptionalDeviceExtensions(const Array& deviceExtensions); 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 VULKAN_USE_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.Get()[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, VkExtent2D& 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); /// /// Saves the pipeline cache. /// bool SavePipelineCache(); #if VK_EXT_validation_cache /// /// Saves the validation cache. /// bool SaveValidationCache(); #endif private: bool IsVkFormatSupported(VkFormat vkFormat, VkFormatFeatureFlags wantedFeatureFlags, bool optimalTiling) const; 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; GPUSampler* CreateSampler() override; GPUSwapChain* CreateSwapChain(Window* window) override; GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) 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 used 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 used 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 (VK_DESCRIPTOR_TYPE_STORAGE_IMAGE). /// /// The GPU context. Can be used 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 (VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER). /// /// The GPU context. Can be used to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { CRASH; } /// /// Gets the storage buffer descriptor (VK_DESCRIPTOR_TYPE_STORAGE_BUFFER). /// /// The GPU context. Can be used 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 storage texel buffer descriptor (VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER). /// /// The GPU context. Can be used to add memory barriers to the pipeline before binding the descriptor to the pipeline. /// The buffer view. virtual void DescriptorAsStorageTexelBuffer(GPUContextVulkan* context, VkBufferView& bufferView) { CRASH; } /// /// Gets the dynamic uniform buffer descriptor (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC). /// /// The GPU context. Can be used 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