209 lines
9.3 KiB
C#
209 lines
9.3 KiB
C#
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;
|
|
using UnityEngine.Experimental.Rendering;
|
|
|
|
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 = "")
|
|
{
|
|
Assertions.Assert.IsFalse(desc.graphicsFormat != GraphicsFormat.None && desc.depthStencilFormat != GraphicsFormat.None,
|
|
"The RenderTextureDescriptor used to create a TextureDesc contains both graphicsFormat and depthStencilFormat which is not allowed.");
|
|
|
|
var format = (desc.depthStencilFormat != GraphicsFormat.None) ? desc.depthStencilFormat : desc.graphicsFormat;
|
|
|
|
TextureDesc rgDesc = new TextureDesc(desc.width, desc.height);
|
|
rgDesc.sizeMode = textureSizeMode;
|
|
rgDesc.slices = desc.volumeDepth;
|
|
rgDesc.format = format;
|
|
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;
|
|
}
|
|
}
|
|
}
|