暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

BuddyAllocator.cs 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. using System;
  2. using Unity.Collections;
  3. using Unity.Collections.LowLevel.Unsafe;
  4. using Unity.Mathematics;
  5. using UnityEngine.Assertions;
  6. namespace UnityEngine.Rendering.Universal
  7. {
  8. struct BuddyAllocation
  9. {
  10. public int level;
  11. public int index;
  12. public BuddyAllocation(int level, int index)
  13. {
  14. this.level = level;
  15. this.index = index;
  16. }
  17. public uint2 index2D => SpaceFillingCurves.DecodeMorton2D((uint)index);
  18. }
  19. unsafe struct BuddyAllocator : IDisposable
  20. {
  21. struct Header
  22. {
  23. public int branchingOrder;
  24. public int levelCount;
  25. public int allocationCount;
  26. public int freeAllocationIdsCount;
  27. }
  28. // This data structure uses one big allocation containing a fixed header and all the arrays.
  29. // Some arrays are sub-divided per order, which can be identified by the presence of an X(int order) method,
  30. // which allows for easy access to the slice of data for the specified order.
  31. // The offsets for the arrays are stored together with the pointer to avoid a double look-up when
  32. // accessing data.
  33. void* m_Data;
  34. ref Header header => ref UnsafeUtility.AsRef<Header>(m_Data);
  35. (int, int) m_ActiveFreeMaskCounts;
  36. NativeArray<int> freeMaskCounts => GetNativeArray<int>(m_ActiveFreeMaskCounts.Item1, m_ActiveFreeMaskCounts.Item2);
  37. (int, int) m_FreeMasksStorage;
  38. NativeArray<ulong> freeMasksStorage => GetNativeArray<ulong>(m_FreeMasksStorage.Item1, m_FreeMasksStorage.Item2);
  39. NativeArray<ulong> FreeMasks(int level) => freeMasksStorage.GetSubArray(LevelOffset64(level, header.branchingOrder), LevelLength64(level, header.branchingOrder));
  40. (int, int) m_FreeMaskIndicesStorage;
  41. NativeArray<int> freeMaskIndicesStorage => GetNativeArray<int>(m_FreeMaskIndicesStorage.Item1, m_FreeMaskIndicesStorage.Item2);
  42. NativeArray<int> FreeMaskIndices(int level) => freeMaskIndicesStorage.GetSubArray(LevelOffset64(level, header.branchingOrder), LevelLength64(level, header.branchingOrder));
  43. Allocator m_Allocator;
  44. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  45. AtomicSafetyHandle m_SafetyHandle;
  46. #endif
  47. public int levelCount => header.levelCount;
  48. public BuddyAllocator(int levelCount, int branchingOrder, Allocator allocator = Allocator.Persistent)
  49. {
  50. // Allows us to support 1D, 2D, and 3D cases.
  51. Assert.IsTrue(branchingOrder is >= 1 and <= 3);
  52. // Memory usage explodes unless capped like this.
  53. Assert.IsTrue((levelCount + branchingOrder - 1) / branchingOrder is >= 1 and <= 24);
  54. var dataSize = sizeof(Header);
  55. m_ActiveFreeMaskCounts = AllocateRange<int>(levelCount, ref dataSize);
  56. m_FreeMasksStorage = AllocateRange<ulong>(LevelOffset64(levelCount, branchingOrder), ref dataSize);
  57. m_FreeMaskIndicesStorage = AllocateRange<int>(LevelOffset64(levelCount, branchingOrder), ref dataSize);
  58. m_Data = UnsafeUtility.Malloc(dataSize, 64, allocator);
  59. UnsafeUtility.MemClear(m_Data, dataSize);
  60. m_Allocator = allocator;
  61. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  62. m_SafetyHandle = AtomicSafetyHandle.Create();
  63. #endif
  64. header = new Header
  65. {
  66. branchingOrder = branchingOrder,
  67. levelCount = levelCount
  68. };
  69. // Initialize level-0 to have 1/1 block available.
  70. var freeMasks0 = FreeMasks(0);
  71. freeMasks0[0] = 0b1111;
  72. var maskCounts = freeMaskCounts;
  73. maskCounts[0] = 1;
  74. }
  75. public bool TryAllocate(int requestedLevel, out BuddyAllocation allocation)
  76. {
  77. allocation = default;
  78. // Find the highest level that has a block available.
  79. var level = requestedLevel;
  80. var maskCounts = freeMaskCounts;
  81. while (level >= 0)
  82. {
  83. if (maskCounts[level] > 0) break;
  84. level--;
  85. }
  86. // No blocks available.
  87. if (level < 0) return false;
  88. // Split a block at the level we found.
  89. int dataIndex;
  90. {
  91. var freeMaskIndices = FreeMaskIndices(level);
  92. var maskIndex = freeMaskIndices[--maskCounts[level]];
  93. var freeMasks = FreeMasks(level);
  94. var freeMask = freeMasks[maskIndex];
  95. Assert.AreNotEqual(freeMask, 0);
  96. var bitIndex = math.tzcnt(freeMask);
  97. freeMask ^= 1ul << bitIndex;
  98. freeMasks[maskIndex] = freeMask;
  99. if (freeMask != 0) freeMaskIndices[maskCounts[level]++] = maskIndex;
  100. dataIndex = maskIndex * 64 + bitIndex;
  101. }
  102. // Walk up the levels until we hit the requested level. For each level we want to mark the remaining parts
  103. // of the newly split blocks as free.
  104. while (level < requestedLevel)
  105. {
  106. level++;
  107. dataIndex <<= header.branchingOrder;
  108. var maskIndex = dataIndex >> 6;
  109. var bitIndex = dataIndex & 63;
  110. var freeMasks = FreeMasks(level);
  111. var freeMask = freeMasks[maskIndex];
  112. // We might have hit a mask that already contained free blocks. If not, add the mask index to the free list.
  113. if (freeMask == 0)
  114. {
  115. var freeMaskIndices = FreeMaskIndices(level);
  116. freeMaskIndices[maskCounts[level]++] = maskIndex;
  117. }
  118. // Mark other bits in the block we just broke apart as free.
  119. // 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
  120. // the least significant bit to be 0, we subtract another 1.
  121. // E.g. for branching order 1 we get 10b, for 2 we get 1110b, for 3 we get 11111110b.
  122. // Finally we shift according to the data index.
  123. Assert.IsTrue(bitIndex + Pow2(header.branchingOrder) - 1 < 64);
  124. freeMask |= ((1ul << Pow2(header.branchingOrder)) - 2ul) << bitIndex;
  125. freeMasks[maskIndex] = freeMask;
  126. }
  127. allocation.level = level;
  128. allocation.index = dataIndex;
  129. return true;
  130. }
  131. public void Free(BuddyAllocation allocation)
  132. {
  133. var level = allocation.level;
  134. var dataIndex = allocation.index;
  135. while (level >= 0)
  136. {
  137. var maskIndex = dataIndex >> 6;
  138. var bitIndex = dataIndex & 63;
  139. var freeMasks = FreeMasks(level);
  140. var freeMask = freeMasks[maskIndex];
  141. var wasZero = freeMask == 0;
  142. freeMask |= 1ul << bitIndex;
  143. var indices = FreeMaskIndices(level);
  144. var counts = freeMaskCounts;
  145. var superBlockMask = ((1ul << Pow2(header.branchingOrder)) - 1) << ((bitIndex >> header.branchingOrder) * Pow2(header.branchingOrder));
  146. // Check if the whole super-block (i.e. making up one block of the next level) is free.
  147. // If it is, we let the loop continue upwards.
  148. if (level == 0 || (~freeMask & superBlockMask) != 0)
  149. {
  150. freeMasks[maskIndex] = freeMask;
  151. if (wasZero)
  152. {
  153. indices[counts[level]++] = maskIndex;
  154. }
  155. break;
  156. }
  157. freeMask &= ~superBlockMask;
  158. freeMasks[maskIndex] = freeMask;
  159. if (!wasZero && freeMask == 0)
  160. {
  161. for (var i = 0; i < indices.Length; i++)
  162. {
  163. if (indices[i] == maskIndex)
  164. {
  165. indices[i] = indices[--counts[level]];
  166. break;
  167. }
  168. }
  169. }
  170. level--;
  171. dataIndex >>= header.branchingOrder;
  172. }
  173. }
  174. public void Dispose()
  175. {
  176. UnsafeUtility.Free(m_Data, m_Allocator);
  177. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  178. AtomicSafetyHandle.Release(m_SafetyHandle);
  179. #endif
  180. m_Data = default;
  181. m_Allocator = default;
  182. }
  183. NativeArray<T> GetNativeArray<T>(int offset, int length) where T : struct
  184. {
  185. var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(PtrAdd(m_Data, offset), length, m_Allocator);
  186. #if ENABLE_UNITY_COLLECTIONS_CHECKS
  187. NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, m_SafetyHandle);
  188. #endif
  189. return array;
  190. }
  191. // sum x^i for i=0..(n-1) = (x ^ n - 1) / (x - 1) where n is order and n is 2^branchingOrder
  192. static int LevelOffset(int level, int branchingOrder) => (Pow2(branchingOrder) * (Pow2(branchingOrder * (level - 1) + branchingOrder) - 1)) / (Pow2(branchingOrder) - 1);
  193. static int LevelLength(int level, int branchingOrder) => Pow2N(branchingOrder, level + 1);
  194. // These are for when orders of length <= 64 only take up 1 item, e.g. ulong bitmasks.
  195. static int LevelOffset64(int level, int branchingOrder) => math.min(level, 6/branchingOrder) + LevelOffset(math.max(0, level - 6/branchingOrder), branchingOrder);
  196. static int LevelLength64(int level, int branchingOrder) => Pow2N(branchingOrder, math.max(0, level - 6/branchingOrder + 1));
  197. static (int, int) AllocateRange<T>(int length, ref int dataSize) where T : struct
  198. {
  199. dataSize = AlignForward(dataSize, UnsafeUtility.AlignOf<T>());
  200. var range = (dataSize, length);
  201. dataSize += length * UnsafeUtility.SizeOf<T>();
  202. return range;
  203. }
  204. static int AlignForward(int offset, int alignment)
  205. {
  206. var modulo = offset % alignment;
  207. if (modulo != 0) offset += (alignment - modulo);
  208. return offset;
  209. }
  210. static void* PtrAdd(void* ptr, int bytes) => (void*) ((IntPtr) ptr + bytes);
  211. static int Pow2(int n) => 1 << n;
  212. // (2^x)^n = 2^(x*n)
  213. static int Pow2N(int x, int n) => 1 << (x * n);
  214. }
  215. }