This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
/// <summary>
/// Use this struct to set up a new Render Pass.
/// </summary>
public struct RenderGraphBuilder : IDisposable
{
RenderGraphPass m_RenderPass;
RenderGraphResourceRegistry m_Resources;
RenderGraph m_RenderGraph;
bool m_Disposed;
#region Public Interface
/// <summary>
/// Specify that the pass will use a Texture resource as a color render target.
/// This has the same effect as WriteTexture and also automatically sets the Texture to use as a render target.
/// </summary>
/// <param name="input">The Texture resource to use as a color render target.</param>
/// <param name="index">Index for multiple render target usage.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public TextureHandle UseColorBuffer(in TextureHandle input, int index)
{
CheckResource(input.handle);
m_Resources.IncrementWriteCount(input.handle);
m_RenderPass.SetColorBuffer(input, index);
return input;
}
/// <summary>
/// Specify that the pass will use a Texture resource as a depth buffer.
/// </summary>
/// <param name="input">The Texture resource to use as a depth buffer during the pass.</param>
/// <param name="flags">Specify the access level for the depth buffer. This allows you to say whether you will read from or write to the depth buffer, or do both.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public TextureHandle UseDepthBuffer(in TextureHandle input, DepthAccess flags)
{
CheckResource(input.handle);
m_Resources.IncrementWriteCount(input.handle);
m_RenderPass.SetDepthBuffer(input, flags);
return input;
}
/// <summary>
/// Specify a Texture resource to read from during the pass.
/// </summary>
/// <param name="input">The Texture resource to read from during the pass.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public TextureHandle ReadTexture(in TextureHandle input)
{
CheckResource(input.handle);
if (!m_Resources.IsRenderGraphResourceImported(input.handle) && m_Resources.TextureNeedsFallback(input))
{
var texDimension = m_Resources.GetTextureResourceDesc(input.handle).dimension;
if (texDimension == TextureXR.dimension)
{
return m_RenderGraph.defaultResources.blackTextureXR;
}
else if (texDimension == TextureDimension.Tex3D)
{
return m_RenderGraph.defaultResources.blackTexture3DXR;
}
else
{
return m_RenderGraph.defaultResources.blackTexture;
}
}
m_RenderPass.AddResourceRead(input.handle);
return input;
}
/// <summary>
/// Specify a Texture resource to write to during the pass.
/// </summary>
/// <param name="input">The Texture resource to write to during the pass.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public TextureHandle WriteTexture(in TextureHandle input)
{
CheckResource(input.handle);
m_Resources.IncrementWriteCount(input.handle);
// TODO RENDERGRAPH: Manage resource "version" for debugging purpose
m_RenderPass.AddResourceWrite(input.handle);
return input;
}
/// <summary>
/// Specify a Texture resource to read and write to during the pass.
/// </summary>
/// <param name="input">The Texture resource to read and write to during the pass.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public TextureHandle ReadWriteTexture(in TextureHandle input)
{
CheckResource(input.handle);
m_Resources.IncrementWriteCount(input.handle);
m_RenderPass.AddResourceWrite(input.handle);
m_RenderPass.AddResourceRead(input.handle);
return input;
}
/// <summary>
/// Create a new Render Graph Texture resource.
/// This texture will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations.
/// </summary>
/// <param name="desc">Texture descriptor.</param>
/// <returns>A new transient TextureHandle.</returns>
public TextureHandle CreateTransientTexture(in TextureDesc desc)
{
var result = m_Resources.CreateTexture(desc, m_RenderPass.index);
m_RenderPass.AddTransientResource(result.handle);
return result;
}
/// <summary>
/// Create a new Render Graph Texture resource using the descriptor from another texture.
/// This texture will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations.
/// </summary>
/// <param name="texture">Texture from which the descriptor should be used.</param>
/// <returns>A new transient TextureHandle.</returns>
public TextureHandle CreateTransientTexture(in TextureHandle texture)
{
var desc = m_Resources.GetTextureResourceDesc(texture.handle);
var result = m_Resources.CreateTexture(desc, m_RenderPass.index);
m_RenderPass.AddTransientResource(result.handle);
return result;
}
/// <summary>
/// Specify a Renderer List resource to use during the pass.
/// </summary>
/// <param name="input">The Renderer List resource to use during the pass.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public RendererListHandle UseRendererList(in RendererListHandle input)
{
m_RenderPass.UseRendererList(input);
return input;
}
/// <summary>
/// Specify a Compute Buffer resource to read from during the pass.
/// </summary>
/// <param name="input">The Compute Buffer resource to read from during the pass.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public ComputeBufferHandle ReadComputeBuffer(in ComputeBufferHandle input)
{
CheckResource(input.handle);
m_RenderPass.AddResourceRead(input.handle);
return input;
}
/// <summary>
/// Specify a Compute Buffer resource to write to during the pass.
/// </summary>
/// <param name="input">The Compute Buffer resource to write to during the pass.</param>
/// <returns>An updated resource handle to the input resource.</returns>
public ComputeBufferHandle WriteComputeBuffer(in ComputeBufferHandle input)
{
CheckResource(input.handle);
m_RenderPass.AddResourceWrite(input.handle);
m_Resources.IncrementWriteCount(input.handle);
return input;
}
/// <summary>
/// Create a new Render Graph Compute Buffer resource.
/// This Compute Buffer will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations.
/// </summary>
/// <param name="desc">Compute Buffer descriptor.</param>
/// <returns>A new transient ComputeBufferHandle.</returns>
public ComputeBufferHandle CreateTransientComputeBuffer(in ComputeBufferDesc desc)
{
var result = m_Resources.CreateComputeBuffer(desc, m_RenderPass.index);
m_RenderPass.AddTransientResource(result.handle);
return result;
}
/// <summary>
/// Create a new Render Graph Compute Buffer resource using the descriptor from another Compute Buffer.
/// This Compute Buffer will only be available for the current pass and will be assumed to be both written and read so users don't need to add explicit read/write declarations.
/// </summary>
/// <param name="computebuffer">Compute Buffer from which the descriptor should be used.</param>
/// <returns>A new transient ComputeBufferHandle.</returns>
public ComputeBufferHandle CreateTransientComputeBuffer(in ComputeBufferHandle computebuffer)
{
var desc = m_Resources.GetComputeBufferResourceDesc(computebuffer.handle);
var result = m_Resources.CreateComputeBuffer(desc, m_RenderPass.index);
m_RenderPass.AddTransientResource(result.handle);
return result;
}
/// <summary>
/// Specify the render function to use for this pass.
/// A call to this is mandatory for the pass to be valid.
/// </summary>
/// <typeparam name="PassData">The Type of the class that provides data to the Render Pass.</typeparam>
/// <param name="renderFunc">Render function for the pass.</param>
public void SetRenderFunc<PassData>(RenderFunc<PassData> renderFunc) where PassData : class, new()
{
((RenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
}
/// <summary>
/// Enable asynchronous compute for this pass.
/// </summary>
/// <param name="value">Set to true to enable asynchronous compute.</param>
public void EnableAsyncCompute(bool value)
{
m_RenderPass.EnableAsyncCompute(value);
}
/// <summary>
/// Allow or not pass culling
/// By default all passes can be culled out if the render graph detects it's not actually used.
/// In some cases, a pass may not write or read any texture but rather do something with side effects (like setting a global texture parameter for example).
/// This function can be used to tell the system that it should not cull this pass.
/// </summary>
/// <param name="value">True to allow pass culling.</param>
public void AllowPassCulling(bool value)
{
m_RenderPass.AllowPassCulling(value);
}
/// <summary>
/// Dispose the RenderGraphBuilder instance.
/// </summary>
public void Dispose()
{
Dispose(true);
}
#endregion
#region Internal Interface
internal RenderGraphBuilder(RenderGraphPass renderPass, RenderGraphResourceRegistry resources, RenderGraph renderGraph)
{
m_RenderPass = renderPass;
m_Resources = resources;
m_RenderGraph = renderGraph;
m_Disposed = false;
}
void Dispose(bool disposing)
{
if (m_Disposed)
return;
m_RenderGraph.OnPassAdded(m_RenderPass);
m_Disposed = true;
}
void CheckResource(in ResourceHandle res)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (res.IsValid())
{
int transientIndex = m_Resources.GetRenderGraphResourceTransientIndex(res);
if (transientIndex == m_RenderPass.index)
{
Debug.LogError($"Trying to read or write a transient resource at pass {m_RenderPass.name}.Transient resource are always assumed to be both read and written.");
}
if (transientIndex != -1 && transientIndex != m_RenderPass.index)
{
throw new ArgumentException($"Trying to use a transient texture (pass index {transientIndex}) in a different pass (pass index {m_RenderPass.index}).");
}
}
else
{
throw new ArgumentException($"Trying to use an invalid resource (pass {m_RenderPass.name}).");
}
#endif
}
internal void GenerateDebugData(bool value)
{
m_RenderPass.GenerateDebugData(value);
}
#endregion
}
}

