A game about forced loneliness, made by TACStudios
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}