No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RenderGraphResourcePool.cs 7.5KB

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