using System; using System.Diagnostics; using System.Threading; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace UnityEngine.Rendering { internal struct ParallelBitArray { private Allocator m_Allocator; private NativeArray m_Bits; private int m_Length; public int Length => m_Length; public bool IsCreated { get { return m_Bits.IsCreated; } } public ParallelBitArray(int length, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) { m_Allocator = allocator; m_Bits = new NativeArray((length + 63) / 64, allocator, options); m_Length = length; } public void Dispose() { m_Bits.Dispose(); m_Length = 0; } public void Dispose(JobHandle inputDeps) { m_Bits.Dispose(inputDeps); m_Length = 0; } public void Resize(int newLength) { int oldLength = m_Length; if (newLength == oldLength) return; int oldBitsLength = m_Bits.Length; int newBitsLength = (newLength + 63) / 64; if (newBitsLength != oldBitsLength) { var newBits = new NativeArray(newBitsLength, m_Allocator, NativeArrayOptions.UninitializedMemory); if (m_Bits.IsCreated) { NativeArray.Copy(m_Bits, newBits, m_Bits.Length); m_Bits.Dispose(); } m_Bits = newBits; } // mask off bits past the length int validLength = Math.Min(oldLength, newLength); int validBitsLength = Math.Min(oldBitsLength, newBitsLength); for (int chunkIndex = validBitsLength; chunkIndex < m_Bits.Length; ++chunkIndex) { int validBitCount = Math.Max(validLength - 64 * chunkIndex, 0); if (validBitCount < 64) { ulong validMask = (1ul << validBitCount) - 1; m_Bits[chunkIndex] &= (long)validMask; } } m_Length = newLength; } public void Set(int index, bool value) { unsafe { Debug.Assert(0 <= index && index < m_Length); int entry_index = index >> 6; long* entries = (long*)m_Bits.GetUnsafePtr(); ulong bit = 1ul << (index & 0x3f); long and_mask = (long)(~bit); long or_mask = value ? (long)bit : 0; long old_entry, new_entry; do { old_entry = Interlocked.Read(ref entries[entry_index]); new_entry = (old_entry & and_mask) | or_mask; } while (Interlocked.CompareExchange(ref entries[entry_index], new_entry, old_entry) != old_entry); } } public bool Get(int index) { unsafe { Debug.Assert(0 <= index && index < m_Length); int entry_index = index >> 6; long* entries = (long*)m_Bits.GetUnsafeReadOnlyPtr(); ulong bit = 1ul << (index & 0x3f); long check_mask = (long)bit; return (entries[entry_index] & check_mask) != 0; } } public ulong GetChunk(int chunk_index) { return (ulong)m_Bits[chunk_index]; } public void SetChunk(int chunk_index, ulong chunk_bits) { m_Bits[chunk_index] = (long)chunk_bits; } public unsafe ulong InterlockedReadChunk(int chunk_index) { long* entries = (long*)m_Bits.GetUnsafeReadOnlyPtr(); return (ulong)Interlocked.Read(ref entries[chunk_index]); } public unsafe void InterlockedOrChunk(int chunk_index, ulong chunk_bits) { long* entries = (long*)m_Bits.GetUnsafePtr(); long old_entry, new_entry; do { old_entry = Interlocked.Read(ref entries[chunk_index]); new_entry = old_entry | (long)chunk_bits; } while (Interlocked.CompareExchange(ref entries[chunk_index], new_entry, old_entry) != old_entry); } public int ChunkCount() { return m_Bits.Length; } public ParallelBitArray GetSubArray(int length) { ParallelBitArray array = new ParallelBitArray(); array.m_Bits = m_Bits.GetSubArray(0, (length + 63) / 64); array.m_Length = length; return array; } public NativeArray GetBitsArray() { return m_Bits; } public void FillZeroes(int length) { length = Math.Min(length, m_Length); int chunkIndex = length / 64; int remainder = length & 63; m_Bits.FillArray(0, 0, chunkIndex); if(remainder > 0) { long lastChunkMask = (1L << remainder) - 1; m_Bits[chunkIndex] &= ~lastChunkMask; } } } }