// Copyright Epic Games, Inc. All Rights Reserved. #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 { 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); }; 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; };