diff --git a/src/Vulkan/VMASharp_OriginalSources/Allocation.cs b/src/Vulkan/VMASharp_OriginalSources/Allocation.cs new file mode 100644 index 0000000000..f5171b33f3 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Allocation.cs @@ -0,0 +1,317 @@ +#pragma warning disable CA1063 + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Diagnostics; + +using Silk.NET.Vulkan; +using Buffer = Silk.NET.Vulkan.Buffer; +using System.Buffers; + +namespace VMASharp +{ + /// + /// The object containing details on a suballocation of Vulkan Memory + /// + public unsafe abstract class Allocation : IDisposable + { + internal VulkanMemoryAllocator Allocator { get; } + + protected Vk VkApi => Allocator.VkApi; + + protected long size; + protected long alignment; + private int lastUseFrameIndex; + protected int memoryTypeIndex; + protected int mapCount; + private bool LostOrDisposed = false; + + /// + /// Size of this allocation, in bytes. + /// Value never changes, unless allocation is lost. + /// + public long Size + { + get + { + if (LostOrDisposed || lastUseFrameIndex == Helpers.FrameIndexLost) + { + return 0; + } + + return size; + } + } + + /// + /// Memory type index that this allocation is from. Value does not change. + /// + public int MemoryTypeIndex { get => memoryTypeIndex; } + + /// + /// Handle to Vulkan memory object. + /// Same memory object can be shared by multiple allocations. + /// It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. + /// If the allocation is lost, it is equal to `VK_NULL_HANDLE`. + /// + public abstract DeviceMemory DeviceMemory { get; } + + /// + /// Offset into deviceMemory object to the beginning of this allocation, in bytes. (deviceMemory, offset) pair is unique to this allocation. + /// It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost. + /// + public abstract long Offset { get; internal set; } + + internal abstract bool CanBecomeLost { get; } + + + internal bool IsPersistantMapped + { + get => this.mapCount < 0; + } + + internal int LastUseFrameIndex + { + get + { + return this.lastUseFrameIndex; + } + } + + internal long Alignment => this.alignment; + + public object? UserData { get; set; } + + internal Allocation(VulkanMemoryAllocator allocator, int currentFrameIndex) + { + this.Allocator = allocator; + this.lastUseFrameIndex = currentFrameIndex; + } + + /// + /// If this allocation is mapped, returns a pointer to the mapped memory region. Returns Null otherwise. + /// + public abstract IntPtr MappedData { get; } + + public void Dispose() + { + if (!this.LostOrDisposed) + { + this.Allocator.FreeMemory(this); + LostOrDisposed = true; + } + } + + public Result BindBufferMemory(Buffer buffer) + { + Debug.Assert(this.Offset >= 0); + + return this.Allocator.BindVulkanBuffer(buffer, this.DeviceMemory, this.Offset, null); + } + + public unsafe Result BindBufferMemory(Buffer buffer, long allocationLocalOffset, IntPtr pNext) + { + return this.BindBufferMemory(buffer, allocationLocalOffset, (void*)pNext); + } + + public unsafe Result BindBufferMemory(Buffer buffer, long allocationLocalOffset, void* pNext = null) + { + if ((ulong)allocationLocalOffset >= (ulong)this.Size) + { + throw new ArgumentOutOfRangeException(nameof(allocationLocalOffset)); + } + + return this.Allocator.BindVulkanBuffer(buffer, this.DeviceMemory, this.Offset + allocationLocalOffset, pNext); + } + + public unsafe Result BindImageMemory(Image image) + { + return this.Allocator.BindVulkanImage(image, this.DeviceMemory, this.Offset, null); + } + + public unsafe Result BindImageMemory(Image image, long allocationLocalOffset, IntPtr pNext) + { + return this.BindImageMemory(image, allocationLocalOffset, (void*)pNext); + } + + public unsafe Result BindImageMemory(Image image, long allocationLocalOffset, void* pNext = null) + { + if ((ulong)allocationLocalOffset >= (ulong)this.Size) + { + throw new ArgumentOutOfRangeException(nameof(allocationLocalOffset)); + } + + return this.Allocator.BindVulkanImage(image, this.DeviceMemory, this.Offset + allocationLocalOffset, pNext); + } + + internal bool MakeLost(int currentFrame, int frameInUseCount) + { + if (!this.CanBecomeLost) + { + throw new InvalidOperationException("Internal Exception, tried to make an allocation lost that cannot become lost."); + } + + int localLastUseFrameIndex = this.lastUseFrameIndex; + + while (true) + { + if (localLastUseFrameIndex == Helpers.FrameIndexLost) + { + Debug.Assert(false); + return false; + } + else if (localLastUseFrameIndex + frameInUseCount >= currentFrame) + { + return false; + } + else + { + var tmp = Interlocked.CompareExchange(ref this.lastUseFrameIndex, Helpers.FrameIndexLost, localLastUseFrameIndex); + + if (tmp == localLastUseFrameIndex) + { + this.LostOrDisposed = true; + return true; + } + + localLastUseFrameIndex = tmp; + } + } + } + + public bool TouchAllocation() + { + if (this.LostOrDisposed) + { + return false; + } + + int currFrameIndexLoc = this.Allocator.CurrentFrameIndex; + int lastUseFrameIndexLoc = this.lastUseFrameIndex; + + if (this.CanBecomeLost) + { + while (true) + { + if (lastUseFrameIndexLoc == Helpers.FrameIndexLost) + { + return false; + } + else if (lastUseFrameIndexLoc == currFrameIndexLoc) + { + return true; + } + + lastUseFrameIndexLoc = Interlocked.CompareExchange(ref this.lastUseFrameIndex, currFrameIndexLoc, lastUseFrameIndexLoc); + } + } + else + { + while (true) + { + Debug.Assert(lastUseFrameIndexLoc != Helpers.FrameIndexLost); + + if (lastUseFrameIndexLoc == currFrameIndexLoc) + break; + + lastUseFrameIndexLoc = Interlocked.CompareExchange(ref this.lastUseFrameIndex, currFrameIndexLoc, lastUseFrameIndexLoc); + } + + return true; + } + } + + /// + /// Flushes a specified region of memory + /// + /// Offset in this allocation + /// Size of region to flush + /// The result of the operation + public Result Flush(long offset, long size) + { + return Allocator.FlushOrInvalidateAllocation(this, offset, size, CacheOperation.Flush); + } + + /// + /// Invalidates a specified region of memory + /// + /// Offset in this allocation + /// Size of region to Invalidate + /// The result of the operation + public Result Invalidate(long offset, long size) + { + return Allocator.FlushOrInvalidateAllocation(this, offset, size, CacheOperation.Invalidate); + } + + public abstract IntPtr Map(); + + public abstract void Unmap(); + + public bool TryGetMemory(out Memory memory) where T: unmanaged + { + if (mapCount != 0) + { + int size = checked((int)this.Size); + + if (size >= sizeof(T)) + { + memory = new UnmanagedMemoryManager((byte*)MappedData, size / sizeof(T)).Memory; + + return true; + } + } + + memory = Memory.Empty; + return false; + } + + public bool TryGetSpan(out Span span) where T: unmanaged + { + if (mapCount != 0) + { + int size = checked((int)this.Size); + + if (size >= sizeof(T)) + { + span = new Span((void*)MappedData, size / sizeof(T)); + + return true; + } + } + + span = Span.Empty; + return false; + } + + private unsafe sealed class UnmanagedMemoryManager : MemoryManager where T: unmanaged + { + private readonly T* Pointer; + private readonly int ElementCount; + + public UnmanagedMemoryManager(void* ptr, int elemCount) + { + Pointer = (T*)ptr; + ElementCount = elemCount; + } + + protected override void Dispose(bool disposing) + { + } + + public override Span GetSpan() + { + return new Span(Pointer, ElementCount); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + return new MemoryHandle(Pointer + elementIndex); + } + + public override void Unpin() + { + } + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/AllocatorEnums.cs b/src/Vulkan/VMASharp_OriginalSources/AllocatorEnums.cs new file mode 100644 index 0000000000..644388f9e9 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/AllocatorEnums.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace VMASharp +{ + [Flags] + public enum AllocatorCreateFlags + { + /// + /// Tells the allocator to not use any internal locking, not currently respected + /// + ExternallySyncronized = 0x00000001, + + //KhrDedicatedAllocation = 0x00000002, + + //KhrBindMemory2 = 0x00000004, + + /// + /// Enables usage of the VK_EXT_memory_budget extension. + /// + /// You may set this flag only if you found out that this device extension is supported, + /// enabled it on the device passed through , + /// and you want it to be used internally by this library. + /// + ExtMemoryBudget = 0x00000008, + + AMDDeviceCoherentMemory = 0x00000010, + + BufferDeviceAddress = 0x00000020 + } + + public enum MemoryUsage + { + /// + /// No Intended memory usage specified + /// + Unknown = 0, + + /// + /// Memory will be used on device only, so fast access from the device is preferred. + /// It usually means device-local GPU (video) memory. + /// No need to be mappable on host. + /// It is roughly equivalent of `D3D12_HEAP_TYPE_DEFAULT`. + /// + /// + /// Usage: + /// - Resources written and read by device, e.g. images used as attachments. + /// - Resources transferred from host once (immutable) or infrequently and read by + /// device multiple times, e.g. textures to be sampled, vertex buffers, uniform + /// (constant) buffers, and majority of other types of resources used on GPU. + /// + /// Allocation may still end up in `HOST_VISIBLE` memory on some implementations. + /// In such case, you are free to map it. + /// You can use #VMA_ALLOCATION_CREATE_MAPPED_BIT with this usage type. + /// + GPU_Only, + + /// + /// Memory will be mappable on host. + /// It usually means CPU (system) memory. + /// Guarantees to be `HOST_VISIBLE` and `HOST_COHERENT`. + /// CPU access is typically uncached. Writes may be write-combined. + /// Resources created in this pool may still be accessible to the device, but access to them can be slow. + /// It is roughly equivalent of `D3D12_HEAP_TYPE_UPLOAD`. + /// + /// + /// Usage: Staging copy of resources used as transfer source. + /// + CPU_Only, + + /// + /// Memory that is mappable on host (guarantees to be `HOST_VISIBLE`) and preferably fast to access by GPU. + /// CPU access is typically uncached. Writes may be write-combined. + /// + /// + /// Usage: Resources written frequently by host (dynamic), read by device. E.g. textures, vertex buffers, uniform buffers updated every frame or every draw call. + /// + CPU_To_GPU, + + /// + /// Memory mappable on host (guarantees to be `HOST_VISIBLE`) and cached. + /// It is roughly equivalent of `D3D12_HEAP_TYPE_READBACK`. + /// + /// + /// Usage: + /// - Resources written by device, read by host - results of some computations, e.g. screen capture, average scene luminance for HDR tone mapping. + /// - Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection. + /// + GPU_To_CPU, + + /// + /// CPU memory - memory that is preferably not `DEVICE_LOCAL`, but also not guaranteed to be `HOST_VISIBLE`. + /// + /// + /// Usage: Staging copy of resources moved from GPU memory to CPU memory as part + /// of custom paging/residency mechanism, to be moved back to GPU memory when needed. + /// + CPU_Copy, + + /// + /// Lazily allocated GPU memory having `VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT`. + /// Exists mostly on mobile platforms. Using it on desktop PC or other GPUs with no such memory type present will fail the allocation. + /// Allocations with this usage are always created as dedicated - it implies #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + /// + /// + /// Usage: Memory for transient attachment images (color attachments, depth attachments etc.), created with `VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`. + /// + GPU_LazilyAllocated + } + + [Flags] + public enum AllocationCreateFlags + { + DedicatedMemory = 0x0001, + NeverAllocate = 0x0002, + Mapped = 0x0004, + CanBecomeLost = 0x0008, + CanMakeOtherLost = 0x0010, + UpperAddress = 0x0040, + DontBind = 0x0080, + WithinBudget = 0x0100 + } + + [Flags] + public enum AllocationStrategyFlags + { + BestFit = 0x1, + WorstFit = 0x2, + FirstFit = 0x4, + MinMemory = BestFit, + MinTime = FirstFit, + MinFragmentation = WorstFit + } + + [Flags] + public enum PoolCreateFlags + { + IgnoreBufferImageGranularity = 0x0001, + LinearAlgorithm = 0x0010, + BuddyAlgorithm = 0x0020 + } + + public enum DefragmentationFlags + { + Incremental = 0x1 + } + + public enum SuballocationType + { + Free = 0, + Unknown, + Buffer, + Image_Unknown, + Image_Linear, + Image_Optimal + } + + public enum AllocationRequestType + { + Normal, + UpperAddress, + EndOfList1, + EndOfList2 + } + + internal enum CacheOperation + { + Flush, + Invalidate + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/AllocatorStructs.cs b/src/Vulkan/VMASharp_OriginalSources/AllocatorStructs.cs new file mode 100644 index 0000000000..a5a8fb6bca --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/AllocatorStructs.cs @@ -0,0 +1,168 @@ +using System; +using Silk.NET.Core; +using Silk.NET.Vulkan; + +#pragma warning disable CA1815 + +namespace VMASharp +{ + public struct VulkanMemoryAllocatorCreateInfo + { + /// + /// Flags for created allocator + /// + public AllocatorCreateFlags Flags; + + public Version32 VulkanAPIVersion; + + public Vk VulkanAPIObject; + + public Instance Instance; + + public PhysicalDevice PhysicalDevice; + + public Device LogicalDevice; + + public long PreferredLargeHeapBlockSize; + + public long[]? HeapSizeLimits; + + public int FrameInUseCount; + + public VulkanMemoryAllocatorCreateInfo(Version32 vulkanApiVersion, + Vk vulkanApiObject, + Instance instance, PhysicalDevice physicalDevice, Device logicalDevice, + AllocatorCreateFlags flags = default, + long preferredLargeHeapBlockSize = 0, + long[]? heapSizeLimits = null, + int frameInUseCount = 0) + { + Flags = flags; + VulkanAPIVersion = vulkanApiVersion; + VulkanAPIObject = vulkanApiObject; + Instance = instance; + PhysicalDevice = physicalDevice; + LogicalDevice = logicalDevice; + PreferredLargeHeapBlockSize = preferredLargeHeapBlockSize; + HeapSizeLimits = heapSizeLimits; + FrameInUseCount = frameInUseCount; + } + } + + public struct AllocationCreateInfo + { + public AllocationCreateFlags Flags; + + public AllocationStrategyFlags Strategy; + + public MemoryUsage Usage; + + public MemoryPropertyFlags RequiredFlags; + + public MemoryPropertyFlags PreferredFlags; + + public uint MemoryTypeBits; + + public VulkanMemoryPool? Pool; + + public object? UserData; + + public AllocationCreateInfo(AllocationCreateFlags flags = default, + AllocationStrategyFlags strategy = default, + MemoryUsage usage = default, + MemoryPropertyFlags requiredFlags = default, + MemoryPropertyFlags preferredFlags = default, + uint memoryTypeBits = 0, + VulkanMemoryPool? pool = null, + object? userData = null) + { + Flags = flags; + Strategy = strategy; + Usage = usage; + RequiredFlags = requiredFlags; + PreferredFlags = preferredFlags; + MemoryTypeBits = memoryTypeBits; + Pool = pool; + UserData = userData; + } + } + + public struct AllocationPoolCreateInfo + { + /// + /// Memory type index to allocate from, non-optional + /// + public int MemoryTypeIndex; + + public PoolCreateFlags Flags; + + public long BlockSize; + + public int MinBlockCount; + + public int MaxBlockCount; + + public int FrameInUseCount; + + public Func? AllocationAlgorithmCreate; + + public AllocationPoolCreateInfo(int memoryTypeIndex, + PoolCreateFlags flags = 0, + long blockSize = 0, + int minBlockCount = 0, + int maxBlockCount = 0, + int frameInUseCount = 0, + Func? allocationAlgorithemCreate = null) + { + MemoryTypeIndex = memoryTypeIndex; + Flags = flags; + BlockSize = blockSize; + MinBlockCount = minBlockCount; + MaxBlockCount = maxBlockCount; + FrameInUseCount = frameInUseCount; + AllocationAlgorithmCreate = allocationAlgorithemCreate; + } + } + + public struct AllocationContext + { + public int CurrentFrame, FrameInUseCount; + public long BufferImageGranularity; + public long AllocationSize; + public long AllocationAlignment; + public AllocationStrategyFlags Strategy; + public SuballocationType SuballocationType; + public bool CanMakeOtherLost; + + public AllocationContext(int currentFrame, int framesInUse, long bufferImageGranularity, long allocationSize, long allocationAlignment, AllocationStrategyFlags strategy, SuballocationType suballocType, bool canMakeOtherLost) + { + CurrentFrame = currentFrame; + FrameInUseCount = framesInUse; + BufferImageGranularity = bufferImageGranularity; + AllocationSize = allocationSize; + AllocationAlignment = allocationAlignment; + Strategy = strategy; + SuballocationType = suballocType; + CanMakeOtherLost = canMakeOtherLost; + } + } + + public struct AllocationRequest + { + public const long LostAllocationCost = 1048576; + + public long Offset, SumFreeSize, SumItemSize; + public long ItemsToMakeLostCount; + + public object Item; + + public object CustomData; + + public AllocationRequestType Type; + + public readonly long CalcCost() + { + return SumItemSize + ItemsToMakeLostCount * LostAllocationCost; + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/BlockAllocation.cs b/src/Vulkan/VMASharp_OriginalSources/BlockAllocation.cs new file mode 100644 index 0000000000..576cb06a33 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/BlockAllocation.cs @@ -0,0 +1,133 @@ +using Silk.NET.Vulkan; +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using VMASharp; + +namespace VMASharp +{ + public sealed class BlockAllocation : Allocation + { + internal VulkanMemoryBlock Block; + internal long offset; + internal SuballocationType suballocationType; + internal bool canBecomeLost; + + internal BlockAllocation(VulkanMemoryAllocator allocator, int currentFrameIndex) : base(allocator, currentFrameIndex) + { + } + + public override DeviceMemory DeviceMemory + { + get => this.Block.DeviceMemory; + } + + public override long Offset + { + get => this.offset; + internal set => this.offset = value; + } + + public override IntPtr MappedData + { + get + { + if (this.mapCount != 0) + { + IntPtr mapdata = this.Block.MappedData; + + Debug.Assert(mapdata != default); + + return new IntPtr(mapdata.ToInt64() + this.offset); + } + else + { + return default; + } + } + } + + internal override bool CanBecomeLost => this.canBecomeLost; + + internal void InitBlockAllocation(VulkanMemoryBlock block, long offset, long alignment, long size, int memoryTypeIndex, SuballocationType subType, bool mapped, bool canBecomeLost) + { + this.Block = block; + this.offset = offset; + this.alignment = alignment; + this.size = size; + this.memoryTypeIndex = memoryTypeIndex; + this.mapCount = mapped ? int.MinValue : 0; + this.suballocationType = subType; + this.canBecomeLost = canBecomeLost; + } + + internal void ChangeAllocation(VulkanMemoryBlock block, long offset) + { + Debug.Assert(block != null && offset >= 0); + + if (!object.ReferenceEquals(block, this.Block)) + { + int mapRefCount = this.mapCount & int.MaxValue; + + if (this.IsPersistantMapped) + { + mapRefCount += 1; + } + + this.Block.Unmap(mapRefCount); + block.Map(mapRefCount); + + this.Block = block; + } + + this.Offset = offset; + } + + private void BlockAllocMap() + { + if ((this.mapCount & int.MaxValue) < int.MaxValue) + { + this.mapCount += 1; + } + else + { + throw new InvalidOperationException("Allocation mapped too many times simultaniously"); + } + } + + private void BlockAllocUnmap() + { + if ((this.mapCount & int.MaxValue) > 0) + { + this.mapCount -= 1; + } + else + { + throw new InvalidOperationException("Unmapping allocation not previously mapped"); + } + } + + public override IntPtr Map() + { + if (this.CanBecomeLost) + { + throw new InvalidOperationException("Cannot map an allocation that can become lost"); + } + + var data = this.Block.Map(1); + + data = new IntPtr(data.ToInt64() + this.Offset); + + this.BlockAllocMap(); + + return data; + } + + public override void Unmap() + { + this.BlockAllocUnmap(); + this.Block.Unmap(1); + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/BlockList.cs b/src/Vulkan/VMASharp_OriginalSources/BlockList.cs new file mode 100644 index 0000000000..b2085dc20f --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/BlockList.cs @@ -0,0 +1,775 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Linq; + +using Silk.NET.Vulkan; + +namespace VMASharp +{ + using Metadata; + + internal class BlockList : IDisposable + { + private const int AllocationTryCount = 32; + + private readonly List blocks = new List(); + private readonly ReaderWriterLockSlim mutex = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + + private readonly int minBlockCount, maxBlockCount; + private readonly bool explicitBlockSize; + + private readonly Func metaObjectCreate; + + private bool hasEmptyBlock; + private uint nextBlockID; + + public BlockList(VulkanMemoryAllocator allocator, VulkanMemoryPool? pool, int memoryTypeIndex, + long preferredBlockSize, int minBlockCount, int maxBlockCount, long bufferImageGranularity, + int frameInUseCount, bool explicitBlockSize, Func algorithm) + { + this.Allocator = allocator; + this.ParentPool = pool; + this.MemoryTypeIndex = memoryTypeIndex; + this.PreferredBlockSize = preferredBlockSize; + this.minBlockCount = minBlockCount; + this.maxBlockCount = maxBlockCount; + this.BufferImageGranularity = bufferImageGranularity; + this.FrameInUseCount = frameInUseCount; + this.explicitBlockSize = explicitBlockSize; + + metaObjectCreate = algorithm; + } + + public void Dispose() + { + foreach (var block in this.blocks) + { + block.Dispose(); + } + } + + public VulkanMemoryAllocator Allocator { get; } + + public VulkanMemoryPool? ParentPool { get; } + + public bool IsCustomPool { get => this.ParentPool != null; } + + public int MemoryTypeIndex { get; } + + public long PreferredBlockSize { get; } + + public long BufferImageGranularity { get; } + + public int FrameInUseCount { get; } + + public bool IsEmpty + { + get + { + this.mutex.EnterReadLock(); + + try + { + return this.blocks.Count == 0; + } + finally + { + this.mutex.ExitReadLock(); + } + } + } + + public bool IsCorruptedDetectionEnabled { get => false; } + + public int BlockCount { get => blocks.Count; } + + public VulkanMemoryBlock this[int index] + { + get => blocks[index]; + } + + private IEnumerable BlocksInReverse //Just gonna take advantage of C#... + { + get + { + List localList = this.blocks; + + for (int index = localList.Count - 1; index >= 0; --index) + { + yield return localList[index]; + } + } + } + + public void CreateMinBlocks() + { + if (this.blocks.Count > 0) + { + throw new InvalidOperationException("Block list not empty"); + } + + for (int i = 0; i < this.minBlockCount; ++i) + { + var res = this.CreateBlock(this.PreferredBlockSize, out _); + + if (res != Result.Success) + { + throw new AllocationException("Unable to allocate device memory block", res); + } + } + } + + public void GetPoolStats(out PoolStats stats) + { + this.mutex.EnterReadLock(); + + try + { + stats = new PoolStats(); + stats.BlockCount = this.blocks.Count; + + foreach (var block in this.blocks) + { + Debug.Assert(block != null); + + block.Validate(); + + block.MetaData.AddPoolStats(ref stats); + } + } + finally + { + this.mutex.ExitReadLock(); + } + } + + public Allocation Allocate(int currentFrame, long size, long alignment, in AllocationCreateInfo allocInfo, SuballocationType suballocType) + { + this.mutex.EnterWriteLock(); + + try + { + return this.AllocatePage(currentFrame, size, alignment, allocInfo, suballocType); + } + finally + { + this.mutex.ExitWriteLock(); + } + } + + public void Free(Allocation allocation) + { + VulkanMemoryBlock? blockToDelete = null; + + bool budgetExceeded = false; + { + int heapIndex = this.Allocator.MemoryTypeIndexToHeapIndex(this.MemoryTypeIndex); + this.Allocator.GetBudget(heapIndex, out var budget); + budgetExceeded = budget.Usage >= budget.Budget; + } + + this.mutex.EnterWriteLock(); + + try + { + var blockAlloc = (BlockAllocation)allocation; + + VulkanMemoryBlock block = blockAlloc.Block; + + //Corruption Detection TODO + + if (allocation.IsPersistantMapped) + { + block.Unmap(1); + } + + block.MetaData.Free(blockAlloc); + + block.Validate(); + + bool canDeleteBlock = this.blocks.Count > this.minBlockCount; + + if (block.MetaData.IsEmpty) + { + if ((this.hasEmptyBlock || budgetExceeded) && canDeleteBlock) + { + blockToDelete = block; + this.Remove(block); + } + } + else if (this.hasEmptyBlock && canDeleteBlock) + { + block = this.blocks[^1]; + + if (block.MetaData.IsEmpty) + { + blockToDelete = block; + this.blocks.RemoveAt(this.blocks.Count - 1); + } + } + + this.UpdateHasEmptyBlock(); + this.IncrementallySortBlocks(); + } + finally + { + this.mutex.ExitWriteLock(); + } + + if (blockToDelete != null) + { + blockToDelete.Dispose(); + } + } + + public void AddStats(Stats stats) + { + var memTypeIndex = this.MemoryTypeIndex; + var memHeapIndex = this.Allocator.MemoryTypeIndexToHeapIndex(memTypeIndex); + + this.mutex.EnterReadLock(); + + try + { + foreach (var block in this.blocks) + { + Debug.Assert(block != null); + block.Validate(); + + block.MetaData.CalcAllocationStatInfo(out var info); + StatInfo.Add(ref stats.Total, info); + StatInfo.Add(ref stats.MemoryType[memTypeIndex], info); + StatInfo.Add(ref stats.MemoryHeap[memHeapIndex], info); + } + } + finally + { + this.mutex.ExitReadLock(); + } + } + + /// + /// + /// + /// + /// + /// Lost Allocation Count + /// + public int MakePoolAllocationsLost(int currentFrame) + { + this.mutex.EnterWriteLock(); + + try + { + int lostAllocationCount = 0; + + foreach (var block in this.blocks) + { + Debug.Assert(block != null); + + lostAllocationCount += block.MetaData.MakeAllocationsLost(currentFrame, this.FrameInUseCount); + } + + return lostAllocationCount; + } + finally + { + this.mutex.ExitWriteLock(); + } + } + + public Result CheckCorruption() + { + throw new NotImplementedException(); + } + + public int CalcAllocationCount() + { + int res = 0; + + foreach (var block in blocks) + { + res += block.MetaData.AllocationCount; + } + + return res; + } + + public bool IsBufferImageGranularityConflictPossible() + { + if (BufferImageGranularity == 1) + return false; + + SuballocationType lastSuballocType = SuballocationType.Free; + + foreach (var block in blocks) + { + var metadata = block.MetaData as BlockMetadata_Generic; + Debug.Assert(metadata != null); + + if (metadata.IsBufferImageGranularityConflictPossible(this.BufferImageGranularity, ref lastSuballocType)) + { + return true; + } + } + + return false; + } + + private long CalcMaxBlockSize() + { + long result = 0; + + for (int i = this.blocks.Count - 1; i >= 0; --i) + { + var blockSize = this.blocks[i].MetaData.Size; + + if (result < blockSize) + { + result = blockSize; + } + + if (result >= this.PreferredBlockSize) + { + break; + } + } + + return result; + } + + [SkipLocalsInit] + private Allocation AllocatePage(int currentFrame, long size, long alignment, in AllocationCreateInfo createInfo, SuballocationType suballocType) + { + bool canMakeOtherLost = (createInfo.Flags & AllocationCreateFlags.CanMakeOtherLost) != 0; + bool mapped = (createInfo.Flags & AllocationCreateFlags.Mapped) != 0; + + long freeMemory; + + { + int heapIndex = this.Allocator.MemoryTypeIndexToHeapIndex(this.MemoryTypeIndex); + + this.Allocator.GetBudget(heapIndex, out AllocationBudget heapBudget); + + freeMemory = (heapBudget.Usage < heapBudget.Budget) ? (heapBudget.Budget - heapBudget.Usage) : 0; + } + + bool canFallbackToDedicated = !this.IsCustomPool; + bool canCreateNewBlock = ((createInfo.Flags & AllocationCreateFlags.NeverAllocate) == 0) && (this.blocks.Count < this.maxBlockCount) && (freeMemory >= size || !canFallbackToDedicated); + + var strategy = createInfo.Strategy; + + //if (this.algorithm == (uint)PoolCreateFlags.LinearAlgorithm && this.maxBlockCount > 1) + //{ + // canMakeOtherLost = false; + //} + + //if (isUpperAddress && (this.algorithm != (uint)PoolCreateFlags.LinearAlgorithm || this.maxBlockCount > 1)) + //{ + // throw new AllocationException("Upper address allocation unavailable", Result.ErrorFeatureNotPresent); + //} + + switch (strategy) + { + case 0: + strategy = AllocationStrategyFlags.BestFit; + break; + case AllocationStrategyFlags.BestFit: + case AllocationStrategyFlags.WorstFit: + case AllocationStrategyFlags.FirstFit: + break; + default: + throw new AllocationException("Invalid allocation strategy", Result.ErrorFeatureNotPresent); + } + + if (size + 2 * Helpers.DebugMargin > this.PreferredBlockSize) + { + throw new AllocationException("Allocation size larger than block size", Result.ErrorOutOfDeviceMemory); + } + + AllocationContext context = new AllocationContext( + currentFrame, + this.FrameInUseCount, + this.BufferImageGranularity, + size, + alignment, + strategy, + suballocType, + canMakeOtherLost); + + Allocation? alloc; + + if (!canMakeOtherLost || canCreateNewBlock) + { + AllocationCreateFlags allocFlagsCopy = createInfo.Flags & ~AllocationCreateFlags.CanMakeOtherLost; + + if (strategy == AllocationStrategyFlags.BestFit) + { + foreach (var block in this.blocks) + { + alloc = this.AllocateFromBlock(block, in context, allocFlagsCopy, createInfo.UserData); + + if (alloc != null) + { + //Possibly Log here + return alloc; + } + } + } + else + { + foreach (var curBlock in this.BlocksInReverse) + { + alloc = this.AllocateFromBlock(curBlock, in context, allocFlagsCopy, createInfo.UserData); + + if (alloc != null) + { + //Possibly Log here + return alloc; + } + } + } + } + + if (canCreateNewBlock) + { + AllocationCreateFlags allocFlagsCopy = createInfo.Flags & ~AllocationCreateFlags.CanMakeOtherLost; + + long newBlockSize = this.PreferredBlockSize; + int newBlockSizeShift = 0; + const int NewBlockSizeShiftMax = 3; + + if (!this.explicitBlockSize) + { + long maxExistingBlockSize = this.CalcMaxBlockSize(); + + for (int i = 0; i < NewBlockSizeShiftMax; ++i) + { + long smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize > maxExistingBlockSize && smallerNewBlockSize >= size * 2) + { + newBlockSize = smallerNewBlockSize; + newBlockSizeShift += 1; + } + else + { + break; + } + } + } + + int newBlockIndex = 0; + + var res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? this.CreateBlock(newBlockSize, out newBlockIndex) : Result.ErrorOutOfDeviceMemory; + + if (!this.explicitBlockSize) + { + while (res < 0 && newBlockSizeShift < NewBlockSizeShiftMax) + { + long smallerNewBlockSize = newBlockSize / 2; + + if (smallerNewBlockSize >= size) + { + newBlockSize = smallerNewBlockSize; + newBlockSizeShift += 1; + res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? this.CreateBlock(newBlockSize, out newBlockIndex) : Result.ErrorOutOfDeviceMemory; + } + else + { + break; + } + } + } + + if (res == Result.Success) + { + var block = this.blocks[newBlockIndex]; + + alloc = this.AllocateFromBlock(block, in context, allocFlagsCopy, createInfo.UserData); + + if (alloc != null) + { + //Possibly Log here + return alloc; + } + } + } + + if (canMakeOtherLost) + { + int tryIndex = 0; + + for (; tryIndex < AllocationTryCount; ++tryIndex) + { + VulkanMemoryBlock? bestRequestBlock = null; + + Unsafe.SkipInit(out AllocationRequest bestAllocRequest); + + long bestRequestCost = long.MaxValue; + + if (strategy == AllocationStrategyFlags.BestFit) + { + foreach (var curBlock in this.blocks) + { + if (curBlock.MetaData.TryCreateAllocationRequest(in context, out var request)) + { + long currRequestCost = request.CalcCost(); + + if (bestRequestBlock == null || currRequestCost < bestRequestCost) + { + bestRequestBlock = curBlock; + bestAllocRequest = request; + bestRequestCost = currRequestCost; + + if (bestRequestCost == 0) + break; + } + } + } + } + else + { + foreach (var curBlock in this.BlocksInReverse) + { + if (curBlock.MetaData.TryCreateAllocationRequest(in context, out var request)) + { + long curRequestCost = request.CalcCost(); + + if (bestRequestBlock == null || curRequestCost < bestRequestCost || strategy == AllocationStrategyFlags.FirstFit) + { + bestRequestBlock = curBlock; + bestRequestCost = curRequestCost; + bestAllocRequest = request; + + if (bestRequestCost == 0 || strategy == AllocationStrategyFlags.FirstFit) + { + break; + } + } + } + } + } + + if (bestRequestBlock != null) + { + if (mapped) + { + bestRequestBlock.Map(1); + } + + if (bestRequestBlock.MetaData.MakeRequestedAllocationsLost(currentFrame, this.FrameInUseCount, ref bestAllocRequest)) + { + var talloc = new BlockAllocation(this.Allocator, this.Allocator.CurrentFrameIndex); + + bestRequestBlock.MetaData.Alloc(in bestAllocRequest, suballocType, size, talloc); + + this.UpdateHasEmptyBlock(); + + //(allocation as BlockAllocation).InitBlockAllocation(); + + try + { + bestRequestBlock.Validate(); //Won't be called in release builds + } + catch + { + talloc.Dispose(); + throw; + } + + talloc.UserData = createInfo.UserData; + + this.Allocator.Budget.AddAllocation(this.Allocator.MemoryTypeIndexToHeapIndex(this.MemoryTypeIndex), size); + + //Maybe put memory init and corruption detection here + + return talloc; + } + } + else + { + break; + } + } + + if (tryIndex == AllocationTryCount) + { + throw new AllocationException("", Result.ErrorTooManyObjects); + } + } + + throw new AllocationException("Unable to allocate memory"); + } + + private Allocation? AllocateFromBlock(VulkanMemoryBlock block, in AllocationContext context, AllocationCreateFlags flags, object? userData) + { + Debug.Assert((flags & AllocationCreateFlags.CanMakeOtherLost) == 0); + bool mapped = (flags & AllocationCreateFlags.Mapped) != 0; + + if (block.MetaData.TryCreateAllocationRequest(in context, out var request)) + { + Debug.Assert(request.ItemsToMakeLostCount == 0); + + if (mapped) + { + block.Map(1); + } + + var allocation = new BlockAllocation(this.Allocator, this.Allocator.CurrentFrameIndex); + + block.MetaData.Alloc(in request, context.SuballocationType, context.AllocationSize, allocation); + + allocation.InitBlockAllocation(block, request.Offset, context.AllocationAlignment, context.AllocationSize, this.MemoryTypeIndex, + context.SuballocationType, mapped, (flags & AllocationCreateFlags.CanBecomeLost) != 0); + + this.UpdateHasEmptyBlock(); + + block.Validate(); + + allocation.UserData = userData; + + this.Allocator.Budget.AddAllocation(this.Allocator.MemoryTypeIndexToHeapIndex(this.MemoryTypeIndex), context.AllocationSize); + + return allocation; + } + + return null; + } + + private unsafe Result CreateBlock(long blockSize, out int newBlockIndex) + { + newBlockIndex = -1; + + MemoryAllocateInfo info = new MemoryAllocateInfo + { + SType = StructureType.MemoryAllocateInfo, + MemoryTypeIndex = (uint)this.MemoryTypeIndex, + AllocationSize = (ulong)blockSize + }; + + // Every standalone block can potentially contain a buffer with BufferUsageFlags.BufferUsageShaderDeviceAddressBitKhr - always enable the feature + MemoryAllocateFlagsInfoKHR allocFlagsInfo = new MemoryAllocateFlagsInfoKHR(StructureType.MemoryAllocateFlagsInfoKhr); + if (Allocator.UseKhrBufferDeviceAddress) + { + allocFlagsInfo.Flags = MemoryAllocateFlags.MemoryAllocateDeviceAddressBitKhr; + info.PNext = &allocFlagsInfo; + } + + var res = this.Allocator.AllocateVulkanMemory(in info, out DeviceMemory mem); + + if (res < 0) + { + return res; + } + + var metaObject = this.metaObjectCreate(blockSize); + + if (metaObject.Size != blockSize) + { + throw new InvalidOperationException("Returned Metadata object reports incorrect block size"); + } + + var block = new VulkanMemoryBlock(this.Allocator, this.ParentPool, this.MemoryTypeIndex, mem, this.nextBlockID++, metaObject); + + this.blocks.Add(block); + + newBlockIndex = this.blocks.Count - 1; + + return Result.Success; + } + + private void FreeEmptyBlocks(ref Defragmentation.DefragmentationStats stats) + { + for (int i = this.blocks.Count - 1; i >= 0; --i) + { + var block = this.blocks[i]; + + if (block.MetaData.IsEmpty) + { + if (this.blocks.Count > this.minBlockCount) + { + stats.DeviceMemoryBlocksFreed += 1; + stats.BytesFreed += block.MetaData.Size; + + this.blocks.RemoveAt(i); + block.Dispose(); + } + else + { + break; + } + } + } + + this.UpdateHasEmptyBlock(); + } + + private void UpdateHasEmptyBlock() + { + this.hasEmptyBlock = false; + + foreach (var block in blocks) + { + if (block.MetaData.IsEmpty) + { + this.hasEmptyBlock = true; + break; + } + } + } + + private void Remove(VulkanMemoryBlock block) + { + var res = blocks.Remove(block); + Debug.Assert(res, ""); + } + + private void IncrementallySortBlocks() + { + if ((uint)this.blocks.Count > 1) + { + var prevBlock = this.blocks[0]; + int i = 1; + + do + { + var curBlock = this.blocks[i]; + + if (prevBlock.MetaData.SumFreeSize > curBlock.MetaData.SumFreeSize) + { + this.blocks[i - 1] = curBlock; + this.blocks[i] = prevBlock; + return; + } + + prevBlock = curBlock; + i += 1; + } + while (i < this.blocks.Count); + } + } + + public class DefragmentationContext + { + private readonly BlockList List; + + public DefragmentationContext(BlockList list) + { + this.List = list; + } + + //public void Defragment(DefragmentationStats stats, DefragmentationFlags flags, ulong maxCpuBytesToMove, ) + + //public void End(DefragmentationStats stats) + + //public uint ProcessDefragmentations(DefragmentationPassMoveInfo move, uint maxMoves) + + //public void CommitDefragmentations(DefragmentationStats stats) + } + + + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Budget.cs b/src/Vulkan/VMASharp_OriginalSources/Budget.cs new file mode 100644 index 0000000000..088d214e2a --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Budget.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Runtime.CompilerServices; +using System.Diagnostics; + +using Silk.NET.Vulkan; + +namespace VMASharp +{ + public struct AllocationBudget + { + public long BlockBytes; + public long AllocationBytes; + public long Usage; + public long Budget; + + public AllocationBudget(long blockBytes, long allocationBytes, long usage, long budget) + { + BlockBytes = blockBytes; + AllocationBytes = allocationBytes; + Usage = usage; + Budget = budget; + } + } + + internal class CurrentBudgetData + { + public readonly InternalBudgetStruct[] BudgetData = new InternalBudgetStruct[Vk.MaxMemoryHeaps]; + public readonly ReaderWriterLockSlim BudgetMutex = new ReaderWriterLockSlim(); + public int OperationsSinceBudgetFetch; + + public CurrentBudgetData() + { + } + + public void AddAllocation(int heapIndex, long allocationSize) + { + if ((uint)heapIndex >= Vk.MaxMemoryHeaps) + { + throw new ArgumentOutOfRangeException(nameof(heapIndex)); + } + + Interlocked.Add(ref this.BudgetData[heapIndex].AllocationBytes, allocationSize); + Interlocked.Increment(ref this.OperationsSinceBudgetFetch); + } + + public void RemoveAllocation(int heapIndex, long allocationSize) + { + ref InternalBudgetStruct heap = ref BudgetData[heapIndex]; + + Debug.Assert(heap.AllocationBytes >= allocationSize); + + Interlocked.Add(ref heap.AllocationBytes, -allocationSize); //Subtraction + + Interlocked.Increment(ref this.OperationsSinceBudgetFetch); + } + + internal struct InternalBudgetStruct + { + public long BlockBytes; + public long AllocationBytes; + public long VulkanUsage; + public long VulkanBudget; + public long BlockBytesAtBudgetFetch; + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/DedicatedAllocation.cs b/src/Vulkan/VMASharp_OriginalSources/DedicatedAllocation.cs new file mode 100644 index 0000000000..0b32cdb22c --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/DedicatedAllocation.cs @@ -0,0 +1,110 @@ +using Silk.NET.Vulkan; +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Text; +using VMASharp; + +namespace VMASharp +{ + internal class DedicatedAllocation : Allocation + { + internal DeviceMemory memory; + internal IntPtr mappedData; + + public DedicatedAllocation(VulkanMemoryAllocator allocator, int memTypeIndex, DeviceMemory memory, SuballocationType suballocType, IntPtr mappedData, long size) : base(allocator, 0) + { + this.memory = memory; + this.mappedData = mappedData; + this.memoryTypeIndex = memTypeIndex; + } + + public override DeviceMemory DeviceMemory => this.memory; + + public override long Offset { get => 0; internal set => throw new InvalidOperationException(); } + + public override IntPtr MappedData => this.mapCount != 0 ? this.mappedData : default; + + internal override bool CanBecomeLost => false; + + internal unsafe Result DedicatedAllocMap(out IntPtr pData) + { + if (this.mapCount != 0) + { + if ((this.mapCount & int.MaxValue) < int.MaxValue) + { + Debug.Assert(this.mappedData != default); + + pData = this.mappedData; + this.mapCount += 1; + + return Result.Success; + } + else + { + throw new InvalidOperationException("Dedicated allocation mapped too many times simultaneously"); + } + } + else + { + pData = default; + + IntPtr tmp; + var res = VkApi.MapMemory(this.Allocator.Device, this.memory, 0, Vk.WholeSize, 0, (void**)&tmp); + + if (res == Result.Success) + { + this.mappedData = tmp; + this.mapCount = 1; + pData = tmp; + } + + return res; + } + } + + internal void DedicatedAllocUnmap() + { + if ((this.mapCount & int.MaxValue) != 0) + { + this.mapCount -= 1; + + if (this.mapCount == 0) + { + this.mappedData = default; + VkApi.UnmapMemory(this.Allocator.Device, this.memory); + } + } + else + { + throw new InvalidOperationException("Unmapping dedicated allocation not previously mapped"); + } + } + + public void CalcStatsInfo(out StatInfo stats) + { + StatInfo.Init(out stats); + stats.BlockCount = 1; + stats.AllocationCount = 1; + stats.UsedBytes = this.Size; + stats.AllocationSizeMin = stats.AllocationSizeMax = this.Size; + } + + public override IntPtr Map() + { + var res = DedicatedAllocMap(out var pData); + + if (res != Result.Success) + { + throw new MapMemoryException(res); + } + + return pData; + } + + public override void Unmap() + { + DedicatedAllocUnmap(); + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Defragmentation/BlockListDefragmentationContext.cs b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/BlockListDefragmentationContext.cs new file mode 100644 index 0000000000..319a4a5bfd --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/BlockListDefragmentationContext.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Silk.NET.Vulkan; + +namespace VMASharp.Defragmentation +{ + internal class BlockListDefragmentationContext + { + public Result Result; + public bool MutexLocked; + + public readonly List blockContexts = new List(); + public readonly List DefragMoves = new List(); + + public int DefragMovesProcessed, DefragMovedCommitted; + public bool HasDefragmentationPlanned; + + + public BlockListDefragmentationContext(VulkanMemoryAllocator allocator, VulkanMemoryPool? customPool, BlockList list, uint currentFrame) + { + + } + + public VulkanMemoryPool? CustomPool { get; } + + public BlockList BlockList { get; } + + public DefragmentationAlgorithm Algorithm { get; } + + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Defragmentation/DefragmentationAlgorithm.cs b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/DefragmentationAlgorithm.cs new file mode 100644 index 0000000000..3083499861 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/DefragmentationAlgorithm.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Silk.NET.Vulkan; + +namespace VMASharp.Defragmentation +{ + internal abstract class DefragmentationAlgorithm : IDisposable + { + protected readonly VulkanMemoryAllocator Allocator; + protected readonly BlockList BlockList; + protected readonly uint CurrentFrame; + + protected DefragmentationAlgorithm(VulkanMemoryAllocator allocator, BlockList list, uint currentFrame) + { + this.Allocator = allocator; + this.BlockList = list; + this.CurrentFrame = currentFrame; + } + + public abstract ulong BytesMoved { get; } + + public abstract int AllocationsMoved { get; } + + public virtual void Dispose() + { + } + + public abstract void AddAllocation(Allocation alloc, out bool changed); + + public abstract void AddAll(); + + public abstract Result Defragment(ulong maxBytesToMove, int maxAllocationsToMove, DefragmentationFlags flags, out DefragmentationMove[] moves); + + protected class AllocateInfo + { + public Allocation Allocation; + public bool Changed; + + public AllocateInfo() + { } + + public AllocateInfo(Allocation allocation) + { + this.Allocation = allocation; + } + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Defragmentation/DefragmentationContext.cs b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/DefragmentationContext.cs new file mode 100644 index 0000000000..bd5fd511f0 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/DefragmentationContext.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Silk.NET.Vulkan; +using VMASharp; + +namespace VMASharp.Defragmentation +{ + public sealed class DefragmentationContext : IDisposable + { + private readonly VulkanMemoryAllocator Allocator; + private readonly uint currentFrame; + private readonly uint Flags; + private DefragmentationStats Stats; + + private ulong MaxCPUBytesToMove, MaxGPUBytesToMove; + private int MaxCPUAllocationsToMove, MaxGPUAllocationsToMove; + + private readonly BlockListDefragmentationContext[] DefaultPoolContexts = new BlockListDefragmentationContext[Vk.MaxMemoryTypes]; + private readonly List CustomPoolContexts = new List(); + + + internal DefragmentationContext(VulkanMemoryAllocator allocator, uint currentFrame, uint flags, DefragmentationStats stats) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + internal void AddPools(params VulkanMemoryPool[] Pools) + { + throw new NotImplementedException(); + } + + internal void AddAllocations(Allocation[] allocations, out bool[] allocationsChanged) + { + throw new NotImplementedException(); + } + + internal Result Defragment(ulong maxCPUBytesToMove, int maxCPUAllocationsToMove, ulong maxGPUBytesToMove, + int maxGPUAllocationsToMove, CommandBuffer cbuffer, DefragmentationStats stats, + DefragmentationFlags flags) + { + throw new NotImplementedException(); + } + + internal Result DefragmentationPassBegin(ref DefragmentationPassMoveInfo[] Info) + { + throw new NotImplementedException(); + } + + internal Result DefragmentationPassEnd() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Defragmentation/FastDefragAlgorithm.cs b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/FastDefragAlgorithm.cs new file mode 100644 index 0000000000..a893cfe75f --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/FastDefragAlgorithm.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using Silk.NET.Vulkan; +using VMASharp; + +namespace VMASharp.Defragmentation +{ + using Metadata; + + internal sealed class FastDefragAlgorithm : DefragmentationAlgorithm + { + private readonly bool overlappingMoveSupported; + private int allocationCount; + private bool allAllocations; + + private ulong bytesMoved; + private int allocationsMoved; + + private readonly List blockInfos = new List(); + + public FastDefragAlgorithm(VulkanMemoryAllocator allocator, BlockList list, uint currentFrame, bool overlappingMoveSupported) : base(allocator, list, currentFrame) + { + this.overlappingMoveSupported = overlappingMoveSupported; + } + + public override ulong BytesMoved => throw new NotImplementedException(); + + public override int AllocationsMoved => throw new NotImplementedException(); + + public override void AddAll() + { + throw new NotImplementedException(); + } + + public override void AddAllocation(Allocation alloc, out bool changed) + { + throw new NotImplementedException(); + } + + public override Result Defragment(ulong maxBytesToMove, int maxAllocationsToMove, DefragmentationFlags flags, out DefragmentationMove[] moves) + { + throw new NotImplementedException(); + } + + private void PreprocessMetadata() + { + + } + + private void PostprocessMetadata() + { + + } + + private void InsertSuballoc(BlockMetadata_Generic metadata, in Suballocation suballoc) + { + + } + + private struct BlockInfo + { + public int OrigBlockIndex; + } + + private class FreeSpaceDatabase + { + private const int MaxCount = 4; + + private FreeSpace[] FreeSpaces = new FreeSpace[MaxCount]; + + public FreeSpaceDatabase() + { + for (int i = 0; i < FreeSpaces.Length; ++i) + { + FreeSpaces[i].BlockInfoIndex = -1; + } + } + + public void Register(int blockInfoIndex, long offset, long size) + { + if (size < Helpers.MinFreeSuballocationSizeToRegister) + { + return; + } + + int bestIndex = -1; + for (int i = 0; i < FreeSpaces.Length; ++i) + { + ref FreeSpace space = ref FreeSpaces[i]; + + if (space.BlockInfoIndex == -1) + { + bestIndex = i; + break; + } + + if (space.Size < size && (bestIndex == -1 || space.Size < FreeSpaces[bestIndex].Size)) + { + bestIndex = i; + } + } + + if (bestIndex != -1) + { + ref FreeSpace bestSpace = ref FreeSpaces[bestIndex]; + + bestSpace.BlockInfoIndex = blockInfoIndex; + bestSpace.Offset = offset; + bestSpace.Size = size; + } + } + + public bool Fetch(long alignment, long size, out int blockInfoIndex, out long destOffset) + { + int bestIndex = -1; + long bestFreeSpaceAfter = 0; + + for (int i = 0; i < FreeSpaces.Length; ++i) + { + ref FreeSpace space = ref FreeSpaces[i]; + + if (space.BlockInfoIndex == -1) + break; + + long tmpOffset = Helpers.AlignUp(space.Offset, alignment); + + if (tmpOffset + size <= space.Offset + space.Size) + { + long freeSpaceAfter = (space.Offset + space.Size) - (tmpOffset + size); + + if (bestIndex == -1 || freeSpaceAfter > bestFreeSpaceAfter) + { + bestIndex = i; + bestFreeSpaceAfter = freeSpaceAfter; + } + } + } + + if (bestIndex != -1) + { + ref FreeSpace bestSpace = ref FreeSpaces[bestIndex]; + + blockInfoIndex = bestSpace.BlockInfoIndex; + destOffset = Helpers.AlignUp(bestSpace.Offset, alignment); + + if (bestFreeSpaceAfter >= Helpers.MinFreeSuballocationSizeToRegister) + { + long alignmentPlusSize = (destOffset - bestSpace.Offset) + size; + + bestSpace.Offset += alignmentPlusSize; + bestSpace.Size -= alignmentPlusSize; + } + else + { + bestSpace.BlockInfoIndex = -1; + } + + return true; + } + + blockInfoIndex = default; + destOffset = default; + return false; + } + + private struct FreeSpace + { + public int BlockInfoIndex; + public long Offset, Size; + } + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Defragmentation/GenericDefragAlgorithm.cs b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/GenericDefragAlgorithm.cs new file mode 100644 index 0000000000..5cdb2314cf --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/GenericDefragAlgorithm.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Silk.NET.Vulkan; + +namespace VMASharp.Defragmentation +{ + internal sealed class GenericDefragAlgorithm : DefragmentationAlgorithm + { + private int allocationCount; + private bool allAllocations; + + private ulong bytesMoved; + private int allocationsMoved; + + public GenericDefragAlgorithm(VulkanMemoryAllocator allocator, BlockList list, uint currentFrame, bool overlappingMoveSupported) : base(allocator, list, currentFrame) + { + } + + public override ulong BytesMoved => throw new NotImplementedException(); + + public override int AllocationsMoved => throw new NotImplementedException(); + + public override void AddAll() + { + throw new NotImplementedException(); + } + + public override void AddAllocation(Allocation alloc, out bool changed) + { + throw new NotImplementedException(); + } + + public override Result Defragment(ulong maxBytesToMove, int maxAllocationsToMove, DefragmentationFlags flags, out DefragmentationMove[] moves) + { + throw new NotImplementedException(); + } + + private class BlockInfo + { + public int OriginalBlockIndex; + public VulkanMemoryBlock Block; + public bool HasNonMovableAllocations; + public readonly List Allocations = new List(); + + public void CalcHasNonMovableAllocations() + { + this.HasNonMovableAllocations = this.Block.MetaData.AllocationCount != this.Allocations.Count; + } + + public void SortAllocationsBySizeDescending() + { + this.Allocations.Sort(delegate (AllocateInfo info1, AllocateInfo info2) + { + return info1.Allocation.Size.CompareTo(info2.Allocation.Size); + }); + } + + public void SortAllocationsByOffsetDescending() + { + this.Allocations.Sort(delegate (AllocateInfo info1, AllocateInfo info2) + { + return info1.Allocation.Offset.CompareTo(info2.Allocation.Offset); + }); + } + } + + private readonly List Blocks = new List(); + + //private Result DefragmentRound() + + private int CalcBlocksWithNonMovableCount() + { + throw new NotImplementedException(); + } + + private static bool MoveMakesSense(int destBlockIndex, ulong destOffset, int sourceBlockIndex, ulong sourceOffset) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Defragmentation/Structures.cs b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/Structures.cs new file mode 100644 index 0000000000..f75725f5a0 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Defragmentation/Structures.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Silk.NET.Vulkan; +using VMASharp; +using Buffer = Silk.NET.Vulkan.Buffer; + +namespace VMASharp.Defragmentation +{ + public struct DefragmentationInfo2 + { + public DefragmentationFlags Flags; + + public Allocation[] Allocations; + + public bool[] AllocationsChanged; + + public VulkanMemoryPool[] Pools; + + public ulong MaxCPUBytesToMove; + + public int MaxCPUAllocationsToMove; + + public ulong MaxGPUBytesToMove; + + public int MaxGPUAllocationsToMove; + + public CommandBuffer CommandBuffer; + } + + public struct DefragmentationPassMoveInfo + { + public Allocation Allocation; + + public DeviceMemory Memory; + + public ulong Offset; + } + + public struct DefragmentationInfo + { + public ulong MaxBytesToMove; + + public int MaxAllocationsToMove; + } + + public class DefragmentationStats + { + public long BytesMoved; + + public long BytesFreed; + + public int AllocationsMoved; + + public int DeviceMemoryBlocksFreed; + } + + internal struct DefragmentationMove + { + public int SourceBlockIndex, DestinationBlockIndex; + + public ulong SourceOffset, DestinationOffset, Size; + + public Allocation Allocation; + + public VulkanMemoryBlock SourceBlock, DestinationBlock; + } + + internal struct BlockDefragmentationContext + { + public enum BlockFlags + { + Used = 0x01 + } + + public BlockFlags Flags; + public Buffer Buffer; + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Exceptions.cs b/src/Vulkan/VMASharp_OriginalSources/Exceptions.cs new file mode 100644 index 0000000000..50f15fb7aa --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Exceptions.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Silk.NET.Vulkan; + +namespace VMASharp +{ + public class VulkanResultException : ApplicationException + { + public readonly Result? Result; + + public VulkanResultException(string message) : base(message) + { + } + + public VulkanResultException(string message, Exception? innerException) : base(message, innerException) + { + } + + public VulkanResultException(Result res) : base("Vulkan returned an API error code") + { + Result = res; + } + + public VulkanResultException(string message, Result res) : base(message) + { + Result = res; + } + } + + public class AllocationException : VulkanResultException + { + public AllocationException(string message) : base (message) + { + } + + public AllocationException(string message, Exception? innerException) : base (message, innerException) + { + } + + public AllocationException(Result res) : base(res) + { + } + + public AllocationException(string message, Result res) : base (message, res) + { + } + } + + public class DefragmentationException : VulkanResultException + { + public DefragmentationException(string message) : base(message) + { + } + + public DefragmentationException(Result res) : base(res) + { + } + + public DefragmentationException(string message, Result res) : base(message, res) + { + } + } + + public class MapMemoryException : VulkanResultException + { + public MapMemoryException(string message) : base(message) + { + } + + public MapMemoryException(Result res) : base("Mapping a Device Memory block encountered an issue", res) + { + } + + public MapMemoryException(string message, Result res) : base(message, res) + { + } + } + + public class ValidationFailedException : ApplicationException + { + public ValidationFailedException() : base("Validation of Allocator structures found a bug!") + { + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Helpers.cs b/src/Vulkan/VMASharp_OriginalSources/Helpers.cs new file mode 100644 index 0000000000..0f7c65fa99 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Helpers.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using Silk.NET.Core; + +namespace VMASharp +{ + internal static class Helpers + { + public const long MinFreeSuballocationSizeToRegister = 16; + public const int FrameIndexLost = -1; + public const uint CorruptionDetectionMagicValue = 0x7F84E666; + + public const byte AllocationFillPattern_Created = 0xDC; + public const byte AllocationFillPattern_Destroyed = 0xEF; + public const bool DebugInitializeAllocations = false; + + public const long DebugMargin = 0; + public const long DebugAlignment = 1; + public const long DebugMinBufferImageGranularity = 1; + + public const AllocationStrategyFlags InternalAllocationStrategy_MinOffset = (AllocationStrategyFlags)0x10000000u; + + internal static readonly Version32 VulkanAPIVersion_1_0 = new Version32(1, 0, 0); + internal static readonly Version32 VulkanAPIVersion_1_1 = new Version32(1, 1, 0); + + public static readonly Comparison> SuballocationNodeItemSizeLess = (first, second) => first.Value.Size.CompareTo(second.Value.Size); + public static readonly Comparison SuballocationItemSizeLess = (first, second) => first.Size.CompareTo(second.Size); + + public static readonly Func DefaultMetaObjectCreate = size => new Metadata.BlockMetadata_Generic(size); + + public static bool IsPow2(int v) + { + return BitOperations.PopCount((uint)v) == 1; + } + + public static bool IsPow2(long v) + { + return BitOperations.PopCount((ulong)v) == 1; + } + + public static int NextPow2(int v) + { + if (IsPow2(v)) + return v; + + return 1 << (32 - BitOperations.LeadingZeroCount((uint)v)); + } + + public static long NextPow2(long v) + { + if (IsPow2(v)) + return v; + + return 1L << (64 - BitOperations.LeadingZeroCount((ulong)v)); + } + + public static int PrevPow(int v) + { + return 1 << (31 - BitOperations.LeadingZeroCount((uint)v)); + } + + public static long PrevPow(long v) + { + return 1L << (63 - BitOperations.LeadingZeroCount((ulong)v)); + } + + public static bool BlocksOnSamePage(long resourceAOffset, long resourceASize, long resourceBOffset, long pageSize) + { + Debug.Assert(resourceAOffset + resourceASize <= resourceBOffset && resourceASize > 0 && pageSize > 0); + + long resourceAEnd = resourceAOffset + resourceASize - 1; + long resourceAEndPage = resourceAEnd & ~(pageSize - 1); + long resourceBStart = resourceBOffset; + long resourceBStartPage = resourceBStart & ~(pageSize - 1); + return resourceAEndPage == resourceBStartPage; + } + + public static bool IsBufferImageGranularityConflict(SuballocationType type1, SuballocationType type2) + { + if (type1 > type2) + { + //Swap + var type3 = type1; + type1 = type2; + type2 = type3; + } + + switch(type1) + { + case SuballocationType.Free: + return false; + case SuballocationType.Unknown: + return true; + case SuballocationType.Buffer: + return type2 == SuballocationType.Image_Unknown || type2 == SuballocationType.Image_Optimal; + case SuballocationType.Image_Unknown: + return type2 == SuballocationType.Image_Unknown || type2 == SuballocationType.Image_Linear || type2 == SuballocationType.Image_Optimal; + case SuballocationType.Image_Linear: + return type2 == SuballocationType.Image_Optimal; + case SuballocationType.Image_Optimal: + return false; + default: + Debug.Assert(false); + return true; + } + } + + public static long AlignUp(long value, long alignment) + { + return ((value + alignment - 1) / alignment) * alignment; + } + + public static long AlignDown(long value, long alignment) + { + return (long)((ulong)value / (ulong)alignment * (ulong)alignment); + } + + public interface IComparer_Single + { + int Compare(T item); + } + + public interface IComparer_Normal + { + int Compare(T l, T r); + } + + public static int BinarySearch(this List list, T value, TComp comp) where TComp : struct, IComparer_Normal + { + int begin = 0, end = list.Count - 1; + + while (begin <= end) + { + int mid = (begin + end) / 2; + + int comparison = comp.Compare(list[mid], value); + + if (comparison == 0) + { + return mid; + } + + if (comparison < 0) + { + begin = mid + 1; + } + else + { + end = mid - 1; + } + } + + return ~begin; + } + + public static int BinarySearch(this List list, TState state, Func SearchCompare) + { + int begin = 0, end = list.Count - 1; + + while (begin <= end) + { + int mid = (begin + end) / 2; + + int comparison = SearchCompare(list[mid], state); + + if (comparison == 0) + { + return mid; + } + + if (comparison < 0) + { + begin = mid + 1; + } + else + { + end = mid - 1; + } + } + + return ~begin; + } + + public static int BinarySearch(this List list, T value, Comparison comparer) + { + int begin = 0, end = list.Count - 1; + + while (begin <= end) + { + int mid = (begin + end) / 2; + + int comparison = comparer(list[mid], value); + + if (comparison == 0) + { + return mid; + } + + if (comparison < 0) + { + begin = mid + 1; + } + else + { + end = mid - 1; + } + } + + return ~begin; + } + + public static int BinarySearch_Leftmost(this List list, TComp comp) where TComp : struct, IComparer_Single + { + int begin = 0, end = list.Count, comparison = -1; + + while (begin < end) + { + int mid = (begin + end) / 2; + + comparison = comp.Compare(list[mid]); + + if (comparison < 0) + { + begin = mid + 1; + } + else + { + end = mid; + } + } + + return (comparison == 0) ? begin : ~begin; + } + + public static int BinarySearch_Leftmost(this List list, T value, Comparison comparer) + { + int begin = 0, end = list.Count; + + while (begin < end) + { + int mid = (begin + end) / 2; + + int comparison = comparer(list[mid], value); + + if (comparison < 0) + { + begin = mid + 1; + } + else + { + end = mid; + } + } + + return (comparer(list[begin], value) == 0) ? begin : ~begin; + } + + public static int InsertSorted(this List list, T value) + { + int i = list.BinarySearch(value); + + if (i < 0) + { + i = ~i; + } + + list.Insert(i, value); + + return i; + } + + public static int InsertSorted(this List list, T value, Comparison comparison) + { + int i = list.BinarySearch(value, comparison); + + if (i < 0) + { + i = ~i; + } + + list.Insert(i, value); + + return i; + } + + public static int FindIndex(this List list, TState state, Func predicate) + { + for (int i = 0; i < list.Count; ++i) + { + if (predicate(list[i], state)) + { + return i; + } + } + + return -1; + } + + [Conditional("DEBUG")] + public static void AssertNotNull(this T instance) where T: class + { + Debug.Assert(instance != null); + } + + [Conditional("DEBUG")] + public static void AssertNotNull(this IntPtr ptr) + { + Debug.Assert(ptr != default); + } + + public static void Validate([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool assertion) + { + if (!assertion) + { + throw new ValidationFailedException(); + } + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Metadata/BlockMetadata_Generic.cs b/src/Vulkan/VMASharp_OriginalSources/Metadata/BlockMetadata_Generic.cs new file mode 100644 index 0000000000..8e0a657398 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Metadata/BlockMetadata_Generic.cs @@ -0,0 +1,788 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace VMASharp.Metadata +{ + internal sealed class BlockMetadata_Generic : IBlockMetadata + { + public long Size { get; } + + private int freeCount; + + private long sumFreeSize; + + private readonly LinkedList suballocations = new LinkedList(); + + private readonly List> freeSuballocationsBySize = new List>(); + + public int AllocationCount => suballocations.Count - freeCount; + + public long SumFreeSize => sumFreeSize; + + public long UnusedRangeSizeMax + { + get + { + var count = this.freeSuballocationsBySize.Count; + + if (count != 0) + { + return this.freeSuballocationsBySize[count - 1].ValueRef.Size; + } + + return 0; + } + } + + public bool IsEmpty => (this.suballocations.Count == 1) && (this.freeCount == 1); + + public BlockMetadata_Generic(long blockSize) + { + this.Size = blockSize; + this.freeCount = 1; + this.sumFreeSize = blockSize; + + Debug.Assert(blockSize > Helpers.MinFreeSuballocationSizeToRegister); + + var node = this.suballocations.AddLast(new Suballocation(0, blockSize)); + + this.freeSuballocationsBySize.Add(node); + } + + public void Alloc(in AllocationRequest request, SuballocationType type, long allocSize, BlockAllocation allocation) + { + Debug.Assert(request.Type == AllocationRequestType.Normal); + Debug.Assert(request.Item != null); + + if (request.Item is not LinkedListNode requestNode) + { + throw new InvalidOperationException(); + } + + Debug.Assert(object.ReferenceEquals(requestNode.List, this.suballocations)); + + ref Suballocation suballoc = ref requestNode.ValueRef; + + Debug.Assert(suballoc.Type == SuballocationType.Free); + Debug.Assert(request.Offset >= suballoc.Offset); + + long paddingBegin = request.Offset - suballoc.Offset; + + Debug.Assert(suballoc.Size >= paddingBegin + allocSize); + + long paddingEnd = suballoc.Size - paddingBegin - allocSize; + + UnregisterFreeSuballocation(requestNode); + + suballoc.Offset = request.Offset; + suballoc.Size = allocSize; + suballoc.Type = type; + suballoc.Allocation = allocation; + + if (paddingEnd > 0) + { + var newNode = this.suballocations.AddAfter(requestNode, new Suballocation(request.Offset + allocSize, paddingEnd)); + RegisterFreeSuballocation(newNode); + } + + if (paddingBegin > 0) + { + var newNode = this.suballocations.AddBefore(requestNode, new Suballocation(request.Offset - paddingBegin, paddingBegin)); + RegisterFreeSuballocation(newNode); + } + + if (paddingBegin > 0) + { + if (paddingEnd > 0) + { + this.freeCount += 1; + } + } + else if (paddingEnd <= 0) + { + this.freeCount -= 1; + } + + this.sumFreeSize -= allocSize; + } + + public void CheckCorruption(nuint blockDataPointer) + { + throw new NotImplementedException(); + } + + public bool TryCreateAllocationRequest(in AllocationContext context, out AllocationRequest request) + { + request = default; + + request.Type = AllocationRequestType.Normal; + + if (context.CanMakeOtherLost == false && this.sumFreeSize < context.AllocationSize + 2 * Helpers.DebugMargin) + { + return false; + } + + var contextCopy = context; + contextCopy.CanMakeOtherLost = false; + + int freeSuballocCount = this.freeSuballocationsBySize.Count; + if (freeSuballocCount > 0) + { + if (context.Strategy == AllocationStrategyFlags.BestFit) + { + var index = this.freeSuballocationsBySize.FindIndex(context.AllocationSize + 2 * Helpers.DebugMargin, (node, size) => node.ValueRef.Size >= size); + + for (; index < freeSuballocCount; ++index) + { + var suballocNode = this.freeSuballocationsBySize[index]; + + if (this.CheckAllocation(in contextCopy, suballocNode, ref request)) + { + request.Item = suballocNode; + return true; + } + } + } + else if (context.Strategy == Helpers.InternalAllocationStrategy_MinOffset) + { + for (var node = this.suballocations.First; node != null; node = node.Next) + { + if (node.Value.Type == SuballocationType.Free + && this.CheckAllocation(in contextCopy, node, ref request)) + { + request.Item = node; + return true; + } + } + } + else //Worst Fit, First Fit + { + for (int i = freeSuballocCount; i >= 0; --i) + { + var item = this.freeSuballocationsBySize[i]; + + if (this.CheckAllocation(in contextCopy, item, ref request)) + { + request.Item = item; + return true; + } + } + } + } + + if (context.CanMakeOtherLost) + { + bool found = false; + AllocationRequest tmpRequest = default; + + for (LinkedListNode? tNode = this.suballocations.First; tNode != null; tNode = tNode.Next) + { + if (this.CheckAllocation(in context, tNode, ref tmpRequest)) + { + if (context.Strategy == AllocationStrategyFlags.FirstFit) + { + request = tmpRequest; + request.Item = tNode; + break; + } + + if (!found || tmpRequest.CalcCost() < request.CalcCost()) + { + request = tmpRequest; + request.Item = tNode; + found = true; + } + } + } + + return found; + } + + return false; + } + + public void Free(BlockAllocation allocation) + { + for (LinkedListNode? node = this.suballocations.First; node != null; node = node.Next) + { + ref var suballoc = ref node.ValueRef; + + if (object.ReferenceEquals(suballoc.Allocation, allocation)) + { + this.FreeSuballocation(node); + return; + } + } + + throw new InvalidOperationException("Allocation not found!"); + } + + public void FreeAtOffset(long offset) + { + for (LinkedListNode? node = this.suballocations.First; node != null; node = node.Next) + { + ref var suballoc = ref node.ValueRef; + + if (suballoc.Offset == offset) + { + this.FreeSuballocation(node); + return; + } + } + + throw new InvalidOperationException("Allocation not found!"); + } + + public int MakeAllocationsLost(int currentFrame, int frameInUseCount) + { + int lost = 0; + + for (var node = this.suballocations.First; node != null; node = node.Next) + { + ref var suballoc = ref node.ValueRef; + + if (suballoc.Type != SuballocationType.Free && + suballoc.Allocation!.CanBecomeLost && + suballoc.Allocation.MakeLost(currentFrame, frameInUseCount)) + { + node = FreeSuballocation(node); + lost += 1; + } + } + + return lost; + } + + public bool MakeRequestedAllocationsLost(int currentFrame, int frameInUseCount, ref AllocationRequest request) + { + if (request.Type != AllocationRequestType.Normal) + { + throw new ArgumentException("Allocation Request Type was not normal"); + } + + LinkedListNode? tNode = request.Item as LinkedListNode ?? throw new InvalidOperationException(); + + while (request.ItemsToMakeLostCount > 0) + { + if (tNode.ValueRef.Type == SuballocationType.Free) + { + tNode = tNode.Next; + } + + Debug.Assert(tNode != null); + + ref var suballoc = ref tNode.ValueRef; + + Debug.Assert(suballoc.Allocation != null); + Debug.Assert(suballoc.Allocation.CanBecomeLost); + + if (suballoc.Allocation.MakeLost(currentFrame, frameInUseCount)) + { + request.Item = tNode = FreeSuballocation(tNode); + request.ItemsToMakeLostCount -= 1; + } + else + { + return false; + } + } + + Debug.Assert(request.Item != null); + Debug.Assert(Unsafe.As>(request.Item).ValueRef.Type == SuballocationType.Free); + + return true; + } + + public void Validate() + { + Helpers.Validate(this.suballocations.Count > 0); + + long calculatedOffset = 0, calculatedSumFreeSize = 0; + int calculatedFreeCount = 0, freeSuballocationsToRegister = 0; + + bool prevFree = false; + + foreach (Suballocation subAlloc in this.suballocations) + { + Helpers.Validate(subAlloc.Offset == calculatedOffset); + + bool currFree = subAlloc.Type == SuballocationType.Free; + + if (currFree) + { + Helpers.Validate(!prevFree); + Helpers.Validate(subAlloc.Allocation == null); + + calculatedSumFreeSize += subAlloc.Size; + calculatedFreeCount += 1; + + if (subAlloc.Size >= Helpers.MinFreeSuballocationSizeToRegister) + { + freeSuballocationsToRegister += 1; + } + + Helpers.Validate(subAlloc.Size >= Helpers.DebugMargin); + } + else + { + Helpers.Validate(subAlloc.Allocation != null); + Helpers.Validate(subAlloc.Allocation!.Offset == subAlloc.Offset); + Helpers.Validate(subAlloc.Allocation.Size == subAlloc.Size); + Helpers.Validate(Helpers.DebugMargin == 0 || prevFree); + } + + calculatedOffset += subAlloc.Size; + prevFree = currFree; + } + + Helpers.Validate(this.freeSuballocationsBySize.Count == freeSuballocationsToRegister); + + this.ValidateFreeSuballocationList(); + + Helpers.Validate(calculatedOffset == this.Size); + Helpers.Validate(calculatedSumFreeSize == this.sumFreeSize); + Helpers.Validate(calculatedFreeCount == this.freeCount); + } + + [Conditional("DEBUG")] + private void ValidateFreeSuballocationList() + { + long lastSize = 0; + + for (int i = 0, count = this.freeSuballocationsBySize.Count; i < count; ++i) + { + var node = this.freeSuballocationsBySize[i]; + + Helpers.Validate(node.ValueRef.Type == SuballocationType.Free); + Helpers.Validate(node.ValueRef.Size >= Helpers.MinFreeSuballocationSizeToRegister); + Helpers.Validate(node.ValueRef.Size >= lastSize); + + lastSize = node.ValueRef.Size; + } + } + + private bool CheckAllocation(in AllocationContext context, LinkedListNode node, ref AllocationRequest request) + { + if (context.AllocationSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(context.AllocationSize)); + } + + if (context.SuballocationType == SuballocationType.Free) + { + throw new ArgumentException("Invalid Allocation Type", nameof(context.SuballocationType)); + } + + if (context.BufferImageGranularity <= 0) + { + throw new ArgumentOutOfRangeException(nameof(context.BufferImageGranularity)); + } + + request.ItemsToMakeLostCount = 0; + request.SumFreeSize = 0; + request.SumItemSize = 0; + + ref Suballocation suballocItem = ref node.ValueRef; + + if (context.CanMakeOtherLost) + { + if (suballocItem.Type == SuballocationType.Free) + { + request.SumFreeSize = suballocItem.Size; + } + else + { + if (suballocItem.Allocation.CanBecomeLost && suballocItem.Allocation.LastUseFrameIndex + context.FrameInUseCount < context.CurrentFrame) + { + request.ItemsToMakeLostCount += 1; + request.SumItemSize = suballocItem.Size; + } + else + { + return false; + } + } + + if (this.Size - suballocItem.Offset < context.AllocationSize) + { + return false; + } + + var offset = (Helpers.DebugMargin > 0) ? suballocItem.Offset + Helpers.DebugMargin : suballocItem.Offset; + + request.Offset = Helpers.AlignUp(offset, context.AllocationAlignment); + + AccountForBackwardGranularityConflict(node, context.BufferImageGranularity, context.SuballocationType, ref request); + + if (request.Offset >= suballocItem.Offset + suballocItem.Size) + { + return false; + } + + long paddingBegin = request.Offset - suballocItem.Offset; + long requiredEndMargin = Helpers.DebugMargin; + long totalSize = paddingBegin + context.AllocationSize + requiredEndMargin; + + if (suballocItem.Offset + totalSize > this.Size) + { + return false; + } + + var prevNode = node; + + if (totalSize > suballocItem.Size) + { + long remainingSize = totalSize - suballocItem.Size; + while (remainingSize > 0) + { + if (prevNode.Next == null) + { + return false; + } + + prevNode = prevNode.Next; + + ref var tmpSuballoc = ref prevNode.ValueRef; //TODO: Make better reference to this variable + + if (prevNode.ValueRef.Type == SuballocationType.Free) + { + request.SumFreeSize += prevNode.ValueRef.Size; + } + else + { + Debug.Assert(prevNode.ValueRef.Allocation != null); + + if (tmpSuballoc.Allocation.CanBecomeLost && tmpSuballoc.Allocation.LastUseFrameIndex + context.FrameInUseCount < context.CurrentFrame) + { + request.ItemsToMakeLostCount += 1; + + request.SumItemSize += tmpSuballoc.Size; + } + else + { + return false; + } + } + + remainingSize = (tmpSuballoc.Size < remainingSize) ? remainingSize - tmpSuballoc.Size : 0; + } + } + + if (context.BufferImageGranularity > 1) + { + var nextNode = prevNode.Next; + + while (nextNode != null) + { + ref Suballocation nextItem = ref nextNode.ValueRef; + + if (Helpers.BlocksOnSamePage(request.Offset, context.AllocationSize, nextItem.Offset, context.BufferImageGranularity)) + { + if (Helpers.IsBufferImageGranularityConflict(context.SuballocationType, nextItem.Type)) + { + Debug.Assert(nextItem.Allocation != null); + + if (nextItem.Allocation.CanBecomeLost && nextItem.Allocation.LastUseFrameIndex + context.FrameInUseCount < context.CurrentFrame) + { + request.ItemsToMakeLostCount += 1; + } + else + { + return false; + } + } + } + else + { + break; + } + + nextNode = nextNode.Next; + } + } + } + else + { + request.SumFreeSize = suballocItem.Size; + + if (suballocItem.Size < context.AllocationSize) + { + return false; + } + + var offset = suballocItem.Offset; + + if (Helpers.DebugMargin > 0) + { + offset += Helpers.DebugMargin; + } + + request.Offset = Helpers.AlignUp(offset, context.AllocationAlignment); + + AccountForBackwardGranularityConflict(node, context.BufferImageGranularity, context.SuballocationType, ref request); + + long paddingBegin = request.Offset - suballocItem.Offset, requiredEndMargin = Helpers.DebugMargin; + + if (paddingBegin + context.AllocationSize + requiredEndMargin > suballocItem.Size) + { + return false; + } + + if (context.BufferImageGranularity > 1) + { + var nextNode = node.Next; + + while (nextNode != null) + { + ref var nextItem = ref nextNode.ValueRef; + + if (Helpers.BlocksOnSamePage(request.Offset, context.AllocationSize, nextItem.Offset, context.BufferImageGranularity)) + { + if (Helpers.IsBufferImageGranularityConflict(context.SuballocationType, nextItem.Type)) + { + return false; + } + } + else + { + break; + } + + nextNode = nextNode.Next; + } + } + } + + return true; + + static void AccountForBackwardGranularityConflict(LinkedListNode node, long granularity, SuballocationType suballocType, ref AllocationRequest request) + { + if (granularity == 1) + { + return; + } + + LinkedListNode prevNode = node; + + while (prevNode.Previous != null) + { + prevNode = prevNode.Previous; + + ref var prevAlloc = ref prevNode.ValueRef; + + if (Helpers.BlocksOnSamePage(prevAlloc.Offset, prevAlloc.Size, request.Offset, granularity)) + { + if (Helpers.IsBufferImageGranularityConflict(prevAlloc.Type, suballocType)) + { + request.Offset = Helpers.AlignUp(request.Offset, granularity); + break; + } + } + else + { + break; + } + } + } + } + + private void MergeFreeWithNext(LinkedListNode node) + { + Debug.Assert(node != null); + Debug.Assert(object.ReferenceEquals(node.List, this.suballocations)); + Debug.Assert(node.ValueRef.Type == SuballocationType.Free); + + var nextNode = node.Next; + + Debug.Assert(nextNode != null); + Debug.Assert(nextNode.ValueRef.Type == SuballocationType.Free); + + ref Suballocation item = ref node.ValueRef, nextItem = ref nextNode.ValueRef; + + item.Size += nextItem.Size; + this.freeCount -= 1; + this.suballocations.Remove(nextNode); + } + + private LinkedListNode FreeSuballocation(LinkedListNode item) + { + ref var suballoc = ref item.ValueRef; + + suballoc.Type = SuballocationType.Free; + suballoc.Allocation = null; + + this.freeCount += 1; + this.sumFreeSize += suballoc.Size; + + var nextItem = item.Next; + var prevItem = item.Previous; + + if (nextItem != null && nextItem.ValueRef.Type == SuballocationType.Free) + { + UnregisterFreeSuballocation(nextItem); + MergeFreeWithNext(item); + } + + if (prevItem != null && prevItem.ValueRef.Type == SuballocationType.Free) + { + UnregisterFreeSuballocation(prevItem); + MergeFreeWithNext(prevItem); + RegisterFreeSuballocation(prevItem); + return prevItem; + } + else + { + RegisterFreeSuballocation(item); + return item; + } + } + + private void RegisterFreeSuballocation(LinkedListNode item) + { + Debug.Assert(item.ValueRef.Type == SuballocationType.Free); + Debug.Assert(item.ValueRef.Size > 0); + + this.ValidateFreeSuballocationList(); + + if (item.Value.Size >= Helpers.MinFreeSuballocationSizeToRegister) + { + if (this.freeSuballocationsBySize.Count == 0) + { + this.freeSuballocationsBySize.Add(item); + } + else + { + this.freeSuballocationsBySize.InsertSorted(item, Helpers.SuballocationNodeItemSizeLess); + } + } + + this.ValidateFreeSuballocationList(); + } + + private void UnregisterFreeSuballocation(LinkedListNode item) + { + Debug.Assert(item.ValueRef.Type == SuballocationType.Free); + Debug.Assert(item.ValueRef.Size > 0); + + if (item.ValueRef.Size >= Helpers.MinFreeSuballocationSizeToRegister) + { + int index = this.freeSuballocationsBySize.BinarySearch_Leftmost(item, Helpers.SuballocationNodeItemSizeLess); + + Debug.Assert(index >= 0); + + while (index < this.freeSuballocationsBySize.Count) + { + var tmp = this.freeSuballocationsBySize[index]; + + if (object.ReferenceEquals(tmp, item)) + { + break; + } + else if (tmp.ValueRef.Size != item.ValueRef.Size) + { + throw new InvalidOperationException("Suballocation Not Found"); + } + + index += 1; + } + + this.freeSuballocationsBySize.RemoveAt(index); + + this.ValidateFreeSuballocationList(); + } + } + + public void CalcAllocationStatInfo(out StatInfo outInfo) + { + outInfo = default; + + outInfo.BlockCount = 1; + + int rangeCount = this.suballocations.Count; + outInfo.AllocationCount = rangeCount - this.freeCount; + outInfo.UnusedRangeCount = this.freeCount; + + outInfo.UnusedBytes = this.sumFreeSize; + outInfo.UsedBytes = this.Size - outInfo.UnusedBytes; + + outInfo.AllocationSizeMin = long.MaxValue; + outInfo.AllocationSizeMax = 0; + outInfo.UnusedRangeSizeMin = long.MaxValue; + outInfo.UnusedRangeSizeMax = 0; + + for (var node = this.suballocations.First; node != null; node = node.Next) + { + ref var item = ref node.ValueRef; + + if (item.Type != SuballocationType.Free) + { + if (item.Size < outInfo.AllocationSizeMin) + outInfo.AllocationSizeMin = item.Size; + + if (item.Size > outInfo.AllocationSizeMax) + outInfo.AllocationSizeMax = item.Size; + } + else + { + if (item.Size < outInfo.UnusedRangeSizeMin) + outInfo.UnusedRangeSizeMin = item.Size; + + if (item.Size > outInfo.UnusedRangeSizeMax) + outInfo.UnusedRangeSizeMax = item.Size; + } + } + } + + public void AddPoolStats(ref PoolStats stats) + { + int rangeCount = this.suballocations.Count; + + stats.Size += this.Size; + + stats.UnusedSize += this.sumFreeSize; + + stats.AllocationCount += rangeCount - this.freeCount; + + stats.UnusedRangeCount += this.freeCount; + + var tmp = this.UnusedRangeSizeMax; + + if (tmp > stats.UnusedRangeSizeMax) + stats.UnusedRangeSizeMax = tmp; + } + + public bool IsBufferImageGranularityConflictPossible(long bufferImageGranularity, ref SuballocationType type) + { + if (bufferImageGranularity == 1 || this.IsEmpty) + { + return false; + } + + long minAlignment = long.MaxValue; + bool typeConflict = false; + + for (var node = this.suballocations.First; node != null; node = node.Next) + { + ref var suballoc = ref node.ValueRef; + + SuballocationType thisType = suballoc.Type; + + if (thisType != SuballocationType.Free) + { + minAlignment = Math.Min(minAlignment, suballoc.Allocation.Alignment); + + if (Helpers.IsBufferImageGranularityConflict(type, thisType)) + { + typeConflict = true; + } + + type = thisType; + } + } + + return typeConflict || minAlignment >= bufferImageGranularity; + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Metadata/IBlockMetadata.cs b/src/Vulkan/VMASharp_OriginalSources/Metadata/IBlockMetadata.cs new file mode 100644 index 0000000000..6926181418 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Metadata/IBlockMetadata.cs @@ -0,0 +1,65 @@ +namespace VMASharp.Metadata +{ + /// + /// Allocation book-keeping for individual Device Memory Blocks. + /// + public interface IBlockMetadata + { + /// + /// Must return the total block size + /// + long Size { get; } + + /// + /// Number of allocations held in this block + /// + int AllocationCount { get; } + + /// + /// Total free bytes in this block + /// + long SumFreeSize { get; } + + /// + /// + /// + long UnusedRangeSizeMax { get; } + + /// + /// Does this block have 0 allocations? + /// + bool IsEmpty { get; } + + /// + /// Validates all data structures inside this object. Throws an exception if validation fails. + /// Only called in Debug builds of VMASharp + /// + void Validate(); + + void CalcAllocationStatInfo(out StatInfo outInfo); + + void AddPoolStats(ref PoolStats stats); + + /// + /// Tries to find a place for suballocation with given parameters inside this block. + /// If succeeded, fills pAllocationRequest and returns true. + /// If failed, returns false. + /// + /// Contextual information for this allocation request + /// + /// Returns whether the method succeeds + bool TryCreateAllocationRequest(in AllocationContext context, out AllocationRequest request); + + bool MakeRequestedAllocationsLost(int currentFrame, int frameInUseCount, ref AllocationRequest request); + + int MakeAllocationsLost(int currentFrame, int frameInUseCount); + + void CheckCorruption(nuint blockDataPointer); + + void Alloc(in AllocationRequest request, SuballocationType type, long allocSize, BlockAllocation allocation); + + void Free(BlockAllocation allocation); + + void FreeAtOffset(long offset); + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Stats.cs b/src/Vulkan/VMASharp_OriginalSources/Stats.cs new file mode 100644 index 0000000000..8711e95aed --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Stats.cs @@ -0,0 +1,109 @@ +using System; +using Silk.NET.Vulkan; + +namespace VMASharp +{ + public struct StatInfo + { + public int BlockCount, AllocationCount, UnusedRangeCount; + public long UsedBytes, UnusedBytes; + public long AllocationSizeMin, AllocationSizeAvg, AllocationSizeMax; + public long UnusedRangeSizeMin, UnusedRangeSizeAvg, UnusedRangeSizeMax; + + internal static void Init(out StatInfo info) + { + info = default; + info.AllocationSizeMin = long.MaxValue; + info.UnusedRangeSizeMin = long.MaxValue; + } + + internal static void Add(ref StatInfo info, in StatInfo other) + { + info.BlockCount += other.BlockCount; + info.AllocationCount += other.AllocationCount; + info.UnusedRangeCount += other.UnusedRangeCount; + info.UsedBytes += other.UsedBytes; + info.UnusedBytes += other.UnusedBytes; + + if (info.AllocationSizeMin > other.AllocationSizeMin) + { + info.AllocationSizeMin = other.AllocationSizeMin; + } + + if (info.AllocationSizeMax < other.AllocationSizeMax) + { + info.AllocationSizeMax = other.AllocationSizeMax; + } + + if (info.UnusedRangeSizeMin > other.UnusedRangeSizeMin) + { + info.UnusedRangeSizeMin = other.UnusedRangeSizeMin; + } + + if (info.UnusedRangeSizeMax < other.UnusedRangeSizeMax) + { + info.UnusedRangeSizeMax = other.UnusedRangeSizeMax; + } + } + + internal static void PostProcessCalcStatInfo(ref StatInfo info) + { + info.AllocationSizeAvg = info.UsedBytes / info.AllocationCount; + info.UnusedRangeSizeAvg = info.UnusedBytes / info.UnusedRangeCount; + } + } + + public class Stats + { + public readonly StatInfo[] MemoryType; + public readonly StatInfo[] MemoryHeap; + public StatInfo Total; + + internal Stats(StatInfo[] memoryTypes, StatInfo[] memoryHeaps, in StatInfo total) + { + MemoryType = memoryTypes; + MemoryHeap = memoryHeaps; + Total = total; + } + + internal Stats() + { + StatInfo.Init(out StatInfo total); + + MemoryType = new StatInfo[Vk.MaxMemoryTypes]; + MemoryHeap = new StatInfo[Vk.MaxMemoryHeaps]; + + for (int i = 0; i < Vk.MaxMemoryTypes; ++i) + StatInfo.Init(out MemoryType[i]); + + for (int i = 0; i < Vk.MaxMemoryHeaps; ++i) + StatInfo.Init(out MemoryHeap[i]); + } + + internal void PostProcess() + { + StatInfo.PostProcessCalcStatInfo(ref this.Total); + + for (int i = 0; i < Vk.MaxMemoryTypes; ++i) + StatInfo.PostProcessCalcStatInfo(ref this.MemoryType[i]); + + for (int i = 0; i < Vk.MaxMemoryHeaps; ++i) + StatInfo.PostProcessCalcStatInfo(ref this.MemoryHeap[i]); + } + } + + public struct PoolStats + { + public long Size; + + public long UnusedSize; + + public int AllocationCount; + + public int UnusedRangeCount; + + public long UnusedRangeSizeMax; + + public int BlockCount; + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/Suballocation.cs b/src/Vulkan/VMASharp_OriginalSources/Suballocation.cs new file mode 100644 index 0000000000..96db4407ec --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/Suballocation.cs @@ -0,0 +1,17 @@ +namespace VMASharp +{ + internal struct Suballocation + { + public long Offset, Size; + public BlockAllocation? Allocation; + public SuballocationType Type; + + public Suballocation(long offset, long size, SuballocationType type = SuballocationType.Free, BlockAllocation? alloc = null) + { + Offset = offset; + Size = size; + Allocation = alloc; + Type = type; + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryAllocator.cs b/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryAllocator.cs new file mode 100644 index 0000000000..7523bfa34f --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryAllocator.cs @@ -0,0 +1,1448 @@ +using Silk.NET.Vulkan; +using Silk.NET.Core; +using Silk.NET.Vulkan.Extensions.KHR; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Numerics; + +using Buffer = Silk.NET.Vulkan.Buffer; + +namespace VMASharp +{ + using Defragmentation; + using VMASharp; + + public sealed unsafe class VulkanMemoryAllocator : IDisposable + { + const long SmallHeapMaxSize = 1024L * 1024 * 1024; + const BufferUsageFlags UnknownBufferUsage = unchecked((BufferUsageFlags)uint.MaxValue); + + internal Vk VkApi { get; } + + public Device Device { get; } + internal readonly Instance Instance; + + internal readonly Version32 VulkanAPIVersion; + + internal readonly bool UseExtMemoryBudget; + internal readonly bool UseAMDDeviceCoherentMemory; + internal readonly bool UseKhrBufferDeviceAddress; + + internal uint HeapSizeLimitMask; + + internal PhysicalDeviceProperties physicalDeviceProperties; + internal PhysicalDeviceMemoryProperties memoryProperties; + + internal readonly BlockList[] BlockLists = new BlockList[Vk.MaxMemoryTypes]; //Default Pools + + internal DedicatedAllocationHandler[] DedicatedAllocations = new DedicatedAllocationHandler[Vk.MaxMemoryTypes]; + + private long PreferredLargeHeapBlockSize; + private PhysicalDevice PhysicalDevice; + private int CurrentFrame; + private uint GPUDefragmentationMemoryTypeBits = uint.MaxValue; + + private readonly ReaderWriterLockSlim PoolsMutex = new ReaderWriterLockSlim(); + private readonly List Pools = new List(); + internal uint NextPoolID; + + internal CurrentBudgetData Budget = new CurrentBudgetData(); + + public VulkanMemoryAllocator(in VulkanMemoryAllocatorCreateInfo createInfo) + { + if (createInfo.VulkanAPIObject == null) + { + throw new ArgumentNullException(nameof(createInfo.VulkanAPIObject), "API vtable is null"); + } + + this.VkApi = createInfo.VulkanAPIObject; + + if (createInfo.Instance.Handle == default) + { + throw new ArgumentNullException("createInfo.Instance"); + } + + if (createInfo.LogicalDevice.Handle == default) + { + throw new ArgumentNullException("createInfo.LogicalDevice"); + } + + if (createInfo.PhysicalDevice.Handle == default) + { + throw new ArgumentNullException("createInfo.PhysicalDevice"); + } + + if (createInfo.VulkanAPIVersion < Vk.Version11) + { + throw new NotSupportedException("Vulkan API Version of less than 1.1 is not supported"); + } + + this.Instance = createInfo.Instance; + this.PhysicalDevice = createInfo.PhysicalDevice; + this.Device = createInfo.LogicalDevice; + + this.VulkanAPIVersion = createInfo.VulkanAPIVersion; + + if (this.VulkanAPIVersion == 0) + { + this.VulkanAPIVersion = Vk.Version10; + } + + this.UseExtMemoryBudget = (createInfo.Flags & AllocatorCreateFlags.ExtMemoryBudget) != 0; + this.UseAMDDeviceCoherentMemory = (createInfo.Flags & AllocatorCreateFlags.AMDDeviceCoherentMemory) != 0; + this.UseKhrBufferDeviceAddress = (createInfo.Flags & AllocatorCreateFlags.BufferDeviceAddress) != 0; + + VkApi.GetPhysicalDeviceProperties(this.PhysicalDevice, out this.physicalDeviceProperties); + VkApi.GetPhysicalDeviceMemoryProperties(this.PhysicalDevice, out this.memoryProperties); + + Debug.Assert(Helpers.IsPow2(Helpers.DebugAlignment)); + Debug.Assert(Helpers.IsPow2(Helpers.DebugMinBufferImageGranularity)); + Debug.Assert(Helpers.IsPow2((long)this.PhysicalDeviceProperties.Limits.BufferImageGranularity)); + Debug.Assert(Helpers.IsPow2((long)this.PhysicalDeviceProperties.Limits.NonCoherentAtomSize)); + + this.PreferredLargeHeapBlockSize = (createInfo.PreferredLargeHeapBlockSize != 0) ? createInfo.PreferredLargeHeapBlockSize : (256L * 1024 * 1024); + + this.GlobalMemoryTypeBits = this.CalculateGlobalMemoryTypeBits(); + + if (createInfo.HeapSizeLimits != null) + { + Span memoryHeaps = MemoryMarshal.CreateSpan(ref this.MemoryHeaps.Element0, this.MemoryHeapCount); + + int heapLimitLength = Math.Min(createInfo.HeapSizeLimits.Length, (int)Vk.MaxMemoryHeaps); + + for (int heapIndex = 0; heapIndex < heapLimitLength; ++heapIndex) + { + long limit = createInfo.HeapSizeLimits[heapIndex]; + + if (limit <= 0) + { + continue; + } + + this.HeapSizeLimitMask |= 1u << heapIndex; + ref MemoryHeap heap = ref memoryHeaps[heapIndex]; + + if ((ulong)limit < heap.Size) + { + heap.Size = (ulong)limit; + } + } + } + + for (int memTypeIndex = 0; memTypeIndex < this.MemoryTypeCount; ++memTypeIndex) + { + long preferredBlockSize = this.CalcPreferredBlockSize(memTypeIndex); + + this.BlockLists[memTypeIndex] = + new BlockList(this, null, memTypeIndex, preferredBlockSize, 0, int.MaxValue, this.BufferImageGranularity, createInfo.FrameInUseCount, false, Helpers.DefaultMetaObjectCreate); + + ref DedicatedAllocationHandler alloc = ref DedicatedAllocations[memTypeIndex]; + + alloc.Allocations = new List(); + alloc.Mutex = new ReaderWriterLockSlim(); + } + + if (UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } + } + + public int CurrentFrameIndex { get; set; } + + internal long BufferImageGranularity + { + get + { + return (long)Math.Max(1, PhysicalDeviceProperties.Limits.BufferImageGranularity); + } + } + + internal int MemoryHeapCount => (int)MemoryProperties.MemoryHeapCount; + + internal int MemoryTypeCount => (int)MemoryProperties.MemoryTypeCount; + + internal bool IsIntegratedGPU + { + get => this.PhysicalDeviceProperties.DeviceType == PhysicalDeviceType.IntegratedGpu; + } + + internal uint GlobalMemoryTypeBits { get; private set; } + + + public void Dispose() + { + if (this.Pools.Count != 0) + { + throw new InvalidOperationException(""); + } + + int i = this.MemoryTypeCount; + + while (i-- != 0) + { + if (this.DedicatedAllocations[i].Allocations.Count != 0) + { + throw new InvalidOperationException("Unfreed dedicatedAllocations found"); + } + + this.BlockLists[i].Dispose(); + } + } + + public ref readonly PhysicalDeviceProperties PhysicalDeviceProperties => ref this.physicalDeviceProperties; + + public ref readonly PhysicalDeviceMemoryProperties MemoryProperties => ref this.memoryProperties; + + public MemoryPropertyFlags GetMemoryTypeProperties(int memoryTypeIndex) + { + return this.MemoryTypes[memoryTypeIndex].PropertyFlags; + } + + public int? FindMemoryTypeIndex(uint memoryTypeBits, in AllocationCreateInfo allocInfo) + { + memoryTypeBits &= this.GlobalMemoryTypeBits; + + if (allocInfo.MemoryTypeBits != 0) + { + memoryTypeBits &= allocInfo.MemoryTypeBits; + } + + MemoryPropertyFlags requiredFlags = allocInfo.RequiredFlags, preferredFlags = allocInfo.PreferredFlags, notPreferredFlags = default; + + switch (allocInfo.Usage) + { + case MemoryUsage.Unknown: + break; + case MemoryUsage.GPU_Only: + if (this.IsIntegratedGPU || (preferredFlags & MemoryPropertyFlags.MemoryPropertyHostVisibleBit) == 0) + { + preferredFlags |= MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; + } + break; + case MemoryUsage.CPU_Only: + requiredFlags |= MemoryPropertyFlags.MemoryPropertyHostVisibleBit | MemoryPropertyFlags.MemoryPropertyHostCoherentBit; + break; + case MemoryUsage.CPU_To_GPU: + requiredFlags |= MemoryPropertyFlags.MemoryPropertyHostVisibleBit; + if (!this.IsIntegratedGPU || (preferredFlags & MemoryPropertyFlags.MemoryPropertyHostVisibleBit) == 0) + { + preferredFlags |= MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; + } + break; + case MemoryUsage.GPU_To_CPU: + requiredFlags |= MemoryPropertyFlags.MemoryPropertyHostVisibleBit; + preferredFlags |= MemoryPropertyFlags.MemoryPropertyHostCachedBit; + break; + case MemoryUsage.CPU_Copy: + notPreferredFlags |= MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; + break; + case MemoryUsage.GPU_LazilyAllocated: + requiredFlags |= MemoryPropertyFlags.MemoryPropertyLazilyAllocatedBit; + break; + default: + throw new ArgumentException("Invalid Usage Flags"); + } + + if (((allocInfo.RequiredFlags | allocInfo.PreferredFlags) & (MemoryPropertyFlags.MemoryPropertyDeviceCoherentBitAmd | MemoryPropertyFlags.MemoryPropertyDeviceUncachedBitAmd)) == 0) + { + notPreferredFlags |= MemoryPropertyFlags.MemoryPropertyDeviceCoherentBitAmd; + } + + int? memoryTypeIndex = null; + int minCost = int.MaxValue; + uint memTypeBit = 1; + + for (int memTypeIndex = 0; memTypeIndex < this.MemoryTypeCount; ++memTypeIndex, memTypeBit <<= 1) + { + if ((memTypeBit & memoryTypeBits) == 0) + continue; + + var currFlags = this.MemoryTypes[memTypeIndex].PropertyFlags; + + if ((requiredFlags & ~currFlags) != 0) + continue; + + int currCost = BitOperations.PopCount((uint)(preferredFlags & ~currFlags)); + + currCost += BitOperations.PopCount((uint)(currFlags & notPreferredFlags)); + + if (currCost < minCost) + { + if (currCost == 0) + { + return memTypeIndex; + } + + memoryTypeIndex = memTypeIndex; + minCost = currCost; + } + } + + return memoryTypeIndex; + } + + public int? FindMemoryTypeIndexForBufferInfo(in BufferCreateInfo bufferInfo, in AllocationCreateInfo allocInfo) + { + Buffer buffer; + fixed (BufferCreateInfo* pBufferInfo = &bufferInfo) + { + var res = VkApi.CreateBuffer(this.Device, pBufferInfo, null, &buffer); + + if (res != Result.Success) + { + throw new VulkanResultException(res); + } + } + + MemoryRequirements memReq; + VkApi.GetBufferMemoryRequirements(this.Device, buffer, &memReq); + + var tmp = this.FindMemoryTypeIndex(memReq.MemoryTypeBits, in allocInfo); + + VkApi.DestroyBuffer(this.Device, buffer, null); + + return tmp; + } + + public int? FindMemoryTypeIndexForImageInfo(in ImageCreateInfo imageInfo, in AllocationCreateInfo allocInfo) + { + Image image; + fixed (ImageCreateInfo* pImageInfo = &imageInfo) + { + var res = VkApi.CreateImage(this.Device, pImageInfo, null, &image); + + if (res != Result.Success) + { + throw new VulkanResultException(res); + } + } + + MemoryRequirements memReq; + VkApi.GetImageMemoryRequirements(this.Device, image, &memReq); + + var tmp = this.FindMemoryTypeIndex(memReq.MemoryTypeBits, in allocInfo); + + VkApi.DestroyImage(this.Device, image, null); + + return tmp; + } + + /// + /// Allocate a block of memory + /// + /// Memory Requirements for the allocation + /// Allocation Creation information + /// An object representing the allocation + public Allocation AllocateMemory(in MemoryRequirements requirements, in AllocationCreateInfo createInfo) + { + DedicatedAllocationInfo dedicatedInfo = DedicatedAllocationInfo.Default; + + return this.AllocateMemory(in requirements, in dedicatedInfo, in createInfo, SuballocationType.Unknown); + } + + /// + /// Allocate a block of memory with the memory requirements of , + /// optionally binding it to the allocation + /// + /// + /// + /// Whether to bind to the allocation + /// + public Allocation AllocateMemoryForBuffer(Buffer buffer, in AllocationCreateInfo createInfo, bool BindToBuffer = false) + { + DedicatedAllocationInfo dedicatedInfo = DedicatedAllocationInfo.Default; + + dedicatedInfo.DedicatedBuffer = buffer; + + this.GetBufferMemoryRequirements(buffer, out MemoryRequirements memReq, out dedicatedInfo.RequiresDedicatedAllocation, out dedicatedInfo.PrefersDedicatedAllocation); + + var alloc = this.AllocateMemory(in memReq, in dedicatedInfo, in createInfo, SuballocationType.Buffer); + + if (BindToBuffer) + { + alloc.BindBufferMemory(buffer); + } + + return alloc; + } + + /// + /// Allocate a block of memory with the memory requirements of , + /// optionally binding it to the allocation + /// + /// + /// + /// Whether to bind to the allocation + /// + public Allocation AllocateMemoryForImage(Image image, in AllocationCreateInfo createInfo, bool BindToImage = false) + { + DedicatedAllocationInfo dedicatedInfo = DedicatedAllocationInfo.Default; + + dedicatedInfo.DedicatedImage = image; + + this.GetImageMemoryRequirements(image, out var memReq, out dedicatedInfo.RequiresDedicatedAllocation, out dedicatedInfo.PrefersDedicatedAllocation); + + var alloc = this.AllocateMemory(in memReq, in dedicatedInfo, in createInfo, SuballocationType.Image_Unknown); + + if (BindToImage) + { + alloc.BindImageMemory(image); + } + + return alloc; + } + + public Result CheckCorruption(uint memoryTypeBits) + { + throw new NotImplementedException(); + } + + public Buffer CreateBuffer(in BufferCreateInfo bufferInfo, in AllocationCreateInfo allocInfo, out Allocation allocation) + { + Result res; + Buffer buffer; + + Allocation? alloc = null; + + fixed (BufferCreateInfo* pInfo = &bufferInfo) + { + res = VkApi.CreateBuffer(this.Device, pInfo, null, &buffer); + + if (res < 0) + { + throw new AllocationException("Buffer creation failed", res); + } + } + + try + { + DedicatedAllocationInfo dedicatedInfo = default; + + dedicatedInfo.DedicatedBuffer = buffer; + dedicatedInfo.DedicatedBufferUsage = bufferInfo.Usage; + + this.GetBufferMemoryRequirements(buffer, out MemoryRequirements memReq, out dedicatedInfo.RequiresDedicatedAllocation, out dedicatedInfo.PrefersDedicatedAllocation); + + alloc = this.AllocateMemory(in memReq, in dedicatedInfo, in allocInfo, SuballocationType.Buffer); + } + catch + { + VkApi.DestroyBuffer(this.Device, buffer, null); + throw; + } + + if ((allocInfo.Flags & AllocationCreateFlags.DontBind) == 0) + { + res = alloc.BindBufferMemory(buffer); + + if (res != Result.Success) + { + VkApi.DestroyBuffer(this.Device, buffer, null); + alloc.Dispose(); + + throw new AllocationException("Unable to bind memory to buffer", res); + } + } + + allocation = alloc; + + return buffer; + } + + /// + /// Create a vulkan image and allocate a block of memory to go with it. + /// Unless specified otherwise with , the Image will be bound to the memory for you. + /// + /// Information to create the image + /// Information to allocate memory for the image + /// The object corresponding to the allocation + /// The created image + /// + public Image CreateImage(in ImageCreateInfo imageInfo, in AllocationCreateInfo allocInfo, out Allocation allocation) + { + if (imageInfo.Extent.Width == 0 || + imageInfo.Extent.Height == 0 || + imageInfo.Extent.Depth == 0 || + imageInfo.MipLevels == 0 || + imageInfo.ArrayLayers == 0) + { + throw new ArgumentException("Invalid Image Info"); + } + + Result res; + Image image; + Allocation alloc; + + fixed (ImageCreateInfo* pInfo = &imageInfo) + { + res = VkApi.CreateImage(this.Device, pInfo, null, &image); + + if (res < 0) + { + throw new AllocationException("Image creation failed", res); + } + } + + SuballocationType suballocType = imageInfo.Tiling == ImageTiling.Optimal ? SuballocationType.Image_Optimal : SuballocationType.Image_Linear; + + try + { + alloc = this.AllocateMemoryForImage(image, allocInfo); + } + catch + { + VkApi.DestroyImage(this.Device, image, null); + throw; + } + + if ((allocInfo.Flags & AllocationCreateFlags.DontBind) == 0) + { + res = alloc.BindImageMemory(image); + + if (res != Result.Success) + { + VkApi.DestroyImage(this.Device, image, null); + alloc.Dispose(); + + throw new AllocationException("Unable to Bind memory to image", res); + } + } + + allocation = alloc; + + return image; + } + + private ref PhysicalDeviceMemoryProperties.MemoryTypesBuffer MemoryTypes + { + get => ref this.memoryProperties.MemoryTypes; + } + + private ref PhysicalDeviceMemoryProperties.MemoryHeapsBuffer MemoryHeaps + { + get => ref this.memoryProperties.MemoryHeaps; + } + + internal int MemoryTypeIndexToHeapIndex(int typeIndex) + { + Debug.Assert(typeIndex < this.MemoryProperties.MemoryTypeCount); + return (int)MemoryTypes[typeIndex].HeapIndex; + } + + internal bool IsMemoryTypeNonCoherent(int memTypeIndex) + { + return (MemoryTypes[memTypeIndex].PropertyFlags & (MemoryPropertyFlags.MemoryPropertyHostVisibleBit | MemoryPropertyFlags.MemoryPropertyHostCoherentBit)) == MemoryPropertyFlags.MemoryPropertyHostVisibleBit; + } + + internal long GetMemoryTypeMinAlignment(int memTypeIndex) + { + return IsMemoryTypeNonCoherent(memTypeIndex) ? (long)Math.Max(1, this.PhysicalDeviceProperties.Limits.NonCoherentAtomSize) : 1; + } + + internal void GetBufferMemoryRequirements(Buffer buffer, out MemoryRequirements memReq, out bool requiresDedicatedAllocation, out bool prefersDedicatedAllocation) + { + var req = new BufferMemoryRequirementsInfo2 + { + SType = StructureType.BufferMemoryRequirementsInfo2, + Buffer = buffer + }; + + var dedicatedRequirements = new MemoryDedicatedRequirements + { + SType = StructureType.MemoryDedicatedRequirements, + }; + + var memReq2 = new MemoryRequirements2 + { + SType = StructureType.MemoryRequirements2, + PNext = &dedicatedRequirements + }; + + VkApi.GetBufferMemoryRequirements2(this.Device, &req, &memReq2); + + memReq = memReq2.MemoryRequirements; + requiresDedicatedAllocation = dedicatedRequirements.RequiresDedicatedAllocation != 0; + prefersDedicatedAllocation = dedicatedRequirements.PrefersDedicatedAllocation != 0; + } + + internal void GetImageMemoryRequirements(Image image, out MemoryRequirements memReq, out bool requiresDedicatedAllocation, out bool prefersDedicatedAllocation) + { + var req = new ImageMemoryRequirementsInfo2 + { + SType = StructureType.ImageMemoryRequirementsInfo2, + Image = image + }; + + var dedicatedRequirements = new MemoryDedicatedRequirements + { + SType = StructureType.MemoryDedicatedRequirements, + }; + + var memReq2 = new MemoryRequirements2 + { + SType = StructureType.MemoryRequirements2, + PNext = &dedicatedRequirements + }; + + VkApi.GetImageMemoryRequirements2(this.Device, &req, &memReq2); + + memReq = memReq2.MemoryRequirements; + requiresDedicatedAllocation = dedicatedRequirements.RequiresDedicatedAllocation != 0; + prefersDedicatedAllocation = dedicatedRequirements.PrefersDedicatedAllocation != 0; + } + + internal Allocation AllocateMemory(in MemoryRequirements memReq, in DedicatedAllocationInfo dedicatedInfo, + in AllocationCreateInfo createInfo, SuballocationType suballocType) + { + Debug.Assert(Helpers.IsPow2((long)memReq.Alignment)); + + if (memReq.Size == 0) + throw new ArgumentException("Allocation size cannot be 0"); + + const AllocationCreateFlags CheckFlags1 = AllocationCreateFlags.DedicatedMemory | AllocationCreateFlags.NeverAllocate; + const AllocationCreateFlags CheckFlags2 = AllocationCreateFlags.Mapped | AllocationCreateFlags.CanBecomeLost; + + if ((createInfo.Flags & CheckFlags1) == CheckFlags1) + { + throw new ArgumentException("Specifying AllocationCreateFlags.DedicatedMemory with AllocationCreateFlags.NeverAllocate is invalid"); + } + else if ((createInfo.Flags & CheckFlags2) == CheckFlags2) + { + throw new ArgumentException("Specifying AllocationCreateFlags.Mapped with AllocationCreateFlags.CanBecomeLost is invalid"); + } + + if (dedicatedInfo.RequiresDedicatedAllocation) + { + if ((createInfo.Flags & AllocationCreateFlags.NeverAllocate) != 0) + { + throw new AllocationException("AllocationCreateFlags.NeverAllocate specified while dedicated allocation required", Result.ErrorOutOfDeviceMemory); + } + + if (createInfo.Pool != null) + { + throw new ArgumentException("Pool specified while dedicated allocation required"); + } + } + + if (createInfo.Pool != null && (createInfo.Flags & AllocationCreateFlags.DedicatedMemory) != 0) + { + throw new ArgumentException("Specified AllocationCreateFlags.DedicatedMemory when createInfo.Pool is not null"); + } + + if (createInfo.Pool != null) + { + int memoryTypeIndex = createInfo.Pool.BlockList.MemoryTypeIndex; + long alignmentForPool = Math.Max((long)memReq.Alignment, this.GetMemoryTypeMinAlignment(memoryTypeIndex)); + + AllocationCreateInfo infoForPool = createInfo; + + if ((createInfo.Flags & AllocationCreateFlags.Mapped) != 0 && (this.MemoryTypes[memoryTypeIndex].PropertyFlags & MemoryPropertyFlags.MemoryPropertyHostVisibleBit) == 0) + { + infoForPool.Flags &= ~AllocationCreateFlags.Mapped; + } + + return createInfo.Pool.BlockList.Allocate(this.CurrentFrameIndex, (long)memReq.Size, alignmentForPool, infoForPool, suballocType); + } + else + { + uint memoryTypeBits = memReq.MemoryTypeBits; + var typeIndex = this.FindMemoryTypeIndex(memoryTypeBits, createInfo); + + if (typeIndex == null) + { + throw new AllocationException("Unable to find suitable memory type for allocation", Result.ErrorFeatureNotPresent); + } + + long alignmentForType = Math.Max((long)memReq.Alignment, this.GetMemoryTypeMinAlignment((int)typeIndex)); + + return this.AllocateMemoryOfType((long)memReq.Size, alignmentForType, in dedicatedInfo, in createInfo, (int)typeIndex, suballocType); + } + } + + public void FreeMemory(Allocation allocation) + { + if (allocation is null) + { + throw new ArgumentNullException(nameof(allocation)); + } + + if (allocation.TouchAllocation()) + { + if (allocation is BlockAllocation blockAlloc) + { + BlockList list; + var pool = blockAlloc.Block.ParentPool; + + if (pool != null) + { + list = pool.BlockList; + } + else + { + list = this.BlockLists[allocation.MemoryTypeIndex]; + Debug.Assert(list != null); + } + + list.Free(allocation); + } + else + { + var dedicated = allocation as DedicatedAllocation; + + Debug.Assert(dedicated != null); + + FreeDedicatedMemory(dedicated); + } + } + + this.Budget.RemoveAllocation(this.MemoryTypeIndexToHeapIndex(allocation.MemoryTypeIndex), allocation.Size); + } + + public Stats CalculateStats() + { + var newStats = new Stats(); + + for (int i = 0; i < this.MemoryTypeCount; ++i) + { + var list = this.BlockLists[i]; + + Debug.Assert(list != null); + + list.AddStats(newStats); + } + + this.PoolsMutex.EnterReadLock(); + try + { + foreach (var pool in this.Pools) + { + pool.BlockList.AddStats(newStats); + } + } + finally + { + this.PoolsMutex.ExitReadLock(); + } + + for (int typeIndex = 0; typeIndex < this.MemoryTypeCount; ++typeIndex) + { + int heapIndex = this.MemoryTypeIndexToHeapIndex(typeIndex); + + ref DedicatedAllocationHandler handler = ref DedicatedAllocations[typeIndex]; + + handler.Mutex.EnterReadLock(); + + try + { + foreach (var alloc in handler.Allocations) + { + ((DedicatedAllocation)alloc).CalcStatsInfo(out var stat); + + StatInfo.Add(ref newStats.Total, stat); + StatInfo.Add(ref newStats.MemoryType[typeIndex], stat); + StatInfo.Add(ref newStats.MemoryHeap[heapIndex], stat); + } + } + finally + { + handler.Mutex.ExitReadLock(); + } + } + + newStats.PostProcess(); + + return newStats; + } + + internal void GetBudget(int heapIndex, out AllocationBudget outBudget) + { + Unsafe.SkipInit(out outBudget); + + if ((uint)heapIndex >= Vk.MaxMemoryHeaps) + { + throw new ArgumentOutOfRangeException(nameof(heapIndex)); + } + + if (this.UseExtMemoryBudget) + { + //Reworked to remove recursion + if (this.Budget.OperationsSinceBudgetFetch >= 30) + { + this.UpdateVulkanBudget(); //Outside of mutex lock + } + + this.Budget.BudgetMutex.EnterReadLock(); + try + { + ref var heapBudget = ref this.Budget.BudgetData[heapIndex]; + + outBudget.BlockBytes = heapBudget.BlockBytes; + outBudget.AllocationBytes = heapBudget.AllocationBytes; + + if (heapBudget.VulkanUsage + outBudget.BlockBytes > heapBudget.BlockBytesAtBudgetFetch) + { + outBudget.Usage = heapBudget.VulkanUsage + outBudget.BlockBytes - heapBudget.BlockBytesAtBudgetFetch; + } + else + { + outBudget.Usage = 0; + } + + outBudget.Budget = Math.Min(heapBudget.VulkanBudget, (long)this.MemoryHeaps[heapIndex].Size); + } + finally + { + this.Budget.BudgetMutex.ExitReadLock(); + } + } + else + { + ref var heapBudget = ref this.Budget.BudgetData[heapIndex]; + + outBudget.BlockBytes = heapBudget.BlockBytes; + outBudget.AllocationBytes = heapBudget.AllocationBytes; + + outBudget.Usage = heapBudget.BlockBytes; + outBudget.Budget = (long)(this.MemoryHeaps[heapIndex].Size * 8 / 10); //80% heuristics + } + } + + internal void GetBudget(int firstHeap, AllocationBudget[] outBudgets) + { + Debug.Assert(outBudgets != null && outBudgets.Length != 0); + Array.Clear(outBudgets, 0, outBudgets.Length); + + if ((uint)(outBudgets.Length + firstHeap) >= Vk.MaxMemoryHeaps) + { + throw new ArgumentOutOfRangeException(); + } + + if (this.UseExtMemoryBudget) + { + //Reworked to remove recursion + if (this.Budget.OperationsSinceBudgetFetch >= 30) + { + this.UpdateVulkanBudget(); //Outside of mutex lock + } + + this.Budget.BudgetMutex.EnterReadLock(); + try + { + for (int i = 0; i < outBudgets.Length; ++i) + { + int heapIndex = i + firstHeap; + ref AllocationBudget outBudget = ref outBudgets[i]; + + ref var heapBudget = ref this.Budget.BudgetData[heapIndex]; + + outBudget.BlockBytes = heapBudget.BlockBytes; + outBudget.AllocationBytes = heapBudget.AllocationBytes; + + if (heapBudget.VulkanUsage + outBudget.BlockBytes > heapBudget.BlockBytesAtBudgetFetch) + { + outBudget.Usage = heapBudget.VulkanUsage + outBudget.BlockBytes - heapBudget.BlockBytesAtBudgetFetch; + } + else + { + outBudget.Usage = 0; + } + + outBudget.Budget = Math.Min(heapBudget.VulkanBudget, (long)this.MemoryHeaps[heapIndex].Size); + } + } + finally + { + this.Budget.BudgetMutex.ExitReadLock(); + } + } + else + { + for (int i = 0; i < outBudgets.Length; ++i) + { + int heapIndex = i + firstHeap; + ref AllocationBudget outBudget = ref outBudgets[i]; + ref var heapBudget = ref this.Budget.BudgetData[heapIndex]; + + outBudget.BlockBytes = heapBudget.BlockBytes; + outBudget.AllocationBytes = heapBudget.AllocationBytes; + + outBudget.Usage = heapBudget.BlockBytes; + outBudget.Budget = (long)(this.MemoryHeaps[heapIndex].Size * 8 / 10); //80% heuristics + } + } + } + + internal Result DefragmentationBegin(in DefragmentationInfo2 info, DefragmentationStats stats, DefragmentationContext context) + { + throw new NotImplementedException(); + } + + internal Result DefragmentationEnd(DefragmentationContext context) + { + throw new NotImplementedException(); + } + + internal Result DefragmentationPassBegin(ref DefragmentationPassMoveInfo[] passInfo, DefragmentationContext context) + { + throw new NotImplementedException(); + } + + internal Result DefragmentationPassEnd(DefragmentationContext context) + { + throw new NotImplementedException(); + } + + public VulkanMemoryPool CreatePool(in AllocationPoolCreateInfo createInfo) + { + var tmpCreateInfo = createInfo; + + if (tmpCreateInfo.MaxBlockCount == 0) + { + tmpCreateInfo.MaxBlockCount = int.MaxValue; + } + + if (tmpCreateInfo.MinBlockCount > tmpCreateInfo.MaxBlockCount) + { + throw new ArgumentException("Min block count is higher than max block count"); + } + + if (tmpCreateInfo.MemoryTypeIndex >= this.MemoryTypeCount || ((1u << tmpCreateInfo.MemoryTypeIndex) & this.GlobalMemoryTypeBits) == 0) + { + throw new ArgumentException("Invalid memory type index"); + } + + long preferredBlockSize = this.CalcPreferredBlockSize(tmpCreateInfo.MemoryTypeIndex); + + var pool = new VulkanMemoryPool(this, tmpCreateInfo, preferredBlockSize); + + this.PoolsMutex.EnterWriteLock(); + try + { + this.Pools.Add(pool); + } + finally + { + this.PoolsMutex.ExitWriteLock(); + } + + return pool; + } + + internal void DestroyPool(VulkanMemoryPool pool) + { + this.PoolsMutex.EnterWriteLock(); + try + { + bool success = this.Pools.Remove(pool); + Debug.Assert(success, "Pool not found in allocator"); + } + finally + { + this.PoolsMutex.ExitWriteLock(); + } + } + + internal void GetPoolStats(VulkanMemoryPool pool, out PoolStats stats) + { + pool.BlockList.GetPoolStats(out stats); + } + + internal int MakePoolAllocationsLost(VulkanMemoryPool pool) + { + return pool.BlockList.MakePoolAllocationsLost(this.CurrentFrame); + } + + internal Result CheckPoolCorruption(VulkanMemoryPool pool) + { + throw new NotImplementedException(); + } + + internal Allocation CreateLostAllocation() + { + throw new NotImplementedException(); + } + + internal Result AllocateVulkanMemory(in MemoryAllocateInfo allocInfo, out DeviceMemory memory) + { + int heapIndex = this.MemoryTypeIndexToHeapIndex((int)allocInfo.MemoryTypeIndex); + ref var budgetData = ref this.Budget.BudgetData[heapIndex]; + + if ((this.HeapSizeLimitMask & (1u << heapIndex)) != 0) + { + long heapSize, blockBytes, blockBytesAfterAlloc; + + heapSize = (long)this.MemoryHeaps[heapIndex].Size; + + do + { + blockBytes = budgetData.BlockBytes; + blockBytesAfterAlloc = blockBytes + (long)allocInfo.AllocationSize; + + if (blockBytesAfterAlloc > heapSize) + { + throw new AllocationException("Budget limit reached for heap index " + heapIndex, Result.ErrorOutOfDeviceMemory); + } + } + while (Interlocked.CompareExchange(ref budgetData.BlockBytes, blockBytesAfterAlloc, blockBytes) != blockBytes); + } + else + { + Interlocked.Add(ref budgetData.BlockBytes, (long)allocInfo.AllocationSize); + } + + fixed (MemoryAllocateInfo* pInfo = &allocInfo) + fixed (DeviceMemory* pMemory = &memory) + { + var res = VkApi.AllocateMemory(this.Device, pInfo, null, pMemory); + + if (res == Result.Success) + { + Interlocked.Increment(ref this.Budget.OperationsSinceBudgetFetch); + } + else + { + Interlocked.Add(ref budgetData.BlockBytes, -(long)allocInfo.AllocationSize); + } + + return res; + } + } + + internal void FreeVulkanMemory(int memoryType, long size, DeviceMemory memory) + { + VkApi.FreeMemory(this.Device, memory, null); + + Interlocked.Add(ref this.Budget.BudgetData[this.MemoryTypeIndexToHeapIndex(memoryType)].BlockBytes, -size); + } + + internal Result BindVulkanBuffer(Buffer buffer, DeviceMemory memory, long offset, void* pNext) + { + if (pNext != null) + { + var info = new BindBufferMemoryInfo(pNext: pNext, buffer: buffer, memory: memory, memoryOffset: (ulong)offset); + + return VkApi.BindBufferMemory2(this.Device, 1, &info); + } + else + { + return VkApi.BindBufferMemory(this.Device, buffer, memory, (ulong)offset); + } + } + + internal Result BindVulkanImage(Image image, DeviceMemory memory, long offset, void* pNext) + { + if (pNext != default) + { + var info = new BindImageMemoryInfo + { + SType = StructureType.BindBufferMemoryInfo, + PNext = pNext, + Image = image, + Memory = memory, + MemoryOffset = (ulong)offset + }; + + return VkApi.BindImageMemory2(this.Device, 1, &info); + } + else + { + return VkApi.BindImageMemory(this.Device, image, memory, (ulong)offset); + } + } + + internal void FillAllocation(Allocation allocation, byte pattern) + { + if (Helpers.DebugInitializeAllocations && !allocation.CanBecomeLost && + (this.MemoryTypes[allocation.MemoryTypeIndex].PropertyFlags & MemoryPropertyFlags.MemoryPropertyHostVisibleBit) != 0) + { + IntPtr pData = allocation.Map(); + + Unsafe.InitBlockUnaligned(ref *(byte*)pData, pattern, (uint)allocation.Size); + + this.FlushOrInvalidateAllocation(allocation, 0, long.MaxValue, CacheOperation.Flush); + + allocation.Unmap(); + } + } + + internal uint GetGPUDefragmentationMemoryTypeBits() + { + uint memTypeBits = this.GPUDefragmentationMemoryTypeBits; + if (memTypeBits == uint.MaxValue) + { + memTypeBits = this.CalculateGpuDefragmentationMemoryTypeBits(); + this.GPUDefragmentationMemoryTypeBits = memTypeBits; + } + return memTypeBits; + } + + private long CalcPreferredBlockSize(int memTypeIndex) + { + var heapIndex = this.MemoryTypeIndexToHeapIndex(memTypeIndex); + + Debug.Assert((uint)heapIndex < Vk.MaxMemoryHeaps); + + var heapSize = (long)MemoryHeaps[heapIndex].Size; + + return Helpers.AlignUp(heapSize <= SmallHeapMaxSize ? (heapSize / 8) : this.PreferredLargeHeapBlockSize, 32); + } + + private Allocation AllocateMemoryOfType(long size, long alignment, in DedicatedAllocationInfo dedicatedInfo, in AllocationCreateInfo createInfo, + int memoryTypeIndex, SuballocationType suballocType) + { + var finalCreateInfo = createInfo; + + if ((finalCreateInfo.Flags & AllocationCreateFlags.Mapped) != 0 && (this.MemoryTypes[memoryTypeIndex].PropertyFlags & MemoryPropertyFlags.MemoryPropertyHostVisibleBit) == 0) + { + finalCreateInfo.Flags &= ~AllocationCreateFlags.Mapped; + } + + if (finalCreateInfo.Usage == MemoryUsage.GPU_LazilyAllocated) + { + finalCreateInfo.Flags |= AllocationCreateFlags.DedicatedMemory; + } + + var blockList = this.BlockLists[memoryTypeIndex]; + + long preferredBlockSize = blockList.PreferredBlockSize; + bool preferDedicatedMemory = (dedicatedInfo.RequiresDedicatedAllocation | dedicatedInfo.PrefersDedicatedAllocation) || size > preferredBlockSize / 2; + + if (preferDedicatedMemory && (finalCreateInfo.Flags & AllocationCreateFlags.NeverAllocate) == 0 && finalCreateInfo.Pool == null) + { + finalCreateInfo.Flags |= AllocationCreateFlags.DedicatedMemory; + } + + Exception? blockAllocException = null; + + if ((finalCreateInfo.Flags & AllocationCreateFlags.DedicatedMemory) == 0) + { + try + { + return blockList.Allocate(this.CurrentFrameIndex, size, alignment, finalCreateInfo, suballocType); + } + catch (Exception e) + { + blockAllocException = e; + } + } + + //Try a dedicated allocation if a block allocation failed, or if specified as a dedicated allocation + if ((finalCreateInfo.Flags & AllocationCreateFlags.NeverAllocate) != 0) + { + throw new AllocationException("Block List allocation failed, and `AllocationCreateFlags.NeverAllocate` specified", blockAllocException); + } + + return this.AllocateDedicatedMemory(size, suballocType, memoryTypeIndex, + (finalCreateInfo.Flags & AllocationCreateFlags.WithinBudget) != 0, + (finalCreateInfo.Flags & AllocationCreateFlags.Mapped) != 0, + finalCreateInfo.UserData, in dedicatedInfo); + } + + private Allocation AllocateDedicatedMemoryPage( + long size, SuballocationType suballocType, int memTypeIndex, in MemoryAllocateInfo allocInfo, bool map, + object? userData) + { + var res = this.AllocateVulkanMemory(in allocInfo, out var memory); + + if (res < 0) + { + throw new AllocationException("Dedicated memory allocation Failed", res); + } + + IntPtr mappedData = default; + if (map) + { + res = VkApi.MapMemory(this.Device, memory, 0, Vk.WholeSize, 0, (void**)&mappedData); + + if (res < 0) + { + this.FreeVulkanMemory(memTypeIndex, size, memory); + + throw new AllocationException("Unable to map dedicated allocation", res); + } + } + + var allocation = new DedicatedAllocation(this, memTypeIndex, memory, suballocType, mappedData, size) + { + UserData = userData + }; + + this.Budget.AddAllocation(this.MemoryTypeIndexToHeapIndex(memTypeIndex), size); + + this.FillAllocation(allocation, Helpers.AllocationFillPattern_Created); + + return allocation; + } + + private Allocation AllocateDedicatedMemory(long size, SuballocationType suballocType, int memTypeIndex, bool withinBudget, bool map, object? userData, in DedicatedAllocationInfo dedicatedInfo) + { + int heapIndex = this.MemoryTypeIndexToHeapIndex(memTypeIndex); + + if (withinBudget) + { + this.GetBudget(heapIndex, out var budget); + if (budget.Usage + size > budget.Budget) + { + throw new AllocationException("Memory Budget limit reached for heap index " + heapIndex, Result.ErrorOutOfDeviceMemory); + } + } + + MemoryAllocateInfo allocInfo = new MemoryAllocateInfo + { + SType = StructureType.MemoryAllocateInfo, + MemoryTypeIndex = (uint)memTypeIndex, + AllocationSize = (ulong)size + }; + + Debug.Assert(!(dedicatedInfo.DedicatedBuffer.Handle != default && dedicatedInfo.DedicatedImage.Handle != default), "dedicated buffer and dedicated image were both specified"); + + var dedicatedAllocInfo = new MemoryDedicatedAllocateInfo(StructureType.MemoryDedicatedAllocateInfo); + + if (dedicatedInfo.DedicatedBuffer.Handle != default) + { + dedicatedAllocInfo.Buffer = dedicatedInfo.DedicatedBuffer; + allocInfo.PNext = &dedicatedAllocInfo; + } + else if (dedicatedInfo.DedicatedImage.Handle != default) + { + dedicatedAllocInfo.Image = dedicatedInfo.DedicatedImage; + allocInfo.PNext = &dedicatedAllocInfo; + } + + var allocFlagsInfo = new MemoryAllocateFlagsInfoKHR(StructureType.MemoryAllocateFlagsInfoKhr); + if (UseKhrBufferDeviceAddress) + { + bool canContainBufferWithDeviceAddress = true; + + if (dedicatedInfo.DedicatedBuffer.Handle != default) + { + canContainBufferWithDeviceAddress = dedicatedInfo.DedicatedBufferUsage == UnknownBufferUsage + || (dedicatedInfo.DedicatedBufferUsage & BufferUsageFlags.BufferUsageShaderDeviceAddressBitKhr) != 0; + } + else if (dedicatedInfo.DedicatedImage.Handle != default) + { + canContainBufferWithDeviceAddress = false; + } + + if (canContainBufferWithDeviceAddress) + { + allocFlagsInfo.Flags = MemoryAllocateFlags.MemoryAllocateDeviceAddressBit; + allocFlagsInfo.PNext = allocInfo.PNext; + allocInfo.PNext = &allocFlagsInfo; + } + } + + var alloc = this.AllocateDedicatedMemoryPage(size, suballocType, memTypeIndex, in allocInfo, map, userData); + + //Register made allocations + ref DedicatedAllocationHandler handler = ref this.DedicatedAllocations[memTypeIndex]; + + handler.Mutex.EnterWriteLock(); + try + { + handler.Allocations.InsertSorted(alloc, (alloc1, alloc2) => alloc1.Offset.CompareTo(alloc2.Offset)); + } + finally + { + handler.Mutex.ExitWriteLock(); + } + + return alloc; + } + + private void FreeDedicatedMemory(DedicatedAllocation allocation) + { + ref DedicatedAllocationHandler handler = ref this.DedicatedAllocations[allocation.MemoryTypeIndex]; + + handler.Mutex.EnterWriteLock(); + + try + { + bool success = handler.Allocations.Remove(allocation); + + Debug.Assert(success); + } + finally + { + handler.Mutex.ExitWriteLock(); + } + + FreeVulkanMemory(allocation.MemoryTypeIndex, allocation.Size, allocation.DeviceMemory); + } + + private uint CalculateGpuDefragmentationMemoryTypeBits() + { + throw new NotImplementedException(); + } + + private const uint AMDVendorID = 0x1002; + + private uint CalculateGlobalMemoryTypeBits() + { + Debug.Assert(this.MemoryTypeCount > 0); + + uint memoryTypeBits = uint.MaxValue; + + if (this.PhysicalDeviceProperties.VendorID == AMDVendorID && !this.UseAMDDeviceCoherentMemory) + { + // Exclude memory types that have VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD. + for (int index = 0; index < this.MemoryTypeCount; ++index) + { + if ((this.MemoryTypes[index].PropertyFlags & MemoryPropertyFlags.MemoryPropertyDeviceCoherentBitAmd) != 0) + { + memoryTypeBits &= ~(1u << index); + } + } + } + + return memoryTypeBits; + } + + private void UpdateVulkanBudget() + { + Debug.Assert(UseExtMemoryBudget); + + PhysicalDeviceMemoryBudgetPropertiesEXT budgetProps = new PhysicalDeviceMemoryBudgetPropertiesEXT(StructureType.PhysicalDeviceMemoryBudgetPropertiesExt); + + PhysicalDeviceMemoryProperties2 memProps = new PhysicalDeviceMemoryProperties2(StructureType.PhysicalDeviceMemoryProperties2, &budgetProps); + + VkApi.GetPhysicalDeviceMemoryProperties2(this.PhysicalDevice, &memProps); + + Budget.BudgetMutex.EnterWriteLock(); + try + { + for (int i = 0; i < MemoryHeapCount; ++i) + { + ref var data = ref Budget.BudgetData[i]; + + data.VulkanUsage = (long)budgetProps.HeapUsage[i]; + data.VulkanBudget = (long)budgetProps.HeapBudget[i]; + + data.BlockBytesAtBudgetFetch = data.BlockBytes; + + // Some bugged drivers return the budget incorrectly, e.g. 0 or much bigger than heap size. + + ref var heap = ref this.MemoryHeaps[i]; + + if (data.VulkanBudget == 0) + { + data.VulkanBudget = (long)(heap.Size * 8 / 10); + } + else if ((ulong)data.VulkanBudget > heap.Size) + { + data.VulkanBudget = (long)heap.Size; + } + + if (data.VulkanUsage == 0 && data.BlockBytesAtBudgetFetch > 0) + { + data.VulkanUsage = data.BlockBytesAtBudgetFetch; + } + } + + Budget.OperationsSinceBudgetFetch = 0; + } + finally + { + Budget.BudgetMutex.ExitWriteLock(); + } + } + + internal Result FlushOrInvalidateAllocation(Allocation allocation, long offset, long size, CacheOperation op) + { + int memTypeIndex = allocation.MemoryTypeIndex; + if (size > 0 && this.IsMemoryTypeNonCoherent(memTypeIndex)) + { + long allocSize = allocation.Size; + + Debug.Assert((ulong)offset <= (ulong)allocSize); + + var nonCoherentAtomSize = (long)this.physicalDeviceProperties.Limits.NonCoherentAtomSize; + + MappedMemoryRange memRange = new MappedMemoryRange(memory: allocation.DeviceMemory); + + if (allocation is BlockAllocation blockAlloc) + { + memRange.Offset = (ulong)Helpers.AlignDown(offset, nonCoherentAtomSize); + + if (size == long.MaxValue) + { + size = allocSize - offset; + } + else + { + Debug.Assert(offset + size <= allocSize); + } + + memRange.Size = (ulong)Helpers.AlignUp(size + (offset - (long)memRange.Offset), nonCoherentAtomSize); + + long allocOffset = blockAlloc.Offset; + + Debug.Assert(allocOffset % nonCoherentAtomSize == 0); + + long blockSize = blockAlloc.Block.MetaData.Size; + + memRange.Offset += (ulong)allocOffset; + memRange.Size = Math.Min(memRange.Size, (ulong)blockSize - memRange.Offset); + } + else if (allocation is DedicatedAllocation) + { + memRange.Offset = (ulong)Helpers.AlignDown(offset, nonCoherentAtomSize); + + if (size == long.MaxValue) + { + memRange.Size = (ulong)allocSize - memRange.Offset; + } + else + { + Debug.Assert(offset + size <= allocSize); + + memRange.Size = (ulong)Helpers.AlignUp(size + (offset - (long)memRange.Offset), nonCoherentAtomSize); + } + } + else + { + Debug.Assert(false); + throw new ArgumentException("allocation type is not BlockAllocation or DedicatedAllocation"); + } + + switch (op) + { + case CacheOperation.Flush: + return VkApi.FlushMappedMemoryRanges(this.Device, 1, &memRange); + case CacheOperation.Invalidate: + return VkApi.InvalidateMappedMemoryRanges(this.Device, 1, &memRange); + default: + Debug.Assert(false); + throw new ArgumentException("Invalid Cache Operation value", nameof(op)); + } + } + + return Result.Success; + } + + internal struct DedicatedAllocationHandler + { + public List Allocations; + public ReaderWriterLockSlim Mutex; + } + + internal struct DedicatedAllocationInfo + { + public Buffer DedicatedBuffer; + public Image DedicatedImage; + public BufferUsageFlags DedicatedBufferUsage; //uint.MaxValue when unknown + public bool RequiresDedicatedAllocation; + public bool PrefersDedicatedAllocation; + + public static readonly DedicatedAllocationInfo Default = new DedicatedAllocationInfo() + { + DedicatedBufferUsage = unchecked((BufferUsageFlags)uint.MaxValue) + }; + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryBlock.cs b/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryBlock.cs new file mode 100644 index 0000000000..a224d6d605 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryBlock.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using Silk.NET.Vulkan; +using Buffer = Silk.NET.Vulkan.Buffer; + +namespace VMASharp +{ + using Metadata; + + internal class VulkanMemoryBlock : IDisposable + { + private Vk VkApi => Allocator.VkApi; + + private readonly VulkanMemoryAllocator Allocator; + internal readonly IBlockMetadata MetaData; + private readonly object SyncLock = new object(); + private int mapCount; + + + public VulkanMemoryBlock(VulkanMemoryAllocator allocator, VulkanMemoryPool? pool, int memoryTypeIndex, DeviceMemory memory, uint id, IBlockMetadata metaObject) + { + Allocator = allocator; + ParentPool = pool; + MemoryTypeIndex = memoryTypeIndex; + DeviceMemory = memory; + ID = id; + + MetaData = metaObject; + } + + public VulkanMemoryPool? ParentPool { get; } + + public DeviceMemory DeviceMemory { get; } + + public int MemoryTypeIndex { get; } + + public uint ID { get; } + + public IntPtr MappedData { get; private set; } + + public void Dispose() + { + if (!this.MetaData.IsEmpty) + { + throw new InvalidOperationException("Some allocations were not freed before destruction of this memory block!"); + } + + Debug.Assert(this.DeviceMemory.Handle != default); + + this.Allocator.FreeVulkanMemory(this.MemoryTypeIndex, this.MetaData.Size, this.DeviceMemory); + } + + [Conditional("DEBUG")] + public void Validate() + { + Helpers.Validate(this.DeviceMemory.Handle != default && this.MetaData.Size > 0); + + MetaData.Validate(); + } + + public void CheckCorruption(VulkanMemoryAllocator allocator) + { + var data = this.Map(1); + + try + { + this.MetaData.CheckCorruption((nuint)(nint)data); + } + finally + { + this.Unmap(1); + } + } + + public unsafe IntPtr Map(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + lock (this.SyncLock) + { + Debug.Assert(this.mapCount >= 0); + + if (this.mapCount > 0) + { + Debug.Assert(this.MappedData != default); + + this.mapCount += count; + return this.MappedData; + } + else + { + if (count == 0) + { + return default; + } + + IntPtr pData; + var res = VkApi.MapMemory(this.Allocator.Device, this.DeviceMemory, 0, Vk.WholeSize, 0, (void**)&pData); + + if (res != Result.Success) + { + throw new MapMemoryException(res); + } + + this.mapCount = count; + this.MappedData = pData; + + return pData; + } + } + } + + public void Unmap(int count) + { + if (count == 0) + { + return; + } + + lock (this.SyncLock) + { + int newCount = this.mapCount - count; + + if (newCount < 0) + { + throw new InvalidOperationException("Memory block is being unmapped while it was not previously mapped"); + } + + this.mapCount = newCount; + + if (newCount == 0) + { + this.MappedData = default; + VkApi.UnmapMemory(this.Allocator.Device, this.DeviceMemory); + } + } + } + + public unsafe Result BindBufferMemory(Allocation allocation, long allocationLocalOffset, Buffer buffer, void* pNext) + { + Debug.Assert(allocation is BlockAllocation blockAlloc && blockAlloc.Block == this); + + Debug.Assert((ulong)allocationLocalOffset < (ulong)allocation.Size, "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + + long memoryOffset = allocationLocalOffset + allocation.Offset; + + lock (SyncLock) + { + return this.Allocator.BindVulkanBuffer(buffer, this.DeviceMemory, memoryOffset, pNext); + } + } + + public unsafe Result BindImageMemory(Allocation allocation, long allocationLocalOffset, Image image, void* pNext) + { + Debug.Assert(allocation is BlockAllocation blockAlloc && blockAlloc.Block == this); + + Debug.Assert((ulong)allocationLocalOffset < (ulong)allocation.Size, "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + + long memoryOffset = allocationLocalOffset + allocation.Offset; + + lock (this.SyncLock) + { + return this.Allocator.BindVulkanImage(image, this.DeviceMemory, memoryOffset, pNext); + } + } + } +} diff --git a/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryPool.cs b/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryPool.cs new file mode 100644 index 0000000000..ee7556c589 --- /dev/null +++ b/src/Vulkan/VMASharp_OriginalSources/VulkanMemoryPool.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Runtime.CompilerServices; + +using Silk.NET.Vulkan; +using VMASharp; + +namespace VMASharp +{ + public sealed class VulkanMemoryPool : IDisposable + { + public VulkanMemoryAllocator Allocator { get; } + + private Vk VkApi => Allocator.VkApi; + + + public string Name { get; set; } + + internal uint ID { get; } + + internal readonly BlockList BlockList; + + internal VulkanMemoryPool(VulkanMemoryAllocator allocator, in AllocationPoolCreateInfo poolInfo, long preferredBlockSize) + { + if (allocator is null) + { + throw new ArgumentNullException(nameof(allocator)); + } + + this.Allocator = allocator; + + ref int tmpRef = ref Unsafe.As(ref allocator.NextPoolID); + + this.ID = (uint)Interlocked.Increment(ref tmpRef); + + if (this.ID == 0) + throw new OverflowException(); + + this.BlockList = new BlockList( + allocator, + this, + poolInfo.MemoryTypeIndex, + poolInfo.BlockSize != 0 ? poolInfo.BlockSize : preferredBlockSize, + poolInfo.MinBlockCount, + poolInfo.MaxBlockCount, + (poolInfo.Flags & PoolCreateFlags.IgnoreBufferImageGranularity) != 0 ? 1 : allocator.BufferImageGranularity, + poolInfo.FrameInUseCount, + poolInfo.BlockSize != 0, + poolInfo.AllocationAlgorithmCreate ?? Helpers.DefaultMetaObjectCreate); + + this.BlockList.CreateMinBlocks(); + } + + public void Dispose() + { + Allocator.DestroyPool(this); + } + + public int MakeAllocationsLost() + { + return Allocator.MakePoolAllocationsLost(this); + } + + public Result CheckForCorruption() + { + return Allocator.CheckPoolCorruption(this); + } + + public void GetPoolStats(out PoolStats stats) + { + Allocator.GetPoolStats(this, out stats); + } + } +}