123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- using System;
- using System.Text;
- using System.Diagnostics;
- using System.Collections.Generic;
- using Unity.Collections;
- using Unity.Collections.LowLevel.Unsafe;
- using UnityEngine.Rendering;
- using UnityEngine.Rendering.RenderGraphModule;
-
- namespace UnityEngine.Rendering.Universal
- {
- // a customized version of RenderGraphResourcePool from SRP core
- internal class RTHandleResourcePool
- {
- // 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).
- // The list needs to be sorted otherwise you could get inconsistent resource usage from one frame to another.
- protected Dictionary<int, SortedList<int, (RTHandle resource, int frameIndex)>> m_ResourcePool = new Dictionary<int, SortedList<int, (RTHandle resource, int frameIndex)>>();
- protected List<int> m_RemoveList = new List<int>(32); // Used to remove stale resources as there is no RemoveAll on SortedLists
-
- protected static int s_CurrentStaleResourceCount = 0;
- // Keep stale resources alive for 3 frames
- protected static int s_StaleResourceLifetime = 3;
- // Store max 32 rtHandles
- // 1080p * 32bpp * 32 = 265.4mb
- protected static int s_StaleResourceMaxCapacity = 32;
-
- /// <summary>
- /// Controls the resource pool's max stale resource capacity.
- /// Increasing the capacity may have a negative impact on the memory usage.
- /// Increasing the capacity may reduce the runtime RTHandle realloc cost in multi view/multi camera setup.
- /// Setting capacity will purge the current pool. It is recommended to setup the capacity upfront and not changing it during the runtime.
- /// Setting capacity won't do anything if new capacity is the same to the current capacity.
- /// </summary>
- internal int staleResourceCapacity
- {
- get { return s_StaleResourceMaxCapacity; }
- set {
- if (s_StaleResourceMaxCapacity != value)
- {
- s_StaleResourceMaxCapacity = value;
- Cleanup();
- }
- }
- }
-
- // Add no longer used resouce to pool
- // Return true if resource is added to pool successfully, return false otherwise.
- internal bool AddResourceToPool(in TextureDesc texDesc, RTHandle resource, int currentFrameIndex)
- {
- if (s_CurrentStaleResourceCount >= s_StaleResourceMaxCapacity)
- return false;
-
- int hashCode = GetHashCodeWithNameHash(texDesc);
-
- if (!m_ResourcePool.TryGetValue(hashCode, out var list))
- {
- // Init list with max capacity to avoid runtime GC.Alloc when calling list.Add(resize list)
- list = new SortedList<int, (RTHandle resource, int frameIndex)>(s_StaleResourceMaxCapacity);
- m_ResourcePool.Add(hashCode, list);
- }
-
- list.Add(resource.GetInstanceID(), (resource, currentFrameIndex));
- s_CurrentStaleResourceCount++;
-
- return true;
- }
-
- // Get resource from the pool using TextureDesc as key
- // Return true if resource successfully retried resource from the pool, return false otherwise.
- internal bool TryGetResource(in TextureDesc texDesc, out RTHandle resource, bool usepool = true)
- {
- int hashCode = GetHashCodeWithNameHash(texDesc);
- if (usepool && m_ResourcePool.TryGetValue(hashCode, out SortedList<int, (RTHandle resource, int frameIndex)> list) && list.Count > 0)
- {
- resource = list.Values[list.Count - 1].resource;
- list.RemoveAt(list.Count - 1); // O(1) since it's the last element.
- s_CurrentStaleResourceCount--;
- return true;
- }
-
- resource = null;
- return false;
- }
-
- // Release all resources in pool.
- internal void Cleanup()
- {
- foreach (var kvp in m_ResourcePool)
- {
- foreach (var res in kvp.Value)
- {
- res.Value.resource.Release();
- }
- }
- m_ResourcePool.Clear();
-
- s_CurrentStaleResourceCount = 0;
- }
-
- static protected bool ShouldReleaseResource(int lastUsedFrameIndex, int currentFrameIndex)
- {
- // We need to have a delay of a few frames before releasing resources for good.
- // Indeed, when having multiple off-screen cameras, they are rendered in a separate SRP render call and thus with a different frame index than main camera
- // This causes texture to be deallocated/reallocated every frame if the two cameras don't need the same buffers.
- return (lastUsedFrameIndex + s_StaleResourceLifetime) < currentFrameIndex;
- }
-
- // Release resources that are not used in last couple frames.
- internal void PurgeUnusedResources(int currentFrameIndex)
- {
- // Update the frame index for the lambda. Static because we don't want to capture.
- m_RemoveList.Clear();
-
- foreach (var kvp in m_ResourcePool)
- {
- // WARNING: No foreach here. Sorted list GetEnumerator generates garbage...
- var list = kvp.Value;
- var keys = list.Keys;
- var values = list.Values;
- for (int i = 0; i < list.Count; ++i)
- {
- var value = values[i];
- if (ShouldReleaseResource(value.frameIndex, currentFrameIndex))
- {
- value.resource.Release();
- m_RemoveList.Add(keys[i]);
- s_CurrentStaleResourceCount--;
- }
- }
-
- foreach (var key in m_RemoveList)
- list.Remove(key);
- }
- }
-
- internal void LogDebugInfo()
- {
- var sb = new StringBuilder();
- sb.AppendFormat("RTHandleResourcePool for frame {0}, Total stale resources {1}", Time.frameCount, s_CurrentStaleResourceCount);
- sb.AppendLine();
-
- foreach (var kvp in m_ResourcePool)
- {
- var list = kvp.Value;
- var keys = list.Keys;
- var values = list.Values;
- for (int i = 0; i < list.Count; ++i)
- {
- var value = values[i];
- sb.AppendFormat("Resrouce in pool: Name {0} Last active frame index {1} Size {2} x {3} x {4}",
- value.resource.name,
- value.frameIndex,
- value.resource.rt.descriptor.width,
- value.resource.rt.descriptor.height,
- value.resource.rt.descriptor.volumeDepth
- );
- sb.AppendLine();
- }
- }
-
- Debug.Log(sb);
- }
-
- // NOTE: Only allow reusing resource with the same name.
- // This is because some URP code uses texture name as key to bind input texture (GBUFFER_2). Different name will result in URP bind texture to different shader input slot.
- // Ideally if URP code uses shaderPropertyID(instead of name string), we can relax the restriction here.
- internal int GetHashCodeWithNameHash(in TextureDesc texDesc)
- {
- int hashCode = texDesc.GetHashCode();
- hashCode = hashCode * 23 + texDesc.name.GetHashCode();
-
- return hashCode;
- }
-
- internal static TextureDesc CreateTextureDesc(RenderTextureDescriptor desc,
- TextureSizeMode textureSizeMode = TextureSizeMode.Explicit, int anisoLevel = 1, float mipMapBias = 0,
- FilterMode filterMode = FilterMode.Point, TextureWrapMode wrapMode = TextureWrapMode.Clamp, string name = "")
- {
- TextureDesc rgDesc = new TextureDesc(desc.width, desc.height);
- rgDesc.sizeMode = textureSizeMode;
- rgDesc.slices = desc.volumeDepth;
- rgDesc.depthBufferBits = (DepthBits)desc.depthBufferBits;
- rgDesc.colorFormat = desc.graphicsFormat;
- rgDesc.filterMode = filterMode;
- rgDesc.wrapMode = wrapMode;
- rgDesc.dimension = desc.dimension;
- rgDesc.enableRandomWrite = desc.enableRandomWrite;
- rgDesc.useMipMap = desc.useMipMap;
- rgDesc.autoGenerateMips = desc.autoGenerateMips;
- rgDesc.isShadowMap = desc.shadowSamplingMode != ShadowSamplingMode.None;
- rgDesc.anisoLevel = anisoLevel;
- rgDesc.mipMapBias = mipMapBias;
- rgDesc.msaaSamples = (MSAASamples)desc.msaaSamples;
- rgDesc.bindTextureMS = desc.bindMS;
- rgDesc.useDynamicScale = desc.useDynamicScale;
- rgDesc.memoryless = RenderTextureMemoryless.None;
- rgDesc.vrUsage = VRTextureUsage.None;
- rgDesc.name = name;
-
- return rgDesc;
- }
- }
- }
|