using System.Collections.Generic; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.LightTransport; using UnityEngine.Rendering; namespace UnityEngine.PathTracing.Core { internal class UnityComputeDeviceContext : IDeviceContext { private readonly Dictionary _buffers = new(); private readonly HashSet _inProgressRequests = new(); private readonly HashSet _failedRequests = new(); private readonly HashSet _successfulRequests = new(); private uint _nextFreeBufferId; private uint _nextFreeEventId; private CommandBuffer _cmdBuffer; private List _temporaryBuffers = new(); private void CreateCommandBuffer() { _cmdBuffer?.Dispose(); _cmdBuffer = new CommandBuffer(); _cmdBuffer.name = "UnityComputeDeviceContextCommandBuffer"; } public BufferID CreateBuffer(ulong count, ulong stride) { Debug.Assert(count != 0, "Buffer element count cannot be zero."); Debug.Assert(stride != 0, "Stride cannot be zero."); Debug.Assert(stride % 4 == 0, "Stride must be a multiple of 4."); Debug.Assert(stride <= 2048, "Stride must be 2048 or less."); GraphicsBuffer buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)count, (int)stride); Debug.Assert(buffer.IsValid(), "Buffer was not successfully created."); var zeros = new NativeArray((int)(count * stride), Allocator.Temp, NativeArrayOptions.ClearMemory); buffer.SetData(zeros); zeros.Dispose(); var idInteger = _nextFreeBufferId++; var id = new BufferID(idInteger); _buffers[id] = buffer; return id; } public void DestroyBuffer(BufferID id) { Debug.Assert(_buffers.ContainsKey(id), "Invalid buffer ID given."); _buffers[id].Release(); _buffers.Remove(id); } public void Dispose() { ReleaseTemporaryBuffers(); _cmdBuffer?.Dispose(); } public bool Flush() { Debug.Assert(_cmdBuffer != null); Graphics.ExecuteCommandBuffer(_cmdBuffer); // TODO(pema.malling): Don't block here https://jira.unity3d.com/browse/LIGHT-1699 // Ideally we shouldn't need this, but if we don't do it, read-backs will never finish unless explicitly waited on. AsyncGPUReadback.WaitAllRequests(); ReleaseTemporaryBuffers(); CreateCommandBuffer(); return true; } public bool Initialize() { CreateCommandBuffer(); return true; } public bool IsCompleted(EventID id) { return _successfulRequests.Contains(id) || _failedRequests.Contains(id); } public bool Wait(EventID id) { AsyncGPUReadback.WaitAllRequests(); if (_failedRequests.Contains(id)) { return false; } return true; } public void ReadBuffer(BufferSlice src, NativeArray result) where T : struct { Debug.Assert(_buffers.ContainsKey(src.Id), "Invalid buffer ID given."); int stride = UnsafeUtility.SizeOf(); int offset = (int)src.Offset * stride; int size = result.Length * stride; _cmdBuffer.RequestAsyncReadbackIntoNativeArray(ref result, _buffers[src.Id], size, offset, delegate { }); } public void ReadBuffer(BufferSlice src, NativeArray result, EventID id) where T : struct { Debug.Assert(_buffers.ContainsKey(src.Id), "Invalid buffer ID given."); int stride = UnsafeUtility.SizeOf(); int offset = (int)src.Offset * stride; int size = result.Length * stride; _cmdBuffer.RequestAsyncReadbackIntoNativeArray(ref result, _buffers[src.Id], size, offset, request => { Debug.Assert(request.done); // The user may have destroyed the event before the readback was completed, so we check if its still there. if (_inProgressRequests.Remove(id)) { if (request.hasError) { _failedRequests.Add(id); } else { _successfulRequests.Add(id); } } }); _inProgressRequests.Add(id); } public void WriteBuffer(BufferSlice dst, NativeArray src) where T : struct { Debug.Assert(_buffers.ContainsKey(dst.Id), "Invalid buffer ID given."); _cmdBuffer.SetBufferData(_buffers[dst.Id], src, 0, (int)dst.Offset, src.Length); } public void WriteBuffer(BufferSlice dst, NativeArray src, EventID id) where T : struct { Debug.Assert(_buffers.ContainsKey(dst.Id), "Invalid buffer ID given."); _cmdBuffer.SetBufferData(_buffers[dst.Id], src, 0, (int)dst.Offset, src.Length); _successfulRequests.Add(id); } public EventID CreateEvent() { var eventIdInteger = _nextFreeEventId++; var eventId = new EventID(eventIdInteger); return eventId; } public void DestroyEvent(EventID id) { if (_inProgressRequests.Contains(id)) { _inProgressRequests.Remove(id); } if (_failedRequests.Contains(id)) { _failedRequests.Remove(id); } if (_successfulRequests.Contains(id)) { _successfulRequests.Remove(id); } } public GraphicsBuffer GetComputeBuffer(BufferID id) { Debug.Assert(_buffers.ContainsKey(id), "Invalid buffer ID given."); return _buffers[id]; } public CommandBuffer GetCommandBuffer() { return _cmdBuffer; } // Temporary buffers are valid until the next call to Flush(). public BufferID GetTemporaryBuffer(ulong count, ulong stride) { BufferID bufferID = CreateBuffer(count, stride); _temporaryBuffers.Add(bufferID); return bufferID; } private void ReleaseTemporaryBuffers() { foreach (var bufferId in _temporaryBuffers) { if (_buffers.ContainsKey(bufferId)) { DestroyBuffer(bufferId); } } } } }