Files
FlaxEngine/Source/Engine/GraphicsDevice/Vulkan/GPUDeviceVulkan.h
2020-12-30 00:52:07 +01:00

847 lines
21 KiB
C++

// Copyright (c) 2012-2020 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:
/// <summary>
/// Initializes a new instance of the <see cref="SemaphoreVulkan"/> class.
/// </summary>
/// <param name="device">The graphics device.</param>
SemaphoreVulkan(GPUDeviceVulkan* device);
/// <summary>
/// Finalizes an instance of the <see cref="SemaphoreVulkan"/> class.
/// </summary>
~SemaphoreVulkan();
/// <summary>
/// Gets the handle.
/// </summary>
/// <returns>The semaphore.</returns>
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<FenceVulkan*> _freeFences;
Array<FenceVulkan*> _usedFences;
public:
FenceManagerVulkan()
: _device(nullptr)
{
}
~FenceManagerVulkan();
public:
/// <summary>
/// Initializes the specified device.
/// </summary>
/// <param name="device">The graphics device.</param>
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 false if it timed out
bool WaitForFence(FenceVulkan* fence, uint64 timeInNanoseconds);
void ResetFence(FenceVulkan* fence);
// Sets it to nullptr
void ReleaseFence(FenceVulkan*& fence);
// Sets it to nullptr
void WaitAndReleaseFence(FenceVulkan*& fence, uint64 timeInNanoseconds);
private:
// Returns true if signaled
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<Entry> _entries;
public:
/// <summary>
/// Initializes a new instance of the <see cref="DeferredDeletionQueueVulkan"/> class.
/// </summary>
/// <param name="device">The graphics device.</param>
DeferredDeletionQueueVulkan(GPUDeviceVulkan* device);
/// <summary>
/// Finalizes an instance of the <see cref="DeferredDeletionQueueVulkan"/> class.
/// </summary>
~DeferredDeletionQueueVulkan();
public:
template<typename T>
inline void EnqueueResource(Type type, T handle)
{
static_assert(sizeof(T) <= sizeof(uint64), "Vulkan resource handle type size too large.");
EnqueueGenericResource(type, (uint64)handle, VK_NULL_HANDLE);
}
template<typename T>
inline void EnqueueResource(Type type, T handle, VmaAllocation allocation)
{
static_assert(sizeof(T) <= sizeof(uint64), "Vulkan resource handle type size too large.");
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<Range> _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<uint64> _queryOutput;
Array<uint64> _usedQueryBits;
Array<uint64> _startedQueryBits;
Array<uint64> _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;
};
/// <summary>
/// The dummy Vulkan resources manager. Helps when user need to pass null texture handle to the shader.
/// </summary>
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<int32>(StaticSamplers::MAX)];
public:
/// <summary>
/// Initializes a new instance of the <see cref="DummyResourcesVulkan"/> class.
/// </summary>
/// <param name="device">The graphics device.</param>
HelperResourcesVulkan(GPUDeviceVulkan* device);
public:
VkSampler GetStaticSampler(StaticSamplers type);
GPUTextureVulkan* GetDummyTexture(SpirvShaderResourceType type);
GPUBufferVulkan* GetDummyBuffer();
GPUBufferVulkan* GetDummyVertexBuffer();
void Dispose();
};
/// <summary>
/// Vulkan staging buffers manager.
/// </summary>
class StagingManagerVulkan
{
private:
struct PendingItems
{
uint64 FenceCounter;
Array<GPUBuffer*> Resources;
};
struct PendingItemsPerCmdBuffer
{
CmdBufferVulkan* CmdBuffer;
Array<PendingItems> Items;
inline PendingItems* FindOrAdd(uint64 fence);
};
struct FreeEntry
{
GPUBuffer* Buffer;
uint64 FrameNumber;
};
GPUDeviceVulkan* _device;
CriticalSection _locker;
Array<GPUBuffer*> _allBuffers;
Array<FreeEntry> _freeBuffers;
Array<PendingItemsPerCmdBuffer> _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);
};
/// <summary>
/// Implementation of Graphics Device for Vulkan backend.
/// </summary>
class GPUDeviceVulkan : public GPUDevice
{
friend GPUContextVulkan;
friend GPUSwapChainVulkan;
friend FenceManagerVulkan;
private:
CriticalSection _fenceLock;
Dictionary<RenderTargetLayoutVulkan, RenderPassVulkan*> _renderPasses;
Dictionary<FramebufferVulkan::Key, FramebufferVulkan*> _framebuffers;
Dictionary<DescriptorSetLayoutInfoVulkan, PipelineLayoutVulkan*> _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();
/// <summary>
/// Initializes a new instance of the <see cref="GPUDeviceVulkan"/> class.
/// </summary>
/// <param name="shaderProfile">The shader profile.</param>
/// <param name="adapter">The GPU device adapter.</param>
GPUDeviceVulkan(ShaderProfile shaderProfile, GPUAdapterVulkan* adapter);
/// <summary>
/// Finalizes an instance of the <see cref="GPUDeviceVulkan"/> class.
/// </summary>
~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<const char*>& outInstanceExtensions, Array<const char*>& outInstanceLayers, bool& outDebugUtils);
static void GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array<const char*>& outDeviceExtensions, Array<const char*>& outDeviceLayers);
void ParseOptionalDeviceExtensions(const Array<const char*>& deviceExtensions);
static OptionalVulkanDeviceExtensions OptionalDeviceExtensions;
public:
/// <summary>
/// The Vulkan instance.
/// </summary>
static VkInstance Instance;
/// <summary>
/// The Vulkan instance extensions.
/// </summary>
static Array<const char*> InstanceExtensions;
/// <summary>
/// The Vulkan instance layers.
/// </summary>
static Array<const char*> InstanceLayers;
public:
/// <summary>
/// The main Vulkan commands context.
/// </summary>
GPUContextVulkan* MainContext;
/// <summary>
/// The Vulkan adapter.
/// </summary>
GPUAdapterVulkan* Adapter;
/// <summary>
/// The Vulkan device.
/// </summary>
VkDevice Device;
/// <summary>
/// The Vulkan device queues family properties.
/// </summary>
Array<VkQueueFamilyProperties> QueueFamilyProps;
/// <summary>
/// The Vulkan fence manager.
/// </summary>
FenceManagerVulkan FenceManager;
/// <summary>
/// The Vulkan resources deferred deletion queue.
/// </summary>
DeferredDeletionQueueVulkan DeferredDeletionQueue;
/// <summary>
/// The staging buffers manager.
/// </summary>
StagingManagerVulkan StagingManager;
/// <summary>
/// The helper device resources manager.
/// </summary>
HelperResourcesVulkan HelperResources;
/// <summary>
/// The graphics queue.
/// </summary>
QueueVulkan* GraphicsQueue;
/// <summary>
/// The compute queue.
/// </summary>
QueueVulkan* ComputeQueue;
/// <summary>
/// The transfer queue.
/// </summary>
QueueVulkan* TransferQueue;
/// <summary>
/// The present queue.
/// </summary>
QueueVulkan* PresentQueue;
/// <summary>
/// The Vulkan memory allocator.
/// </summary>
VmaAllocator Allocator;
/// <summary>
/// The pipeline cache.
/// </summary>
VkPipelineCache PipelineCache;
#if VK_EXT_validation_cache
/// <summary>
/// The optional validation cache.
/// </summary>
VkValidationCacheEXT ValidationCache;
#endif
/// <summary>
/// The uniform buffers uploader.
/// </summary>
UniformBufferUploaderVulkan* UniformBufferUploader;
/// <summary>
/// The descriptor pools manager.
/// </summary>
DescriptorPoolsManagerVulkan* DescriptorPoolsManager;
/// <summary>
/// The physical device limits.
/// </summary>
VkPhysicalDeviceLimits PhysicalDeviceLimits;
/// <summary>
/// The physical device enabled features.
/// </summary>
VkPhysicalDeviceFeatures PhysicalDeviceFeatures;
Array<BufferedQueryPoolVulkan*> TimestampQueryPools;
#if VULKAN_RESET_QUERY_POOLS
Array<QueryPoolVulkan*> QueriesToReset;
#endif
inline BufferedQueryPoolVulkan* FindAvailableQueryPool(Array<BufferedQueryPoolVulkan*>& 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<BufferedQueryPoolVulkan>(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:
/// <summary>
/// Setups the present queue to be ready for the given window surface.
/// </summary>
/// <param name="surface">The surface.</param>
void SetupPresentQueue(VkSurfaceKHR surface);
/// <summary>
/// Finds the closest pixel format that a specific Vulkan device supports.
/// </summary>
/// <param name="format">The input format.</param>
/// <param name="flags">The texture usage flags.</param>
/// <param name="optimalTiling">If set to <c>true</c> the optimal tiling should be used, otherwise use linear tiling.</param>
/// <returns>The output format.</returns>
PixelFormat GetClosestSupportedPixelFormat(PixelFormat format, GPUTextureFlags flags, bool optimalTiling);
#if VK_EXT_validation_cache
/// <summary>
/// Loads the validation cache.
/// </summary>
void LoadValidationCache();
/// <summary>
/// Saves the validation cache.
/// </summary>
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;
};
/// <summary>
/// GPU resource implementation for Vulkan backend.
/// </summary>
template<class BaseType>
class GPUResourceVulkan : public GPUResourceBase<GPUDeviceVulkan, BaseType>
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceVulkan"/> class.
/// </summary>
/// <param name="device">The graphics device.</param>
/// <param name="name">The resource name.</param>
GPUResourceVulkan(GPUDeviceVulkan* device, const StringView& name)
: GPUResourceBase<GPUDeviceVulkan, BaseType>(device, name)
{
}
};
/// <summary>
/// Represents a GPU resource that contain descriptor resource for binding to the pipeline (shader resource, sampler, buffer, etc.).
/// </summary>
class DescriptorOwnerResourceVulkan
{
public:
/// <summary>
/// Finalizes an instance of the <see cref="DescriptorOwnerResourceVulkan"/> class.
/// </summary>
virtual ~DescriptorOwnerResourceVulkan()
{
}
public:
/// <summary>
/// Gets the sampler descriptor.
/// </summary>
/// <param name="context">The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline.</param>
/// <param name="sampler">The sampler.</param>
virtual void DescriptorAsSampler(GPUContextVulkan* context, VkSampler& sampler)
{
CRASH;
}
/// <summary>
/// Gets the image descriptor.
/// </summary>
/// <param name="context">The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline.</param>
/// <param name="imageView">The image view.</param>
/// <param name="layout">The image layout.</param>
virtual void DescriptorAsImage(GPUContextVulkan* context, VkImageView& imageView, VkImageLayout& layout)
{
CRASH;
}
/// <summary>
/// Gets the storage image descriptor.
/// </summary>
/// <param name="context">The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline.</param>
/// <param name="imageView">The image view.</param>
/// <param name="layout">The image layout.</param>
virtual void DescriptorAsStorageImage(GPUContextVulkan* context, VkImageView& imageView, VkImageLayout& layout)
{
CRASH;
}
/// <summary>
/// Gets the uniform texel buffer descriptor.
/// </summary>
/// <param name="context">The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline.</param>
/// <param name="bufferView">The buffer view.</param>
virtual void DescriptorAsUniformTexelBuffer(GPUContextVulkan* context, const VkBufferView*& bufferView)
{
CRASH;
}
/// <summary>
/// Gets the storage buffer descriptor.
/// </summary>
/// <param name="context">The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="offset">The range offset (in bytes).</param>
/// <param name="range">The range size (in bytes).</param>
virtual void DescriptorAsStorageBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range)
{
CRASH;
}
/// <summary>
/// Gets the dynamic uniform buffer descriptor.
/// </summary>
/// <param name="context">The GPU context. Can be sued to add memory barriers to the pipeline before binding the descriptor to the pipeline.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="offset">The range offset (in bytes).</param>
/// <param name="range">The range size (in bytes).</param>
/// <param name="dynamicOffset">The dynamic offset (in bytes).</param>
virtual void DescriptorAsDynamicUniformBuffer(GPUContextVulkan* context, VkBuffer& buffer, VkDeviceSize& offset, VkDeviceSize& range, uint32& dynamicOffset)
{
CRASH;
}
};
extern GPUDevice* CreateGPUDeviceVulkan();
#endif