You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
9.9 KiB
272 lines
9.9 KiB
using System;
|
|
using Unity.Collections;
|
|
using Unity.Mathematics;
|
|
using UnityEngine.Assertions;
|
|
|
|
namespace UnityEngine.Rendering.UnifiedRayTracing
|
|
{
|
|
internal struct BlockAllocator : IDisposable
|
|
{
|
|
public struct Block
|
|
{
|
|
public int offset;
|
|
public int count;
|
|
|
|
public static readonly Block Invalid = new Block() { offset = 0, count = 0 };
|
|
}
|
|
|
|
public struct Allocation
|
|
{
|
|
public int handle;
|
|
public Block block;
|
|
|
|
public static readonly Allocation Invalid = new Allocation() { handle = -1 };
|
|
public readonly bool valid => handle != -1;
|
|
}
|
|
|
|
private int m_FreeElementCount;
|
|
private int m_MaxElementCount;
|
|
private NativeList<Block> m_freeBlocks;
|
|
private NativeList<Block> m_usedBlocks;
|
|
private NativeList<int> m_freeSlots;
|
|
|
|
public int freeElementsCount => m_FreeElementCount;
|
|
public int freeBlocks => m_freeBlocks.Length;
|
|
public int capacity => m_MaxElementCount;
|
|
public int allocatedSize => m_MaxElementCount - m_FreeElementCount;
|
|
|
|
public void Initialize(int maxElementCounts)
|
|
{
|
|
m_MaxElementCount = maxElementCounts;
|
|
m_FreeElementCount = maxElementCounts;
|
|
|
|
if (!m_freeBlocks.IsCreated)
|
|
m_freeBlocks = new NativeList<Block>(Allocator.Persistent);
|
|
else
|
|
m_freeBlocks.Clear();
|
|
m_freeBlocks.Add(new Block() { offset = 0, count = m_FreeElementCount });
|
|
|
|
if (!m_usedBlocks.IsCreated)
|
|
m_usedBlocks = new NativeList<Block>(Allocator.Persistent);
|
|
else
|
|
m_usedBlocks.Clear();
|
|
|
|
if (!m_freeSlots.IsCreated)
|
|
m_freeSlots = new NativeList<int>(Allocator.Persistent);
|
|
else
|
|
m_freeSlots.Clear();
|
|
}
|
|
|
|
private int CalculateGeometricGrowthCapacity(int desiredNewCapacity, int maxAllowedNewCapacity)
|
|
{
|
|
var oldCapacity = capacity;
|
|
|
|
if (oldCapacity > maxAllowedNewCapacity - oldCapacity / 2)
|
|
{
|
|
return maxAllowedNewCapacity; // geometric growth would overflow
|
|
}
|
|
|
|
var geometricNewCapacity = oldCapacity + oldCapacity / 2;
|
|
|
|
if (geometricNewCapacity < desiredNewCapacity)
|
|
return desiredNewCapacity; // geometric growth would be insufficient
|
|
|
|
else
|
|
return geometricNewCapacity;
|
|
}
|
|
|
|
public int Grow(int newDesiredCapacity, int maxAllowedCapacity = Int32.MaxValue)
|
|
{
|
|
Debug.Assert(newDesiredCapacity > 0, "newDesiredCapacity must be positive");
|
|
Debug.Assert(maxAllowedCapacity > 0, "maxAllowedCapacity must be positive");
|
|
Debug.Assert(capacity < newDesiredCapacity, "newDesiredCapacity must be greater than curent capacity");
|
|
Debug.Assert(maxAllowedCapacity >= newDesiredCapacity, "newDesiredCapacity must be smaller than maxAllowedCapacity");
|
|
|
|
var newCapacity = CalculateGeometricGrowthCapacity(newDesiredCapacity, maxAllowedCapacity);
|
|
var oldCapacity = m_MaxElementCount;
|
|
var addedElements = newCapacity - oldCapacity;
|
|
Debug.Assert(addedElements > 0);
|
|
|
|
m_FreeElementCount += addedElements;
|
|
m_MaxElementCount = newCapacity;
|
|
|
|
int blockToMerge = m_freeBlocks.Length;
|
|
m_freeBlocks.Add(new Block() { offset = oldCapacity, count = addedElements });
|
|
|
|
while (blockToMerge != -1)
|
|
blockToMerge = MergeBlockFrontBack(blockToMerge);
|
|
|
|
return m_MaxElementCount;
|
|
}
|
|
|
|
public Allocation GrowAndAllocate(int elementCounts, out int oldCapacity, out int newCapacity)
|
|
{
|
|
return GrowAndAllocate(elementCounts, Int32.MaxValue, out oldCapacity, out newCapacity);
|
|
}
|
|
|
|
public Allocation GrowAndAllocate(int elementCounts, int maxAllowedCapacity, out int oldCapacity, out int newCapacity)
|
|
{
|
|
oldCapacity = capacity;
|
|
|
|
var additionalRequiredElements = m_freeBlocks.IsEmpty ? elementCounts : math.max(elementCounts - m_freeBlocks[m_freeBlocks.Length - 1].count, 0);
|
|
if (maxAllowedCapacity < capacity || (maxAllowedCapacity - capacity) < additionalRequiredElements)
|
|
{
|
|
newCapacity = capacity;
|
|
return Allocation.Invalid;
|
|
}
|
|
|
|
newCapacity = additionalRequiredElements > 0 ? Grow(capacity + additionalRequiredElements, maxAllowedCapacity) : capacity;
|
|
Debug.Assert(newCapacity >= oldCapacity + additionalRequiredElements);
|
|
|
|
var alloc = Allocate(elementCounts);
|
|
Assert.IsTrue(alloc.valid);
|
|
return alloc;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
m_MaxElementCount = 0;
|
|
m_FreeElementCount = 0;
|
|
if (m_freeBlocks.IsCreated)
|
|
m_freeBlocks.Dispose();
|
|
if (m_usedBlocks.IsCreated)
|
|
m_usedBlocks.Dispose();
|
|
if (m_freeSlots.IsCreated)
|
|
m_freeSlots.Dispose();
|
|
}
|
|
|
|
public Allocation Allocate(int elementCounts)
|
|
{
|
|
if (elementCounts > m_FreeElementCount || m_freeBlocks.IsEmpty)
|
|
return Allocation.Invalid;
|
|
|
|
int selectedBlock = -1;
|
|
int currentBlockCount = 0;
|
|
for (int b = 0; b < m_freeBlocks.Length; ++b)
|
|
{
|
|
Block block = m_freeBlocks[b];
|
|
|
|
//simple naive allocator, we find the smallest possible space to allocate in our blocks.
|
|
if (elementCounts <= block.count && (selectedBlock == -1 || block.count < currentBlockCount))
|
|
{
|
|
currentBlockCount = block.count;
|
|
selectedBlock = b;
|
|
}
|
|
}
|
|
|
|
if (selectedBlock == -1)
|
|
return Allocation.Invalid;
|
|
|
|
Block allocationBlock = m_freeBlocks[selectedBlock];
|
|
Block split = allocationBlock;
|
|
|
|
split.offset += elementCounts;
|
|
split.count -= elementCounts;
|
|
allocationBlock.count = elementCounts;
|
|
|
|
if (split.count > 0)
|
|
m_freeBlocks[selectedBlock] = split;
|
|
else
|
|
m_freeBlocks.RemoveAtSwapBack(selectedBlock);
|
|
|
|
int allocationHandle;
|
|
if (m_freeSlots.IsEmpty)
|
|
{
|
|
allocationHandle = m_usedBlocks.Length;
|
|
m_usedBlocks.Add(allocationBlock);
|
|
}
|
|
else
|
|
{
|
|
allocationHandle = m_freeSlots[m_freeSlots.Length - 1];
|
|
m_freeSlots.RemoveAtSwapBack(m_freeSlots.Length - 1);
|
|
m_usedBlocks[allocationHandle] = allocationBlock;
|
|
}
|
|
|
|
m_FreeElementCount -= elementCounts;
|
|
return new Allocation() { handle = allocationHandle, block = allocationBlock };
|
|
}
|
|
|
|
private int MergeBlockFrontBack(int freeBlockId)
|
|
{
|
|
Block targetBlock = m_freeBlocks[freeBlockId];
|
|
for (int i = 0; i < m_freeBlocks.Length; ++i)
|
|
{
|
|
if (i == freeBlockId)
|
|
continue;
|
|
|
|
Block freeBlock = m_freeBlocks[i];
|
|
bool mergeTargetBlock = false;
|
|
if (targetBlock.offset == (freeBlock.offset + freeBlock.count))
|
|
{
|
|
freeBlock.count += targetBlock.count;
|
|
mergeTargetBlock = true;
|
|
}
|
|
else if (freeBlock.offset == (targetBlock.offset + targetBlock.count))
|
|
{
|
|
freeBlock.offset = targetBlock.offset;
|
|
freeBlock.count += targetBlock.count;
|
|
mergeTargetBlock = true;
|
|
}
|
|
|
|
if (mergeTargetBlock)
|
|
{
|
|
m_freeBlocks[i] = freeBlock;
|
|
m_freeBlocks.RemoveAtSwapBack(freeBlockId);
|
|
return i == m_freeBlocks.Length ? freeBlockId : i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public void FreeAllocation(in Allocation allocation)
|
|
{
|
|
Debug.Assert(allocation.valid, "Cannot free invalid allocation");
|
|
|
|
m_freeSlots.Add(allocation.handle);
|
|
m_usedBlocks[allocation.handle] = Block.Invalid;
|
|
|
|
int blockToMerge = m_freeBlocks.Length;
|
|
m_freeBlocks.Add(allocation.block);
|
|
|
|
while (blockToMerge != -1)
|
|
blockToMerge = MergeBlockFrontBack(blockToMerge);
|
|
|
|
m_FreeElementCount += allocation.block.count;
|
|
}
|
|
|
|
public Allocation[] SplitAllocation(in Allocation allocation, int count)
|
|
{
|
|
Debug.Assert(allocation.valid, "Invalid allocation");
|
|
|
|
var newAllocs = new Allocation[count];
|
|
var newAllocsSize = allocation.block.count / count;
|
|
|
|
var newBlock0 = new Block { offset = allocation.block.offset, count = newAllocsSize };
|
|
m_usedBlocks[allocation.handle] = newBlock0;
|
|
newAllocs[0] = new Allocation() { handle = allocation.handle, block = newBlock0 };
|
|
|
|
for (int i = 1; i < count; ++i)
|
|
{
|
|
Block block = new Block { offset = allocation.block.offset + i * newAllocsSize, count = newAllocsSize };
|
|
|
|
int allocationHandle;
|
|
if (m_freeSlots.IsEmpty)
|
|
{
|
|
allocationHandle = m_usedBlocks.Length;
|
|
m_usedBlocks.Add(block);
|
|
}
|
|
else
|
|
{
|
|
allocationHandle = m_freeSlots[m_freeSlots.Length - 1];
|
|
m_freeSlots.RemoveAtSwapBack(m_freeSlots.Length - 1);
|
|
m_usedBlocks[allocationHandle] = block;
|
|
}
|
|
|
|
newAllocs[i] = new Allocation() { handle = allocationHandle, block = block };
|
|
}
|
|
|
|
return newAllocs;
|
|
}
|
|
}
|
|
}
|