using System; using System.Collections; using System.Runtime.InteropServices; using Unity.Collections; using UnityEngine.Assertions; namespace UnityEngine.Rendering.UnifiedRayTracing { internal sealed class PersistentGpuArray : IDisposable where Tstruct : struct { BlockAllocator m_SlotAllocator; ComputeBuffer m_GpuBuffer; NativeArray m_CpuList; BitArray m_Updates; bool m_gpuBufferDirty = true; int m_ElementCount = 0; public int elementCount { get { return m_ElementCount; } } public PersistentGpuArray(int initialSize) { m_SlotAllocator.Initialize(initialSize); m_GpuBuffer = new ComputeBuffer(initialSize, Marshal.SizeOf()); m_CpuList = new NativeArray(initialSize, Allocator.Persistent); m_Updates = new BitArray(initialSize); m_ElementCount = 0; } public void Dispose() { m_ElementCount = 0; m_SlotAllocator.Dispose(); m_GpuBuffer.Dispose(); m_CpuList.Dispose(); } public BlockAllocator.Allocation Add(Tstruct element) { m_ElementCount++; var slotAllocation = m_SlotAllocator.Allocate(1); if (!slotAllocation.valid) { Grow(); slotAllocation = m_SlotAllocator.Allocate(1); Assert.IsTrue(slotAllocation.valid); } m_CpuList[slotAllocation.block.offset] = element; m_Updates[slotAllocation.block.offset] = true; m_gpuBufferDirty = true; return slotAllocation; } public BlockAllocator.Allocation[] Add(int elementCount) { m_ElementCount+= elementCount; var slotAllocation = m_SlotAllocator.Allocate(elementCount); if (!slotAllocation.valid) { Grow(); slotAllocation = m_SlotAllocator.Allocate(elementCount); Assert.IsTrue(slotAllocation.valid); } return m_SlotAllocator.SplitAllocation(slotAllocation, elementCount); } public void Remove(BlockAllocator.Allocation allocation) { m_ElementCount--; m_SlotAllocator.FreeAllocation(allocation); } public void Clear() { m_ElementCount = 0; var currentCapacity = m_SlotAllocator.capacity; m_SlotAllocator.Dispose(); m_SlotAllocator = new BlockAllocator(); m_SlotAllocator.Initialize(currentCapacity); m_Updates = new BitArray(currentCapacity); m_gpuBufferDirty = false; } public void Set(BlockAllocator.Allocation allocation, Tstruct element) { m_CpuList[allocation.block.offset] = element; m_Updates[allocation.block.offset] = true; m_gpuBufferDirty = true; } public Tstruct Get(BlockAllocator.Allocation allocation) { return m_CpuList[allocation.block.offset]; } public void ModifyForEach(Func lambda) { for (int i = 0; i < m_CpuList.Length; ++i) { m_CpuList[i] = lambda(m_CpuList[i]); m_Updates[i] = true; } m_gpuBufferDirty = true; } // Note: this should ideally be used with only one command buffer. If used with more than one cmd buffers, the order of their execution is important. public ComputeBuffer GetGpuBuffer(CommandBuffer cmd) { if (m_gpuBufferDirty) { int copyStartIndex = -1; for (int i = 0; i < m_Updates.Length; ++i) { if (m_Updates[i]) { if (copyStartIndex == -1) copyStartIndex = i; m_Updates[i] = false; } else if (copyStartIndex != -1) { int copyEndIndex = i; cmd.SetBufferData(m_GpuBuffer, m_CpuList, copyStartIndex, copyStartIndex, copyEndIndex - copyStartIndex); copyStartIndex = -1; } } if (copyStartIndex != -1) { int copyEndIndex = m_Updates.Length; cmd.SetBufferData(m_GpuBuffer, m_CpuList, copyStartIndex, copyStartIndex, copyEndIndex - copyStartIndex); } m_gpuBufferDirty = false; } return m_GpuBuffer; } private void Grow() { var oldCapacity = m_SlotAllocator.capacity; m_SlotAllocator.Grow(m_SlotAllocator.capacity + 1); m_GpuBuffer.Dispose(); m_GpuBuffer = new ComputeBuffer(m_SlotAllocator.capacity, Marshal.SizeOf()); var oldList = m_CpuList; m_CpuList = new NativeArray(m_SlotAllocator.capacity, Allocator.Persistent); NativeArray.Copy(oldList, m_CpuList, oldCapacity); oldList.Dispose(); var oldUpdates = m_Updates; m_Updates = new BitArray(m_SlotAllocator.capacity); for (int i = 0; i < oldCapacity; ++i) m_Updates[i] = oldUpdates[i]; } } }