using System; using System.Diagnostics; using System.Collections.Generic; using Unity.Collections; using CellStreamingScratchBuffer = UnityEngine.Rendering.ProbeReferenceVolume.CellStreamingScratchBuffer; using CellStreamingScratchBufferLayout = UnityEngine.Rendering.ProbeReferenceVolume.CellStreamingScratchBufferLayout; namespace UnityEngine.Rendering { class ProbeVolumeScratchBufferPool { [DebuggerDisplay("ChunkCount = {chunkCount} ElementCount = {pool.Count}")] class ScratchBufferPool : IComparable { public int chunkCount = -1; public Stack pool = new Stack(); public ScratchBufferPool(int chunkCount) { this.chunkCount = chunkCount; } private ScratchBufferPool() { } public int CompareTo(ScratchBufferPool other) { if (chunkCount < other.chunkCount) return -1; else if (chunkCount > other.chunkCount) return 1; else return 0; } } // Size in bytes of a full SH chunk (L0+L1+L2+ValidityMask+Occlusion) public int chunkSize { get; private set; } // Maximum number of chunks that should allocated in total. This is a hint, size may differ a bit to ensure at least one scratch buffer for each required size. public int maxChunkCount { get; private set; } public int allocatedMemory => chunkSize * m_CurrentlyAllocatedChunkCount; int m_L0Size; int m_L1Size; int m_ValiditySize; int m_ValidityLayerCount; int m_L2Size; int m_ProbeOcclusionSize; int m_SkyOcclusionSize; int m_SkyShadingDirectionSize; int m_CurrentlyAllocatedChunkCount = 0; // List and not a Dictionary because we need the list sorted. List m_Pools = new List(); // We store layouts separately because we might use a bigger buffer than required but we still want the layout to match the exact chunk count. Dictionary m_Layouts = new Dictionary(); public ProbeVolumeScratchBufferPool(ProbeVolumeBakingSet bakingSet, ProbeVolumeSHBands shBands) { chunkSize = bakingSet.GetChunkGPUMemory(shBands); maxChunkCount = bakingSet.maxSHChunkCount; m_L0Size = bakingSet.L0ChunkSize; m_L1Size = bakingSet.L1ChunkSize; m_ValiditySize = bakingSet.sharedValidityMaskChunkSize; m_ValidityLayerCount = bakingSet.bakedMaskCount; m_SkyOcclusionSize = bakingSet.sharedSkyOcclusionL0L1ChunkSize; m_SkyShadingDirectionSize = bakingSet.sharedSkyShadingDirectionIndicesChunkSize; m_L2Size = bakingSet.L2TextureChunkSize; m_ProbeOcclusionSize = bakingSet.ProbeOcclusionChunkSize; } CellStreamingScratchBufferLayout GetOrCreateScratchBufferLayout(int chunkCount) { if (m_Layouts.TryGetValue(chunkCount, out var layout)) { return layout; } else { var bufferLayout = new CellStreamingScratchBufferLayout(); bufferLayout._L0Size = m_L0Size; bufferLayout._L1Size = m_L1Size; bufferLayout._ValiditySize = m_ValiditySize; bufferLayout._ValidityProbeSize = m_ValidityLayerCount; // 1layer => 1xbyte => 4 probes at a time. if (m_SkyOcclusionSize != 0) { bufferLayout._SkyOcclusionSize = m_SkyOcclusionSize; bufferLayout._SkyOcclusionProbeSize = 8; // 4xFP16 if (m_SkyShadingDirectionSize != 0) { bufferLayout._SkyShadingDirectionSize = m_SkyShadingDirectionSize; bufferLayout._SkyShadingDirectionProbeSize = 1; // 1xbyte => 4 probes at a time. } else { bufferLayout._SkyShadingDirectionSize = 0; bufferLayout._SkyShadingDirectionProbeSize = 0; } } else { bufferLayout._SkyOcclusionSize = 0; bufferLayout._SkyOcclusionProbeSize = 0; bufferLayout._SkyShadingDirectionSize = 0; bufferLayout._SkyShadingDirectionProbeSize = 0; } bufferLayout._L2Size = m_L2Size; if (m_ProbeOcclusionSize != 0) { bufferLayout._ProbeOcclusionSize = m_ProbeOcclusionSize; bufferLayout._ProbeOcclusionProbeSize = 4; // 4xbyte } else { bufferLayout._ProbeOcclusionSize = 0; bufferLayout._ProbeOcclusionProbeSize = 0; } // TODO: Find a generic way to pass this down? (depends on the gfx format we use). bufferLayout._L0ProbeSize = 8; // 4xFP16 bufferLayout._L1ProbeSize = 4; // 4xbyte bufferLayout._L2ProbeSize = 4; // 4xbyte int destChunksSize = chunkCount * sizeof(uint) * 4; // 1 Chunk == Vector4Int // First destination chunks at offset 0 (no explicit member for this). // Then, shared data destination chunks. Can be different from SH data destination in case of blending // (one pool for blending and one other pool for shared data and blending destination). bufferLayout._SharedDestChunksOffset = destChunksSize; bufferLayout._L0L1rxOffset = bufferLayout._SharedDestChunksOffset + destChunksSize; bufferLayout._L1GryOffset = bufferLayout._L0L1rxOffset + m_L0Size * chunkCount; bufferLayout._L1BrzOffset = bufferLayout._L1GryOffset + m_L1Size * chunkCount; bufferLayout._ValidityOffset = bufferLayout._L1BrzOffset + m_L1Size * chunkCount; bufferLayout._ProbeOcclusionOffset = bufferLayout._ValidityOffset + m_ValiditySize * chunkCount; bufferLayout._SkyOcclusionOffset = bufferLayout._ProbeOcclusionOffset + m_ProbeOcclusionSize * chunkCount; bufferLayout._SkyShadingDirectionOffset = bufferLayout._SkyOcclusionOffset + m_SkyOcclusionSize * chunkCount; bufferLayout._L2_0Offset = bufferLayout._SkyShadingDirectionOffset + m_SkyShadingDirectionSize * chunkCount; bufferLayout._L2_1Offset = bufferLayout._L2_0Offset + m_L2Size * chunkCount; bufferLayout._L2_2Offset = bufferLayout._L2_1Offset + m_L2Size * chunkCount; bufferLayout._L2_3Offset = bufferLayout._L2_2Offset + m_L2Size * chunkCount; bufferLayout._ProbeCountInChunkLine = ProbeBrickPool.kChunkProbeCountPerDim; bufferLayout._ProbeCountInChunkSlice = ProbeBrickPool.kChunkProbeCountPerDim * ProbeBrickPool.kBrickProbeCountPerDim; m_Layouts.Add(chunkCount, bufferLayout); return bufferLayout; } } CellStreamingScratchBuffer CreateScratchBuffer(int chunkCount, bool allocateGraphicsBuffers) { var scratchBuffer = new CellStreamingScratchBuffer(chunkCount, chunkSize, allocateGraphicsBuffers); m_CurrentlyAllocatedChunkCount += chunkCount; return scratchBuffer; } // Used to avoid gcalloc in m_Pools.FindIndex static int s_ChunkCount; // Will return a the smallest GraphicsBuffer with at least enough space for chunkCount chunks. public bool AllocateScratchBuffer(int chunkCount, out CellStreamingScratchBuffer scratchBuffer, out CellStreamingScratchBufferLayout layout, bool allocateGraphicsBuffers) { s_ChunkCount = chunkCount; int index = m_Pools.FindIndex(0, (o) => o.chunkCount == s_ChunkCount); // The size of buffer we return may not be the exact requested size (can be bigger). // So we need to make sure the layout is the right one for the number of requested chunks. layout = GetOrCreateScratchBufferLayout(chunkCount); if (index != -1) { var pool = m_Pools[index].pool; // If one is available, return it. if (pool.Count > 0) { scratchBuffer = pool.Pop(); scratchBuffer.Swap(); return true; } else { // If none are available of the exact size, try to find a bigger buffer instead. // We won't go above twice the size to avoid "wasting" bigger buffers for requests that are too small. for (int i = index; i < m_Pools.Count; ++i) { var biggerPool = m_Pools[i]; // Next existing pools scratch size are too big. Break out of the loop. // We don't want to hog a big buffer for a small request. if (biggerPool.chunkCount >= (chunkCount * 2)) break; else if (biggerPool.pool.Count > 0) { scratchBuffer = biggerPool.pool.Pop(); scratchBuffer.Swap(); return true; } } // We did not find any suitable buffer. Create a new one with the exact requested size. if ((m_CurrentlyAllocatedChunkCount + chunkCount) < maxChunkCount) { scratchBuffer = CreateScratchBuffer(chunkCount, allocateGraphicsBuffers); return true; } // If we are above the maximum, we don't allocate more buffers. // It's ok since we know at least one buffer will exist for any given chunkCount so we won't get impossible to fulfill requests. else { scratchBuffer = null; return false; } } } else { // No pool of this size exists. Create a new pool of that size and return a new buffer; ScratchBufferPool newPool = new ScratchBufferPool(chunkCount); m_Pools.Add(newPool); m_Pools.Sort(); scratchBuffer = CreateScratchBuffer(chunkCount, allocateGraphicsBuffers); return true; } } public void ReleaseScratchBuffer(CellStreamingScratchBuffer scratchBuffer) { if (scratchBuffer.chunkSize != chunkSize) { scratchBuffer.Dispose(); return; } s_ChunkCount = scratchBuffer.chunkCount; var pool = m_Pools.Find((o) => o.chunkCount == s_ChunkCount); Debug.Assert(pool != null); pool.pool.Push(scratchBuffer); } public void Cleanup() { foreach (var pool in m_Pools) { while(pool.pool.Count > 0) { var scratchBuffer = pool.pool.Pop(); scratchBuffer.Dispose(); } } m_Pools.Clear(); m_CurrentlyAllocatedChunkCount = 0; chunkSize = 0; maxChunkCount = 0; } } }