View File

@@ -0,0 +1,61 @@
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
/// <summary>
/// Helper class allowing access to default resources (black or white texture, etc.) during render passes.
/// </summary>
public class RenderGraphDefaultResources
{
bool m_IsValid;
// We need to keep around a RTHandle version of default regular 2D textures since RenderGraph API is all RTHandle.
RTHandle m_BlackTexture2D;
RTHandle m_WhiteTexture2D;
/// <summary>Default black 2D texture.</summary>
public TextureHandle blackTexture { get; private set; }
/// <summary>Default white 2D texture.</summary>
public TextureHandle whiteTexture { get; private set; }
/// <summary>Default clear color XR 2D texture.</summary>
public TextureHandle clearTextureXR { get; private set; }
/// <summary>Default magenta XR 2D texture.</summary>
public TextureHandle magentaTextureXR { get; private set; }
/// <summary>Default black XR 2D texture.</summary>
public TextureHandle blackTextureXR { get; private set; }
/// <summary>Default black XR 2D Array texture.</summary>
public TextureHandle blackTextureArrayXR { get; private set; }
/// <summary>Default black (UInt) XR 2D texture.</summary>
public TextureHandle blackUIntTextureXR { get; private set; }
/// <summary>Default black XR 3D texture.</summary>
public TextureHandle blackTexture3DXR { get; private set; }
/// <summary>Default white XR 2D texture.</summary>
public TextureHandle whiteTextureXR { get; private set; }
internal RenderGraphDefaultResources()
{
m_BlackTexture2D = RTHandles.Alloc(Texture2D.blackTexture);
m_WhiteTexture2D = RTHandles.Alloc(Texture2D.whiteTexture);
}
internal void Cleanup()
{
m_BlackTexture2D.Release();
m_WhiteTexture2D.Release();
}
internal void InitializeForRendering(RenderGraph renderGraph)
{
blackTexture = renderGraph.ImportTexture(m_BlackTexture2D);
whiteTexture = renderGraph.ImportTexture(m_WhiteTexture2D);
clearTextureXR = renderGraph.ImportTexture(TextureXR.GetClearTexture());
magentaTextureXR = renderGraph.ImportTexture(TextureXR.GetMagentaTexture());
blackTextureXR = renderGraph.ImportTexture(TextureXR.GetBlackTexture());
blackTextureArrayXR = renderGraph.ImportTexture(TextureXR.GetBlackTextureArray());
blackUIntTextureXR = renderGraph.ImportTexture(TextureXR.GetBlackUIntTexture());
blackTexture3DXR = renderGraph.ImportTexture(TextureXR.GetBlackTexture3D());
whiteTextureXR = renderGraph.ImportTexture(TextureXR.GetWhiteTexture());
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
using System.Text;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
struct RenderGraphLogIndent : IDisposable
{
int m_Indentation;
RenderGraphLogger m_Logger;
bool m_Disposed;
public RenderGraphLogIndent(RenderGraphLogger logger, int indentation = 1)
{
m_Disposed = false;
m_Indentation = indentation;
m_Logger = logger;
m_Logger.IncrementIndentation(m_Indentation);
}
public void Dispose()
{
Dispose(true);
}
void Dispose(bool disposing)
{
Debug.Assert(m_Logger != null, "RenderGraphLogIndent: logger parameter should not be null.");
if (m_Disposed)
return;
if (disposing && m_Logger != null)
{
m_Logger.DecrementIndentation(m_Indentation);
}
m_Disposed = true;
}
}
class RenderGraphLogger
{
StringBuilder m_Builder = new StringBuilder();
int m_CurrentIndentation;
public void Initialize()
{
m_Builder.Clear();
m_CurrentIndentation = 0;
}
public void IncrementIndentation(int value)
{
m_CurrentIndentation += Math.Abs(value);
}
public void DecrementIndentation(int value)
{
m_CurrentIndentation = Math.Max(0, m_CurrentIndentation - Math.Abs(value));
}
public void LogLine(string format, params object[] args)
{
for (int i = 0; i < m_CurrentIndentation; ++i)
m_Builder.Append('\t');
m_Builder.AppendFormat(format, args);
m_Builder.AppendLine();
}
public string GetLog()
{
return m_Builder.ToString();
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
/// <summary>
/// Helper class provided in the RenderGraphContext to all Render Passes.
/// It allows you to do temporary allocations of various objects during a Render Pass.
/// </summary>
public sealed class RenderGraphObjectPool
{
class SharedObjectPool<T> where T : new()
{
Stack<T> m_Pool = new Stack<T>();
public T Get()
{
var result = m_Pool.Count == 0 ? new T() : m_Pool.Pop();
return result;
}
public void Release(T value)
{
m_Pool.Push(value);
}
static readonly Lazy<SharedObjectPool<T>> s_Instance = new Lazy<SharedObjectPool<T>>();
public static SharedObjectPool<T> sharedPool => s_Instance.Value;
}
Dictionary<(Type, int), Stack<object>> m_ArrayPool = new Dictionary<(Type, int), Stack<object>>();
List<(object, (Type, int))> m_AllocatedArrays = new List<(object, (Type, int))>();
List<MaterialPropertyBlock> m_AllocatedMaterialPropertyBlocks = new List<MaterialPropertyBlock>();
internal RenderGraphObjectPool() {}
/// <summary>
/// Allocate a temporary typed array of a specific size.
/// Unity releases the array at the end of the Render Pass.
/// </summary>
/// <typeparam name="T">Type of the array to be allocated.</typeparam>
/// <param name="size">Number of element in the array.</param>
/// <returns>A new array of type T with size number of elements.</returns>
public T[] GetTempArray<T>(int size)
{
if (!m_ArrayPool.TryGetValue((typeof(T), size), out var stack))
{
stack = new Stack<object>();
m_ArrayPool.Add((typeof(T), size), stack);
}
var result = stack.Count > 0 ? (T[])stack.Pop() : new T[size];
m_AllocatedArrays.Add((result, (typeof(T), size)));
return result;
}
/// <summary>
/// Allocate a temporary MaterialPropertyBlock for the Render Pass.
/// </summary>
/// <returns>A new clean MaterialPropertyBlock.</returns>
public MaterialPropertyBlock GetTempMaterialPropertyBlock()
{
var result = SharedObjectPool<MaterialPropertyBlock>.sharedPool.Get();
result.Clear();
m_AllocatedMaterialPropertyBlocks.Add(result);
return result;
}
internal void ReleaseAllTempAlloc()
{
foreach (var arrayDesc in m_AllocatedArrays)
{
bool result = m_ArrayPool.TryGetValue(arrayDesc.Item2, out var stack);
Debug.Assert(result, "Correct stack type should always be allocated.");
stack.Push(arrayDesc.Item1);
}
m_AllocatedArrays.Clear();
foreach (var mpb in m_AllocatedMaterialPropertyBlocks)
{
SharedObjectPool<MaterialPropertyBlock>.sharedPool.Release(mpb);
}
m_AllocatedMaterialPropertyBlocks.Clear();
}
// Regular pooling API. Only internal use for now
internal T Get<T>() where T : new()
{
var pool = SharedObjectPool<T>.sharedPool;
return pool.Get();
}
internal void Release<T>(T value) where T : new()
{
var pool = SharedObjectPool<T>.sharedPool;
pool.Release(value);
}
}
}

View File

@@ -0,0 +1,163 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
abstract class RenderGraphPass
{
public RenderFunc<PassData> GetExecuteDelegate<PassData>()
where PassData : class, new() => ((RenderGraphPass<PassData>) this).renderFunc;
public abstract void Execute(RenderGraphContext renderGraphContext);
public abstract void Release(RenderGraphObjectPool pool);
public abstract bool HasRenderFunc();
public string name { get; protected set; }
public int index { get; protected set; }
public ProfilingSampler customSampler { get; protected set; }
public bool enableAsyncCompute { get; protected set; }
public bool allowPassCulling { get; protected set; }
public TextureHandle depthBuffer { get; protected set; }
public TextureHandle[] colorBuffers { get; protected set; } = new TextureHandle[RenderGraph.kMaxMRTCount];
public int colorBufferMaxIndex { get; protected set; } = -1;
public int refCount { get; protected set; }
public bool generateDebugData { get; protected set; }
public List<ResourceHandle>[] resourceReadLists = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
public List<ResourceHandle>[] resourceWriteLists = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
public List<ResourceHandle>[] transientResourceList = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
public List<RendererListHandle> usedRendererListList = new List<RendererListHandle>();
public RenderGraphPass()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
resourceReadLists[i] = new List<ResourceHandle>();
resourceWriteLists[i] = new List<ResourceHandle>();
transientResourceList[i] = new List<ResourceHandle>();
}
}
public void Clear()
{
name = "";
index = -1;
customSampler = null;
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
resourceReadLists[i].Clear();
resourceWriteLists[i].Clear();
transientResourceList[i].Clear();
}
usedRendererListList.Clear();
enableAsyncCompute = false;
allowPassCulling = true;
generateDebugData = true;
refCount = 0;
// Invalidate everything
colorBufferMaxIndex = -1;
depthBuffer = TextureHandle.nullHandle;
for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i)
{
colorBuffers[i] = TextureHandle.nullHandle;
}
}
public void AddResourceWrite(in ResourceHandle res)
{
resourceWriteLists[res.iType].Add(res);
}
public void AddResourceRead(in ResourceHandle res)
{
resourceReadLists[res.iType].Add(res);
}
public void AddTransientResource(in ResourceHandle res)
{
transientResourceList[res.iType].Add(res);
}
public void UseRendererList(RendererListHandle rendererList)
{
usedRendererListList.Add(rendererList);
}
public void EnableAsyncCompute(bool value)
{
enableAsyncCompute = value;
}
public void AllowPassCulling(bool value)
{
allowPassCulling = value;
}
public void GenerateDebugData(bool value)
{
generateDebugData = value;
}
public void SetColorBuffer(TextureHandle resource, int index)
{
Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index);
colorBuffers[index] = resource;
AddResourceWrite(resource.handle);
}
public void SetDepthBuffer(TextureHandle resource, DepthAccess flags)
{
depthBuffer = resource;
if ((flags & DepthAccess.Read) != 0)
AddResourceRead(resource.handle);
if ((flags & DepthAccess.Write) != 0)
AddResourceWrite(resource.handle);
}
}
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
internal sealed class RenderGraphPass<PassData> : RenderGraphPass
where PassData : class, new()
{
internal PassData data;
internal RenderFunc<PassData> renderFunc;
public override void Execute(RenderGraphContext renderGraphContext)
{
GetExecuteDelegate<PassData>()(data, renderGraphContext);
}
public void Initialize(int passIndex, PassData passData, string passName, ProfilingSampler sampler)
{
Clear();
index = passIndex;
data = passData;
name = passName;
customSampler = sampler;
}
public override void Release(RenderGraphObjectPool pool)
{
Clear();
pool.Release(data);
data = null;
renderFunc = null;
// We need to do the release from here because we need the final type.
pool.Release(this);
}
public override bool HasRenderFunc()
{
return renderFunc != null;
}
}
}

