using System;
using System.Collections.Generic;
using UnityEngine.Rendering;
using UnityEngine.Profiling;
namespace UnityEngine.Experimental.Rendering.RenderGraphModule
{
///
/// Sets the read and write access for the depth buffer.
///
[Flags]
public enum DepthAccess
{
Read = 1 << 0,
Write = 1 << 1,
ReadWrite = Read | Write,
}
///
/// This struct specifies the context given to every render pass.
///
public ref struct RenderGraphContext
{
public ScriptableRenderContext renderContext;
public CommandBuffer cmd;
public RenderGraphObjectPool renderGraphPool;
public RenderGraphResourceRegistry resources;
}
///
/// This struct contains properties which control the execution of the Render Graph.
///
public struct RenderGraphExecuteParams
{
public int renderingWidth;
public int renderingHeight;
public MSAASamples msaaSamples;
}
class RenderGraphDebugParams
{
public bool enableRenderGraph = false; // TODO: TEMP TO REMOVE
public bool tagResourceNamesWithRG;
public bool clearRenderTargetsAtCreation;
public bool clearRenderTargetsAtRelease;
public bool unbindGlobalTextures;
public bool logFrameInformation;
public bool logResources;
public void RegisterDebug()
{
var list = new List();
list.Add(new DebugUI.BoolField { displayName = "Enable Render Graph", getter = () => enableRenderGraph, setter = value => enableRenderGraph = value });
list.Add(new DebugUI.BoolField { displayName = "Tag Resources with RG", getter = () => tagResourceNamesWithRG, setter = value => tagResourceNamesWithRG = value });
list.Add(new DebugUI.BoolField { displayName = "Clear Render Targets at creation", getter = () => clearRenderTargetsAtCreation, setter = value => clearRenderTargetsAtCreation = value });
list.Add(new DebugUI.BoolField { displayName = "Clear Render Targets at release", getter = () => clearRenderTargetsAtRelease, setter = value => clearRenderTargetsAtRelease = value });
list.Add(new DebugUI.BoolField { displayName = "Unbind Global Textures", getter = () => unbindGlobalTextures, setter = value => unbindGlobalTextures = value });
list.Add(new DebugUI.Button { displayName = "Log Frame Information", action = () => logFrameInformation = true });
list.Add(new DebugUI.Button { displayName = "Log Resources", action = () => logResources = true });
var testPanel = DebugManager.instance.GetPanel("Render Graph", true);
testPanel.children.Add(list.ToArray());
}
public void UnRegisterDebug()
{
DebugManager.instance.RemovePanel("Render Graph");
}
}
///
/// The Render Pass rendering delegate.
///
/// The type of the class used to provide data to the Render Pass.
/// Render Pass specific data.
/// Global Render Graph context.
public delegate void RenderFunc(PassData data, RenderGraphContext renderGraphContext) where PassData : class, new();
///
/// This class is the main entry point of the Render Graph system.
///
public class RenderGraph
{
public static readonly int kMaxMRTCount = 8;
internal abstract class RenderPass
{
internal RenderFunc GetExecuteDelegate()
where PassData : class, new() => ((RenderPass)this).renderFunc;
internal abstract void Execute(RenderGraphContext renderGraphContext);
internal abstract void Release(RenderGraphContext renderGraphContext);
internal abstract bool HasRenderFunc();
internal string name;
internal int index;
internal CustomSampler customSampler;
internal List resourceReadList = new List();
internal List resourceWriteList = new List();
internal List usedRendererListList = new List();
internal bool enableAsyncCompute;
internal RenderGraphMutableResource depthBuffer { get { return m_DepthBuffer; } }
internal RenderGraphMutableResource[] colorBuffers { get { return m_ColorBuffers; } }
internal int colorBufferMaxIndex { get { return m_MaxColorBufferIndex; } }
protected RenderGraphMutableResource[] m_ColorBuffers = new RenderGraphMutableResource[RenderGraph.kMaxMRTCount];
protected RenderGraphMutableResource m_DepthBuffer;
protected int m_MaxColorBufferIndex = -1;
internal void Clear()
{
name = "";
index = -1;
customSampler = null;
resourceReadList.Clear();
resourceWriteList.Clear();
usedRendererListList.Clear();
enableAsyncCompute = false;
// Invalidate everything
m_MaxColorBufferIndex = -1;
m_DepthBuffer = new RenderGraphMutableResource();
for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i)
{
m_ColorBuffers[i] = new RenderGraphMutableResource();
}
}
internal void SetColorBuffer(in RenderGraphMutableResource resource, int index)
{
Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
m_MaxColorBufferIndex = Math.Max(m_MaxColorBufferIndex, index);
m_ColorBuffers[index] = resource;
resourceWriteList.Add(resource);
}
internal void SetDepthBuffer(in RenderGraphMutableResource resource, DepthAccess flags)
{
m_DepthBuffer = resource;
if ((flags | DepthAccess.Read) != 0)
resourceReadList.Add(resource);
if ((flags | DepthAccess.Write) != 0)
resourceWriteList.Add(resource);
}
}
internal sealed class RenderPass : RenderPass
where PassData : class, new()
{
internal PassData data;
internal RenderFunc renderFunc;
internal override void Execute(RenderGraphContext renderGraphContext)
{
GetExecuteDelegate()(data, renderGraphContext);
}
internal override void Release(RenderGraphContext renderGraphContext)
{
Clear();
renderGraphContext.renderGraphPool.Release(data);
data = null;
renderFunc = null;
renderGraphContext.renderGraphPool.Release(this);
}
internal override bool HasRenderFunc()
{
return renderFunc != null;
}
}
RenderGraphResourceRegistry m_Resources;
RenderGraphObjectPool m_RenderGraphPool = new RenderGraphObjectPool();
List m_RenderPasses = new List();
List m_RendererLists = new List();
RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams();
RenderGraphLogger m_Logger = new RenderGraphLogger();
#region Public Interface
public bool enabled { get { return m_DebugParameters.enableRenderGraph; } }
// TODO: Currently only needed by SSAO to sample correctly depth texture mips. Need to figure out a way to hide this behind a proper formalization.
///
/// Gets the RTHandleProperties structure associated with the Render Graph's RTHandle System.
///
public RTHandleProperties rtHandleProperties { get { return m_Resources.GetRTHandleProperties(); } }
///
/// Render Graph constructor.
///
/// Specify if this Render Graph should support MSAA.
/// Specify the initial sample count of MSAA render textures.
public RenderGraph(bool supportMSAA, MSAASamples initialSampleCount)
{
m_Resources = new RenderGraphResourceRegistry(supportMSAA, initialSampleCount, m_DebugParameters, m_Logger);
}
///
/// Cleanup the Render Graph.
///
public void Cleanup()
{
m_Resources.Cleanup();
}
///
/// Register this Render Graph to the debug window.
///
public void RegisterDebug()
{
m_DebugParameters.RegisterDebug();
}
///
/// Unregister this Render Graph from the debug window.
///
public void UnRegisterDebug()
{
m_DebugParameters.UnRegisterDebug();
}
///
/// Import an external texture to the Render Graph.
///
/// External RTHandle that needs to be imported.
/// Optional property that allows you to specify a Shader property name to use for automatic resource binding.
/// A new RenderGraphMutableResource.
public RenderGraphMutableResource ImportTexture(RTHandle rt, int shaderProperty = 0)
{
return m_Resources.ImportTexture(rt, shaderProperty);
}
///
/// Create a new Render Graph Texture resource.
///
/// Texture descriptor.
/// Optional property that allows you to specify a Shader property name to use for automatic resource binding.
/// A new RenderGraphMutableResource.
public RenderGraphMutableResource CreateTexture(TextureDesc desc, int shaderProperty = 0)
{
if (m_DebugParameters.tagResourceNamesWithRG)
desc.name = string.Format("{0}_RenderGraph", desc.name);
return m_Resources.CreateTexture(desc, shaderProperty);
}
///
/// Create a new Render Graph Texture resource using the descriptor from another texture.
///
/// Texture from which the descriptor should be used.
/// Optional property that allows you to specify a Shader property name to use for automatic resource binding.
/// A new RenderGraphMutableResource.
public RenderGraphMutableResource CreateTexture(in RenderGraphResource texture, int shaderProperty = 0)
{
var desc = m_Resources.GetTextureResourceDesc(texture);
if (m_DebugParameters.tagResourceNamesWithRG)
desc.name = string.Format("{0}_RenderGraph", desc.name);
return m_Resources.CreateTexture(desc, shaderProperty);
}
///
/// Gets the descriptor of the specified Texture resource.
///
///
/// The input texture descriptor.
public TextureDesc GetTextureDesc(in RenderGraphResource texture)
{
if (texture.type != RenderGraphResourceType.Texture)
{
throw new ArgumentException("Trying to retrieve a TextureDesc from a resource that is not a texture.");
}
return m_Resources.GetTextureResourceDesc(texture);
}
///
/// Creates a new Renderer List Render Graph resource.
///
/// Renderer List descriptor.
/// A new RenderGraphResource.
public RenderGraphResource CreateRendererList(in RendererListDesc desc)
{
return m_Resources.CreateRendererList(desc);
}
///
/// Add a new Render Pass to the current Render Graph.
///
/// Type of the class to use to provide data to the Render Pass.
/// Name of the new Render Pass (this is also be used to generate a GPU profiling marker).
/// Instance of PassData that is passed to the render function and you must fill.
/// Optional C# profiling object.
/// A new instance of a RenderGraphBuilder used to setup the new Render Pass.
public RenderGraphBuilder AddRenderPass(string passName, out PassData passData, CustomSampler customSampler = null) where PassData : class, new()
{
var renderPass = m_RenderGraphPool.Get>();
renderPass.Clear();
renderPass.index = m_RenderPasses.Count;
renderPass.data = m_RenderGraphPool.Get();
renderPass.name = passName;
renderPass.customSampler = customSampler;
passData = renderPass.data;
m_RenderPasses.Add(renderPass);
return new RenderGraphBuilder(renderPass, m_Resources);
}
///
/// Execute the Render Graph in its current state.
///
/// ScriptableRenderContext used to execute Scriptable Render Pipeline.
/// Command Buffer used for Render Passes rendering.
/// Render Graph execution parameters.
public void Execute(ScriptableRenderContext renderContext, CommandBuffer cmd, in RenderGraphExecuteParams parameters)
{
m_Logger.Initialize();
// Update RTHandleSystem with size for this rendering pass.
m_Resources.SetRTHandleReferenceSize(parameters.renderingWidth, parameters.renderingHeight, parameters.msaaSamples);
LogFrameInformation(parameters.renderingWidth, parameters.renderingHeight);
// First pass, traversal and pruning
for (int passIndex = 0; passIndex < m_RenderPasses.Count; ++passIndex)
{
var pass = m_RenderPasses[passIndex];
// TODO: Pruning
// Gather all renderer lists
m_RendererLists.AddRange(pass.usedRendererListList);
}
// Creates all renderer lists
m_Resources.CreateRendererLists(m_RendererLists);
LogRendererListsCreation();
// Second pass, execution
RenderGraphContext rgContext = new RenderGraphContext();
rgContext.cmd = cmd;
rgContext.renderContext = renderContext;
rgContext.renderGraphPool = m_RenderGraphPool;
rgContext.resources = m_Resources;
try
{
for (int passIndex = 0; passIndex < m_RenderPasses.Count; ++passIndex)
{
var pass = m_RenderPasses[passIndex];
if (!pass.HasRenderFunc())
{
throw new InvalidOperationException(string.Format("RenderPass {0} was not provided with an execute function.", pass.name));
}
using (new ProfilingSample(cmd, pass.name, pass.customSampler))
{
LogRenderPassBegin(pass);
using (new RenderGraphLogIndent(m_Logger))
{
PreRenderPassExecute(passIndex, pass, rgContext);
pass.Execute(rgContext);
PostRenderPassExecute(passIndex, pass, rgContext);
}
}
}
}
catch(Exception e)
{
Debug.LogError("Render Graph Execution error");
Debug.LogException(e);
}
finally
{
ClearRenderPasses();
m_Resources.Clear();
m_RendererLists.Clear();
if (m_DebugParameters.logFrameInformation || m_DebugParameters.logResources)
Debug.Log(m_Logger.GetLog());
m_DebugParameters.logFrameInformation = false;
m_DebugParameters.logResources = false;
}
}
#endregion
#region Internal Interface
private RenderGraph()
{
}
void PreRenderPassSetRenderTargets(in RenderPass pass, RenderGraphContext rgContext)
{
if (pass.depthBuffer.IsValid() || pass.colorBufferMaxIndex != -1)
{
var mrtArray = rgContext.renderGraphPool.GetTempArray(pass.colorBufferMaxIndex + 1);
var colorBuffers = pass.colorBuffers;
if (pass.colorBufferMaxIndex > 0)
{
for (int i = 0; i <= pass.colorBufferMaxIndex; ++i)
{
if (!colorBuffers[i].IsValid())
throw new InvalidOperationException("MRT setup is invalid. Some indices are not used.");
mrtArray[i] = m_Resources.GetTexture(colorBuffers[i]);
}
if (pass.depthBuffer.IsValid())
{
CoreUtils.SetRenderTarget(rgContext.cmd, mrtArray, m_Resources.GetTexture(pass.depthBuffer));
}
else
{
throw new InvalidOperationException("Setting MRTs without a depth buffer is not supported.");
}
}
else
{
if (pass.depthBuffer.IsValid())
{
if (pass.colorBufferMaxIndex > -1)
CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.colorBuffers[0]), m_Resources.GetTexture(pass.depthBuffer));
else
CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.depthBuffer));
}
else
{
CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.colorBuffers[0]));
}
}
}
}
void PreRenderPassExecute(int passIndex, in RenderPass pass, RenderGraphContext rgContext)
{
// TODO merge clear and setup here if possible
m_Resources.CreateAndClearTexturesForPass(rgContext, pass.index, pass.resourceWriteList);
PreRenderPassSetRenderTargets(pass, rgContext);
m_Resources.PreRenderPassSetGlobalTextures(rgContext, pass.resourceReadList);
}
void PostRenderPassExecute(int passIndex, in RenderPass pass, RenderGraphContext rgContext)
{
if (m_DebugParameters.unbindGlobalTextures)
m_Resources.PostRenderPassUnbindGlobalTextures(rgContext, pass.resourceReadList);
m_RenderGraphPool.ReleaseAllTempAlloc();
m_Resources.ReleaseTexturesForPass(rgContext, pass.index, pass.resourceReadList, pass.resourceWriteList);
pass.Release(rgContext);
}
void ClearRenderPasses()
{
m_RenderPasses.Clear();
}
void LogFrameInformation(int renderingWidth, int renderingHeight)
{
if (m_DebugParameters.logFrameInformation)
{
m_Logger.LogLine("==== Staring frame at resolution ({0}x{1}) ====", renderingWidth, renderingHeight);
m_Logger.LogLine("Number of passes declared: {0}", m_RenderPasses.Count);
}
}
void LogRendererListsCreation()
{
if (m_DebugParameters.logFrameInformation)
{
m_Logger.LogLine("Number of renderer lists created: {0}", m_RendererLists.Count);
}
}
void LogRenderPassBegin(in RenderPass pass)
{
if (m_DebugParameters.logFrameInformation)
{
m_Logger.LogLine("Executing pass \"{0}\" (index: {1})", pass.name, pass.index);
}
}
#endregion
}
}