A game about forced loneliness, made by TACStudios
at master 179 lines 7.6 kB view raw
1using System.Collections.Generic; 2using System.Diagnostics; 3using System.Runtime.CompilerServices; 4 5namespace UnityEngine.Rendering.RenderGraphModule 6{ 7 abstract class IRenderGraphResourcePool 8 { 9 public abstract void PurgeUnusedResources(int currentFrameIndex); 10 public abstract void Cleanup(); 11 public abstract void CheckFrameAllocation(bool onException, int frameIndex); 12 public abstract void LogResources(RenderGraphLogger logger); 13 } 14 15 abstract class RenderGraphResourcePool<Type> : IRenderGraphResourcePool where Type : class 16 { 17 // Dictionary tracks resources by hash and stores resources with same hash in a List (list instead of a stack because we need to be able to remove stale allocations, potentially in the middle of the stack). 18 // The list needs to be sorted otherwise you could get inconsistent resource usage from one frame to another. 19 20 protected Dictionary<int, SortedList<int, (Type resource, int frameIndex)>> m_ResourcePool = new Dictionary<int, SortedList<int, (Type resource, int frameIndex)>>(); 21 22 // This list allows us to determine if all resources were correctly released in the frame when validity checks are enabled. 23 // This is useful to warn in case of user error or avoid leaks when a render graph execution errors occurs for example. 24 List<(int, Type)> m_FrameAllocatedResources = new List<(int, Type)>(); 25 const int kStaleResourceLifetime = 10; 26 27 // Release the GPU resource itself 28 protected abstract void ReleaseInternalResource(Type res); 29 protected abstract string GetResourceName(in Type res); 30 protected abstract long GetResourceSize(in Type res); 31 protected abstract string GetResourceTypeName(); 32 protected abstract int GetSortIndex(Type res); 33 34 public void ReleaseResource(int hash, Type resource, int currentFrameIndex) 35 { 36 if (!m_ResourcePool.TryGetValue(hash, out var list)) 37 { 38 list = new SortedList<int, (Type, int)>(); 39 m_ResourcePool.Add(hash, list); 40 } 41 42 list.Add(GetSortIndex(resource), (resource, currentFrameIndex)); 43 } 44 45 public bool TryGetResource(int hashCode, out Type resource) 46 { 47 if (m_ResourcePool.TryGetValue(hashCode, out SortedList<int, (Type resource, int frameIndex)> list) && list.Count > 0) 48 { 49 var index = list.Count - 1; 50 resource = list.Values[index].resource; 51 list.RemoveAt(index); // O(1) since it's the last element. 52 return true; 53 } 54 55 resource = null; 56 return false; 57 } 58 59 public override void Cleanup() 60 { 61 foreach (var kvp in m_ResourcePool) 62 { 63 foreach (var res in kvp.Value) 64 { 65 ReleaseInternalResource(res.Value.resource); 66 } 67 } 68 } 69 70 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] 71 public void RegisterFrameAllocation(int hash, Type value) 72 { 73 if (RenderGraph.enableValidityChecks && hash != -1) 74 m_FrameAllocatedResources.Add((hash, value)); 75 } 76 77 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] 78 public void UnregisterFrameAllocation(int hash, Type value) 79 { 80 if (RenderGraph.enableValidityChecks && hash != -1) 81 m_FrameAllocatedResources.Remove((hash, value)); 82 } 83 84 public override void CheckFrameAllocation(bool onException, int frameIndex) 85 { 86#if DEVELOPMENT_BUILD || UNITY_EDITOR // conditional not working with override 87 if (RenderGraph.enableValidityChecks) 88 { 89 // In case of exception we need to release all resources to the pool to avoid leaking. 90 // If it's not an exception then it's a user error so we need to log the problem. 91 if (m_FrameAllocatedResources.Count != 0) 92 { 93 string logMessage = ""; 94 if (!onException) 95 logMessage = $"RenderGraph: Not all resources of type {GetResourceTypeName()} were released. This can be caused by a resources being allocated but never read by any pass."; 96 97 foreach (var value in m_FrameAllocatedResources) 98 { 99 if (!onException) 100 logMessage = $"{logMessage}\n\t{GetResourceName(value.Item2)}"; 101 ReleaseResource(value.Item1, value.Item2, frameIndex); 102 } 103 104 Debug.LogWarning(logMessage); 105 } 106 107 // If an error occurred during execution, it's expected that textures are not all released so we clear the tracking list. 108 m_FrameAllocatedResources.Clear(); 109 } 110#endif 111 } 112 113 struct ResourceLogInfo 114 { 115 public string name; 116 public long size; 117 } 118 119 public override void LogResources(RenderGraphLogger logger) 120 { 121 List<ResourceLogInfo> allocationList = new List<ResourceLogInfo>(); 122 foreach (var kvp in m_ResourcePool) 123 { 124 foreach (var res in kvp.Value) 125 { 126 allocationList.Add(new ResourceLogInfo { name = GetResourceName(res.Value.resource), size = GetResourceSize(res.Value.resource) }); 127 } 128 } 129 130 logger.LogLine($"== {GetResourceTypeName()} Resources =="); 131 132 allocationList.Sort((a, b) => a.size < b.size ? 1 : -1); 133 int index = 0; 134 float total = 0; 135 foreach (var element in allocationList) 136 { 137 float size = element.size / (1024.0f * 1024.0f); 138 total += size; 139 logger.LogLine($"[{index++:D2}]\t[{size:0.00} MB]\t{element.name}"); 140 } 141 142 logger.LogLine($"\nTotal Size [{total:0.00}]"); 143 } 144 145 static List<int> s_ToRemoveList = new List<int>(32); 146 147 public override void PurgeUnusedResources(int currentFrameIndex) 148 { 149 foreach (var kvp in m_ResourcePool) 150 { 151 s_ToRemoveList.Clear(); 152 153 var list = kvp.Value; 154 var keys = list.Keys; 155 var values = list.Values; 156 for (int i = 0; i < list.Count; ++i) 157 { 158 var value = values[i]; 159 var key = keys[i]; 160 161 // When a resource hasn't been used for a few frames, we release it for good to reduce memory footprint. 162 // We wait a few frames because when having multiple off-screen cameras, 163 // they are rendered in a separate SRP render call and thus with a different frame index than main camera 164 // This causes texture to be deallocated/reallocated every frame if the two cameras don't need the same buffers. 165 if (value.frameIndex + kStaleResourceLifetime < currentFrameIndex) 166 { 167 ReleaseInternalResource(value.resource); 168 // Adding stale resource to the remove list 169 s_ToRemoveList.Add(key); 170 } 171 } 172 173 // Removing the stale resource from the pool 174 for (int j = 0; j < s_ToRemoveList.Count; ++j) 175 list.Remove(s_ToRemoveList[j]); 176 } 177 } 178 } 179}