View File

@@ -0,0 +1,10 @@
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
internal enum RenderGraphProfileId
{
RenderGraphClear,
RenderGraphClearDebug,
}
}

View File

@@ -0,0 +1,216 @@
using System;
using System.Diagnostics;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
/// <summary>
/// Compute Buffer resource handle.
/// </summary>
[DebuggerDisplay("ComputeBuffer ({handle.index})")]
public struct ComputeBufferHandle
{
private static ComputeBufferHandle s_NullHandle = new ComputeBufferHandle();
/// <summary>
/// Returns a null compute buffer handle
/// </summary>
/// <returns>A null compute buffer handle.</returns>
public static ComputeBufferHandle nullHandle { get { return s_NullHandle; } }
internal ResourceHandle handle;
internal ComputeBufferHandle(int handle, bool shared = false) { this.handle = new ResourceHandle(handle, RenderGraphResourceType.ComputeBuffer, shared); }
/// <summary>
/// Cast to ComputeBuffer
/// </summary>
/// <param name="buffer">Input ComputeBufferHandle</param>
/// <returns>Resource as a Compute Buffer.</returns>
public static implicit operator ComputeBuffer(ComputeBufferHandle buffer) => buffer.IsValid() ? RenderGraphResourceRegistry.current.GetComputeBuffer(buffer) : null;
/// <summary>
/// Return true if the handle is valid.
/// </summary>
/// <returns>True if the handle is valid.</returns>
public bool IsValid() => handle.IsValid();
}
/// <summary>
/// Descriptor used to create compute buffer resources
/// </summary>
public struct ComputeBufferDesc
{
///<summary>Number of elements in the buffer..</summary>
public int count;
///<summary>Size of one element in the buffer. Has to match size of buffer type in the shader.</summary>
public int stride;
///<summary>Type of the buffer, default is ComputeBufferType.Default (structured buffer).</summary>
public ComputeBufferType type;
/// <summary>Compute Buffer name.</summary>
public string name;
/// <summary>
/// ComputeBufferDesc constructor.
/// </summary>
/// <param name="count">Number of elements in the buffer.</param>
/// <param name="stride">Size of one element in the buffer.</param>
public ComputeBufferDesc(int count, int stride)
: this()
{
this.count = count;
this.stride = stride;
type = ComputeBufferType.Default;
}
/// <summary>
/// ComputeBufferDesc constructor.
/// </summary>
/// <param name="count">Number of elements in the buffer.</param>
/// <param name="stride">Size of one element in the buffer.</param>
/// <param name="type">Type of the buffer.</param>
public ComputeBufferDesc(int count, int stride, ComputeBufferType type)
: this()
{
this.count = count;
this.stride = stride;
this.type = type;
}
/// <summary>
/// Hash function
/// </summary>
/// <returns>The texture descriptor hash.</returns>
public override int GetHashCode()
{
int hashCode = 17;
hashCode = hashCode * 23 + count;
hashCode = hashCode * 23 + stride;
hashCode = hashCode * 23 + (int)type;
return hashCode;
}
}
[DebuggerDisplay("ComputeBufferResource ({desc.name})")]
class ComputeBufferResource : RenderGraphResource<ComputeBufferDesc, ComputeBuffer>
{
public override string GetName()
{
if (imported)
return "ImportedComputeBuffer"; // No getter for compute buffer name.
else
return desc.name;
}
// NOTE:
// Next two functions should have been implemented in RenderGraphResource<DescType, ResType> but for some reason,
// when doing so, it's impossible to break in the Texture version of the virtual function (with VS2017 at least), making this completely un-debuggable.
// To work around this, we just copy/pasted the implementation in each final class...
public override void CreatePooledGraphicsResource()
{
Debug.Assert(m_Pool != null, "CreatePooledGraphicsResource should only be called for regular pooled resources");
int hashCode = desc.GetHashCode();
if (graphicsResource != null)
throw new InvalidOperationException(string.Format("Trying to create an already created resource ({0}). Resource was probably declared for writing more than once in the same pass.", GetName()));
var pool = m_Pool as ComputeBufferPool;
if (!pool.TryGetResource(hashCode, out graphicsResource))
{
CreateGraphicsResource();
}
cachedHash = hashCode;
pool.RegisterFrameAllocation(cachedHash, graphicsResource);
}
public override void ReleasePooledGraphicsResource(int frameIndex)
{
if (graphicsResource == null)
throw new InvalidOperationException($"Tried to release a resource ({GetName()}) that was never created. Check that there is at least one pass writing to it first.");
// Shared resources don't use the pool
var pool = m_Pool as ComputeBufferPool;
if (pool != null)
{
pool.ReleaseResource(cachedHash, graphicsResource, frameIndex);
pool.UnregisterFrameAllocation(cachedHash, graphicsResource);
}
Reset(null);
}
public override void CreateGraphicsResource(string name = "")
{
graphicsResource = new ComputeBuffer(desc.count, desc.stride, desc.type);
graphicsResource.name = name == "" ? $"RenderGraphComputeBuffer_{desc.count}_{desc.stride}_{desc.type}" : name;
}
public override void ReleaseGraphicsResource()
{
if (graphicsResource != null)
graphicsResource.Release();
base.ReleaseGraphicsResource();
}
public override void LogCreation(RenderGraphLogger logger)
{
logger.LogLine($"Created ComputeBuffer: {desc.name}");
}
public override void LogRelease(RenderGraphLogger logger)
{
logger.LogLine($"Released ComputeBuffer: {desc.name}");
}
}
class ComputeBufferPool : RenderGraphResourcePool<ComputeBuffer>
{
protected override void ReleaseInternalResource(ComputeBuffer res)
{
res.Release();
}
protected override string GetResourceName(ComputeBuffer res)
{
return "ComputeBufferNameNotAvailable"; // ComputeBuffer.name is a setter only :(
}
protected override long GetResourceSize(ComputeBuffer res)
{
return res.count * res.stride;
}
override protected string GetResourceTypeName()
{
return "ComputeBuffer";
}
// Another C# nicety.
// We need to re-implement the whole thing every time because:
// - obj.resource.Release is Type specific so it cannot be called on a generic (and there's no shared interface for resources like RTHandle, ComputeBuffers etc)
// - We can't use a virtual release function because it will capture this in the lambda for RemoveAll generating GCAlloc in the process.
override public void PurgeUnusedResources(int currentFrameIndex)
{
// Update the frame index for the lambda. Static because we don't want to capture.
s_CurrentFrameIndex = currentFrameIndex;
foreach (var kvp in m_ResourcePool)
{
var list = kvp.Value;
list.RemoveAll(obj =>
{
if (ShouldReleaseResource(obj.frameIndex, s_CurrentFrameIndex))
{
obj.resource.Release();
return true;
}
return false;
});
}
}
}
}

