// Copyright (c) 2012-2022 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;
bool BlendEnable;
PixelFormat DepthFormat;
PixelFormat RTVsFormats[GPU_MAX_RT_BINDED];
VkExtent2D Extent;
uint32 Layers;
public:
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];
public:
bool operator==(const Key& other) const
{
return Platform::MemoryCompare(this, &other, sizeof(Key)) == 0;
}
};
private:
GPUDeviceVulkan* _device;
VkFramebuffer _handle;
public:
FramebufferVulkan(GPUDeviceVulkan* device, Key& key, VkExtent2D& extent, uint32 layers);
~FramebufferVulkan();
public:
VkImageView Attachments[GPU_MAX_RT_BINDED + 1];
VkExtent2D 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
{
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;
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;
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, 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;
};
///
/// 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