using System.Collections.Generic; using UnityEngine.Rendering; using UnityEngine.Rendering.RenderGraphModule; using UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler; class RenderGraphCompilationCache { struct HashEntry { public int hash; public int lastFrameUsed; public T compiledGraph; } DynamicArray> m_HashEntries = new(); DynamicArray> m_NativeHashEntries = new(); Stack m_CompiledGraphPool = new(); Stack m_NativeCompiledGraphPool = new(); static int HashEntryComparer(HashEntry a, HashEntry b) { if (a.lastFrameUsed < b.lastFrameUsed) return -1; else if (a.lastFrameUsed > b.lastFrameUsed) return 1; else return 0; } static DynamicArray>.SortComparer s_EntryComparer = HashEntryComparer; static DynamicArray>.SortComparer s_NativeEntryComparer = HashEntryComparer; const int k_CachedGraphCount = 20; public RenderGraphCompilationCache() { for (int i = 0; i < k_CachedGraphCount; ++i) { m_CompiledGraphPool.Push(new RenderGraph.CompiledGraph()); m_NativeCompiledGraphPool.Push(new CompilerContextData()); } } // Avoid GC in lambda. static int s_Hash; bool GetCompilationCache(int hash, int frameIndex, out T outGraph, DynamicArray> hashEntries, Stack pool, DynamicArray>.SortComparer comparer) where T : RenderGraph.ICompiledGraph { s_Hash = hash; int index = hashEntries.FindIndex(value => value.hash == s_Hash); if (index != -1) { ref var entry = ref hashEntries[index]; outGraph = entry.compiledGraph; entry.lastFrameUsed = frameIndex; return true; } else { if (pool.Count != 0) { var newEntry = new HashEntry() { hash = hash, lastFrameUsed = frameIndex, compiledGraph = pool.Pop() }; hashEntries.Add(newEntry); outGraph = newEntry.compiledGraph; return false; } else { // Reuse the oldest one. hashEntries.QuickSort(comparer); ref var oldestEntry = ref hashEntries[0]; oldestEntry.hash = hash; oldestEntry.lastFrameUsed = frameIndex; oldestEntry.compiledGraph.Clear(); outGraph = oldestEntry.compiledGraph; return false; } } } public bool GetCompilationCache(int hash, int frameIndex, out RenderGraph.CompiledGraph outGraph) { return GetCompilationCache(hash, frameIndex, out outGraph, m_HashEntries, m_CompiledGraphPool, s_EntryComparer); } public bool GetCompilationCache(int hash, int frameIndex, out CompilerContextData outGraph) { return GetCompilationCache(hash, frameIndex, out outGraph, m_NativeHashEntries, m_NativeCompiledGraphPool, s_NativeEntryComparer); } public void Clear() { for (int i = 0; i < m_HashEntries.size; ++i) { var compiledGraph = m_HashEntries[i].compiledGraph; compiledGraph.Clear(); m_CompiledGraphPool.Push(m_HashEntries[i].compiledGraph); } m_HashEntries.Clear(); for (int i = 0; i < m_NativeHashEntries.size; ++i) { var compiledGraph = m_NativeHashEntries[i].compiledGraph; compiledGraph.Clear(); m_NativeCompiledGraphPool.Push(compiledGraph); } m_NativeHashEntries.Clear(); } public void Cleanup() { // We clear the contents of the pools but not the pool themselves, because they are only // filled at the beginning of the renderer pipeline and never after. This means when we call // Cleanup() after an error, if we were clearing the pools, the render graph could not gracefully start // back up because the cache would have a size of 0 (so no room to cache anything). // Cleanup compiled graphs currently in the cache for (int i = 0; i < m_HashEntries.size; ++i) { var compiledGraph = m_HashEntries[i].compiledGraph; compiledGraph.Clear(); } m_HashEntries.Clear(); // Cleanup compiled graphs that might be left in the pool var compiledGraphs = m_CompiledGraphPool.ToArray(); for (int i = 0; i < compiledGraphs.Length; ++i) { compiledGraphs[i].Clear(); } // Dispose of CompilerContextData currently in the cache for (int i = 0; i < m_NativeHashEntries.size; ++i) { var compiledGraph = m_NativeHashEntries[i].compiledGraph; compiledGraph.Dispose(); } m_NativeHashEntries.Clear(); // Dispose of CompilerContextData that might be left in the pool var nativeCompiledGraphs = m_NativeCompiledGraphPool.ToArray(); for (int i = 0; i < nativeCompiledGraphs.Length; ++i) { nativeCompiledGraphs[i].Dispose(); } } }