View File

@@ -0,0 +1,143 @@
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
abstract class IRenderGraphResourcePool
{
public abstract void PurgeUnusedResources(int currentFrameIndex);
public abstract void Cleanup();
public abstract void CheckFrameAllocation(bool onException, int frameIndex);
public abstract void LogResources(RenderGraphLogger logger);
}
abstract class RenderGraphResourcePool<Type> : IRenderGraphResourcePool where Type : class
{
// 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).
protected Dictionary<int, List<(Type resource, int frameIndex)>> m_ResourcePool = new Dictionary<int, List<(Type resource, int frameIndex)>>();
// This list allows us to determine if all resources were correctly released in the frame.
// This is useful to warn in case of user error or avoid leaks when a render graph execution errors occurs for example.
List<(int, Type)> m_FrameAllocatedResources = new List<(int, Type)>();
protected static int s_CurrentFrameIndex;
const int kStaleResourceLifetime = 10;
// Release the GPU resource itself
protected abstract void ReleaseInternalResource(Type res);
protected abstract string GetResourceName(Type res);
protected abstract long GetResourceSize(Type res);
protected abstract string GetResourceTypeName();
public void ReleaseResource(int hash, Type resource, int currentFrameIndex)
{
if (!m_ResourcePool.TryGetValue(hash, out var list))
{
list = new List<(Type resource, int frameIndex)>();
m_ResourcePool.Add(hash, list);
}
list.Add((resource, currentFrameIndex));
}
public bool TryGetResource(int hashCode, out Type resource)
{
if (m_ResourcePool.TryGetValue(hashCode, out var list) && list.Count > 0)
{
resource = list[list.Count - 1].resource;
list.RemoveAt(list.Count - 1); // O(1) since it's the last element.
return true;
}
resource = null;
return false;
}
public override void Cleanup()
{
foreach (var kvp in m_ResourcePool)
{
foreach (var res in kvp.Value)
{
ReleaseInternalResource(res.resource);
}
}
}
public void RegisterFrameAllocation(int hash, Type value)
{
if (hash != -1)
m_FrameAllocatedResources.Add((hash, value));
}
public void UnregisterFrameAllocation(int hash, Type value)
{
if (hash != -1)
m_FrameAllocatedResources.Remove((hash, value));
}
public override void CheckFrameAllocation(bool onException, int frameIndex)
{
// In case of exception we need to release all resources to the pool to avoid leaking.
// If it's not an exception then it's a user error so we need to log the problem.
if (m_FrameAllocatedResources.Count != 0)
{
string logMessage = "";
if (!onException)
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.";
foreach (var value in m_FrameAllocatedResources)
{
if (!onException)
logMessage = $"{logMessage}\n\t{GetResourceName(value.Item2)}";
ReleaseResource(value.Item1, value.Item2, frameIndex);
}
Debug.LogWarning(logMessage);
}
// If an error occurred during execution, it's expected that textures are not all released so we clear the tracking list.
m_FrameAllocatedResources.Clear();
}
struct ResourceLogInfo
{
public string name;
public long size;
}
public override void LogResources(RenderGraphLogger logger)
{
List<ResourceLogInfo> allocationList = new List<ResourceLogInfo>();
foreach (var kvp in m_ResourcePool)
{
foreach (var res in kvp.Value)
{
allocationList.Add(new ResourceLogInfo { name = GetResourceName(res.resource), size = GetResourceSize(res.resource) });
}
}
logger.LogLine($"== {GetResourceTypeName()} Resources ==");
allocationList.Sort((a, b) => a.size < b.size ? 1 : -1);
int index = 0;
float total = 0;
foreach (var element in allocationList)
{
float size = element.size / (1024.0f * 1024.0f);
total += size;
logger.LogLine($"[{index++:D2}]\t[{size:0.00} MB]\t{element.name}");
}
logger.LogLine($"\nTotal Size [{total:0.00}]");
}
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 + kStaleResourceLifetime) < currentFrameIndex;
}
}
}

View File

