// 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