123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- using System;
- using Unity.Collections;
- using Unity.Collections.LowLevel.Unsafe;
- using Unity.Mathematics;
- using UnityEngine.Assertions;
-
- namespace UnityEngine.Rendering.Universal
- {
- struct BuddyAllocation
- {
- public int level;
- public int index;
-
- public BuddyAllocation(int level, int index)
- {
- this.level = level;
- this.index = index;
- }
-
- public uint2 index2D => SpaceFillingCurves.DecodeMorton2D((uint)index);
- }
-
- unsafe struct BuddyAllocator : IDisposable
- {
- struct Header
- {
- public int branchingOrder;
- public int levelCount;
- public int allocationCount;
- public int freeAllocationIdsCount;
- }
-
- // This data structure uses one big allocation containing a fixed header and all the arrays.
- // Some arrays are sub-divided per order, which can be identified by the presence of an X(int order) method,
- // which allows for easy access to the slice of data for the specified order.
- // The offsets for the arrays are stored together with the pointer to avoid a double look-up when
- // accessing data.
- void* m_Data;
-
- ref Header header => ref UnsafeUtility.AsRef<Header>(m_Data);
-
- (int, int) m_ActiveFreeMaskCounts;
- NativeArray<int> freeMaskCounts => GetNativeArray<int>(m_ActiveFreeMaskCounts.Item1, m_ActiveFreeMaskCounts.Item2);
-
- (int, int) m_FreeMasksStorage;
- NativeArray<ulong> freeMasksStorage => GetNativeArray<ulong>(m_FreeMasksStorage.Item1, m_FreeMasksStorage.Item2);
- NativeArray<ulong> FreeMasks(int level) => freeMasksStorage.GetSubArray(LevelOffset64(level, header.branchingOrder), LevelLength64(level, header.branchingOrder));
-
- (int, int) m_FreeMaskIndicesStorage;
- NativeArray<int> freeMaskIndicesStorage => GetNativeArray<int>(m_FreeMaskIndicesStorage.Item1, m_FreeMaskIndicesStorage.Item2);
- NativeArray<int> FreeMaskIndices(int level) => freeMaskIndicesStorage.GetSubArray(LevelOffset64(level, header.branchingOrder), LevelLength64(level, header.branchingOrder));
-
- Allocator m_Allocator;
-
- #if ENABLE_UNITY_COLLECTIONS_CHECKS
- AtomicSafetyHandle m_SafetyHandle;
- #endif
-
- public int levelCount => header.levelCount;
-
- public BuddyAllocator(int levelCount, int branchingOrder, Allocator allocator = Allocator.Persistent)
- {
- // Allows us to support 1D, 2D, and 3D cases.
- Assert.IsTrue(branchingOrder is >= 1 and <= 3);
- // Memory usage explodes unless capped like this.
- Assert.IsTrue((levelCount + branchingOrder - 1) / branchingOrder is >= 1 and <= 24);
-
- var dataSize = sizeof(Header);
- m_ActiveFreeMaskCounts = AllocateRange<int>(levelCount, ref dataSize);
- m_FreeMasksStorage = AllocateRange<ulong>(LevelOffset64(levelCount, branchingOrder), ref dataSize);
- m_FreeMaskIndicesStorage = AllocateRange<int>(LevelOffset64(levelCount, branchingOrder), ref dataSize);
-
- m_Data = UnsafeUtility.Malloc(dataSize, 64, allocator);
- UnsafeUtility.MemClear(m_Data, dataSize);
- m_Allocator = allocator;
- #if ENABLE_UNITY_COLLECTIONS_CHECKS
- m_SafetyHandle = AtomicSafetyHandle.Create();
- #endif
-
- header = new Header
- {
- branchingOrder = branchingOrder,
- levelCount = levelCount
- };
-
- // Initialize level-0 to have 1/1 block available.
- var freeMasks0 = FreeMasks(0);
- freeMasks0[0] = 0b1111;
- var maskCounts = freeMaskCounts;
- maskCounts[0] = 1;
- }
-
- public bool TryAllocate(int requestedLevel, out BuddyAllocation allocation)
- {
- allocation = default;
-
- // Find the highest level that has a block available.
- var level = requestedLevel;
- var maskCounts = freeMaskCounts;
- while (level >= 0)
- {
- if (maskCounts[level] > 0) break;
- level--;
- }
-
- // No blocks available.
- if (level < 0) return false;
-
- // Split a block at the level we found.
- int dataIndex;
- {
- var freeMaskIndices = FreeMaskIndices(level);
- var maskIndex = freeMaskIndices[--maskCounts[level]];
- var freeMasks = FreeMasks(level);
- var freeMask = freeMasks[maskIndex];
- Assert.AreNotEqual(freeMask, 0);
- var bitIndex = math.tzcnt(freeMask);
- freeMask ^= 1ul << bitIndex;
- freeMasks[maskIndex] = freeMask;
- if (freeMask != 0) freeMaskIndices[maskCounts[level]++] = maskIndex;
- dataIndex = maskIndex * 64 + bitIndex;
- }
-
- // Walk up the levels until we hit the requested level. For each level we want to mark the remaining parts
- // of the newly split blocks as free.
- while (level < requestedLevel)
- {
- level++;
- dataIndex <<= header.branchingOrder;
- var maskIndex = dataIndex >> 6;
- var bitIndex = dataIndex & 63;
- var freeMasks = FreeMasks(level);
- var freeMask = freeMasks[maskIndex];
- // We might have hit a mask that already contained free blocks. If not, add the mask index to the free list.
- if (freeMask == 0)
- {
- var freeMaskIndices = FreeMaskIndices(level);
- freeMaskIndices[maskCounts[level]++] = maskIndex;
- }
-
- // Mark other bits in the block we just broke apart as free.
- // In binary form, 2^b will give us a 1 followed by b 0s. So to get b ones, we subtract 1. Since we want
- // the least significant bit to be 0, we subtract another 1.
- // E.g. for branching order 1 we get 10b, for 2 we get 1110b, for 3 we get 11111110b.
- // Finally we shift according to the data index.
- Assert.IsTrue(bitIndex + Pow2(header.branchingOrder) - 1 < 64);
- freeMask |= ((1ul << Pow2(header.branchingOrder)) - 2ul) << bitIndex;
- freeMasks[maskIndex] = freeMask;
- }
-
- allocation.level = level;
- allocation.index = dataIndex;
- return true;
- }
-
- public void Free(BuddyAllocation allocation)
- {
- var level = allocation.level;
- var dataIndex = allocation.index;
- while (level >= 0)
- {
- var maskIndex = dataIndex >> 6;
- var bitIndex = dataIndex & 63;
- var freeMasks = FreeMasks(level);
- var freeMask = freeMasks[maskIndex];
- var wasZero = freeMask == 0;
- freeMask |= 1ul << bitIndex;
-
- var indices = FreeMaskIndices(level);
- var counts = freeMaskCounts;
-
- var superBlockMask = ((1ul << Pow2(header.branchingOrder)) - 1) << ((bitIndex >> header.branchingOrder) * Pow2(header.branchingOrder));
- // Check if the whole super-block (i.e. making up one block of the next level) is free.
- // If it is, we let the loop continue upwards.
- if (level == 0 || (~freeMask & superBlockMask) != 0)
- {
- freeMasks[maskIndex] = freeMask;
- if (wasZero)
- {
- indices[counts[level]++] = maskIndex;
- }
- break;
- }
-
- freeMask &= ~superBlockMask;
- freeMasks[maskIndex] = freeMask;
-
- if (!wasZero && freeMask == 0)
- {
- for (var i = 0; i < indices.Length; i++)
- {
- if (indices[i] == maskIndex)
- {
- indices[i] = indices[--counts[level]];
- break;
- }
- }
- }
-
- level--;
- dataIndex >>= header.branchingOrder;
- }
- }
-
- public void Dispose()
- {
- UnsafeUtility.Free(m_Data, m_Allocator);
- #if ENABLE_UNITY_COLLECTIONS_CHECKS
- AtomicSafetyHandle.Release(m_SafetyHandle);
- #endif
- m_Data = default;
- m_Allocator = default;
- }
-
- NativeArray<T> GetNativeArray<T>(int offset, int length) where T : struct
- {
- var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(PtrAdd(m_Data, offset), length, m_Allocator);
- #if ENABLE_UNITY_COLLECTIONS_CHECKS
- NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_SafetyHandle);
- #endif
- return array;
- }
-
- // sum x^i for i=0..(n-1) = (x ^ n - 1) / (x - 1) where n is order and n is 2^branchingOrder
- static int LevelOffset(int level, int branchingOrder) => (Pow2(branchingOrder) * (Pow2(branchingOrder * (level - 1) + branchingOrder) - 1)) / (Pow2(branchingOrder) - 1);
- static int LevelLength(int level, int branchingOrder) => Pow2N(branchingOrder, level + 1);
-
- // These are for when orders of length <= 64 only take up 1 item, e.g. ulong bitmasks.
- static int LevelOffset64(int level, int branchingOrder) => math.min(level, 6/branchingOrder) + LevelOffset(math.max(0, level - 6/branchingOrder), branchingOrder);
- static int LevelLength64(int level, int branchingOrder) => Pow2N(branchingOrder, math.max(0, level - 6/branchingOrder + 1));
-
- static (int, int) AllocateRange<T>(int length, ref int dataSize) where T : struct
- {
- dataSize = AlignForward(dataSize, UnsafeUtility.AlignOf<T>());
- var range = (dataSize, length);
- dataSize += length * UnsafeUtility.SizeOf<T>();
- return range;
- }
-
- static int AlignForward(int offset, int alignment)
- {
- var modulo = offset % alignment;
- if (modulo != 0) offset += (alignment - modulo);
- return offset;
- }
-
- static void* PtrAdd(void* ptr, int bytes) => (void*) ((IntPtr) ptr + bytes);
-
- static int Pow2(int n) => 1 << n;
-
- // (2^x)^n = 2^(x*n)
- static int Pow2N(int x, int n) => 1 << (x * n);
- }
- }
|