@@ -0,0 +1,598 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
class RenderGraphResourceRegistry
{
const int kSharedResourceLifetime = 30;
static RenderGraphResourceRegistry m_CurrentRegistry;
internal static RenderGraphResourceRegistry current
{
get
{
// We assume that it's enough to only check in editor because we don't want to pay the cost at runtime.
#if UNITY_EDITOR
if (m_CurrentRegistry == null)
{
throw new InvalidOperationException("Current Render Graph Resource Registry is not set. You are probably trying to cast a Render Graph handle to a resource outside of a Render Graph Pass.");
}
#endif
return m_CurrentRegistry;
}
set
{
m_CurrentRegistry = value;
}
}
delegate void ResourceCallback(RenderGraphContext rgContext, IRenderGraphResource res);
class RenderGraphResourcesData
{
public DynamicArray<IRenderGraphResource> resourceArray = new DynamicArray<IRenderGraphResource>();
public int sharedResourcesCount;
public IRenderGraphResourcePool pool;
public ResourceCallback createResourceCallback;
public ResourceCallback releaseResourceCallback;
public void Clear(bool onException, int frameIndex)
{
resourceArray.Resize(sharedResourcesCount); // First N elements are reserved for shared persistent resources and are kept as is.
pool.CheckFrameAllocation(onException, frameIndex);
}
public void Cleanup()
{
// Cleanup all shared resources.
for (int i = 0; i < sharedResourcesCount; ++i)
{
var resource = resourceArray[i];
if (resource != null)
{
resource.ReleaseGraphicsResource();
}
}
// Then cleanup the pool
pool.Cleanup();
}
public void PurgeUnusedGraphicsResources(int frameIndex)
{
pool.PurgeUnusedResources(frameIndex);
}
public int AddNewRenderGraphResource<ResType>(out ResType outRes, bool pooledResource = true)
where ResType : IRenderGraphResource, new()
{
// In order to not create garbage, instead of using Add, we keep the content of the array while resizing and we just reset the existing ref (or create it if it's null).
int result = resourceArray.size;
resourceArray.Resize(resourceArray.size + 1, true);
if (resourceArray[result] == null)
resourceArray[result] = new ResType();
outRes = resourceArray[result] as ResType;
outRes.Reset(pooledResource ? pool : null);
return result;
}
}
RenderGraphResourcesData[] m_RenderGraphResources = new RenderGraphResourcesData[(int)RenderGraphResourceType.Count];
DynamicArray<RendererListResource> m_RendererListResources = new DynamicArray<RendererListResource>();
RenderGraphDebugParams m_RenderGraphDebug;
RenderGraphLogger m_Logger;
int m_CurrentFrameIndex;
int m_ExecutionCount;
RTHandle m_CurrentBackbuffer;
#region Internal Interface
internal RTHandle GetTexture(in TextureHandle handle)
{
if (!handle.IsValid())
return null;
return GetTextureResource(handle.handle).graphicsResource;
}
internal bool TextureNeedsFallback(in TextureHandle handle)
{
if (!handle.IsValid())
return false;
return GetTextureResource(handle.handle).NeedsFallBack();
}
internal RendererList GetRendererList(in RendererListHandle handle)
{
if (!handle.IsValid() || handle >= m_RendererListResources.size)
return RendererList.nullRendererList;
return m_RendererListResources[handle].rendererList;
}
internal ComputeBuffer GetComputeBuffer(in ComputeBufferHandle handle)
{
if (!handle.IsValid())
return null;
return GetComputeBufferResource(handle.handle).graphicsResource;
}
private RenderGraphResourceRegistry()
{
}
internal RenderGraphResourceRegistry(RenderGraphDebugParams renderGraphDebug, RenderGraphLogger logger)
{
m_RenderGraphDebug = renderGraphDebug;
m_Logger = logger;
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
m_RenderGraphResources[i] = new RenderGraphResourcesData();
}
m_RenderGraphResources[(int)RenderGraphResourceType.Texture].createResourceCallback = CreateTextureCallback;
m_RenderGraphResources[(int)RenderGraphResourceType.Texture].releaseResourceCallback = ReleaseTextureCallback;
m_RenderGraphResources[(int)RenderGraphResourceType.Texture].pool = new TexturePool();
m_RenderGraphResources[(int)RenderGraphResourceType.ComputeBuffer].pool = new ComputeBufferPool();
}
internal void BeginRenderGraph(int executionCount)
{
m_ExecutionCount = executionCount;
ResourceHandle.NewFrame(executionCount);
}
internal void BeginExecute(int currentFrameIndex)
{
m_CurrentFrameIndex = currentFrameIndex;
ManageSharedRenderGraphResources();
current = this;
}
internal void EndExecute()
{
current = null;
}
void CheckHandleValidity(in ResourceHandle res)
{
CheckHandleValidity(res.type, res.index);
}
void CheckHandleValidity(RenderGraphResourceType type, int index)
{
var resources = m_RenderGraphResources[(int)type].resourceArray;
if (index >= resources.size)
throw new ArgumentException($"Trying to access resource of type {type} with an invalid resource index {index}");
}
internal void IncrementWriteCount(in ResourceHandle res)
{
CheckHandleValidity(res);
m_RenderGraphResources[res.iType].resourceArray[res.index].IncrementWriteCount();
}
internal string GetRenderGraphResourceName(in ResourceHandle res)
{
CheckHandleValidity(res);
return m_RenderGraphResources[res.iType].resourceArray[res.index].GetName();
}
internal string GetRenderGraphResourceName(RenderGraphResourceType type, int index)
{
CheckHandleValidity(type, index);
return m_RenderGraphResources[(int)type].resourceArray[index].GetName();
}
internal bool IsRenderGraphResourceImported(in ResourceHandle res)
{
CheckHandleValidity(res);
return m_RenderGraphResources[res.iType].resourceArray[res.index].imported;
}
internal bool IsRenderGraphResourceShared(RenderGraphResourceType type, int index)
{
CheckHandleValidity(type, index);
return index < m_RenderGraphResources[(int)type].sharedResourcesCount;
}
internal bool IsGraphicsResourceCreated(in ResourceHandle res)
{
CheckHandleValidity(res);
return m_RenderGraphResources[res.iType].resourceArray[res.index].IsCreated();
}
internal bool IsRendererListCreated(in RendererListHandle res)
{
return m_RendererListResources[res].rendererList.isValid;
}
internal bool IsRenderGraphResourceImported(RenderGraphResourceType type, int index)
{
CheckHandleValidity(type, index);
return m_RenderGraphResources[(int)type].resourceArray[index].imported;
}
internal int GetRenderGraphResourceTransientIndex(in ResourceHandle res)
{
CheckHandleValidity(res);
return m_RenderGraphResources[res.iType].resourceArray[res.index].transientPassIndex;
}
// Texture Creation/Import APIs are internal because creation should only go through RenderGraph
internal TextureHandle ImportTexture(RTHandle rt)
{
int newHandle = m_RenderGraphResources[(int)RenderGraphResourceType.Texture].AddNewRenderGraphResource(out TextureResource texResource);
texResource.graphicsResource = rt;
texResource.imported = true;
return new TextureHandle(newHandle);
}
internal TextureHandle CreateSharedTexture(in TextureDesc desc, bool explicitRelease)
{
var textureResources = m_RenderGraphResources[(int)RenderGraphResourceType.Texture];
int sharedTextureCount = textureResources.sharedResourcesCount;
Debug.Assert(textureResources.resourceArray.size <= sharedTextureCount);
// try to find an available slot.
TextureResource texResource = null;
int textureIndex = -1;
for (int i = 0; i < sharedTextureCount; ++i)
{
var resource = textureResources.resourceArray[i];
if (resource.shared == false) // unused
{
texResource = (TextureResource)textureResources.resourceArray[i];
textureIndex = i;
break;
}
}
// if none is available, add a new resource.
if (texResource == null)
{
textureIndex = m_RenderGraphResources[(int)RenderGraphResourceType.Texture].AddNewRenderGraphResource(out texResource, pooledResource: false);
textureResources.sharedResourcesCount++;
}
texResource.imported = true;
texResource.shared = true;
texResource.sharedExplicitRelease = explicitRelease;
texResource.desc = desc;
return new TextureHandle(textureIndex, shared: true);
}
internal void ReleaseSharedTexture(TextureHandle texture)
{
var texResources = m_RenderGraphResources[(int)RenderGraphResourceType.Texture];
if (texture.handle >= texResources.sharedResourcesCount)
throw new InvalidOperationException("Tried to release a non shared texture.");
// Decrement if we release the last one.
if (texture.handle == (texResources.sharedResourcesCount - 1))
texResources.sharedResourcesCount--;
var texResource = GetTextureResource(texture.handle);
texResource.ReleaseGraphicsResource();
texResource.Reset(null);
}
internal TextureHandle ImportBackbuffer(RenderTargetIdentifier rt)
{
if (m_CurrentBackbuffer != null)
m_CurrentBackbuffer.SetTexture(rt);
else
m_CurrentBackbuffer = RTHandles.Alloc(rt, "Backbuffer");
int newHandle = m_RenderGraphResources[(int)RenderGraphResourceType.Texture].AddNewRenderGraphResource(out TextureResource texResource);
texResource.graphicsResource = m_CurrentBackbuffer;
texResource.imported = true;
return new TextureHandle(newHandle);
}
internal TextureHandle CreateTexture(in TextureDesc desc, int transientPassIndex = -1)
{
ValidateTextureDesc(desc);
int newHandle = m_RenderGraphResources[(int)RenderGraphResourceType.Texture].AddNewRenderGraphResource(out TextureResource texResource);
texResource.desc = desc;
texResource.transientPassIndex = transientPassIndex;
texResource.requestFallBack = desc.fallBackToBlackTexture;
return new TextureHandle(newHandle);
}
internal int GetTextureResourceCount()
{
return m_RenderGraphResources[(int)RenderGraphResourceType.Texture].resourceArray.size;
}
TextureResource GetTextureResource(in ResourceHandle handle)
{
return m_RenderGraphResources[(int)RenderGraphResourceType.Texture].resourceArray[handle] as TextureResource;
}
internal TextureDesc GetTextureResourceDesc(in ResourceHandle handle)
{
return (m_RenderGraphResources[(int)RenderGraphResourceType.Texture].resourceArray[handle] as TextureResource).desc;
}
internal RendererListHandle CreateRendererList(in RendererListDesc desc)
{
ValidateRendererListDesc(desc);
int newHandle = m_RendererListResources.Add(new RendererListResource(desc));
return new RendererListHandle(newHandle);
}
internal ComputeBufferHandle ImportComputeBuffer(ComputeBuffer computeBuffer)
{
int newHandle = m_RenderGraphResources[(int)RenderGraphResourceType.ComputeBuffer].AddNewRenderGraphResource(out ComputeBufferResource bufferResource);
bufferResource.graphicsResource = computeBuffer;
bufferResource.imported = true;
return new ComputeBufferHandle(newHandle);
}
internal ComputeBufferHandle CreateComputeBuffer(in ComputeBufferDesc desc, int transientPassIndex = -1)
{
ValidateComputeBufferDesc(desc);
int newHandle = m_RenderGraphResources[(int)RenderGraphResourceType.ComputeBuffer].AddNewRenderGraphResource(out ComputeBufferResource bufferResource);
bufferResource.desc = desc;
bufferResource.transientPassIndex = transientPassIndex;
return new ComputeBufferHandle(newHandle);
}
internal ComputeBufferDesc GetComputeBufferResourceDesc(in ResourceHandle handle)
{
return (m_RenderGraphResources[(int)RenderGraphResourceType.ComputeBuffer].resourceArray[handle] as ComputeBufferResource).desc;
}
internal int GetComputeBufferResourceCount()
{
return m_RenderGraphResources[(int)RenderGraphResourceType.ComputeBuffer].resourceArray.size;
}
ComputeBufferResource GetComputeBufferResource(in ResourceHandle handle)
{
return m_RenderGraphResources[(int)RenderGraphResourceType.ComputeBuffer].resourceArray[handle] as ComputeBufferResource;
}
internal void UpdateSharedResourceLastFrameIndex(int type, int index)
{
m_RenderGraphResources[type].resourceArray[index].sharedResourceLastFrameUsed = m_ExecutionCount;
}
void ManageSharedRenderGraphResources()
{
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resources = m_RenderGraphResources[type];
for (int i = 0; i < resources.sharedResourcesCount; ++i)
{
var resource = m_RenderGraphResources[type].resourceArray[i];
bool isCreated = resource.IsCreated();
// Alloc if needed.
if (resource.sharedResourceLastFrameUsed == m_ExecutionCount && !isCreated)
{
// Here we want the resource to have the name given by users because we know that it won't be reused at all.
// So no need for an automatic generic name.
resource.CreateGraphicsResource(resource.GetName());
}
// Release if not used anymore.
else if (isCreated && !resource.sharedExplicitRelease && ((resource.sharedResourceLastFrameUsed + kSharedResourceLifetime) < m_ExecutionCount))
{
resource.ReleaseGraphicsResource();
}
}
}
}
internal void CreatePooledResource(RenderGraphContext rgContext, int type, int index)
{
var resource = m_RenderGraphResources[type].resourceArray[index];
if (!resource.imported)
{
resource.CreatePooledGraphicsResource();
if (m_RenderGraphDebug.logFrameInformation)
resource.LogCreation(m_Logger);
m_RenderGraphResources[type].createResourceCallback?.Invoke(rgContext, resource);
}
}
void CreateTextureCallback(RenderGraphContext rgContext, IRenderGraphResource res)
{
var resource = res as TextureResource;
#if UNITY_2020_2_OR_NEWER
var fastMemDesc = resource.desc.fastMemoryDesc;
if (fastMemDesc.inFastMemory)
{
resource.graphicsResource.SwitchToFastMemory(rgContext.cmd, fastMemDesc.residencyFraction, fastMemDesc.flags);
}
#endif
if (resource.desc.clearBuffer || m_RenderGraphDebug.clearRenderTargetsAtCreation)
{
bool debugClear = m_RenderGraphDebug.clearRenderTargetsAtCreation && !resource.desc.clearBuffer;
using (new ProfilingScope(rgContext.cmd, ProfilingSampler.Get(debugClear ? RenderGraphProfileId.RenderGraphClearDebug : RenderGraphProfileId.RenderGraphClear)))
{
var clearFlag = resource.desc.depthBufferBits != DepthBits.None ? ClearFlag.Depth : ClearFlag.Color;
var clearColor = debugClear ? Color.magenta : resource.desc.clearColor;
CoreUtils.SetRenderTarget(rgContext.cmd, resource.graphicsResource, clearFlag, clearColor);
}
}
}
internal void ReleasePooledResource(RenderGraphContext rgContext, int type, int index)
{
var resource = m_RenderGraphResources[type].resourceArray[index];
if (!resource.imported)
{
m_RenderGraphResources[type].releaseResourceCallback?.Invoke(rgContext, resource);
if (m_RenderGraphDebug.logFrameInformation)
{
resource.LogRelease(m_Logger);
}
resource.ReleasePooledGraphicsResource(m_CurrentFrameIndex);
}
}
void ReleaseTextureCallback(RenderGraphContext rgContext, IRenderGraphResource res)
{
var resource = res as TextureResource;
if (m_RenderGraphDebug.clearRenderTargetsAtRelease)
{
using (new ProfilingScope(rgContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.RenderGraphClearDebug)))
{
var clearFlag = resource.desc.depthBufferBits != DepthBits.None ? ClearFlag.Depth : ClearFlag.Color;
CoreUtils.SetRenderTarget(rgContext.cmd, resource.graphicsResource, clearFlag, Color.magenta);
}
}
}
void ValidateTextureDesc(in TextureDesc desc)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (desc.colorFormat == GraphicsFormat.None && desc.depthBufferBits == DepthBits.None)
{
throw new ArgumentException("Texture was created with an invalid color format.");
}
if (desc.dimension == TextureDimension.None)
{
throw new ArgumentException("Texture was created with an invalid texture dimension.");
}
if (desc.slices == 0)
{
throw new ArgumentException("Texture was created with a slices parameter value of zero.");
}
if (desc.sizeMode == TextureSizeMode.Explicit)
{
if (desc.width == 0 || desc.height == 0)
throw new ArgumentException("Texture using Explicit size mode was create with either width or height at zero.");
if (desc.enableMSAA)
throw new ArgumentException("enableMSAA TextureDesc parameter is not supported for textures using Explicit size mode.");
}
if (desc.sizeMode == TextureSizeMode.Scale || desc.sizeMode == TextureSizeMode.Functor)
{
if (desc.msaaSamples != MSAASamples.None)
throw new ArgumentException("msaaSamples TextureDesc parameter is not supported for textures using Scale or Functor size mode.");
}
#endif
}
void ValidateRendererListDesc(in RendererListDesc desc)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (desc.passName != ShaderTagId.none && desc.passNames != null
|| desc.passName == ShaderTagId.none && desc.passNames == null)
{
throw new ArgumentException("Renderer List creation descriptor must contain either a single passName or an array of passNames.");
}
if (desc.renderQueueRange.lowerBound == 0 && desc.renderQueueRange.upperBound == 0)
{
throw new ArgumentException("Renderer List creation descriptor must have a valid RenderQueueRange.");
}
if (desc.camera == null)
{
throw new ArgumentException("Renderer List creation descriptor must have a valid Camera.");
}
#endif
}
void ValidateComputeBufferDesc(in ComputeBufferDesc desc)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (desc.stride % 4 != 0)
{
throw new ArgumentException("Invalid Compute Buffer creation descriptor: Compute Buffer stride must be at least 4.");
}
if (desc.count == 0)
{
throw new ArgumentException("Invalid Compute Buffer creation descriptor: Compute Buffer count must be non zero.");
}
#endif
}
internal void CreateRendererLists(List<RendererListHandle> rendererLists)
{
// For now we just create a simple structure
// but when the proper API is available in trunk we'll kick off renderer lists creation jobs here.
foreach (var rendererList in rendererLists)
{
ref var rendererListResource = ref m_RendererListResources[rendererList];
ref var desc = ref rendererListResource.desc;
RendererList newRendererList = RendererList.Create(desc);
rendererListResource.rendererList = newRendererList;
}
}
internal void Clear(bool onException)
{
LogResources();
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
m_RenderGraphResources[i].Clear(onException, m_CurrentFrameIndex);
m_RendererListResources.Clear();
}
internal void PurgeUnusedGraphicsResources()
{
// TODO RENDERGRAPH: Might not be ideal to purge stale resources every frame.
// In case users enable/disable features along a level it might provoke performance spikes when things are reallocated...
// Will be much better when we have actual resource aliasing and we can manage memory more efficiently.
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
m_RenderGraphResources[i].PurgeUnusedGraphicsResources(m_CurrentFrameIndex);
}
internal void Cleanup()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
m_RenderGraphResources[i].Cleanup();
RTHandles.Release(m_CurrentBackbuffer);
}
void LogResources()
{
if (m_RenderGraphDebug.logResources)
{
m_Logger.LogLine("==== Allocated Resources ====\n");
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
m_RenderGraphResources[type].pool.LogResources(m_Logger);
m_Logger.LogLine("");
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,47 @@
using System.Diagnostics;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
/// <summary>
/// Renderer List resource handle.
/// </summary>
[DebuggerDisplay("RendererList ({handle})")]
public struct RendererListHandle
{
bool m_IsValid;
internal int handle { get; private set; }
internal RendererListHandle(int handle) { this.handle = handle; m_IsValid = true; }
/// <summary>
/// Conversion to int.
/// </summary>
/// <param name="handle">Renderer List handle to convert.</param>
/// <returns>The integer representation of the handle.</returns>
public static implicit operator int(RendererListHandle handle) { return handle.handle; }
/// <summary>
/// Cast to RendererList
/// </summary>
/// <param name="rendererList">Input RendererListHandle.</param>
/// <returns>Resource as a RendererList.</returns>
public static implicit operator RendererList(RendererListHandle rendererList) => rendererList.IsValid() ? RenderGraphResourceRegistry.current.GetRendererList(rendererList) : RendererList.nullRendererList;
/// <summary>
/// Return true if the handle is valid.
/// </summary>
/// <returns>True if the handle is valid.</returns>
public bool IsValid() => m_IsValid;
}
internal struct RendererListResource
{
public RendererListDesc desc;
public RendererList rendererList;
internal RendererListResource(in RendererListDesc desc)
{
this.desc = desc;
this.rendererList = new RendererList(); // Invalid by default
}
}
}

View File

@@ -0,0 +1,426 @@
using System;
using System.Diagnostics;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
/// <summary>
/// Texture resource handle.
/// </summary>
[DebuggerDisplay("Texture ({handle.index})")]
public struct TextureHandle
{
private static TextureHandle s_NullHandle = new TextureHandle();
/// <summary>
/// Returns a null texture handle
/// </summary>
/// <returns>A null texture handle.</returns>
public static TextureHandle nullHandle { get { return s_NullHandle; } }
internal ResourceHandle handle;
internal TextureHandle(int handle, bool shared = false) { this.handle = new ResourceHandle(handle, RenderGraphResourceType.Texture, shared); }
/// <summary>
/// Cast to RenderTargetIdentifier
/// </summary>
/// <param name="texture">Input TextureHandle.</param>
/// <returns>Resource as a RenderTargetIdentifier.</returns>
public static implicit operator RenderTargetIdentifier(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : default(RenderTargetIdentifier);
/// <summary>
/// Cast to Texture
/// </summary>
/// <param name="texture">Input TextureHandle.</param>
/// <returns>Resource as a Texture.</returns>
public static implicit operator Texture(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null;
/// <summary>
/// Cast to RenderTexture
/// </summary>
/// <param name="texture">Input TextureHandle.</param>
/// <returns>Resource as a RenderTexture.</returns>
public static implicit operator RenderTexture(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null;
/// <summary>
/// Cast to RTHandle
/// </summary>
/// <param name="texture">Input TextureHandle.</param>
/// <returns>Resource as a RTHandle.</returns>
public static implicit operator RTHandle(TextureHandle texture) => texture.IsValid() ? RenderGraphResourceRegistry.current.GetTexture(texture) : null;
/// <summary>
/// Return true if the handle is valid.
/// </summary>
/// <returns>True if the handle is valid.</returns>
public bool IsValid() => handle.IsValid();
}
/// <summary>
/// The mode that determines the size of a Texture.
/// </summary>
public enum TextureSizeMode
{
///<summary>Explicit size.</summary>
Explicit,
///<summary>Size automatically scaled by a Vector.</summary>
Scale,
///<summary>Size automatically scaled by a Functor.</summary>
Functor
}
#if UNITY_2020_2_OR_NEWER
/// <summary>
/// Subset of the texture desc containing information for fast memory allocation (when platform supports it)
/// </summary>
public struct FastMemoryDesc
{
///<summary>Whether the texture will be in fast memory.</summary>
public bool inFastMemory;
///<summary>Flag to determine what parts of the render target is spilled if not fully resident in fast memory.</summary>
public FastMemoryFlags flags;
///<summary>How much of the render target is to be switched into fast memory (between 0 and 1).</summary>
public float residencyFraction;
}
#endif
/// <summary>
/// Descriptor used to create texture resources
/// </summary>
public struct TextureDesc
{
///<summary>Texture sizing mode.</summary>
public TextureSizeMode sizeMode;
///<summary>Texture width.</summary>
public int width;
///<summary>Texture height.</summary>
public int height;
///<summary>Number of texture slices..</summary>
public int slices;
///<summary>Texture scale.</summary>
public Vector2 scale;
///<summary>Texture scale function.</summary>
public ScaleFunc func;
///<summary>Depth buffer bit depth.</summary>
public DepthBits depthBufferBits;
///<summary>Color format.</summary>
public GraphicsFormat colorFormat;
///<summary>Filtering mode.</summary>
public FilterMode filterMode;
///<summary>Addressing mode.</summary>
public TextureWrapMode wrapMode;
///<summary>Texture dimension.</summary>
public TextureDimension dimension;
///<summary>Enable random UAV read/write on the texture.</summary>
public bool enableRandomWrite;
///<summary>Texture needs mip maps.</summary>
public bool useMipMap;
///<summary>Automatically generate mip maps.</summary>
public bool autoGenerateMips;
///<summary>Texture is a shadow map.</summary>
public bool isShadowMap;
///<summary>Anisotropic filtering level.</summary>
public int anisoLevel;
///<summary>Mip map bias.</summary>
public float mipMapBias;
///<summary>Textre is multisampled. Only supported for Scale and Functor size mode.</summary>
public bool enableMSAA;
///<summary>Number of MSAA samples. Only supported for Explicit size mode.</summary>
public MSAASamples msaaSamples;
///<summary>Bind texture multi sampled.</summary>
public bool bindTextureMS;
///<summary>Texture uses dynamic scaling.</summary>
public bool useDynamicScale;
///<summary>Memory less flag.</summary>
public RenderTextureMemoryless memoryless;
///<summary>Texture name.</summary>
public string name;
#if UNITY_2020_2_OR_NEWER
///<summary>Descriptor to determine how the texture will be in fast memory on platform that supports it.</summary>
public FastMemoryDesc fastMemoryDesc;
#endif
///<summary>Determines whether the texture will fallback to a black texture if it is read without ever writing to it.</summary>
public bool fallBackToBlackTexture;
// Initial state. Those should not be used in the hash
///<summary>Texture needs to be cleared on first use.</summary>
public bool clearBuffer;
///<summary>Clear color.</summary>
public Color clearColor;
void InitDefaultValues(bool dynamicResolution, bool xrReady)
{
useDynamicScale = dynamicResolution;
// XR Ready
if (xrReady)
{
slices = TextureXR.slices;
dimension = TextureXR.dimension;
}
else
{
slices = 1;
dimension = TextureDimension.Tex2D;
}
}
/// <summary>
/// TextureDesc constructor for a texture using explicit size
/// </summary>
/// <param name="width">Texture width</param>
/// <param name="height">Texture height</param>
/// <param name="dynamicResolution">Use dynamic resolution</param>
/// <param name="xrReady">Set this to true if the Texture is a render texture in an XR setting.</param>
public TextureDesc(int width, int height, bool dynamicResolution = false, bool xrReady = false)
: this()
{
// Size related init
sizeMode = TextureSizeMode.Explicit;
this.width = width;
this.height = height;
// Important default values not handled by zero construction in this()
msaaSamples = MSAASamples.None;
InitDefaultValues(dynamicResolution, xrReady);
}
/// <summary>
/// TextureDesc constructor for a texture using a fixed scaling
/// </summary>
/// <param name="scale">RTHandle scale used for this texture</param>
/// <param name="dynamicResolution">Use dynamic resolution</param>
/// <param name="xrReady">Set this to true if the Texture is a render texture in an XR setting.</param>
public TextureDesc(Vector2 scale, bool dynamicResolution = false, bool xrReady = false)
: this()
{
// Size related init
sizeMode = TextureSizeMode.Scale;
this.scale = scale;
// Important default values not handled by zero construction in this()
msaaSamples = MSAASamples.None;
dimension = TextureDimension.Tex2D;
InitDefaultValues(dynamicResolution, xrReady);
}
/// <summary>
/// TextureDesc constructor for a texture using a functor for scaling
/// </summary>
/// <param name="func">Function used to determnine the texture size</param>
/// <param name="dynamicResolution">Use dynamic resolution</param>
/// <param name="xrReady">Set this to true if the Texture is a render texture in an XR setting.</param>
public TextureDesc(ScaleFunc func, bool dynamicResolution = false, bool xrReady = false)
: this()
{
// Size related init
sizeMode = TextureSizeMode.Functor;
this.func = func;
// Important default values not handled by zero construction in this()
msaaSamples = MSAASamples.None;
dimension = TextureDimension.Tex2D;
InitDefaultValues(dynamicResolution, xrReady);
}
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="input"></param>
public TextureDesc(TextureDesc input)
{
this = input;
}
/// <summary>
/// Hash function
/// </summary>
/// <returns>The texture descriptor hash.</returns>
public override int GetHashCode()
{
int hashCode = 17;
unchecked
{
switch (sizeMode)
{
case TextureSizeMode.Explicit:
hashCode = hashCode * 23 + width;
hashCode = hashCode * 23 + height;
hashCode = hashCode * 23 + (int)msaaSamples;
break;
case TextureSizeMode.Functor:
if (func != null)
hashCode = hashCode * 23 + func.GetHashCode();
hashCode = hashCode * 23 + (enableMSAA ? 1 : 0);
break;
case TextureSizeMode.Scale:
hashCode = hashCode * 23 + scale.x.GetHashCode();
hashCode = hashCode * 23 + scale.y.GetHashCode();
hashCode = hashCode * 23 + (enableMSAA ? 1 : 0);
break;
}
hashCode = hashCode * 23 + mipMapBias.GetHashCode();
hashCode = hashCode * 23 + slices;
hashCode = hashCode * 23 + (int)depthBufferBits;
hashCode = hashCode * 23 + (int)colorFormat;
hashCode = hashCode * 23 + (int)filterMode;
hashCode = hashCode * 23 + (int)wrapMode;
hashCode = hashCode * 23 + (int)dimension;
hashCode = hashCode * 23 + (int)memoryless;
hashCode = hashCode * 23 + anisoLevel;
hashCode = hashCode * 23 + (enableRandomWrite ? 1 : 0);
hashCode = hashCode * 23 + (useMipMap ? 1 : 0);
hashCode = hashCode * 23 + (autoGenerateMips ? 1 : 0);
hashCode = hashCode * 23 + (isShadowMap ? 1 : 0);
hashCode = hashCode * 23 + (bindTextureMS ? 1 : 0);
hashCode = hashCode * 23 + (useDynamicScale ? 1 : 0);
#if UNITY_2020_2_OR_NEWER
hashCode = hashCode * 23 + (fastMemoryDesc.inFastMemory ? 1 : 0);
#endif
}
return hashCode;
}
}
[DebuggerDisplay("TextureResource ({desc.name})")]
class TextureResource : RenderGraphResource<TextureDesc, RTHandle>
{
static int m_TextureCreationIndex;
public override string GetName()
{
if (imported && !shared)
return graphicsResource != null ? graphicsResource.name : "null resource";
else
return desc.name;
}
// NOTE:
// Next two functions should have been implemented in RenderGraphResource<DescType, ResType> but for some reason,
// when doing so, it's impossible to break in the Texture version of the virtual function (with VS2017 at least), making this completely un-debuggable.
// To work around this, we just copy/pasted the implementation in each final class...
public override void CreatePooledGraphicsResource()
{
Debug.Assert(m_Pool != null, "CreatePooledGraphicsResource should only be called for regular pooled resources");
int hashCode = desc.GetHashCode();
if (graphicsResource != null)
throw new InvalidOperationException(string.Format("Trying to create an already created resource ({0}). Resource was probably declared for writing more than once in the same pass.", GetName()));
var pool = m_Pool as TexturePool;
if (!pool.TryGetResource(hashCode, out graphicsResource))
{
CreateGraphicsResource();
}
cachedHash = hashCode;
pool.RegisterFrameAllocation(cachedHash, graphicsResource);
}
public override void ReleasePooledGraphicsResource(int frameIndex)
{
if (graphicsResource == null)
throw new InvalidOperationException($"Tried to release a resource ({GetName()}) that was never created. Check that there is at least one pass writing to it first.");
// Shared resources don't use the pool
var pool = m_Pool as TexturePool;
if (pool != null)
{
pool.ReleaseResource(cachedHash, graphicsResource, frameIndex);
pool.UnregisterFrameAllocation(cachedHash, graphicsResource);
}
Reset(null);
}
public override void CreateGraphicsResource(string name = "")
{
// Textures are going to be reused under different aliases along the frame so we can't provide a specific name upon creation.
// The name in the desc is going to be used for debugging purpose and render graph visualization.
if (name == "")
name = $"RenderGraphTexture_{m_TextureCreationIndex++}";
switch (desc.sizeMode)
{
case TextureSizeMode.Explicit:
graphicsResource = RTHandles.Alloc(desc.width, desc.height, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite,
desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.msaaSamples, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name);
break;
case TextureSizeMode.Scale:
graphicsResource = RTHandles.Alloc(desc.scale, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite,
desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.enableMSAA, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name);
break;
case TextureSizeMode.Functor:
graphicsResource = RTHandles.Alloc(desc.func, desc.slices, desc.depthBufferBits, desc.colorFormat, desc.filterMode, desc.wrapMode, desc.dimension, desc.enableRandomWrite,
desc.useMipMap, desc.autoGenerateMips, desc.isShadowMap, desc.anisoLevel, desc.mipMapBias, desc.enableMSAA, desc.bindTextureMS, desc.useDynamicScale, desc.memoryless, name);
break;
}
}
public override void ReleaseGraphicsResource()
{
if (graphicsResource != null)
graphicsResource.Release();
base.ReleaseGraphicsResource();
}
public override void LogCreation(RenderGraphLogger logger)
{
logger.LogLine($"Created Texture: {desc.name} (Cleared: {desc.clearBuffer})");
}
public override void LogRelease(RenderGraphLogger logger)
{
logger.LogLine($"Released Texture: {desc.name}");
}
}
class TexturePool : RenderGraphResourcePool<RTHandle>
{
protected override void ReleaseInternalResource(RTHandle res)
{
res.Release();
}
protected override string GetResourceName(RTHandle res)
{
return res.rt.name;
}
protected override long GetResourceSize(RTHandle res)
{
return Profiling.Profiler.GetRuntimeMemorySizeLong(res.rt);
}
override protected string GetResourceTypeName()
{
return "Texture";
}
// Another C# nicety.
// We need to re-implement the whole thing every time because:
// - obj.resource.Release is Type specific so it cannot be called on a generic (and there's no shared interface for resources like RTHandle, ComputeBuffers etc)
// - We can't use a virtual release function because it will capture 'this' in the lambda for RemoveAll generating GCAlloc in the process.
override public void PurgeUnusedResources(int currentFrameIndex)
{
// Update the frame index for the lambda. Static because we don't want to capture.
s_CurrentFrameIndex = currentFrameIndex;
foreach (var kvp in m_ResourcePool)
{
var list = kvp.Value;
list.RemoveAll(obj =>
{
if (ShouldReleaseResource(obj.frameIndex, s_CurrentFrameIndex))
{
obj.resource.Release();
return true;
}
return false;
});
}
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Diagnostics;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
// RendererList is a different case so not represented here.
internal enum RenderGraphResourceType
{
Texture = 0,
ComputeBuffer,
Count
}
internal struct ResourceHandle
{
// Note on handles validity.
// PassData classes used during render graph passes are pooled and because of that, when users don't fill them completely,
// they can contain stale handles from a previous render graph execution that could still be considered valid if we only checked the index.
// In order to avoid using those, we incorporate the execution index in a 16 bits hash to make sure the handle is coming from the current execution.
// If not, it's considered invalid.
// We store this validity mask in the upper 16 bits of the index.
const uint kValidityMask = 0xFFFF0000;
const uint kIndexMask = 0xFFFF;
uint m_Value;
static uint s_CurrentValidBit = 1 << 16;
static uint s_SharedResourceValidBit = 0x7FFF << 16;
public int index { get { return (int)(m_Value & kIndexMask); } }
public RenderGraphResourceType type { get; private set; }
public int iType { get { return (int)type; } }
internal ResourceHandle(int value, RenderGraphResourceType type, bool shared)
{
Debug.Assert(value <= 0xFFFF);
m_Value = ((uint)value & kIndexMask) | (shared ? s_SharedResourceValidBit : s_CurrentValidBit);
this.type = type;
}
public static implicit operator int(ResourceHandle handle) => handle.index;
public bool IsValid()
{
var validity = m_Value & kValidityMask;
return validity != 0 && (validity == s_CurrentValidBit || validity == s_SharedResourceValidBit);
}
static public void NewFrame(int executionIndex)
{
// Scramble frame count to avoid collision when wrapping around.
s_CurrentValidBit = (uint)(((executionIndex >> 16) ^ (executionIndex & 0xffff) * 58546883) << 16);
// In case the current valid bit is 0, even though perfectly valid, 0 represents an invalid handle, hence we'll
// trigger an invalid state incorrectly. To account for this, we actually skip 0 as a viable s_CurrentValidBit and
// start from 1 again.
if (s_CurrentValidBit == 0)
{
s_CurrentValidBit = 1 << 16;
}
}
}
class IRenderGraphResource
{
public bool imported;
public bool shared;
public bool sharedExplicitRelease;
public bool requestFallBack;
public uint writeCount;
public int cachedHash;
public int transientPassIndex;
public int sharedResourceLastFrameUsed;
protected IRenderGraphResourcePool m_Pool;
public virtual void Reset(IRenderGraphResourcePool pool)
{
imported = false;
shared = false;
sharedExplicitRelease = false;
cachedHash = -1;
transientPassIndex = -1;
sharedResourceLastFrameUsed = -1;
requestFallBack = false;
writeCount = 0;
m_Pool = pool;
}
public virtual string GetName()
{
return "";
}
public virtual bool IsCreated()
{
return false;
}
public virtual void IncrementWriteCount()
{
writeCount++;
}
public virtual bool NeedsFallBack()
{
return requestFallBack && writeCount == 0;
}
public virtual void CreatePooledGraphicsResource() {}
public virtual void CreateGraphicsResource(string name = "") {}
public virtual void ReleasePooledGraphicsResource(int frameIndex) {}
public virtual void ReleaseGraphicsResource() {}
public virtual void LogCreation(RenderGraphLogger logger) {}
public virtual void LogRelease(RenderGraphLogger logger) {}
}
[DebuggerDisplay("Resource ({GetType().Name}:{GetName()})")]
abstract class RenderGraphResource<DescType, ResType>
: IRenderGraphResource
where DescType : struct
where ResType : class
{
public DescType desc;
public ResType graphicsResource;
protected RenderGraphResource()
{
}
public override void Reset(IRenderGraphResourcePool pool)
{
base.Reset(pool);
graphicsResource = null;
}
public override bool IsCreated()
{
return graphicsResource != null;
}
public override void ReleaseGraphicsResource()
{
graphicsResource = null;
}
}
}