// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VulkanMemory.h: Vulkan Memory RHI definitions. =============================================================================*/ #pragma once #include "Containers/Map.h" #include "Misc/ScopeRWLock.h" #include "VulkanConfiguration.h" #include "VulkanThirdParty.h" #define UE_API VULKANRHI_API //enable to store FILE/LINE, and optionally a stacktrace via r.vulkan.backtrace #if !UE_BUILD_SHIPPING #define VULKAN_MEMORY_TRACK 1 #else #define VULKAN_MEMORY_TRACK 0 #endif #define VULKAN_MEMORY_LOW_PRIORITY 0.f #define VULKAN_MEMORY_MEDIUM_PRIORITY 0.5f #define VULKAN_MEMORY_HIGHER_PRIORITY 0.75f #define VULKAN_MEMORY_HIGHEST_PRIORITY 1.f class FVulkanContextCommon; class FVulkanCommandListContext; class FVulkanDevice; class FVulkanQueue; class FVulkanCommandBuffer; class FVulkanTexture; using FVulkanSyncPoint = FGraphEvent; using FVulkanSyncPointRef = TRefCountPtr; #if !VULKAN_OBJECT_TRACKING #define VULKAN_TRACK_OBJECT_CREATE(Type, Ptr) do{}while(0) #define VULKAN_TRACK_OBJECT_DELETE(Type, Ptr) do{}while(0) #else #define VULKAN_TRACK_OBJECT_CREATE(Type, Ptr) do{TVulkanTrackBase::Add(Ptr);}while(0) #define VULKAN_TRACK_OBJECT_DELETE(Type, Ptr) do{TVulkanTrackBase::Remove(Ptr);}while(0) template class TVulkanTrackBase { public: static FCriticalSection Lock; static TSet Objects; template static uint32 CollectAll(Callback CB) { uint32 Count = 0; FScopeLock L(&Lock); TArray Temp; for(Type* Object : Objects) { Object->DumpMemory(CB); Count++; } return Count; } static void Add(Type* Object) { FScopeLock L(&Lock); Objects.Add(Object); } static void Remove(Type* Object) { FScopeLock L(&Lock); Objects.Remove(Object); } }; template FCriticalSection TVulkanTrackBase::Lock; template TSet TVulkanTrackBase::Objects; #endif namespace VulkanRHI { class FFenceManager; class FDeviceMemoryManager; // Manager of low level vulkan memory allocations class FDeviceMemoryAllocation; // A single low level allocation class FVulkanSubresourceAllocator; // handles suballocation of one allocation into many class FVulkanResourceHeap; // has multiple suballocators. On resource heap per memory type class FMemoryManager; // entry point for everything. Contains all heaps. has a small allocator for small stuff. struct FRange; // Helper class for suballocation (this is effectively the allocator implementation) class FVulkanAllocation; // External handle to any allocation handled by FMemoryManager struct FVulkanAllocationInternal; // Internal representation mirroring vulkan allocation struct FResourceHeapStats; } class FVulkanEvictable { public: virtual bool CanMove() const { return false; } virtual bool CanEvict() const { return false; } virtual void Evict(FVulkanDevice& Device, FVulkanCommandListContext& Context) { checkNoEntry(); } virtual void Move(FVulkanDevice& Device, FVulkanCommandListContext& Context, VulkanRHI::FVulkanAllocation& Allocation) { checkNoEntry(); } virtual FVulkanTexture* GetEvictableTexture() { return nullptr; } }; struct FVulkanTrackInfo { FVulkanTrackInfo(); void* Data; int32 SizeOrLine; //negative indicates a stack trace, Positive line number }; enum class EDelayAcquireImageType { None, // acquire next image on frame start DelayAcquire, // acquire next image just before presenting, rendering is done to intermediate image which is copied to real backbuffer }; extern EDelayAcquireImageType GVulkanDelayAcquireImage; extern int32 GVulkanUseBufferBinning; namespace VulkanRHI { enum { NUM_FRAMES_TO_WAIT_BEFORE_RELEASING_TO_OS = 3, }; enum EVulkanAllocationType : uint8 { EVulkanAllocationEmpty, EVulkanAllocationPooledBuffer, EVulkanAllocationBuffer, EVulkanAllocationImage, EVulkanAllocationImageDedicated, EVulkanAllocationSize, // keep last }; enum EVulkanAllocationMetaType : uint8 { EVulkanAllocationMetaUnknown, EVulkanAllocationMetaUniformBuffer, EVulkanAllocationMetaMultiBuffer, EVulkanAllocationMetaRingBuffer, EVulkanAllocationMetaFrameTempBuffer, EVulkanAllocationMetaImageRenderTarget, EVulkanAllocationMetaImageOther, EVulkanAllocationMetaBufferUAV, EVulkanAllocationMetaBufferStaging, EVulkanAllocationMetaBufferOther, EVulkanAllocationMetaSize, // keep last }; enum ELegacyVulkanAllocationFlags { VulkanAllocationFlagsMapped = 0x1, VulkanAllocationFlagsCanEvict= 0x2, }; enum class EType { Image, Buffer, }; // Custom ref counting class FRefCount { public: FRefCount() {} virtual ~FRefCount() { check(NumRefs.GetValue() == 0); } inline uint32 AddRef() { int32 NewValue = NumRefs.Increment(); check(NewValue > 0); return (uint32)NewValue; } inline uint32 Release() { int32 NewValue = NumRefs.Decrement(); if (NewValue == 0) { delete this; } check(NewValue >= 0); return (uint32)NewValue; } inline uint32 GetRefCount() const { int32 Value = NumRefs.GetValue(); check(Value >= 0); return (uint32)Value; } private: FThreadSafeCounter NumRefs; }; class FDeviceChild { public: FDeviceChild(FVulkanDevice* InDevice) : Device(InDevice) {} FVulkanDevice* GetParent() const { // Has to have one if we are asking for it... check(Device); return Device; } protected: FVulkanDevice* const Device; }; // An Allocation off a Device Heap. Lowest level of allocations and bounded by VkPhysicalDeviceLimits::maxMemoryAllocationCount. class FDeviceMemoryAllocation { public: FDeviceMemoryAllocation() : Size(0) , DeviceHandle(VK_NULL_HANDLE) , Handle(VK_NULL_HANDLE) , MappedPointer(nullptr) , MemoryTypeIndex(0) , bCanBeMapped(0) , bIsCoherent(0) , bIsCached(0) , bFreedBySystem(false) , bDedicatedMemory(0) { } void* Map(VkDeviceSize Size, VkDeviceSize Offset); void Unmap(); inline bool CanBeMapped() const { return bCanBeMapped != 0; } inline bool IsMapped() const { return !!MappedPointer; } inline void* GetMappedPointer() { check(IsMapped()); return MappedPointer; } inline bool IsCoherent() const { return bIsCoherent != 0; } void FlushMappedMemory(VkDeviceSize InOffset, VkDeviceSize InSize); void InvalidateMappedMemory(VkDeviceSize InOffset, VkDeviceSize InSize); inline VkDeviceMemory GetHandle() const { return Handle; } inline VkDeviceSize GetSize() const { return Size; } inline uint32 GetMemoryTypeIndex() const { return MemoryTypeIndex; } protected: VkDeviceSize Size; VkDevice DeviceHandle; VkDeviceMemory Handle; void* MappedPointer; uint32 MemoryTypeIndex : 8; uint32 bCanBeMapped : 1; uint32 bIsCoherent : 1; uint32 bIsCached : 1; uint32 bFreedBySystem : 1; uint32 bDedicatedMemory : 1; uint32 : 0; #if VULKAN_MEMORY_TRACK FVulkanTrackInfo Track; #endif ~FDeviceMemoryAllocation(); friend class FDeviceMemoryManager; }; struct FDeviceMemoryBlockKey { uint32 MemoryTypeIndex; VkDeviceSize BlockSize; bool operator==(const FDeviceMemoryBlockKey& Other) const { return MemoryTypeIndex == Other.MemoryTypeIndex && BlockSize == Other.BlockSize; } }; FORCEINLINE uint32 GetTypeHash(const FDeviceMemoryBlockKey& BlockKey) { return HashCombine(FCrc::TypeCrc32(BlockKey.MemoryTypeIndex), FCrc::TypeCrc32(BlockKey.BlockSize)); } struct FDeviceMemoryBlock { FDeviceMemoryBlockKey Key; struct FFreeBlock { FDeviceMemoryAllocation* Allocation; uint32 FrameFreed; }; TArray Allocations; }; // Manager of Device Heap Allocations. Calling Alloc/Free is expensive! class FDeviceMemoryManager { TMap Allocations; void UpdateMemoryProperties(); public: FDeviceMemoryManager(); ~FDeviceMemoryManager(); void Init(FVulkanDevice* InDevice); void Deinit(); uint32 GetEvictedMemoryProperties(); inline bool HasUnifiedMemory() const { return bHasUnifiedMemory; } inline bool SupportsMemoryless() const { return bSupportsMemoryless; } inline uint32 GetNumMemoryTypes() const { return MemoryProperties.memoryTypeCount; } bool SupportsMemoryType(VkMemoryPropertyFlags Properties) const; void GetPrimaryHeapStatus(uint64& OutAllocated, uint64& OutLimit); VkResult GetMemoryTypeFromProperties(uint32 TypeBits, VkMemoryPropertyFlags Properties, uint32* OutTypeIndex); VkResult GetMemoryTypeFromPropertiesExcluding(uint32 TypeBits, VkMemoryPropertyFlags Properties, uint32 ExcludeTypeIndex, uint32* OutTypeIndex); const VkPhysicalDeviceMemoryProperties& GetMemoryProperties() const; // bCanFail means an allocation failing is not a fatal error, just returns nullptr FDeviceMemoryAllocation* Alloc(bool bCanFail, VkDeviceSize AllocationSize, uint32 MemoryTypeIndex, void* DedicatedAllocateInfo, float Priority, bool bExternal, const char* File, uint32 Line); FDeviceMemoryAllocation* Alloc(bool bCanFail, VkDeviceSize AllocationSize, uint32 MemoryTypeBits, VkMemoryPropertyFlags MemoryPropertyFlags, void* DedicatedAllocateInfo, float Priority, bool bExternal, const char* File, uint32 Line); // Sets the Allocation to nullptr void Free(FDeviceMemoryAllocation*& Allocation); uint64 GetTotalMemory(bool bGPU) const; VkDeviceSize GetBaseHeapSize(uint32 HeapIndex) const; uint32 GetHeapIndex(uint32 MemoryTypeIndex); protected: void FreeInternal(FDeviceMemoryAllocation* Allocation); void TrimMemory(bool bFullTrim); friend class FMemoryManager; void GetMemoryDump(TArray& OutDeviceHeapsStats); void DumpMemory(); void PrintMemInfo() const; double MemoryUpdateTime; VkPhysicalDeviceMemoryBudgetPropertiesEXT MemoryBudget; VkPhysicalDeviceMemoryProperties MemoryProperties; VkDevice DeviceHandle; FVulkanDevice* Device; uint32 NumAllocations; uint32 PeakNumAllocations; bool bHasUnifiedMemory; bool bSupportsMemoryless; struct FHeapInfo { VkDeviceSize UsedSize; VkDeviceSize PeakSize; TArray Allocations; FHeapInfo() : UsedSize(0), PeakSize(0) { } }; TArray HeapInfos; int32 PrimaryHeapIndex; // memory usage of this heap will decide when to evict. FCriticalSection DeviceMemLock; }; struct FRange { uint32 Offset; uint32 Size; inline bool operator<(const FRange& In) const { return Offset < In.Offset; } static void JoinConsecutiveRanges(TArray& Ranges); /** Tries to insert the item so it has index ProposedIndex, but may end up merging it with neighbors */ static int32 InsertAndTryToMerge(TArray& Ranges, const FRange& Item, int32 ProposedIndex); /** Tries to append the item to the end but may end up merging it with the neighbor */ static int32 AppendAndTryToMerge(TArray& Ranges, const FRange& Item); /** Attempts to allocate from an entry - can remove it if it was used up*/ static void AllocateFromEntry(TArray& Ranges, int32 Index, uint32 SizeToAllocate); /** Sanity checks an array of ranges */ static void SanityCheck(TArray& Ranges); /** Adds to the array while maintaing the sort. */ static int32 Add(TArray& Ranges, const FRange& Item); }; struct FVulkanPageSizeBucket { uint64 AllocationMax; uint32 PageSize; enum { BUCKET_MASK_IMAGE=0x1, BUCKET_MASK_BUFFER=0x2, }; uint32 BucketMask; }; // Holds a reference to -any- vulkan gpu allocation // !Intentionally not reference counted // !User must call Free exactly once class FVulkanAllocation { public: FVulkanAllocation(); ~FVulkanAllocation(); void Init(EVulkanAllocationType Type, EVulkanAllocationMetaType MetaType, uint64 Handle, uint32 InSize, uint32 AlignedOffset, uint32 AllocatorIndex, uint32 AllocationIndex, uint32 BufferId); void Free(FVulkanDevice& Device); void Swap(FVulkanAllocation& Other); void Reference(const FVulkanAllocation& Other); //point to other, but don't take ownership bool HasAllocation() const; void Disown(); //disown & own should be used if ownership is transferred. void Own(); bool IsValid() const; uint64 VulkanHandle = 0; uint32 HandleId = 0; uint32 Size = 0; uint32 Offset = 0; uint32 AllocationIndex = 0; uint16 AllocatorIndex = 0; EVulkanAllocationMetaType MetaType = EVulkanAllocationMetaUnknown; uint8 Type : 7; uint8 bHasOwnership : 1; static_assert(EVulkanAllocationSize < 128, "Not enough bits to hold EVulkanAllocationType"); inline EVulkanAllocationType GetType() const { return (EVulkanAllocationType)Type; } inline void SetType(EVulkanAllocationType InType) { Type = (uint8)InType; } //helper functions void* GetMappedPointer(FVulkanDevice* Device); void FlushMappedMemory(FVulkanDevice* Device); void InvalidateMappedMemory(FVulkanDevice* Device); VkBuffer GetBufferHandle() const; uint32 GetBufferAlignment(FVulkanDevice* Device) const; VkDeviceMemory GetDeviceMemoryHandle(FVulkanDevice* Device) const; FVulkanSubresourceAllocator* GetSubresourceAllocator(FVulkanDevice* Device) const; void BindBuffer(FVulkanDevice* Device, VkBuffer Buffer); void BindImage(FVulkanDevice* Device, VkImage Image); }; struct FVulkanAllocationInternal { enum { EUNUSED, EALLOCATED, EFREED, EFREEPENDING, EFREEDISCARDED, // if a defrag happens with a pending free, its put into this state, so we can ignore the deferred delete once it happens. }; int State = EUNUSED; void Init(const FVulkanAllocation& Alloc, FVulkanEvictable* InAllocationOwner, uint32 AllocationOffset, uint32 AllocationSize, uint32 Alignment); EVulkanAllocationType Type = EVulkanAllocationEmpty; EVulkanAllocationMetaType MetaType = EVulkanAllocationMetaUnknown; uint32 Size = 0; uint32 AllocationSize = 0; uint32 AllocationOffset = 0; FVulkanEvictable* AllocationOwner = 0; uint32 Alignment = 0; int32 NextFree = -1; #if VULKAN_MEMORY_TRACK FVulkanTrackInfo Track; #endif #if VULKAN_USE_LLM uint64 LLMTrackerID; void SetLLMTrackerID(uint64 InTrackerID) { LLMTrackerID = InTrackerID; } uint64 GetLLMTrackerID() { return LLMTrackerID; } #endif }; class FVulkanSubresourceAllocator { friend class FMemoryManager; friend class FVulkanResourceHeap; public: FVulkanSubresourceAllocator(EVulkanAllocationType InType, FMemoryManager* InOwner, uint8 InSubResourceAllocatorFlags, FDeviceMemoryAllocation* InDeviceMemoryAllocation, uint32 InMemoryTypeIndex, VkMemoryPropertyFlags InMemoryPropertyFlags, uint32 InAlignment, VkBuffer InBuffer, uint32 InBufferSize, uint32 InBufferId, VkBufferUsageFlags InBufferUsageFlags, int32 InPoolSizeIndex); FVulkanSubresourceAllocator(EVulkanAllocationType InType, FMemoryManager* InOwner, uint8 InSubResourceAllocatorFlags, FDeviceMemoryAllocation* InDeviceMemoryAllocation, uint32 InMemoryTypeIndex, uint32 BufferId = 0xffffffff); ~FVulkanSubresourceAllocator(); void Destroy(FVulkanDevice* Device); bool TryAllocate2(FVulkanAllocation& OutAllocation, FVulkanEvictable* Owner, uint32 InSize, uint32 InAlignment, EVulkanAllocationMetaType InMetaType, const char* File, uint32 Line); void Free(FVulkanAllocation& Allocation); inline uint32 GetAlignment() const { return Alignment; } inline void* GetMappedPointer() { return MemoryAllocation->GetMappedPointer(); } void Flush(VkDeviceSize Offset, VkDeviceSize AllocationSize); void Invalidate(VkDeviceSize Offset, VkDeviceSize AllocationSize); uint32 GetMaxSize() const { return MaxSize; } uint32 GetUsedSize() const { return UsedSize; } inline uint32 GetHandleId() const { return BufferId; } FDeviceMemoryAllocation* GetMemoryAllocation() { return MemoryAllocation; } EVulkanAllocationType GetType(){ return Type; } TArrayView GetMemoryUsed(); uint32 GetNumSubAllocations(); uint8 GetSubresourceAllocatorFlags() { return SubresourceAllocatorFlags; } uint32 GetId() { return BufferId; } bool GetIsDefragging() { return bIsDefragging; } protected: void SetIsDefragging(bool bInIsDefragging){ bIsDefragging = bInIsDefragging; } void DumpFullHeap(); int32 DefragTick(FVulkanDevice& Device, FVulkanCommandListContext& Context, FVulkanResourceHeap* Heap, uint32 Count); bool CanDefrag(); uint64 EvictToHost(FVulkanDevice& Device, FVulkanCommandListContext& Context); void SetFreePending(FVulkanAllocation& Allocation); void FreeInternalData(int32 Index); int32 AllocateInternalData(); uint32 MemoryUsed[EVulkanAllocationMetaSize]; EVulkanAllocationType Type; FMemoryManager* Owner; uint32 MemoryTypeIndex; VkMemoryPropertyFlags MemoryPropertyFlags; FDeviceMemoryAllocation* MemoryAllocation; uint32 MaxSize; uint32 Alignment; uint32 FrameFreed; uint32 LastDefragFrame = 0; int64 UsedSize; VkBufferUsageFlags BufferUsageFlags; VkBuffer Buffer; uint32 BufferId; int32 PoolSizeIndex; uint32 AllocatorIndex; uint8 SubresourceAllocatorFlags; uint8 BucketId; bool bIsEvicting = false; bool bLocked = false; bool bIsDefragging = false; uint32 NumSubAllocations = 0; uint32 AllocCalls = 0; uint32 FreeCalls = 0; // List of free ranges TArray FreeList; bool JoinFreeBlocks(); uint32 GetAllocatorIndex() { return AllocatorIndex; } #if VULKAN_MEMORY_TRACK FVulkanTrackInfo Track; #endif TArray InternalData; int32 InternalFreeList= -1; FCriticalSection SubresourceAllocatorCS; }; // A set of Device Allocations (Heap Pages) for a specific memory type. This handles pooling allocations inside memory pages to avoid // doing allocations directly off the device's heaps class FVulkanResourceHeap { public: FVulkanResourceHeap(FMemoryManager* InOwner, uint32 InMemoryTypeIndex, uint32 InOverridePageSize = 0); ~FVulkanResourceHeap(); void FreePage(FVulkanSubresourceAllocator* InPage); void ReleasePage(FVulkanSubresourceAllocator* InPage); //void ReleaseFreedPages(bool bImmediately); inline FMemoryManager* GetOwner() { return Owner; } inline bool IsHostCachedSupported() const { return bIsHostCachedSupported; } inline bool IsLazilyAllocatedSupported() const { return bIsLazilyAllocatedSupported; } inline uint32 GetMemoryTypeIndex() const { return MemoryTypeIndex; } uint64 EvictOne(FVulkanDevice& Device, FVulkanCommandListContext& Context); void DefragTick(FVulkanDevice& Device, FVulkanCommandListContext& Context, uint32 Count); void DumpMemory(FResourceHeapStats& Stats); void SetDefragging(FVulkanSubresourceAllocator* Allocator); bool GetIsDefragging(FVulkanSubresourceAllocator* Allocator); uint32 GetPageSizeBucket(FVulkanPageSizeBucket& BucketOut, EType Type, uint32 AllocationSize, bool bForceSingleAllocation); protected: enum { MAX_BUCKETS = 5, }; TArray > PageSizeBuckets; uint32 GetPageSize(); FMemoryManager* Owner; uint16 MemoryTypeIndex; const uint16 HeapIndex; bool bIsHostCachedSupported; bool bIsLazilyAllocatedSupported; uint8 DefragCountDown = 0; uint32 OverridePageSize; uint32 PeakPageSize; uint64 UsedMemory; uint32 PageIDCounter; FCriticalSection PagesLock; TArray ActivePages[MAX_BUCKETS]; TArray UsedDedicatedImagePages; bool TryRealloc(FVulkanAllocation& OutAllocation, FVulkanEvictable* AllocationOwner, EType Type, uint32 Size, uint32 Alignment, EVulkanAllocationMetaType MetaType); bool AllocateResource(FVulkanAllocation& OutAllocation, FVulkanEvictable* AllocationOwner, EType Type, uint32 Size, uint32 Alignment, bool bMapAllocation, bool bForceSeparateAllocation, EVulkanAllocationMetaType MetaType, bool bExternal, const char* File, uint32 Line); bool AllocateDedicatedImage(FVulkanAllocation& OutAllocation, FVulkanEvictable* AllocationOwner, VkImage Image, uint32 Size, uint32 Alignment, EVulkanAllocationMetaType MetaType, bool bExternal, const char* File, uint32 Line); friend class FMemoryManager; friend class FVulkanSubresourceAllocator; }; enum EVulkanFreeFlags { EVulkanFreeFlag_None = 0x0, EVulkanFreeFlag_DontDefer = 0x1, }; // Allocation flags to account for conditions not covered by vkGet*MemoryRequirements or resource usage flags enum class EVulkanAllocationFlags : uint16 { None = 0x0000, HostVisible = 0x0001, // Will be written from CPU, will likely contain the HOST_VISIBLE + HOST_COHERENT flags HostCached = 0x0002, // Will be used for readback, will likely contain the HOST_CACHED flag. Implies HostVisible. PreferBAR = 0x0004, // Will be allocated from a HOST_VISIBLE + DEVICE_LOCAL heap if available and possible (HOST_VISIBLE if not) Dedicated = 0x0008, // Will not share a memory block with other resources External = 0x0010, // To be used with VK_KHR_external_memory Memoryless = 0x0020, // Will use LAZILY_ALLOCATED NoError = 0x0040, // OOM is not fatal, return an invalid allocation AutoBind = 0x0080, // Will automatically bind the allocation to the supplied VkBuffer/VkImage, avoids an extra lock to bind separately }; ENUM_CLASS_FLAGS(EVulkanAllocationFlags); // Manages heaps and their interactions class FMemoryManager : public FDeviceChild { friend class FVulkanAllocation; public: FMemoryManager(FVulkanDevice* InDevice); ~FMemoryManager(); void Init(); void Deinit(); static uint32 CalculateBufferAlignment(FVulkanDevice& InDevice, EBufferUsageFlags InUEUsage, bool bZeroSize); static float CalculateBufferPriority(const VkBufferUsageFlags BufferUsageFlags); void FreeVulkanAllocation(FVulkanAllocation& Allocation, EVulkanFreeFlags FreeFlags = EVulkanFreeFlag_None); void FreeVulkanAllocationPooledBuffer(FVulkanAllocation& Allocation); void FreeVulkanAllocationBuffer(FVulkanAllocation& Allocation); void FreeVulkanAllocationImage(FVulkanAllocation& Allocation); void FreeVulkanAllocationImageDedicated(FVulkanAllocation& Allocation); // Legacy calls bool AllocateBufferPooled(FVulkanAllocation& Allocation, FVulkanEvictable* AllocationOwner, uint32 Size, uint32 MinAlignment, VkBufferUsageFlags BufferUsageFlags, VkMemoryPropertyFlags MemoryPropertyFlags, EVulkanAllocationMetaType MetaType, const char* File, uint32 Line); bool AllocateImageMemory(FVulkanAllocation& Allocation, FVulkanEvictable* AllocationOwner, const VkMemoryRequirements& MemoryReqs, VkMemoryPropertyFlags MemoryPropertyFlags, EVulkanAllocationMetaType MetaType, bool bExternal, const char* File, uint32 Line); bool AllocateDedicatedImageMemory(FVulkanAllocation& Allocation, FVulkanEvictable* AllocationOwner, VkImage Image, const VkMemoryRequirements& MemoryReqs, VkMemoryPropertyFlags MemoryPropertyFlags, EVulkanAllocationMetaType MetaType, bool bExternal, const char* File, uint32 Line); private: bool AllocateBufferMemory(FVulkanAllocation& Allocation, FVulkanEvictable* AllocationOwner, const VkMemoryRequirements& MemoryReqs, VkMemoryPropertyFlags MemoryPropertyFlags, EVulkanAllocationMetaType MetaType, bool bExternal, bool bForceSeparateAllocation, const char* File, uint32 Line); // New calls public: bool AllocateBufferMemory(FVulkanAllocation& OutAllocation, VkBuffer InBuffer, EVulkanAllocationFlags InAllocFlags, const TCHAR* InDebugName, uint32 InForceMinAlignment = 1); void RegisterSubresourceAllocator(FVulkanSubresourceAllocator* SubresourceAllocator); void UnregisterSubresourceAllocator(FVulkanSubresourceAllocator* SubresourceAllocator); bool ReleaseSubresourceAllocator(FVulkanSubresourceAllocator* SubresourceAllocator); void ReleaseFreedPages(FVulkanCommandListContext& Context); void DumpMemory(bool bFullDump = true); void AllocUniformBuffer(FVulkanAllocation& OutAllocation, uint32 Size); void FreeUniformBuffer(FVulkanAllocation& InAllocation); void HandleOOM(bool bCanResume = false, VkResult Result = VK_SUCCESS, uint64 AllocationSize = 0, uint32 MemoryTypeIndex = 0); bool UpdateEvictThreshold(bool bLog); protected: FDeviceMemoryManager* DeviceMemoryManager; TArray ResourceTypeHeaps; enum { BufferAllocationSize = 1 * 1024 * 1024, UniformBufferAllocationSize = 2 * 1024 * 1024, }; // pool sizes that we support enum class EPoolSizes : uint8 { // E32, // E64, E128, E256, E512, E1k, E2k, E8k, E16k, SizesCount, }; constexpr static uint32 PoolSizes[(int32)EPoolSizes::SizesCount] = { // 32, // 64, 128, 256, 512, 1024, 2048, 8192, // 16 * 1024, }; constexpr static uint32 BufferSizes[(int32)EPoolSizes::SizesCount + 1] = { // 64 * 1024, // 64 * 1024, 128 * 1024, 128 * 1024, 256 * 1024, 256 * 1024, 512 * 1024, 512 * 1024, 1024 * 1024, 1 * 1024 * 1024, }; EPoolSizes GetPoolTypeForAlloc(uint32 Size, uint32 Alignment) { EPoolSizes PoolSize = EPoolSizes::SizesCount; if (GVulkanUseBufferBinning != 0) { for (int32 i = 0; i < (int32)EPoolSizes::SizesCount; ++i) { if (PoolSizes[i] >= Size) { PoolSize = (EPoolSizes)i; break; } } } return PoolSize; } FCriticalSection UsedFreeBufferAllocationsLock; TArray UsedBufferAllocations[(int32)EPoolSizes::SizesCount + 1]; TArray FreeBufferAllocations[(int32)EPoolSizes::SizesCount + 1]; FRWLock AllBufferAllocationsLock; // protects against resizing of array (RenderThread<->RHIThread) TArray AllBufferAllocations; PTRINT AllBufferAllocationsFreeListHead = (PTRINT)-1; inline FVulkanSubresourceAllocator* GetSubresourceAllocator(const uint32 AllocatorIndex) { FRWScopeLock ScopedReadLock(AllBufferAllocationsLock, SLT_ReadOnly); return AllBufferAllocations[AllocatorIndex]; } uint64 PendingEvictBytes = 0; bool bIsEvicting = false; bool bWantEviction = false; friend class FVulkanResourceHeap; void ReleaseFreedResources(bool bImmediately); void DestroyResourceAllocations(); struct FUBPendingFree { FVulkanAllocation Allocation; uint64 Frame = 0; }; struct { FCriticalSection CS; TArray PendingFree; uint32 Peak = 0; } UBAllocations; void ProcessPendingUBFreesNoLock(bool bForce); void ProcessPendingUBFrees(bool bForce); }; class FStagingBuffer : public FRefCount { public: FStagingBuffer(FVulkanDevice* InDevice); VkBuffer GetHandle() const; void* GetMappedPointer(); uint32 GetSize() const; VkDeviceMemory GetDeviceMemoryHandle() const; void FlushMappedMemory(); void InvalidateMappedMemory(); #if VULKAN_MEMORY_TRACK FVulkanTrackInfo Track; #endif protected: FVulkanDevice* Device; FVulkanAllocation Allocation; VkBuffer Buffer; VkMemoryPropertyFlagBits MemoryReadFlags; uint32 BufferSize; // Owner maintains lifetime virtual ~FStagingBuffer(); void Destroy(); friend class FStagingManager; }; class FStagingManager { public: FStagingManager() : PeakUsedMemory(0), UsedMemory(0), Device(nullptr) { } ~FStagingManager(); void Init(FVulkanDevice* InDevice) { Device = InDevice; } void Deinit(); FStagingBuffer* AcquireBuffer(uint32 Size, VkBufferUsageFlags InUsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VkMemoryPropertyFlagBits InMemoryReadFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); // Sets pointer to nullptr void ReleaseBuffer(FVulkanContextCommon* Context, FStagingBuffer*& StagingBuffer); void ProcessPendingFree(bool bImmediately, bool bFreeToOS); void GetMemoryDump(FResourceHeapStats& Stats); void DumpMemory(); protected: friend class FMemoryManager; FCriticalSection StagingLock; TArray UsedStagingBuffers; TMap> PendingFreeStagingBuffers; struct FFreeEntry { FStagingBuffer* StagingBuffer; uint32 FrameNumber; }; TArray FreeStagingBuffers; uint64 PeakUsedMemory; uint64 UsedMemory; void ProcessPendingFreeNoLock(bool bImmediately, bool bFreeToOS); FVulkanDevice* Device; }; class FFence { public: FFence(FVulkanDevice* InDevice, FFenceManager* InOwner, bool bCreateSignaled); inline VkFence GetHandle() const { return Handle; } inline bool IsSignaled() const { return State == EState::Signaled; } FFenceManager* GetOwner() { return Owner; } protected: VkFence Handle; enum class EState { // Initial state NotReady, // After GPU processed it Signaled, }; EState State; FFenceManager* Owner; // Only owner can delete! ~FFence(); friend class FFenceManager; }; class FFenceManager { public: FFenceManager() : Device(nullptr) { } ~FFenceManager(); void Init(FVulkanDevice* InDevice); void Deinit(); FFence* AllocateFence(bool bCreateSignaled = false); inline bool IsFenceSignaled(FFence* Fence) { if (Fence->IsSignaled()) { return true; } return CheckFenceState(Fence); } // Returns false if it timed out bool WaitForAnyFence(TArrayView Fences, uint64 TimeInNanoseconds); bool WaitForFence(FFence* Fence, uint64 TimeInNanoseconds); void ResetFence(FFence* Fence); // Sets it to nullptr void ReleaseFence(FFence*& Fence); // Sets it to nullptr void WaitAndReleaseFence(FFence*& Fence, uint64 TimeInNanoseconds); protected: FVulkanDevice* Device; FCriticalSection FenceLock; TArray FreeFences; TArray UsedFences; // Returns true if signaled bool CheckFenceState(FFence* Fence); void DestroyFence(FFence* Fence); }; class FGPUEvent : public FDeviceChild, public FRefCount { public: FGPUEvent(FVulkanDevice* InDevice); virtual ~FGPUEvent(); inline VkEvent GetHandle() const { return Handle; } protected: VkEvent Handle; }; // Simple temp allocation blocks used for volatile allocations class FTempBlockAllocator : public FDeviceChild { public: struct FTempMemoryBlock { FVulkanAllocation Allocation; VkBuffer Buffer = 0; uint8* MappedPointer = 0; VkDeviceAddress BufferAddress = 0; // Used by owners to suballocate TArray SyncPoints; std::atomic CurrentOffset = 0; }; struct FInternalAlloc { FTempMemoryBlock* Block = nullptr; uint32 Offset = 0; }; FTempBlockAllocator(FVulkanDevice* InDevice, uint32 InBlockSize, uint32 InBlockAlignment, VkBufferUsageFlags InBufferUsage); virtual ~FTempBlockAllocator(); uint8* Alloc(uint32 InSize, FVulkanContextCommon& Context, VkDescriptorBufferBindingInfoEXT& OutBindingInfo, VkDeviceSize& OutOffset); uint8* Alloc(uint32 InSize, uint32 InAlignment, FVulkanContextCommon& Context, FVulkanAllocation& OutAllocation, VkDescriptorAddressInfoEXT* OutDescriptorAddressInfo = nullptr); uint8* Alloc(uint32 InSize, FVulkanContextCommon& Context, VkStridedDeviceAddressRegionKHR* OutStridedDeviceAddressRegion); void UpdateBlocks(); protected: FTempMemoryBlock* AllocBlock(); FInternalAlloc InternalAlloc(uint32 InSize, FVulkanContextCommon& Context); const uint32 BlockSize; const uint32 BlockAlignment; const VkBufferUsageFlags BufferUsage; FTempMemoryBlock* CurrentBlock = nullptr; TArray BusyBlocks; TArray AvailableBlocks; FRWLock RWLock; }; enum class EVulkanSemaphoreFlags : uint8 { None = 0, // Will not delete handle on destruction ExternallyOwned = 1 << 1, // Inform submission pipeline that the signal will not come from a payload (acquired image semaphore for example) ExternallySignaled = 1 << 2, // Create a timeline semaphore (must be supported) Timeline = 1 << 3, // Will not be queued for deferred deletion ImmediateDeletion = 1 << 4 }; ENUM_CLASS_FLAGS(EVulkanSemaphoreFlags); class FSemaphore : public FRefCount { public: UE_API FSemaphore(FVulkanDevice& InDevice, EVulkanSemaphoreFlags InFlags=EVulkanSemaphoreFlags::None, uint64 InInitialTimelineValue=0); UE_API FSemaphore(FVulkanDevice& InDevice, const VkSemaphore& InExternalSemaphore); UE_API virtual ~FSemaphore(); VkSemaphore GetHandle() const { return SemaphoreHandle; } bool IsExternallyOwned() const { return EnumHasAnyFlags(Flags, EVulkanSemaphoreFlags::ExternallyOwned); } bool IsExternallySignaled() const { return EnumHasAnyFlags(Flags, EVulkanSemaphoreFlags::ExternallySignaled); } bool IsTimeline() const { return EnumHasAnyFlags(Flags, EVulkanSemaphoreFlags::Timeline); } UE_API uint64 GetTimelineSemaphoreValue(); UE_API bool WaitForTimelineSemaphoreValue(uint64 Value, uint64 Timeout); private: FVulkanDevice& Device; VkSemaphore SemaphoreHandle; const EVulkanSemaphoreFlags Flags; }; } #if VULKAN_CUSTOM_MEMORY_MANAGER_ENABLED struct FVulkanCustomMemManager { static VKAPI_ATTR void* Alloc(void* UserData, size_t Size, size_t Alignment, VkSystemAllocationScope AllocScope); static VKAPI_ATTR void Free(void* pUserData, void* pMem); static VKAPI_ATTR void* Realloc(void* pUserData, void* pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocScope); static VKAPI_ATTR void InternalAllocationNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope); static VKAPI_ATTR void InternalFreeNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope); FVulkanCustomMemManager(); enum { VK_SYSTEM_ALLOCATION_SCOPE_RANGE_SIZE = 5, }; struct FType { size_t UsedMemory = 0; size_t MaxAllocSize = 0; TMap Allocs; }; TStaticArray Types; static FType& GetType(void* pUserData, VkSystemAllocationScope AllocScope); }; #endif #undef UE_API