// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VulkanPipelineState.h: Vulkan pipeline state definitions. =============================================================================*/ #pragma once #include "Async/AsyncWork.h" #include "VulkanConfiguration.h" #include "VulkanDevice.h" #include "VulkanMemory.h" #include "VulkanRHIPrivate.h" #include "VulkanShaderResources.h" #if (UE_BUILD_SHIPPING || UE_BUILD_TEST) #define VULKAN_VALIDATE_DESCRIPTORS_WRITTEN 0 #else #define VULKAN_VALIDATE_DESCRIPTORS_WRITTEN 1 #endif class FVulkanCommandBufferPool; // (AlwaysCompareData == true) because of CRC-32 hash collisions class FVulkanDSetKey : public TDataKey {}; class FVulkanDSetsKey : public TDataKey {}; struct FUniformBufferGatherInfo { FUniformBufferGatherInfo() { FMemory::Memzero(CodeHeaders); } const FVulkanShaderHeader* CodeHeaders[ShaderStage::MaxNumStages]; }; // Information for the layout of descriptor sets; does not hold runtime objects class FVulkanDescriptorSetsLayoutInfo { public: FVulkanDescriptorSetsLayoutInfo() { // Add expected descriptor types for (uint32 i = VK_DESCRIPTOR_TYPE_BEGIN_RANGE; i <= VK_DESCRIPTOR_TYPE_END_RANGE; ++i) { LayoutTypes.Add(static_cast(i), 0); } LayoutTypes.Add(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 0); } inline uint32 GetTypesUsed(VkDescriptorType Type) const { if (LayoutTypes.Contains(Type)) { return LayoutTypes[Type]; } else { return 0; } } struct FSetLayout { TArray LayoutBindings; uint32 Hash; inline void GenerateHash() { Hash = FCrc::MemCrc32(LayoutBindings.GetData(), sizeof(VkDescriptorSetLayoutBinding) * LayoutBindings.Num()); } friend uint32 GetTypeHash(const FSetLayout& In) { return In.Hash; } inline bool operator == (const FSetLayout& In) const { if (In.Hash != Hash) { return false; } const int32 NumBindings = LayoutBindings.Num(); if (In.LayoutBindings.Num() != NumBindings) { return false; } if (NumBindings != 0 && FMemory::Memcmp(In.LayoutBindings.GetData(), LayoutBindings.GetData(), NumBindings * sizeof(VkDescriptorSetLayoutBinding)) != 0) { return false; } return true; } inline bool operator != (const FSetLayout& In) const { return !(*this == In); } }; const TArray& GetLayouts() const { return SetLayouts; } void ProcessBindingsForStage(VkShaderStageFlagBits StageFlags, ShaderStage::EStage DescSetStage, const FVulkanShaderHeader& CodeHeader, FUniformBufferGatherInfo& OutUBGatherInfo) const; template void FinalizeBindings(const FVulkanDevice& Device, const FUniformBufferGatherInfo& UBGatherInfo, const TArrayView& ImmutableSamplers, bool bUsesBindless); void GenerateHash(const TArrayView& ImmutableSamplers, VkPipelineBindPoint InBindPoint); friend uint32 GetTypeHash(const FVulkanDescriptorSetsLayoutInfo& In) { return In.Hash; } inline bool operator == (const FVulkanDescriptorSetsLayoutInfo& In) const { if (In.Hash != Hash) { return false; } if (In.BindPoint != BindPoint) { return false; } if (In.SetLayouts.Num() != SetLayouts.Num()) { return false; } if (In.TypesUsageID != TypesUsageID) { return false; } for (int32 Index = 0; Index < In.SetLayouts.Num(); ++Index) { if (In.SetLayouts[Index] != SetLayouts[Index]) { return false; } } if (StageInfos != In.StageInfos) { return false; } return true; } void CopyFrom(const FVulkanDescriptorSetsLayoutInfo& Info) { LayoutTypes = Info.LayoutTypes; Hash = Info.Hash; TypesUsageID = Info.TypesUsageID; SetLayouts = Info.SetLayouts; StageInfos = Info.StageInfos; } inline const TMap& GetLayoutTypes() const { return LayoutTypes; } inline uint32 GetTypesUsageID() const { return TypesUsageID; } inline bool HasInputAttachments() const { return GetTypesUsed(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) > 0; } struct FStageInfo { TArray Types; uint32 PackedGlobalsSize = 0; uint32 NumBoundUniformBuffers = 0; uint16 NumImageInfos = 0; uint16 NumBufferInfos = 0; uint16 NumAccelerationStructures = 0; inline bool IsEmpty() const { if ((Types.Num() != 0) || (PackedGlobalsSize != 0) || (NumBoundUniformBuffers != 0)) { return false; } return true; } inline bool operator==(const FStageInfo& In) const { if (PackedGlobalsSize != In.PackedGlobalsSize || NumBoundUniformBuffers != In.NumBoundUniformBuffers || NumBufferInfos != In.NumBufferInfos || NumImageInfos != In.NumImageInfos || NumAccelerationStructures != In.NumAccelerationStructures || Types.Num() != In.Types.Num() || FMemory::Memcmp(Types.GetData(), In.Types.GetData(), Types.NumBytes())) { return false; } return true; } }; TStaticArray StageInfos; protected: TMap LayoutTypes; TArray SetLayouts; uint32 Hash = 0; uint32 TypesUsageID = ~0; VkPipelineBindPoint BindPoint = VK_PIPELINE_BIND_POINT_MAX_ENUM; void CompileTypesUsageID(); void AddDescriptor(int32 DescriptorSetIndex, const VkDescriptorSetLayoutBinding& Descriptor); friend class FVulkanPipelineStateCacheManager; friend class FVulkanCommonPipelineDescriptorState; friend class FVulkanLayout; }; struct FVulkanDescriptorSetLayoutEntry { VkDescriptorSetLayout Handle = 0; uint32 HandleId = 0; }; using FVulkanDescriptorSetLayoutMap = TMap; // The actual run-time descriptor set layouts class FVulkanDescriptorSetsLayout : public FVulkanDescriptorSetsLayoutInfo { public: FVulkanDescriptorSetsLayout(FVulkanDevice* InDevice); ~FVulkanDescriptorSetsLayout(); // Can be called only once, the idea is that the Layout remains fixed. void Compile(FVulkanDescriptorSetLayoutMap& DSetLayoutMap); inline const TArray& GetHandles() const { return LayoutHandles; } inline const TArray& GetHandleIds() const { return LayoutHandleIds; } inline const VkDescriptorSetAllocateInfo& GetAllocateInfo() const { return DescriptorSetAllocateInfo; } inline uint32 GetHash() const { return Hash; } private: FVulkanDevice* Device; //uint32 Hash = 0; TArray LayoutHandles; TArray LayoutHandleIds; VkDescriptorSetAllocateInfo DescriptorSetAllocateInfo; }; class FVulkanDescriptorPool { public: FVulkanDescriptorPool(FVulkanDevice* InDevice, const FVulkanDescriptorSetsLayout& Layout, uint32 MaxSetsAllocations); ~FVulkanDescriptorPool(); inline VkDescriptorPool GetHandle() const { return DescriptorPool; } inline bool CanAllocate(const FVulkanDescriptorSetsLayout& InLayout) const { return MaxDescriptorSets > NumAllocatedDescriptorSets + InLayout.GetLayouts().Num(); } void TrackAddUsage(const FVulkanDescriptorSetsLayout& InLayout); void TrackRemoveUsage(const FVulkanDescriptorSetsLayout& InLayout); inline bool IsEmpty() const { return NumAllocatedDescriptorSets == 0; } void Reset(); bool AllocateDescriptorSets(const VkDescriptorSetAllocateInfo& InDescriptorSetAllocateInfo, VkDescriptorSet* OutSets); inline uint32 GetNumAllocatedDescriptorSets() const { return NumAllocatedDescriptorSets; } private: FVulkanDevice* Device; uint32 MaxDescriptorSets; uint32 NumAllocatedDescriptorSets; uint32 PeakAllocatedDescriptorSets; // Tracks number of allocated types, to ensure that we are not exceeding our allocated limit const FVulkanDescriptorSetsLayout& Layout; VkDescriptorPool DescriptorPool; friend class FVulkanCommandListContext; }; class FVulkanTypedDescriptorPoolSet { typedef TList FPoolList; FVulkanDescriptorPool* GetFreePool(bool bForceNewPool = false); FVulkanDescriptorPool* PushNewPool(); protected: friend class FVulkanDescriptorPoolSetContainer; friend class FVulkanCommandBuffer; FVulkanTypedDescriptorPoolSet(FVulkanDevice* InDevice, const FVulkanDescriptorSetsLayout& InLayout) : Device(InDevice) , Layout(InLayout) , PoolsCount(0) { PushNewPool(); }; ~FVulkanTypedDescriptorPoolSet(); void Reset(); public: bool AllocateDescriptorSets(const FVulkanDescriptorSetsLayout& Layout, VkDescriptorSet* OutSets); private: FVulkanDevice* Device; const FVulkanDescriptorSetsLayout& Layout; uint32 PoolsCount; FPoolList* PoolListHead = nullptr; FPoolList* PoolListCurrent = nullptr; }; class FVulkanDescriptorPoolSetContainer { public: FVulkanDescriptorPoolSetContainer(FVulkanDevice* InDevice) : Device(InDevice) , LastFrameUsed(GFrameNumberRenderThread) , bUsed(true) { } ~FVulkanDescriptorPoolSetContainer(); FVulkanTypedDescriptorPoolSet* AcquireTypedPoolSet(const FVulkanDescriptorSetsLayout& Layout); void Reset(); inline void SetUsed(bool bInUsed) { bUsed = bInUsed; LastFrameUsed = bUsed ? GFrameNumberRenderThread : LastFrameUsed; } inline bool IsUnused() const { return !bUsed; } inline uint32 GetLastFrameUsed() const { return LastFrameUsed; } private: FVulkanDevice* Device; TMap TypedDescriptorPools; uint32 LastFrameUsed; bool bUsed; }; class FVulkanDescriptorPoolsManager { class FVulkanAsyncPoolSetDeletionWorker : public FNonAbandonableTask { FVulkanDescriptorPoolSetContainer* PoolSet; public: FVulkanAsyncPoolSetDeletionWorker(FVulkanDescriptorPoolSetContainer* InPoolSet) : PoolSet(InPoolSet) {}; void DoWork() { check(PoolSet != nullptr); delete PoolSet; PoolSet = nullptr; } void SetPoolSet(FVulkanDescriptorPoolSetContainer* InPoolSet) { check(PoolSet == nullptr); PoolSet = InPoolSet; } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FVulkanAsyncPoolSetDeletionWorker, STATGROUP_ThreadPoolAsyncTasks); } }; public: ~FVulkanDescriptorPoolsManager(); void Init(FVulkanDevice* InDevice) { Device = InDevice; } FVulkanDescriptorPoolSetContainer& AcquirePoolSetContainer(); void ReleasePoolSet(FVulkanDescriptorPoolSetContainer& PoolSet); void GC(); private: FVulkanDevice* Device = nullptr; FAsyncTask* AsyncDeletionTask = nullptr; FCriticalSection CS; TArray PoolSets; }; union FVulkanHashableDescriptorInfo { struct { uint32 Max0; uint32 Max1; uint32 LayoutId; } Layout; struct { uint32 Id; uint32 Offset; uint32 Range; } Buffer; struct { uint32 SamplerId; uint32 ImageViewId; uint32 ImageLayout; } Image; struct { uint32 Id; uint32 Zero1; uint32 Zero2; } BufferView; }; // This container holds the actual VkWriteDescriptorSet structures; a Compute pipeline uses the arrays 'as-is', whereas a // Gfx PSO will have one big array and chunk it depending on the stage (eg Vertex, Pixel). struct FVulkanDescriptorSetWriteContainer { TArray HashableDescriptorInfo; TArray DescriptorImageInfo; TArray DescriptorBufferInfo; TArray DescriptorWrites; TArray AccelerationStructures; TArray AccelerationStructureWrites; TArray BindingToDynamicOffsetMap; }; // This class encapsulates updating VkWriteDescriptorSet structures (but doesn't own them), and their flags for dirty ranges; it is intended // to be used to access a sub-region of a long array of VkWriteDescriptorSet (ie FVulkanDescriptorSetWriteContainer) class FVulkanDescriptorSetWriter { public: FVulkanDescriptorSetWriter() : WriteDescriptors(nullptr) , BindingToDynamicOffsetMap(nullptr) , DynamicOffsets(nullptr) , NumWrites(0) , HashableDescriptorInfos(nullptr) , bIsKeyDirty(true) { } const FVulkanDSetKey& GetKey() const { check(UseVulkanDescriptorCache()); if (bIsKeyDirty) { Key.GenerateFromData(HashableDescriptorInfos, sizeof(FVulkanHashableDescriptorInfo) * (NumWrites + 1)); // Add 1 for the Layout bIsKeyDirty = false; } return Key; } const VkWriteDescriptorSet* GetWriteDescriptors() const { return WriteDescriptors; } const uint32 GetNumWrites() const { return NumWrites; } bool WriteUniformBuffer(uint32 DescriptorIndex, VkBuffer BufferHandle, uint32 HandleId, VkDeviceSize Offset, VkDeviceSize Range) { return WriteBuffer(DescriptorIndex, BufferHandle, HandleId, Offset, Range); } bool WriteDynamicUniformBuffer(uint32 DescriptorIndex, VkBuffer BufferHandle, uint32 HandleId, VkDeviceSize Offset, VkDeviceSize Range, uint32 DynamicOffset) { return WriteBuffer(DescriptorIndex, BufferHandle, HandleId, Offset, Range, DynamicOffset); } bool WriteSampler(uint32 DescriptorIndex, const FVulkanSamplerState& Sampler) { check(DescriptorIndex < NumWrites); check(WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER || WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); VkDescriptorImageInfo* ImageInfo = const_cast(WriteDescriptors[DescriptorIndex].pImageInfo); check(ImageInfo); bool bChanged = false; if (UseVulkanDescriptorCache()) { FVulkanHashableDescriptorInfo& HashableInfo = HashableDescriptorInfos[DescriptorIndex]; check(Sampler.SamplerId > 0); if (HashableInfo.Image.SamplerId != Sampler.SamplerId) { HashableInfo.Image.SamplerId = Sampler.SamplerId; ImageInfo->sampler = Sampler.Sampler; bChanged = true; } bIsKeyDirty |= bChanged; } else { bChanged = CopyAndReturnNotEqual(ImageInfo->sampler, Sampler.Sampler); } return bChanged; } bool WriteImage(uint32 DescriptorIndex, const FVulkanView::FTextureView& TextureView, VkImageLayout Layout) { return WriteTextureView(DescriptorIndex, TextureView, Layout); } bool WriteInputAttachment(uint32 DescriptorIndex, const FVulkanView::FTextureView& TextureView, VkImageLayout Layout) { return WriteTextureView(DescriptorIndex, TextureView, Layout); } bool WriteStorageImage(uint32 DescriptorIndex, const FVulkanView::FTextureView& TextureView, VkImageLayout Layout) { return WriteTextureView(DescriptorIndex, TextureView, Layout); } bool WriteStorageTexelBuffer(uint32 DescriptorIndex, const FVulkanView::FTypedBufferView& View) { return WriteBufferView(DescriptorIndex, View); } bool WriteStorageBuffer(uint32 DescriptorIndex, const FVulkanView::FStructuredBufferView& View) { return WriteBuffer(DescriptorIndex, View.Buffer, View.HandleId, View.Offset, View.Size); } bool WriteUniformTexelBuffer(uint32 DescriptorIndex, const FVulkanView::FTypedBufferView& View) { return WriteBufferView(DescriptorIndex, View); } bool WriteAccelerationStructure(uint32 DescriptorIndex, VkAccelerationStructureKHR InAccelerationStructure) { checkf(!UseVulkanDescriptorCache(), TEXT("Descriptor cache path for WriteAccelerationStructure() is not implemented")); check(DescriptorIndex < NumWrites); SetWritten(DescriptorIndex); check(WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR); // Find the acceleration structure extension in the generic VkWriteDescriptorSet. const VkWriteDescriptorSetAccelerationStructureKHR* FoundWrite = nullptr; const VkBaseInStructure* Cursor = reinterpret_cast(WriteDescriptors[DescriptorIndex].pNext); while (Cursor) { if (Cursor->sType == VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR) { FoundWrite = reinterpret_cast(Cursor); break; } Cursor = Cursor->pNext; } checkf(FoundWrite, TEXT("Expected to find a VkWriteDescriptorSetAccelerationStructureKHR that's needed to bind an acceleration structure descriptor. ") TEXT("Possibly something went wrong in SetupDescriptorWrites().")); checkf(FoundWrite->accelerationStructureCount == 1, TEXT("Acceleration structure write operation is expected to contain exactly one descriptor")); VkAccelerationStructureKHR& AccelerationStructure = *const_cast(FoundWrite->pAccelerationStructures); bool bChanged = CopyAndReturnNotEqual(AccelerationStructure, InAccelerationStructure); return bChanged; } void SetDescriptorSet(VkDescriptorSet DescriptorSet) { for (uint32 Index = 0; Index < NumWrites; ++Index) { WriteDescriptors[Index].dstSet = DescriptorSet; } } protected: template bool WriteBuffer(uint32 DescriptorIndex, VkBuffer BufferHandle, uint32 HandleId, VkDeviceSize Offset, VkDeviceSize Range, uint32 DynamicOffset = 0) { check(DescriptorIndex < NumWrites); SetWritten(DescriptorIndex); if (DescriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) { checkf(WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, TEXT("DescriptorType mismatch at index %d: called WriteBuffer<%s> and was expecting %s."), DescriptorIndex, VK_TYPE_TO_STRING(VkDescriptorType, DescriptorType), VK_TYPE_TO_STRING(VkDescriptorType, WriteDescriptors[DescriptorIndex].descriptorType)); } else { checkf(WriteDescriptors[DescriptorIndex].descriptorType == DescriptorType, TEXT("DescriptorType mismatch at index %d: called WriteBuffer<%s> and was expecting %s."), DescriptorIndex, VK_TYPE_TO_STRING(VkDescriptorType, DescriptorType), VK_TYPE_TO_STRING(VkDescriptorType, WriteDescriptors[DescriptorIndex].descriptorType)); } VkDescriptorBufferInfo* BufferInfo = const_cast(WriteDescriptors[DescriptorIndex].pBufferInfo); check(BufferInfo); bool bChanged = false; if (UseVulkanDescriptorCache()) { FVulkanHashableDescriptorInfo& HashableInfo = HashableDescriptorInfos[DescriptorIndex]; check(HandleId > 0); if (HashableInfo.Buffer.Id != HandleId) { HashableInfo.Buffer.Id = HandleId; BufferInfo->buffer = BufferHandle; bChanged = true; } if (HashableInfo.Buffer.Offset != static_cast(Offset)) { HashableInfo.Buffer.Offset = static_cast(Offset); BufferInfo->offset = Offset; bChanged = true; } if (HashableInfo.Buffer.Range != static_cast(Range)) { HashableInfo.Buffer.Range = static_cast(Range); BufferInfo->range = Range; bChanged = true; } bIsKeyDirty |= bChanged; } else { bChanged = CopyAndReturnNotEqual(BufferInfo->buffer, BufferHandle); bChanged |= CopyAndReturnNotEqual(BufferInfo->offset, Offset); bChanged |= CopyAndReturnNotEqual(BufferInfo->range, Range); } if (DescriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) { const uint8 DynamicOffsetIndex = BindingToDynamicOffsetMap[DescriptorIndex]; DynamicOffsets[DynamicOffsetIndex] = DynamicOffset; } return bChanged; } template bool WriteTextureView(uint32 DescriptorIndex, const FVulkanView::FTextureView& TextureView, VkImageLayout Layout) { check(DescriptorIndex < NumWrites); SetWritten(DescriptorIndex); if (DescriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { checkf(WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || WriteDescriptors[DescriptorIndex].descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, TEXT("DescriptorType mismatch at index %d: called WriteTextureView<%d> and was expecting %d."), DescriptorIndex, (uint32)DescriptorType, (uint32)WriteDescriptors[DescriptorIndex].descriptorType); ensureMsgf(Layout == VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL || Layout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR || Layout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL || Layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL || Layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL || Layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL || Layout == VK_IMAGE_LAYOUT_GENERAL, TEXT("Invalid Layout %s, Index %d, Type %s\n"), VK_TYPE_TO_STRING(VkImageLayout, Layout), DescriptorIndex, VK_TYPE_TO_STRING(VkDescriptorType, WriteDescriptors[DescriptorIndex].descriptorType)); } else { checkf(WriteDescriptors[DescriptorIndex].descriptorType == DescriptorType, TEXT("DescriptorType mismatch at index %d: called WriteTextureView<%s> and was expecting %s."), DescriptorIndex, VK_TYPE_TO_STRING(VkDescriptorType, DescriptorType), VK_TYPE_TO_STRING(VkDescriptorType, WriteDescriptors[DescriptorIndex].descriptorType)); } VkDescriptorImageInfo* ImageInfo = const_cast(WriteDescriptors[DescriptorIndex].pImageInfo); check(ImageInfo); bool bChanged = false; if (UseVulkanDescriptorCache()) { FVulkanHashableDescriptorInfo& HashableInfo = HashableDescriptorInfos[DescriptorIndex]; check(TextureView.ViewId > 0); if (HashableInfo.Image.ImageViewId != TextureView.ViewId) { HashableInfo.Image.ImageViewId = TextureView.ViewId; ImageInfo->imageView = TextureView.View; bChanged = true; } if (HashableInfo.Image.ImageLayout != static_cast(Layout)) { HashableInfo.Image.ImageLayout = static_cast(Layout); ImageInfo->imageLayout = Layout; bChanged = true; } bIsKeyDirty |= bChanged; } else { bChanged = CopyAndReturnNotEqual(ImageInfo->imageView, TextureView.View); bChanged |= CopyAndReturnNotEqual(ImageInfo->imageLayout, Layout); } return bChanged; } template bool WriteBufferView(uint32 DescriptorIndex, const FVulkanView::FTypedBufferView& View) { check(DescriptorIndex < NumWrites); checkf(WriteDescriptors[DescriptorIndex].descriptorType == DescriptorType, TEXT("DescriptorType mismatch at index %d: called WriteBufferView<%s> and was expecting %s."), DescriptorIndex, VK_TYPE_TO_STRING(VkDescriptorType, DescriptorType), VK_TYPE_TO_STRING(VkDescriptorType, WriteDescriptors[DescriptorIndex].descriptorType)); SetWritten(DescriptorIndex); WriteDescriptors[DescriptorIndex].pTexelBufferView = &View.View; const bool bVolatile = View.bVolatile; bHasVolatileResources|= bVolatile; if (!bVolatile && UseVulkanDescriptorCache()) { bool bChanged = false; FVulkanHashableDescriptorInfo& HashableInfo = HashableDescriptorInfos[DescriptorIndex]; check(View.ViewId > 0); if (HashableInfo.BufferView.Id != View.ViewId) { HashableInfo.BufferView.Id = View.ViewId; bChanged = true; } bIsKeyDirty |= bChanged; return bChanged; } else { return true; } } protected: // A view into someone else's descriptors VkWriteDescriptorSet* WriteDescriptors; // A view into the mapping from binding index to dynamic uniform buffer offsets uint8* BindingToDynamicOffsetMap; // A view into someone else's dynamic uniform buffer offsets uint32* DynamicOffsets; uint32 NumWrites; FVulkanHashableDescriptorInfo* HashableDescriptorInfos; mutable FVulkanDSetKey Key; mutable bool bIsKeyDirty; bool bHasVolatileResources = false; uint32 SetupDescriptorWrites(const TArray& Types, FVulkanHashableDescriptorInfo* InHashableDescriptorInfos, VkWriteDescriptorSet* InWriteDescriptors, VkDescriptorImageInfo* InImageInfo, VkDescriptorBufferInfo* InBufferInfo, uint8* InBindingToDynamicOffsetMap, VkWriteDescriptorSetAccelerationStructureKHR* InAccelerationStructuresWriteDescriptors, VkAccelerationStructureKHR* InAccelerationStructures, const FVulkanSamplerState& DefaultSampler, const FVulkanView::FTextureView& DefaultImageView); friend class FVulkanCommonPipelineDescriptorState; friend class FVulkanComputePipelineDescriptorState; friend class FVulkanGraphicsPipelineDescriptorState; friend class FVulkanDescriptorSetCache; #if VULKAN_VALIDATE_DESCRIPTORS_WRITTEN TArray > WrittenMask; TArray > BaseWrittenMask; #endif void CheckAllWritten(); void Reset(); void SetWritten(uint32 DescriptorIndex); void SetWrittenBase(uint32 DescriptorIndex); void InitWrittenMasks(uint32 NumDescriptorWrites); }; // Manager for resource descriptors used in bindless rendering. class FVulkanBindlessDescriptorManager { public: FVulkanBindlessDescriptorManager(FVulkanDevice& InDevice); ~FVulkanBindlessDescriptorManager(); typedef TStaticArray, ShaderStage::MaxNumStages> FUniformBufferDescriptorArrays; void Init(); void Deinit(); static bool VerifySupport(FVulkanDevice& InDevice); inline bool IsSupported() { return bIsSupported; } inline VkPipelineLayout GetPipelineLayout() const { return BindlessPipelineLayout; } void BindDescriptorBuffers(VkCommandBuffer CommandBuffer, VkPipelineStageFlags SupportedStages); FRHIDescriptorHandle ReserveDescriptor(VkDescriptorType DescriptorType); void UpdateSampler(FRHIDescriptorHandle DescriptorHandle, VkSampler VulkanSampler); void UpdateImage(FRHIDescriptorHandle DescriptorHandle, VkDescriptorType DescriptorType, VkImageView VulkanImage, bool bIsDepthStencil, bool bImmediateUpdate = true); void UpdateBuffer(FRHIDescriptorHandle DescriptorHandle, VkDescriptorType DescriptorType, VkBuffer VulkanBuffer, VkDeviceSize BufferOffset, VkDeviceSize BufferSize, bool bImmediateUpdate = true); void UpdateBuffer(FRHIDescriptorHandle DescriptorHandle, VkDescriptorType DescriptorType, VkDeviceAddress BufferAddress, VkDeviceSize BufferSize, bool bImmediateUpdate = true); void UpdateTexelBuffer(FRHIDescriptorHandle DescriptorHandle, VkDescriptorType DescriptorType, const VkBufferViewCreateInfo& ViewInfo, bool bImmediateUpdate = true); void UpdateAccelerationStructure(FRHIDescriptorHandle DescriptorHandle, VkAccelerationStructureKHR AccelerationStructure, bool bImmediateUpdate = true); void RegisterUniformBuffers(FVulkanCommandListContext& Context, VkPipelineBindPoint BindPoint, const FUniformBufferDescriptorArrays& StageUBs); void Unregister(FRHIDescriptorHandle DescriptorHandle); void UpdateUBAllocator(); private: FVulkanDevice& Device; const bool bIsSupported; VkDescriptorSetLayout EmptyDescriptorSetLayout = VK_NULL_HANDLE; struct BindlessSetState { TArray DescriptorTypes; uint32 MaxDescriptorCount = 0; std::atomic PeakDescriptorCount = 1; // always keep a null descriptor in slot 0 VkDescriptorSetLayout DescriptorSetLayout = VK_NULL_HANDLE; FCriticalSection FreeListCS; uint32 FreeListHead = MAX_uint32; uint32 DescriptorSize = 0; VkBuffer BufferHandle = VK_NULL_HANDLE; VkDeviceMemory MemoryHandle = VK_NULL_HANDLE; uint8* MappedPointer = nullptr; TArray DebugDescriptors; }; BindlessSetState BindlessSetStates[VulkanBindless::NumBindlessSets]; VkDescriptorSetLayout SingleUseUBDescriptorSetLayout = VK_NULL_HANDLE; VulkanRHI::FTempBlockAllocator* SingleUseUBAllocator = nullptr; VkDescriptorBufferBindingInfoEXT BufferBindingInfo[VulkanBindless::NumBindlessSets]; uint32_t BufferIndices[VulkanBindless::MaxNumSets]; VkPipelineLayout BindlessPipelineLayout = VK_NULL_HANDLE; uint32 GetFreeResourceIndex(BindlessSetState& Desc); void UpdateStatsForHandle(VkDescriptorType DescriptorType); void UpdateDescriptor(FRHIDescriptorHandle DescriptorHandle, VkDescriptorType DescriptorType, VkDescriptorDataEXT DescriptorData, bool bImmediateUpdate); }; // Layout for a Pipeline, also includes DescriptorSets layout class FVulkanLayout : public VulkanRHI::FDeviceChild { public: FVulkanLayout(FVulkanDevice* InDevice, bool InGfxLayout, bool InUsesBindless); virtual ~FVulkanLayout(); bool IsGfxLayout() const { return bIsGfxLayout; } inline const FVulkanDescriptorSetsLayout& GetDescriptorSetsLayout() const { return DescriptorSetLayout; } inline VkPipelineLayout GetPipelineLayout() const { return bUsesBindless ? Device->GetBindlessDescriptorManager()->GetPipelineLayout() : PipelineLayout; } inline bool HasDescriptors() const { return DescriptorSetLayout.GetLayouts().Num() > 0; } inline uint32 GetDescriptorSetLayoutHash() const { return DescriptorSetLayout.GetHash(); } protected: const bool bIsGfxLayout; const bool bUsesBindless; FVulkanDescriptorSetsLayout DescriptorSetLayout; VkPipelineLayout PipelineLayout; template inline void FinalizeBindings(const FUniformBufferGatherInfo& UBGatherInfo) { // Setting descriptor is only allowed prior to compiling the layout check(DescriptorSetLayout.GetHandles().Num() == 0); DescriptorSetLayout.FinalizeBindings(UBGatherInfo); } inline void ProcessBindingsForStage(VkShaderStageFlagBits StageFlags, ShaderStage::EStage DescSet, const FVulkanShaderHeader& CodeHeader, FUniformBufferGatherInfo& OutUBGatherInfo) const { // Setting descriptor is only allowed prior to compiling the layout check(DescriptorSetLayout.GetHandles().Num() == 0); DescriptorSetLayout.ProcessBindingsForStage(StageFlags, DescSet, CodeHeader, OutUBGatherInfo); } void Compile(FVulkanDescriptorSetLayoutMap& DSetLayoutMap); friend class FVulkanComputePipeline; friend class FVulkanGfxPipeline; friend class FVulkanPipelineStateCacheManager; friend class FVulkanRayTracingPipelineState; }; class FVulkanGenericDescriptorPool : FNoncopyable { public: FVulkanGenericDescriptorPool(FVulkanDevice* InDevice, uint32 InMaxDescriptorSets, const float PoolSizes[VK_DESCRIPTOR_TYPE_RANGE_SIZE]); ~FVulkanGenericDescriptorPool(); FVulkanDevice* GetDevice() const { return Device; } uint32 GetMaxDescriptorSets() const { return MaxDescriptorSets; } void Reset(); bool AllocateDescriptorSet(VkDescriptorSetLayout Layout, VkDescriptorSet& OutSet); private: FVulkanDevice* const Device; const uint32 MaxDescriptorSets; VkDescriptorPool DescriptorPool; // information for debugging uint32 PoolSizes[VK_DESCRIPTOR_TYPE_RANGE_SIZE]; }; class FVulkanDescriptorSetCache : FNoncopyable { public: FVulkanDescriptorSetCache(FVulkanDevice* InDevice); ~FVulkanDescriptorSetCache(); void GetDescriptorSets(const FVulkanDSetsKey& DSetsKey, const FVulkanDescriptorSetsLayout& SetsLayout, TArray& DSWriters, VkDescriptorSet* OutSets); void GC(); private: void UpdateAllocRatio(); void AddCachedPool(); private: struct FSetsEntry { TStaticArray Sets; int32 NumSets; }; class FCachedPool : FNoncopyable { public: FCachedPool(FVulkanDevice* InDevice, uint32 InMaxDescriptorSets, const float PoolSizesRatio[VK_DESCRIPTOR_TYPE_RANGE_SIZE]); inline uint32 GetMaxDescriptorSets() const { return Pool.GetMaxDescriptorSets(); } void Reset(); bool CanGC() const; float CalcAllocRatio() const; bool FindDescriptorSets(const FVulkanDSetsKey& DSetsKey, VkDescriptorSet* OutSets); bool CreateDescriptorSets(const FVulkanDSetsKey& DSetsKey, const FVulkanDescriptorSetsLayout& SetsLayout, TArray& DSWriters, VkDescriptorSet* OutSets); void CalcPoolSizesRatio(float PoolSizesRatio[VK_DESCRIPTOR_TYPE_RANGE_SIZE]); private: static const float MinAllocRatio; static const float MaxAllocRatio; private: const uint32 SetCapacity; FVulkanGenericDescriptorPool Pool; TMap SetsCache; TMap SetCache; uint32 RecentFrame; public: uint32 PoolSizesStatistic[VK_DESCRIPTOR_TYPE_RANGE_SIZE]; }; private: FVulkanDevice* const Device; TArray> CachedPools; TUniquePtr FreePool; float PoolAllocRatio; };