UnityGame/Library/PackageCache/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraph.cs
2024-10-27 10:53:47 +03:00

2668 lines
127 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Scripting.APIUpdating;
// Typedef for the in-engine RendererList API (to avoid conflicts with the experimental version)
using CoreRendererListDesc = UnityEngine.Rendering.RendererUtils.RendererListDesc;
namespace UnityEngine.Rendering.RenderGraphModule
{
/// <summary>
/// Sets the read and write access for the depth buffer.
/// </summary>
[Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public enum DepthAccess
{
///<summary>Read Access.</summary>
Read = 1 << 0,
///<summary>Write Access.</summary>
Write = 1 << 1,
///<summary>Read and Write Access.</summary>
ReadWrite = Read | Write,
}
/// <summary>
/// Express the operations the rendergraph pass will do on a resource.
/// </summary>
[Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public enum AccessFlags
{
///<summary>The pass does not access the resource at all. Calling Use* functions with none has no effect.</summary>
None = 0,
///<summary>This pass will read data the resource. Data in the resource should never be written unless one of the write flags is also present. Writing to a read-only resource may lead to undefined results, significant performance penaties, and GPU crashes.</summary>
Read = 1 << 0,
///<summary>This pass will at least write some data to the resource. Data in the resource should never be read unless one of the read flags is also present. Reading from a write-only resource may lead to undefined results, significant performance penaties, and GPU crashes.</summary>
Write = 1 << 1,
///<summary>Previous data in the resource is not preserved. The resource will contain undefined data at the beginning of the pass.</summary>
Discard = 1 << 2,
///<summary>All data in the resource will be written by this pass. Data in the resource should never be read.</summary>
WriteAll = Write | Discard,
///<summary> Shortcut for Read | Write</summary>
ReadWrite = Read | Write
}
/// <summary>
/// An object representing the internal context of a rendergraph pass execution.
/// This object is public for technical reasons only and should not be used.
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public class InternalRenderGraphContext
{
internal ScriptableRenderContext renderContext;
internal CommandBuffer cmd;
internal RenderGraphObjectPool renderGraphPool;
internal RenderGraphDefaultResources defaultResources;
internal RenderGraphPass executingPass;
internal bool contextlessTesting;
}
// This whole thing is a bit of a mess InternalRenderGraphContext is public (but all members are internal)
// just because the C# standard says that all interface member function implementations should be public.
// So below in for example the RasterGraphContext we can't implement the (internal) interface as
// internal void FromInternalContext(InternalRenderGraphContext context) { ... }
// So we have to make FromInternalContext public so InternalRenderGraphContext also becomes public.
// This seems an oversight in c# where Interfaces used as Generic constraints could very well be useful
// with internal only functions.
internal interface IDerivedRendergraphContext
{
/// <summary>
/// This function is only public for techical resons of the c# language and should not be called outside the package.
/// </summary>
/// <param name="context">The context to convert</param>
public void FromInternalContext(InternalRenderGraphContext context);
}
/// <summary>
/// This class specifies the context given to every render pass. This context type passes a generic
/// command buffer that can be used to schedule all commands. This will eventually be deprecated
/// in favor of more specific contexts that have more specific command buffer types.
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public struct RenderGraphContext : IDerivedRendergraphContext
{
private InternalRenderGraphContext wrappedContext;
/// <inheritdoc />
public void FromInternalContext(InternalRenderGraphContext context)
{
wrappedContext = context;
}
///<summary>Scriptable Render Context used for rendering.</summary>
public ScriptableRenderContext renderContext { get => wrappedContext.renderContext; }
///<summary>Command Buffer used for rendering.</summary>
public CommandBuffer cmd { get => wrappedContext.cmd; }
///<summary>Render Graph pool used for temporary data.</summary>
public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
///<summary>Render Graph default resources.</summary>
public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
}
/// <summary>
/// This class declares the context object passed to the execute function of a raster render pass.
/// <see cref="RenderGraph.AddRasterRenderPass"/>
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public struct RasterGraphContext : IDerivedRendergraphContext
{
private InternalRenderGraphContext wrappedContext;
///<summary>Command Buffer used for rendering.</summary>
public RasterCommandBuffer cmd;
///<summary>Render Graph default resources.</summary>
public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
///<summary>Render Graph pool used for temporary data.</summary>
public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
static internal RasterCommandBuffer rastercmd = new RasterCommandBuffer(null, null, false);
/// <inheritdoc />
public void FromInternalContext(InternalRenderGraphContext context)
{
wrappedContext = context;
rastercmd.m_WrappedCommandBuffer = wrappedContext.cmd;
rastercmd.m_ExecutingPass = context.executingPass;
cmd = rastercmd;
}
}
/// <summary>
/// This class declares the context object passed to the execute function of a compute render pass.
/// <see cref="RenderGraph.AddComputePass"/>
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public class ComputeGraphContext : IDerivedRendergraphContext
{
private InternalRenderGraphContext wrappedContext;
///<summary>Command Buffer used for rendering.</summary>
public ComputeCommandBuffer cmd;
///<summary>Render Graph default resources.</summary>
public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
///<summary>Render Graph pool used for temporary data.</summary>
public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
static internal ComputeCommandBuffer computecmd = new ComputeCommandBuffer(null, null, false);
/// <inheritdoc />
public void FromInternalContext(InternalRenderGraphContext context)
{
wrappedContext = context;
computecmd.m_WrappedCommandBuffer = wrappedContext.cmd;
computecmd.m_ExecutingPass = context.executingPass;
cmd = computecmd;
}
}
/// <summary>
/// This class declares the context object passed to the execute function of an unsafe render pass.
/// <see cref="RenderGraph.AddUnsafePass"/>
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public class UnsafeGraphContext : IDerivedRendergraphContext
{
private InternalRenderGraphContext wrappedContext;
///<summary>Unsafe Command Buffer used for rendering.</summary>
public UnsafeCommandBuffer cmd;
///<summary>Render Graph default resources.</summary>
public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
///<summary>Render Graph pool used for temporary data.</summary>
public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
internal static UnsafeCommandBuffer unsCmd = new UnsafeCommandBuffer(null, null, false);
/// <inheritdoc />
public void FromInternalContext(InternalRenderGraphContext context)
{
wrappedContext = context;
unsCmd.m_WrappedCommandBuffer = wrappedContext.cmd;
unsCmd.m_ExecutingPass = context.executingPass;
cmd = unsCmd;
}
}
/// <summary>
/// This struct contains properties which control the execution of the Render Graph.
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public struct RenderGraphParameters
{
///<summary>Identifier for this render graph execution.</summary>
public string executionName;
///<summary>Index of the current frame being rendered.</summary>
public int currentFrameIndex;
///<summary> Controls whether to enable Renderer List culling or not.</summary>
public bool rendererListCulling;
///<summary>Scriptable Render Context used by the render pipeline.</summary>
public ScriptableRenderContext scriptableRenderContext;
///<summary>Command Buffer used to execute graphic commands.</summary>
public CommandBuffer commandBuffer;
///<summary>When running tests indicate the context is intentionally invalid and all calls on it should just do nothing.
///This allows you to run tests that rely on code execution the way to the pass render functions
///This also changes some behaviours with exception handling and error logging so the test framework can act on exceptions to validate behaviour better.</summary>
internal bool invalidContextForTesting;
}
/// <summary>
/// The Render Pass rendering delegate to use with typed contexts.
/// </summary>
/// <typeparam name="PassData">The type of the class used to provide data to the Render Pass.</typeparam>
/// <typeparam name="ContextType">The type of the context that will be passed to the render function.</typeparam>
/// <param name="data">Render Pass specific data.</param>
/// <param name="renderGraphContext">Global Render Graph context.</param>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public delegate void BaseRenderFunc<PassData, ContextType>(PassData data, ContextType renderGraphContext) where PassData : class, new();
/// <summary>
/// This class is the main entry point of the Render Graph system.
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public partial class RenderGraph
{
///<summary>Maximum number of MRTs supported by Render Graph.</summary>
public static readonly int kMaxMRTCount = 8;
internal struct CompiledResourceInfo
{
public List<int> producers;
public List<int> consumers;
public int refCount;
public bool imported;
public void Reset()
{
if (producers == null)
producers = new List<int>();
if (consumers == null)
consumers = new List<int>();
producers.Clear();
consumers.Clear();
refCount = 0;
imported = false;
}
}
[DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
internal struct CompiledPassInfo
{
public string name;
public int index;
public List<int>[] resourceCreateList;
public List<int>[] resourceReleaseList;
public GraphicsFence fence;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
// This members are only here to ease debugging.
public List<string>[] debugResourceReads;
public List<string>[] debugResourceWrites;
#endif
public int refCount;
public int syncToPassIndex; // Index of the pass that needs to be waited for.
public int syncFromPassIndex; // Smaller pass index that waits for this pass.
public bool enableAsyncCompute;
public bool allowPassCulling;
public bool needGraphicsFence;
public bool culled;
public bool culledByRendererList;
public bool hasSideEffect;
public bool enableFoveatedRasterization;
public void Reset(RenderGraphPass pass, int index)
{
name = pass.name;
this.index = index;
enableAsyncCompute = pass.enableAsyncCompute;
allowPassCulling = pass.allowPassCulling;
enableFoveatedRasterization = pass.enableFoveatedRasterization;
if (resourceCreateList == null)
{
resourceCreateList = new List<int>[(int)RenderGraphResourceType.Count];
resourceReleaseList = new List<int>[(int)RenderGraphResourceType.Count];
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
resourceCreateList[i] = new List<int>();
resourceReleaseList[i] = new List<int>();
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
debugResourceReads = new List<string>[(int)RenderGraphResourceType.Count];
debugResourceWrites = new List<string>[(int)RenderGraphResourceType.Count];
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
debugResourceReads[i] = new List<string>();
debugResourceWrites[i] = new List<string>();
}
#endif
}
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
resourceCreateList[i].Clear();
resourceReleaseList[i].Clear();
}
refCount = 0;
culled = false;
culledByRendererList = false;
hasSideEffect = false;
syncToPassIndex = -1;
syncFromPassIndex = -1;
needGraphicsFence = false;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
debugResourceReads[i].Clear();
debugResourceWrites[i].Clear();
}
#endif
}
}
/// <summary>
/// Enable the use of the render pass API by the graph instead of traditional SetRenderTarget. This is an advanced
/// feature and users have to be aware of the specific impact it has on rendergraph/graphics APIs below.
///
/// When enabled, the render graph try to use render passes and supasses instead of relying on SetRendertarget. It
/// will try to aggressively optimize the number of BeginRenderPass+EndRenderPass calls as well as calls to NextSubPass.
/// This with the aim to maximize the time spent "on chip" on tile based renderers.
///
/// The Graph will automatically determine when to break render passes as well as the load and store actions to apply to these render passes.
/// To do this, the graph will analyze the use of textures. E.g. when a texture is used twice in a row as a active render target, the two
/// render graph passes will be merged in a single render pass with two surpasses. On the other hand if a render target is sampled as a texture in
/// a later pass this render target will be stored (and possibly resolved) and the render pass will be broken up.
///
/// When setting this setting to true some existing render graph API is no longer valid as it can't express detailed frame information needed to emit
/// native render pases. In particular:
/// - The ImportBackbuffer overload without a RenderTargetInfo argument.
/// - Any AddRenderPass overloads. The more specific AddRasterRenderPass/AddComputePass/AddUnsafePass functions should be used to register passes.
///
/// In addition to this, additional validation will be done on the correctness of arguments of existing API that was not previously done. This could lead
/// to new errors when using existing render graph code with nativeRenderPassesEnabled.
///
/// Note: that CommandBuffer.BeginRenderPass/EndRenderPass calls are different by design from SetRenderTarget so this could also have
/// effects outside of render graph (e.g. for code relying on the currently active render target as this will not be updated when using render passes).
/// </summary>
public bool nativeRenderPassesEnabled
{
get; set;
}
internal/*for tests*/ RenderGraphResourceRegistry m_Resources;
RenderGraphObjectPool m_RenderGraphPool = new RenderGraphObjectPool();
RenderGraphBuilders m_builderInstance = new RenderGraphBuilders();
internal/*for tests*/ List<RenderGraphPass> m_RenderPasses = new List<RenderGraphPass>(64);
List<RendererListHandle> m_RendererLists = new List<RendererListHandle>(32);
RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams();
RenderGraphLogger m_FrameInformationLogger = new RenderGraphLogger();
RenderGraphDefaultResources m_DefaultResources = new RenderGraphDefaultResources();
Dictionary<int, ProfilingSampler> m_DefaultProfilingSamplers = new Dictionary<int, ProfilingSampler>();
InternalRenderGraphContext m_RenderGraphContext = new InternalRenderGraphContext();
CommandBuffer m_PreviousCommandBuffer;
List<int>[] m_ImmediateModeResourceList = new List<int>[(int)RenderGraphResourceType.Count];
RenderGraphCompilationCache m_CompilationCache;
RenderTargetIdentifier[][] m_TempMRTArrays = null;
internal interface ICompiledGraph
{
public void Clear();
}
// Compiled Render Graph info.
internal class CompiledGraph : ICompiledGraph
{
// This is a 1:1 mapping on the resource handle indexes, this means the first element with index 0 will represent the "null" handle
public DynamicArray<CompiledResourceInfo>[] compiledResourcesInfos = new DynamicArray<CompiledResourceInfo>[(int)RenderGraphResourceType.Count];
public DynamicArray<CompiledPassInfo> compiledPassInfos = new DynamicArray<CompiledPassInfo>();
public int lastExecutionFrame;
public CompiledGraph()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
compiledResourcesInfos[i] = new DynamicArray<CompiledResourceInfo>();
}
}
public void Clear()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
compiledResourcesInfos[i].Clear();
compiledPassInfos.Clear();
}
void InitResourceInfosData(DynamicArray<CompiledResourceInfo> resourceInfos, int count)
{
resourceInfos.Resize(count);
for (int i = 0; i < resourceInfos.size; ++i)
resourceInfos[i].Reset();
}
public void InitializeCompilationData(List<RenderGraphPass> passes, RenderGraphResourceRegistry resources)
{
InitResourceInfosData(compiledResourcesInfos[(int)RenderGraphResourceType.Texture], resources.GetTextureResourceCount());
InitResourceInfosData(compiledResourcesInfos[(int)RenderGraphResourceType.Buffer], resources.GetBufferResourceCount());
InitResourceInfosData(compiledResourcesInfos[(int)RenderGraphResourceType.AccelerationStructure], resources.GetRayTracingAccelerationStructureResourceCount());
compiledPassInfos.Resize(passes.Count);
for (int i = 0; i < compiledPassInfos.size; ++i)
compiledPassInfos[i].Reset(passes[i], i);
}
}
Stack<int> m_CullingStack = new Stack<int>();
string m_CurrentExecutionName;
int m_ExecutionCount;
int m_CurrentFrameIndex;
int m_CurrentImmediatePassIndex;
bool m_ExecutionExceptionWasRaised;
bool m_HasRenderGraphBegun;
bool m_RendererListCulling;
bool m_EnableCompilationCaching;
CompiledGraph m_DefaultCompiledGraph = new();
CompiledGraph m_CurrentCompiledGraph;
string m_CaptureDebugDataForExecution; // Null unless debug data has been requested
Dictionary<string, DebugData> m_DebugData = new Dictionary<string, DebugData>();
// Global list of living render graphs
static List<RenderGraph> s_RegisteredGraphs = new List<RenderGraph>();
#region Public Interface
/// <summary>Name of the Render Graph.</summary>
public string name { get; private set; } = "RenderGraph";
/// <summary>Request debug data be captured for the provided execution on the next frame.</summary>
internal void RequestCaptureDebugData(string executionName)
{
m_CaptureDebugDataForExecution = executionName;
}
/// <summary>If true, the Render Graph Viewer is active.</summary>
public static bool isRenderGraphViewerActive { get; internal set; }
/// <summary>If true, the Render Graph will run its various validity checks while processing (not considered in release mode).</summary>
internal static bool enableValidityChecks { get; private set; }
/// <summary>
/// Set of default resources usable in a pass rendering code.
/// </summary>
public RenderGraphDefaultResources defaultResources
{
get
{
return m_DefaultResources;
}
}
/// <summary>
/// Render Graph constructor.
/// </summary>
/// <param name="name">Optional name used to identify the render graph instnace.</param>
public RenderGraph(string name = "RenderGraph")
{
this.name = name;
if (GraphicsSettings.TryGetRenderPipelineSettings<RenderGraphGlobalSettings>(out var renderGraphGlobalSettings))
{
m_EnableCompilationCaching = renderGraphGlobalSettings.enableCompilationCaching;
if (m_EnableCompilationCaching)
m_CompilationCache = new RenderGraphCompilationCache();
enableValidityChecks = renderGraphGlobalSettings.enableValidityChecks;
}
else // No SRP pipeline is present/active, it can happen with unit tests
{
enableValidityChecks = true;
}
m_TempMRTArrays = new RenderTargetIdentifier[kMaxMRTCount][];
for (int i = 0; i < kMaxMRTCount; ++i)
m_TempMRTArrays[i] = new RenderTargetIdentifier[i + 1];
m_Resources = new RenderGraphResourceRegistry(m_DebugParameters, m_FrameInformationLogger);
s_RegisteredGraphs.Add(this);
onGraphRegistered?.Invoke(this);
}
/// <summary>
/// Cleanup the Render Graph.
/// </summary>
public void Cleanup()
{
m_Resources.Cleanup();
m_DefaultResources.Cleanup();
m_RenderGraphPool.Cleanup();
s_RegisteredGraphs.Remove(this);
onGraphUnregistered?.Invoke(this);
nativeCompiler?.contextData?.Dispose();
m_CompilationCache?.Clear();
}
internal RenderGraphDebugParams debugParams => m_DebugParameters;
internal List<DebugUI.Widget> GetWidgetList()
{
return m_DebugParameters.GetWidgetList(name);
}
internal bool areAnySettingsActive => m_DebugParameters.AreAnySettingsActive;
/// <summary>
/// Register the render graph to the debug window.
/// </summary>
/// <param name="panel">Optional debug panel to which the render graph debug parameters will be registered.</param>
public void RegisterDebug(DebugUI.Panel panel = null)
{
m_DebugParameters.RegisterDebug(name, panel);
}
/// <summary>
/// Unregister render graph from the debug window.
/// </summary>
public void UnRegisterDebug()
{
m_DebugParameters.UnRegisterDebug(this.name);
}
/// <summary>
/// Get the list of all registered render graphs.
/// </summary>
/// <returns>The list of all registered render graphs.</returns>
public static List<RenderGraph> GetRegisteredRenderGraphs()
{
return s_RegisteredGraphs;
}
/// <summary>
/// Returns the last rendered frame debug data. Can be null if requireDebugData is set to false.
/// </summary>
/// <returns>The last rendered frame debug data</returns>
internal DebugData GetDebugData(string executionName)
{
if (m_DebugData.TryGetValue(executionName, out var debugData))
return debugData;
return null;
}
/// <summary>
/// End frame processing. Purge resources that have been used since last frame and resets internal states.
/// This need to be called once per frame.
/// </summary>
public void EndFrame()
{
m_Resources.PurgeUnusedGraphicsResources();
if (m_DebugParameters.logFrameInformation)
{
Debug.Log(m_FrameInformationLogger.GetAllLogs());
m_DebugParameters.logFrameInformation = false;
}
if (m_DebugParameters.logResources)
{
m_Resources.FlushLogs();
m_DebugParameters.logResources = false;
}
}
/// <summary>
/// Import an external texture to the Render Graph.
/// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled.
/// </summary>
/// <param name="rt">External RTHandle that needs to be imported.</param>
/// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
public TextureHandle ImportTexture(RTHandle rt)
{
return m_Resources.ImportTexture(rt);
}
/// <summary>
/// Import an external texture to the Render Graph.
/// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled.
///
/// Note: RTHandles that wrap RenderTargetIdentifier will fail to import using this overload as render graph can't derive the render texture's properties.
/// In that case the overload taking a RenderTargetInfo argument should be used instead.
/// </summary>
/// <param name="rt">External RTHandle that needs to be imported.</param>
/// <param name="importParams">Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware.</param>
/// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
public TextureHandle ImportTexture(RTHandle rt, ImportResourceParams importParams )
{
return m_Resources.ImportTexture(rt, importParams);
}
/// <summary>
/// Import an external texture to the Render Graph. This overload should be used for RTHandles wrapping a RenderTargetIdentifier.
/// If the RTHandle is wrapping a RenderTargetIdentifer, Rendergrpah can't derive the render texture's properties so the user has to provide this info to the graph through RenderTargetInfo.
///
/// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled.
///
/// Note: To avoid inconsistencies between the passed in RenderTargetInfo and render texture this overload can only be used when the RTHandle is wrapping a RenderTargetIdentifier.
/// If this is not the case, the overload of ImportTexture without a RenderTargetInfo argument should be used instead.
/// </summary>
/// <param name="rt">External RTHandle that needs to be imported.</param>
/// <param name="info">The properties of the passed in RTHandle.</param>
/// <param name="importParams">Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware.</param>
/// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
public TextureHandle ImportTexture(RTHandle rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams() )
{
return m_Resources.ImportTexture(rt, info, importParams);
}
/// <summary>
/// Import an external texture to the Render Graph and set the handle as builtin handle. This can only happen from within the graph module
/// so it is internal.
/// </summary>
internal TextureHandle ImportTexture(RTHandle rt, bool isBuiltin)
{
return m_Resources.ImportTexture(rt, isBuiltin);
}
/// <summary>
/// Import the final backbuffer to render graph. The rendergraph can't derive the properties of a RenderTargetIdentifier as it is an opaque handle so the user has to pass them in through the info argument.
/// </summary>
/// <param name="rt">Backbuffer render target identifier.</param>
/// <param name="info">The properties of the passed in RTHandle.</param>
/// <param name="importParams">Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware.</param>
/// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams())
{
return m_Resources.ImportBackbuffer(rt, info, importParams);
}
/// <summary>
/// Import the final backbuffer to render graph.
/// This function can only be used when nativeRenderPassesEnabled is false.
/// </summary>
/// <param name="rt">Backbuffer render target identifier.</param>
/// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt)
{
RenderTargetInfo dummy = new RenderTargetInfo();
dummy.width = dummy.height = dummy.volumeDepth = dummy.msaaSamples = 1;
dummy.format = GraphicsFormat.R8G8B8A8_SRGB;
return m_Resources.ImportBackbuffer(rt, dummy, new ImportResourceParams());
}
/// <summary>
/// Create a new Render Graph Texture resource.
/// </summary>
/// <param name="desc">Texture descriptor.</param>
/// <returns>A new TextureHandle.</returns>
public TextureHandle CreateTexture(in TextureDesc desc)
{
return m_Resources.CreateTexture(desc);
}
/// <summary>
/// Create a new Render Graph Shared Texture resource.
/// This texture will be persistent across render graph executions.
/// </summary>
/// <param name="desc">Creation descriptor of the texture.</param>
/// <param name="explicitRelease">Set to true if you want to manage the lifetime of the resource yourself. Otherwise the resource will be released automatically if unused for a time.</param>
/// <returns>A new TextureHandle.</returns>
public TextureHandle CreateSharedTexture(in TextureDesc desc, bool explicitRelease = false)
{
if (m_HasRenderGraphBegun)
throw new InvalidOperationException("A shared texture can only be created outside of render graph execution.");
return m_Resources.CreateSharedTexture(desc, explicitRelease);
}
/// <summary>
/// Refresh a shared texture with a new descriptor.
/// </summary>
/// <param name="handle">Shared texture that needs to be updated.</param>
/// <param name="desc">New Descriptor for the texture.</param>
public void RefreshSharedTextureDesc(TextureHandle handle, in TextureDesc desc)
{
m_Resources.RefreshSharedTextureDesc(handle, desc);
}
/// <summary>
/// Release a Render Graph shared texture resource.
/// </summary>
/// <param name="texture">The handle to the texture that needs to be release.</param>
public void ReleaseSharedTexture(TextureHandle texture)
{
if (m_HasRenderGraphBegun)
throw new InvalidOperationException("A shared texture can only be release outside of render graph execution.");
m_Resources.ReleaseSharedTexture(texture);
}
/// <summary>
/// Create a new Render Graph Texture resource using the descriptor from another texture.
/// </summary>
/// <param name="texture">Texture from which the descriptor should be used.</param>
/// <returns>A new TextureHandle.</returns>
public TextureHandle CreateTexture(TextureHandle texture)
{
return m_Resources.CreateTexture(m_Resources.GetTextureResourceDesc(texture.handle));
}
/// <summary>
/// Create a new Render Graph Texture if the passed handle is invalid and use said handle as output.
/// If the passed handle is valid, no texture is created.
/// </summary>
/// <param name="desc">Desc used to create the texture.</param>
/// <param name="texture">Texture from which the descriptor should be used.</param>
public void CreateTextureIfInvalid(in TextureDesc desc, ref TextureHandle texture)
{
if (!texture.IsValid())
texture = m_Resources.CreateTexture(desc);
}
/// <summary>
/// Gets the descriptor of the specified Texture resource.
/// </summary>
/// <param name="texture">Texture resource from which the descriptor is requested.</param>
/// <returns>The input texture descriptor.</returns>
public TextureDesc GetTextureDesc(TextureHandle texture)
{
return m_Resources.GetTextureResourceDesc(texture.handle);
}
/// <summary>
/// Gets the descriptor of the specified Texture resource.
/// </summary>
/// <param name="texture">Texture resource from which the descriptor is requested.</param>
/// <returns>The input texture descriptor.</returns>
public RenderTargetInfo GetRenderTargetInfo(TextureHandle texture)
{
RenderTargetInfo info;
m_Resources.GetRenderTargetInfo(texture.handle, out info);
return info;
}
/// <summary>
/// Creates a new Renderer List Render Graph resource.
/// </summary>
/// <param name="desc">Renderer List descriptor.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateRendererList(in CoreRendererListDesc desc)
{
return m_Resources.CreateRendererList(desc);
}
/// <summary>
/// Creates a new Renderer List Render Graph resource.
/// </summary>
/// <param name="desc">Renderer List descriptor.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateRendererList(in RendererListParams desc)
{
return m_Resources.CreateRendererList(desc);
}
/// <summary>
/// Creates a new Shadow Renderer List Render Graph resource.
/// </summary>
/// <param name="shadowDrawingSettings">DrawSettings that describe the shadow drawcall.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateShadowRendererList(ref ShadowDrawingSettings shadowDrawingSettings)
{
return m_Resources.CreateShadowRendererList(m_RenderGraphContext.renderContext, ref shadowDrawingSettings);
}
/// <summary>
/// Creates a new Gizmo Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering the Gizmo.</param>
/// <param name="gizmoSubset">GizmoSubset that specifies whether gizmos render before or after postprocessing for a camera render. </param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateGizmoRendererList(in Camera camera, in GizmoSubset gizmoSubset)
{
return m_Resources.CreateGizmoRendererList(m_RenderGraphContext.renderContext, camera, gizmoSubset);
}
/// <summary>
/// Creates a new UIOverlay Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering the full UIOverlay.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateUIOverlayRendererList(in Camera camera)
{
return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, UISubset.All);
}
/// <summary>
/// Creates a new UIOverlay Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering some subset of the UIOverlay.</param>
/// <param name="uiSubset">Enum flag that specifies which subset to render.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateUIOverlayRendererList(in Camera camera, in UISubset uiSubset)
{
return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, uiSubset);
}
/// <summary>
/// Creates a new WireOverlay Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering the WireOverlay.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateWireOverlayRendererList(in Camera camera)
{
return m_Resources.CreateWireOverlayRendererList(m_RenderGraphContext.renderContext, camera);
}
/// <summary>
/// Creates a new Skybox Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering the Skybox.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateSkyboxRendererList(in Camera camera)
{
return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera);
}
/// <summary>
/// Creates a new Skybox Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering the Skybox.</param>
/// <param name="projectionMatrix">The projection matrix used during XR rendering of the skybox.</param>
/// <param name="viewMatrix">The view matrix used during XR rendering of the skybox.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrix, Matrix4x4 viewMatrix)
{
return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrix, viewMatrix);
}
/// <summary>
/// Creates a new Skybox Renderer List Render Graph resource.
/// </summary>
/// <param name="camera">The camera that is used for rendering the Skybox.</param>
/// <param name="projectionMatrixL">The left eye projection matrix used during Legacy single pass XR rendering of the skybox.</param>
/// <param name="viewMatrixL">The left eye view matrix used during Legacy single pass XR rendering of the skybox.</param>
/// <param name="projectionMatrixR">The right eye projection matrix used during Legacy single pass XR rendering of the skybox.</param>
/// <param name="viewMatrixR">The right eye view matrix used during Legacy single pass XR rendering of the skybox.</param>
/// <returns>A new RendererListHandle.</returns>
public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrixL, Matrix4x4 viewMatrixL, Matrix4x4 projectionMatrixR, Matrix4x4 viewMatrixR)
{
return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrixL, viewMatrixL, projectionMatrixR, viewMatrixR);
}
/// <summary>
/// Import an external Graphics Buffer to the Render Graph.
/// Any pass writing to an imported graphics buffer will be considered having side effects and can't be automatically culled.
/// </summary>
/// <param name="graphicsBuffer">External Graphics Buffer that needs to be imported.</param>
/// <param name="forceRelease">The imported graphics buffer will be released after usage.</param>
/// <returns>A new GraphicsBufferHandle.</returns>
public BufferHandle ImportBuffer(GraphicsBuffer graphicsBuffer, bool forceRelease = false)
{
return m_Resources.ImportBuffer(graphicsBuffer, forceRelease);
}
/// <summary>
/// Create a new Render Graph Graphics Buffer resource.
/// </summary>
/// <param name="desc">Graphics Buffer descriptor.</param>
/// <returns>A new GraphicsBufferHandle.</returns>
public BufferHandle CreateBuffer(in BufferDesc desc)
{
return m_Resources.CreateBuffer(desc);
}
/// <summary>
/// Create a new Render Graph Graphics Buffer resource using the descriptor from another graphics buffer.
/// </summary>
/// <param name="graphicsBuffer">Graphics Buffer from which the descriptor should be used.</param>
/// <returns>A new GraphicsBufferHandle.</returns>
public BufferHandle CreateBuffer(in BufferHandle graphicsBuffer)
{
return m_Resources.CreateBuffer(m_Resources.GetBufferResourceDesc(graphicsBuffer.handle));
}
/// <summary>
/// Gets the descriptor of the specified Graphics Buffer resource.
/// </summary>
/// <param name="graphicsBuffer">Graphics Buffer resource from which the descriptor is requested.</param>
/// <returns>The input graphics buffer descriptor.</returns>
public BufferDesc GetBufferDesc(in BufferHandle graphicsBuffer)
{
return m_Resources.GetBufferResourceDesc(graphicsBuffer.handle);
}
/// <summary>
/// Import an external RayTracingAccelerationStructure to the Render Graph.
/// Any pass writing to (building) an imported RayTracingAccelerationStructure will be considered having side effects and can't be automatically culled.
/// </summary>
/// <param name="accelStruct">External RayTracingAccelerationStructure that needs to be imported.</param>
/// <param name="name">Optional name for identifying the RayTracingAccelerationStructure in the Render Graph.</param>
/// <returns>A new RayTracingAccelerationStructureHandle.</returns>
public RayTracingAccelerationStructureHandle ImportRayTracingAccelerationStructure(in RayTracingAccelerationStructure accelStruct, string name = null)
{
return m_Resources.ImportRayTracingAccelerationStructure(accelStruct, name);
}
/// <summary>
/// Add a new Raster Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass.</returns>
public IRasterRenderGraphBuilder AddRasterRenderPass<PassData>(string passName, out PassData passData
#if !CORE_PACKAGE_DOCTOOLS
, [CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
return AddRasterRenderPass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
}
/// <summary>
/// Add a new Raster Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="sampler">Profiling sampler used around the pass.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass.</returns>
public IRasterRenderGraphBuilder AddRasterRenderPass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
#if !CORE_PACKAGE_DOCTOOLS
,[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
AddPassDebugMetadata(passName, file, line);
var renderPass = m_RenderGraphPool.Get<RasterRenderGraphPass<PassData>>();
renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Raster, sampler);
passData = renderPass.data;
m_RenderPasses.Add(renderPass);
m_builderInstance.Setup(renderPass, m_Resources, this);
return m_builderInstance;
}
/// <summary>
/// Add a new Compute Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass.</returns>
public IComputeRenderGraphBuilder AddComputePass<PassData>(string passName, out PassData passData
#if !CORE_PACKAGE_DOCTOOLS
, [CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
return AddComputePass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
}
/// <summary>
/// Add a new Compute Render Pass to the Render Graph. Compute passes can execute compute workloads but cannot do rasterization.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="sampler">Profiling sampler used around the pass.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a IComputeRenderGraphBuilder used to setup the new Compute Render Pass.</returns>
public IComputeRenderGraphBuilder AddComputePass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
#if !CORE_PACKAGE_DOCTOOLS
,[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
AddPassDebugMetadata(passName, file, line);
var renderPass = m_RenderGraphPool.Get<ComputeRenderGraphPass<PassData>>();
renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Compute, sampler);
passData = renderPass.data;
m_RenderPasses.Add(renderPass);
m_builderInstance.Setup(renderPass, m_Resources, this);
return m_builderInstance;
}
/// <summary>
/// Add a new Unsafe Render Pass to the Render Graph. Unsafe passes can do certain operations compute/raster render passes cannot do and have
/// access to the full command buffer API. The unsafe API should be used sparingly as it has the following downsides:
/// - Limited automatic validation of the commands and resource dependencies. The user is responsible to ensure that all dependencies are correctly declared.
/// - All native render passes will be serialized out.
/// - In the future the render graph compiler may generate a sub-optimal command stream for unsafe passes.
/// When using a unsafe pass the graph will also not automatically set up graphics state like rendertargets. The pass should do this itself
/// using cmd.SetRenderTarget and related commands.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a IUnsafeRenderGraphBuilder used to setup the new Unsafe Render Pass.</returns>
public IUnsafeRenderGraphBuilder AddUnsafePass<PassData>(string passName, out PassData passData
#if !CORE_PACKAGE_DOCTOOLS
, [CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
return AddUnsafePass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
}
/// <summary>
/// Add a new unsafe Render Pass to the Render Graph. Unsafe passes can do certain operations compute/raster render passes cannot do and have
/// access to the full command buffer API. The unsafe API should be used sparingly as it has the following downsides:
/// - Limited automatic validation of the commands and resource dependencies. The user is responsible to ensure that all dependencies are correctly declared.
/// - All native render passes will be serialized out.
/// - In the future the render graph compiler may generate a sub-optimal command stream for unsafe passes.
/// When using an unsafe pass the graph will also not automatically set up graphics state like rendertargets. The pass should do this itself
/// using cmd.SetRenderTarget and related commands.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="sampler">Profiling sampler used around the pass.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a IUnsafeRenderGraphBuilder used to setup the new unsafe Render Pass.</returns>
public IUnsafeRenderGraphBuilder AddUnsafePass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
#if !CORE_PACKAGE_DOCTOOLS
, [CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
AddPassDebugMetadata(passName, file, line);
var renderPass = m_RenderGraphPool.Get<UnsafeRenderGraphPass<PassData>>();
renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Unsafe, sampler);
renderPass.AllowGlobalState(true);
passData = renderPass.data;
m_RenderPasses.Add(renderPass);
m_builderInstance.Setup(renderPass, m_Resources, this);
return m_builderInstance;
}
/// <summary>
/// Add a new Render Pass to the Render Graph.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="sampler">Profiling sampler used around the pass.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a RenderGraphBuilder used to setup the new Render Pass.</returns>
public RenderGraphBuilder AddRenderPass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
#if !CORE_PACKAGE_DOCTOOLS
,[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
AddPassDebugMetadata(passName, file, line);
var renderPass = m_RenderGraphPool.Get<RenderGraphPass<PassData>>();
renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Legacy, sampler);
renderPass.AllowGlobalState(true);// Old pass types allow global state by default as HDRP relies on it
passData = renderPass.data;
m_RenderPasses.Add(renderPass);
return new RenderGraphBuilder(renderPass, m_Resources, this);
}
/// <summary>
/// Add a new Render Pass to the Render Graph.
/// </summary>
/// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
/// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
/// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <returns>A new instance of a RenderGraphBuilder used to setup the new Render Pass.</returns>
public RenderGraphBuilder AddRenderPass<PassData>(string passName, out PassData passData
#if !CORE_PACKAGE_DOCTOOLS
,[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0) where PassData : class, new()
#endif
{
return AddRenderPass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
}
/// <summary>
/// Starts the recording of the render graph.
/// This must be called before adding any pass to the render graph.
/// </summary>
/// <param name="parameters">Parameters necessary for the render graph execution.</param>
/// <example>
/// <para>Begin recording the Render Graph.</para>
/// <code>
/// renderGraph.BeginRecording(parameters)
/// // Add your render graph passes here.
/// renderGraph.EndRecordingAndExecute()
/// </code>
/// </example>
public void BeginRecording(in RenderGraphParameters parameters)
{
m_CurrentFrameIndex = parameters.currentFrameIndex;
m_CurrentExecutionName = parameters.executionName != null ? parameters.executionName : "RenderGraphExecution";
m_HasRenderGraphBegun = true;
// Cannot do renderer list culling with compilation caching because it happens after compilation is done so it can lead to discrepancies.
m_RendererListCulling = parameters.rendererListCulling && !m_EnableCompilationCaching;
m_Resources.BeginRenderGraph(m_ExecutionCount++);
if (m_DebugParameters.enableLogging)
{
m_FrameInformationLogger.Initialize(m_CurrentExecutionName);
}
m_DefaultResources.InitializeForRendering(this);
m_RenderGraphContext.cmd = parameters.commandBuffer;
m_RenderGraphContext.renderContext = parameters.scriptableRenderContext;
m_RenderGraphContext.contextlessTesting = parameters.invalidContextForTesting;
m_RenderGraphContext.renderGraphPool = m_RenderGraphPool;
m_RenderGraphContext.defaultResources = m_DefaultResources;
if (m_DebugParameters.immediateMode)
{
UpdateCurrentCompiledGraph(graphHash: -1, forceNoCaching: true);
LogFrameInformation();
// Prepare the list of compiled pass info for immediate mode.
// Conservative resize because we don't know how many passes there will be.
// We might still need to grow the array later on anyway if it's not enough.
m_CurrentCompiledGraph.compiledPassInfos.Resize(m_CurrentCompiledGraph.compiledPassInfos.capacity);
m_CurrentImmediatePassIndex = 0;
for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
{
if (m_ImmediateModeResourceList[i] == null)
m_ImmediateModeResourceList[i] = new List<int>();
m_ImmediateModeResourceList[i].Clear();
}
m_Resources.BeginExecute(m_CurrentFrameIndex);
}
}
/// <summary>
/// Ends the recording and executes the render graph.
/// This must be called once all passes have been added to the render graph.
/// </summary>
public void EndRecordingAndExecute()
{
Execute();
}
/// <summary>
/// Execute the Render Graph in its current state.
/// </summary>
internal void Execute()
{
m_ExecutionExceptionWasRaised = false;
try
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (m_RenderGraphContext.cmd == null)
throw new InvalidOperationException("RenderGraph.BeginRecording was not called before executing the render graph.");
#endif
if (!m_DebugParameters.immediateMode)
{
LogFrameInformation();
int graphHash = 0;
if (m_EnableCompilationCaching)
graphHash = ComputeGraphHash();
if (nativeRenderPassesEnabled)
CompileNativeRenderGraph(graphHash);
else
CompileRenderGraph(graphHash);
m_Resources.BeginExecute(m_CurrentFrameIndex);
#if UNITY_EDITOR
// Feeding Render Graph Viewer before resource deallocation at pass execution
GenerateDebugData();
#endif
if (nativeRenderPassesEnabled)
ExecuteNativeRenderGraph();
else
ExecuteRenderGraph();
// Clear the shader bindings for all global textures to make sure bindings don't leak outside the graph
ClearGlobalBindings();
}
}
catch (Exception e)
{
if (m_RenderGraphContext.contextlessTesting)
{
// Throw it for the tests to handle
throw;
}
else
{
// If we're not testing log the exception and swallow it.
// TODO: Do we really want to swallow exceptions here? Not a very c# thing to do.
Debug.LogError("Render Graph Execution error");
if (!m_ExecutionExceptionWasRaised) // Already logged. TODO: There is probably a better way in C# to handle that.
Debug.LogException(e);
m_ExecutionExceptionWasRaised = true;
}
}
finally
{
if (m_DebugParameters.immediateMode)
ReleaseImmediateModeResources();
ClearCompiledGraph(m_CurrentCompiledGraph, m_EnableCompilationCaching);
m_Resources.EndExecute();
InvalidateContext();
m_HasRenderGraphBegun = false;
}
}
class ProfilingScopePassData
{
public ProfilingSampler sampler;
}
const string k_BeginProfilingSamplerPassName = "BeginProfile";
const string k_EndProfilingSamplerPassName = "EndProfile";
/// <summary>
/// Begin a profiling scope.
/// </summary>
/// <param name="sampler">Sampler used for profiling.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
public void BeginProfilingSampler(ProfilingSampler sampler,
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0)
{
if (sampler == null)
return;
using (var builder = AddRenderPass<ProfilingScopePassData>(k_BeginProfilingSamplerPassName, out var passData, (ProfilingSampler)null, file, line))
{
passData.sampler = sampler;
builder.AllowPassCulling(false);
builder.GenerateDebugData(false);
builder.SetRenderFunc((ProfilingScopePassData data, RenderGraphContext ctx) =>
{
data.sampler.Begin(ctx.cmd);
});
}
}
/// <summary>
/// End a profiling scope.
/// </summary>
/// <param name="sampler">Sampler used for profiling.</param>
/// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
/// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
public void EndProfilingSampler(ProfilingSampler sampler,
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0)
{
if (sampler == null)
return;
using (var builder = AddRenderPass<ProfilingScopePassData>(k_EndProfilingSamplerPassName, out var passData, (ProfilingSampler)null, file, line))
{
passData.sampler = sampler;
builder.AllowPassCulling(false);
builder.GenerateDebugData(false);
builder.SetRenderFunc((ProfilingScopePassData data, RenderGraphContext ctx) =>
{
data.sampler.End(ctx.cmd);
});
}
}
#endregion
#region Internal Interface
// Internal for testing purpose only
internal DynamicArray<CompiledPassInfo> GetCompiledPassInfos() { return m_CurrentCompiledGraph.compiledPassInfos; }
// Internal for testing purpose only
internal void ClearCompiledGraph()
{
ClearCompiledGraph(m_CurrentCompiledGraph, false);
}
void ClearCompiledGraph(CompiledGraph compiledGraph, bool useCompilationCaching)
{
ClearRenderPasses();
m_Resources.Clear(m_ExecutionExceptionWasRaised);
m_RendererLists.Clear();
registeredGlobals.Clear();
// When using compilation caching, we need to keep alive the result of the compiled graph.
if (!useCompilationCaching)
{
if (!nativeRenderPassesEnabled)
compiledGraph?.Clear();
}
}
void InvalidateContext()
{
m_RenderGraphContext.cmd = null;
m_RenderGraphContext.renderGraphPool = null;
m_RenderGraphContext.defaultResources = null;
}
internal void OnPassAdded(RenderGraphPass pass)
{
if (m_DebugParameters.immediateMode)
{
ExecutePassImmediately(pass);
}
}
internal delegate void OnGraphRegisteredDelegate(RenderGraph graph);
internal static event OnGraphRegisteredDelegate onGraphRegistered;
internal static event OnGraphRegisteredDelegate onGraphUnregistered;
internal delegate void OnExecutionRegisteredDelegate(RenderGraph graph, string executionName);
internal static event OnExecutionRegisteredDelegate onExecutionRegistered;
internal static event OnExecutionRegisteredDelegate onExecutionUnregistered;
internal static event Action onDebugDataCaptured;
#endregion
#region Private Interface
// Internal for testing purpose only.
internal int ComputeGraphHash()
{
using (new ProfilingScope(ProfilingSampler.Get(RenderGraphProfileId.ComputeHashRenderGraph)))
{
var hash128 = HashFNV1A32.Create();
for (int i = 0; i < m_RenderPasses.Count; ++i)
m_RenderPasses[i].ComputeHash(ref hash128, m_Resources);
return hash128.value;
}
}
void CountReferences()
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
var compiledResourceInfo = m_CurrentCompiledGraph.compiledResourcesInfos;
for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
{
RenderGraphPass pass = m_RenderPasses[passIndex];
ref CompiledPassInfo passInfo = ref compiledPassInfo[passIndex];
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resourceRead = pass.resourceReadLists[type];
foreach (var resource in resourceRead)
{
ref CompiledResourceInfo info = ref compiledResourceInfo[type][resource.index];
info.imported = m_Resources.IsRenderGraphResourceImported(resource);
info.consumers.Add(passIndex);
info.refCount++;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
passInfo.debugResourceReads[type].Add(m_Resources.GetRenderGraphResourceName(resource));
#endif
}
var resourceWrite = pass.resourceWriteLists[type];
foreach (var resource in resourceWrite)
{
ref CompiledResourceInfo info = ref compiledResourceInfo[type][resource.index];
info.imported = m_Resources.IsRenderGraphResourceImported(resource);
info.producers.Add(passIndex);
// Writing to an imported texture is considered as a side effect because we don't know what users will do with it outside of render graph.
passInfo.hasSideEffect = info.imported;
passInfo.refCount++;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
passInfo.debugResourceWrites[type].Add(m_Resources.GetRenderGraphResourceName(resource));
#endif
}
foreach (var resource in pass.transientResourceList[type])
{
ref CompiledResourceInfo info = ref compiledResourceInfo[type][resource.index];
info.refCount++;
info.consumers.Add(passIndex);
info.producers.Add(passIndex);
}
}
}
}
void CullUnusedPasses()
{
if (m_DebugParameters.disablePassCulling)
{
if (m_DebugParameters.enableLogging)
{
m_FrameInformationLogger.LogLine("- Pass Culling Disabled -\n");
}
return;
}
// This will cull all passes that produce resource that are never read.
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
DynamicArray<CompiledResourceInfo> resourceUsageList = m_CurrentCompiledGraph.compiledResourcesInfos[type];
// Gather resources that are never read.
m_CullingStack.Clear();
for (int i = 1; i < resourceUsageList.size; ++i) // 0 == null resource skip it
{
if (resourceUsageList[i].refCount == 0)
{
m_CullingStack.Push(i);
}
}
while (m_CullingStack.Count != 0)
{
var unusedResource = resourceUsageList[m_CullingStack.Pop()];
foreach (var producerIndex in unusedResource.producers)
{
ref var producerInfo = ref m_CurrentCompiledGraph.compiledPassInfos[producerIndex];
var producerPass = m_RenderPasses[producerIndex];
producerInfo.refCount--;
if (producerInfo.refCount == 0 && !producerInfo.hasSideEffect && producerInfo.allowPassCulling)
{
// Producer is not necessary anymore as it produces zero resources
// Cull it and decrement refCount of all the textures it reads.
producerInfo.culled = true;
foreach (var resource in producerPass.resourceReadLists[type])
{
ref CompiledResourceInfo resourceInfo = ref resourceUsageList[resource.index];
resourceInfo.refCount--;
// If a resource is not used anymore, add it to the stack to be processed in subsequent iteration.
if (resourceInfo.refCount == 0)
m_CullingStack.Push(resource.index);
}
}
}
}
}
LogCulledPasses();
}
void UpdatePassSynchronization(ref CompiledPassInfo currentPassInfo, ref CompiledPassInfo producerPassInfo, int currentPassIndex, int lastProducer, ref int intLastSyncIndex)
{
// Current pass needs to wait for pass index lastProducer
currentPassInfo.syncToPassIndex = lastProducer;
// Update latest pass waiting for the other pipe.
intLastSyncIndex = lastProducer;
// Producer will need a graphics fence that this pass will wait on.
producerPassInfo.needGraphicsFence = true;
// We update the producer pass with the index of the smallest pass waiting for it.
// This will be used to "lock" resource from being reused until the pipe has been synchronized.
if (producerPassInfo.syncFromPassIndex == -1)
producerPassInfo.syncFromPassIndex = currentPassIndex;
}
void UpdateResourceSynchronization(ref int lastGraphicsPipeSync, ref int lastComputePipeSync, int currentPassIndex, in CompiledResourceInfo resource)
{
int lastProducer = GetLatestProducerIndex(currentPassIndex, resource);
if (lastProducer != -1)
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
ref CompiledPassInfo currentPassInfo = ref compiledPassInfo[currentPassIndex];
//If the passes are on different pipes, we need synchronization.
if (m_CurrentCompiledGraph.compiledPassInfos[lastProducer].enableAsyncCompute != currentPassInfo.enableAsyncCompute)
{
// Pass is on compute pipe, need sync with graphics pipe.
if (currentPassInfo.enableAsyncCompute)
{
if (lastProducer > lastGraphicsPipeSync)
{
UpdatePassSynchronization(ref currentPassInfo, ref compiledPassInfo[lastProducer], currentPassIndex, lastProducer, ref lastGraphicsPipeSync);
}
}
else
{
if (lastProducer > lastComputePipeSync)
{
UpdatePassSynchronization(ref currentPassInfo, ref compiledPassInfo[lastProducer], currentPassIndex, lastProducer, ref lastComputePipeSync);
}
}
}
}
}
int GetFirstValidConsumerIndex(int passIndex, in CompiledResourceInfo info)
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
// We want to know the lowest pass index after the current pass that reads from the resource.
foreach (int consumer in info.consumers)
{
// consumers are by construction in increasing order.
if (consumer > passIndex && !compiledPassInfo[consumer].culled)
return consumer;
}
return -1;
}
int FindTextureProducer(int consumerPass, in CompiledResourceInfo info, out int index)
{
// We check all producers before the consumerPass. The first one not culled will be the one allocating the resource
// If they are all culled, we need to get the one right before the consumer, it will allocate or reuse the resource
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
int previousPass = 0;
for (index = 0; index < info.producers.Count; index++)
{
int currentPass = info.producers[index];
// We found a valid producer - he will allocate the texture
if (!compiledPassInfo[currentPass].culled)
return currentPass;
// We reached consumer pass, return last producer even if it's culled
if (currentPass >= consumerPass)
return previousPass;
previousPass = currentPass;
}
return previousPass;
}
int GetLatestProducerIndex(int passIndex, in CompiledResourceInfo info)
{
// We want to know the highest pass index below the current pass that writes to the resource.
int result = -1;
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
foreach (var producer in info.producers)
{
var producerPassInfo = compiledPassInfo[producer];
// producers are by construction in increasing order.
// We also need to make sure we don't return a pass that was culled (can happen at this point because of renderer list culling).
if (producer < passIndex && !(producerPassInfo.culled || producerPassInfo.culledByRendererList))
result = producer;
else
return result;
}
return result;
}
int GetLatestValidReadIndex(in CompiledResourceInfo info)
{
if (info.consumers.Count == 0)
return -1;
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
var consumers = info.consumers;
for (int i = consumers.Count - 1; i >= 0; --i)
{
if (!compiledPassInfo[consumers[i]].culled)
return consumers[i];
}
return -1;
}
int GetFirstValidWriteIndex(in CompiledResourceInfo info)
{
if (info.producers.Count == 0)
return -1;
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
var producers = info.producers;
for (int i = 0; i < producers.Count; i++)
{
if (!compiledPassInfo[producers[i]].culled)
return producers[i];
}
return -1;
}
int GetLatestValidWriteIndex(in CompiledResourceInfo info)
{
if (info.producers.Count == 0)
return -1;
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
var producers = info.producers;
for (int i = producers.Count - 1; i >= 0; --i)
{
if (!compiledPassInfo[producers[i]].culled)
return producers[i];
}
return -1;
}
void CreateRendererLists()
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
{
ref CompiledPassInfo passInfo = ref compiledPassInfo[passIndex];
if (passInfo.culled)
continue;
// Gather all renderer lists
m_RendererLists.AddRange(m_RenderPasses[passInfo.index].usedRendererListList);
}
// Anything related to renderer lists needs a real context to be able to use/test it
Debug.Assert(m_RendererLists.Count == 0 || m_RenderGraphContext.contextlessTesting == false);
// Creates all renderer lists
m_Resources.CreateRendererLists(m_RendererLists, m_RenderGraphContext.renderContext, m_RendererListCulling);
}
internal bool GetImportedFallback(TextureDesc desc, out TextureHandle fallback)
{
fallback = TextureHandle.nullHandle;
// We don't have any fallback texture with MSAA
if (!desc.bindTextureMS)
{
if (desc.depthBufferBits != DepthBits.None)
{
fallback = defaultResources.whiteTexture;
}
else if (desc.clearColor == Color.black || desc.clearColor == default)
{
if (desc.dimension == TextureXR.dimension)
fallback = defaultResources.blackTextureXR;
else if (desc.dimension == TextureDimension.Tex3D)
fallback = defaultResources.blackTexture3DXR;
else if (desc.dimension == TextureDimension.Tex2D)
fallback = defaultResources.blackTexture;
}
else if (desc.clearColor == Color.white)
{
if (desc.dimension == TextureXR.dimension)
fallback = defaultResources.whiteTextureXR;
else if (desc.dimension == TextureDimension.Tex2D)
fallback = defaultResources.whiteTexture;
}
}
return fallback.IsValid();
}
void AllocateCulledPassResources(ref CompiledPassInfo passInfo)
{
var passIndex = passInfo.index;
var pass = m_RenderPasses[passIndex];
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resourcesInfo = m_CurrentCompiledGraph.compiledResourcesInfos[type];
foreach (var resourceHandle in pass.resourceWriteLists[type])
{
ref var compiledResource = ref resourcesInfo[resourceHandle.index];
// Check if there is a valid consumer and no other valid producer
int consumerPass = GetFirstValidConsumerIndex(passIndex, compiledResource);
int producerPass = FindTextureProducer(consumerPass, compiledResource, out int index);
if (consumerPass != -1 && passIndex == producerPass)
{
if (type == (int)RenderGraphResourceType.Texture)
{
// Try to transform into an imported resource - for some textures, this will save an allocation
// We have a way to disable the fallback, because we can't fallback to RenderTexture and sometimes it's necessary (eg. SampleCopyChannel_xyzw2x)
var textureResource = m_Resources.GetTextureResource(resourceHandle);
if (!textureResource.desc.disableFallBackToImportedTexture && GetImportedFallback(textureResource.desc, out var fallback))
{
compiledResource.imported = true;
textureResource.imported = true;
textureResource.graphicsResource = m_Resources.GetTexture(fallback);
continue;
}
textureResource.desc.sizeMode = TextureSizeMode.Explicit;
textureResource.desc.width = 1;
textureResource.desc.height = 1;
textureResource.desc.clearBuffer = true;
}
// Delegate resource allocation to the consumer
compiledResource.producers[index - 1] = consumerPass;
}
}
}
}
void UpdateResourceAllocationAndSynchronization()
{
int lastGraphicsPipeSync = -1;
int lastComputePipeSync = -1;
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
var compiledResourceInfo = m_CurrentCompiledGraph.compiledResourcesInfos;
// First go through all passes.
// - Update the last pass read index for each resource.
// - Add texture to creation list for passes that first write to a texture.
// - Update synchronization points for all resources between compute and graphics pipes.
for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
{
ref CompiledPassInfo passInfo = ref compiledPassInfo[passIndex];
// If this pass is culled, we need to make sure that any texture read by a later pass is still allocated
// We also try to find an imported fallback to save an allocation
if (passInfo.culledByRendererList)
AllocateCulledPassResources(ref passInfo);
if (passInfo.culled)
continue;
var pass = m_RenderPasses[passInfo.index];
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resourcesInfo = compiledResourceInfo[type];
foreach (var resource in pass.resourceReadLists[type])
{
UpdateResourceSynchronization(ref lastGraphicsPipeSync, ref lastComputePipeSync, passIndex, resourcesInfo[resource.index]);
}
foreach (var resource in pass.resourceWriteLists[type])
{
UpdateResourceSynchronization(ref lastGraphicsPipeSync, ref lastComputePipeSync, passIndex, resourcesInfo[resource.index]);
}
}
}
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resourceInfos = compiledResourceInfo[type];
// Now push resources to the release list of the pass that reads it last.
for (int i = 1; i < resourceInfos.size; ++i) // 0 == null resource skip it
{
CompiledResourceInfo resourceInfo = resourceInfos[i];
bool sharedResource = m_Resources.IsRenderGraphResourceShared((RenderGraphResourceType)type, i);
bool forceRelease = m_Resources.IsRenderGraphResourceForceReleased((RenderGraphResourceType) type, i);
// Imported resource needs neither creation nor release.
if (resourceInfo.imported && !sharedResource && !forceRelease)
continue;
// Resource creation
int firstWriteIndex = GetFirstValidWriteIndex(resourceInfo);
// Index -1 can happen for imported resources (for example an imported dummy black texture will never be written to but does not need creation anyway)
// Or when the only pass that was writting to this resource was culled dynamically by renderer lists
if (firstWriteIndex != -1)
compiledPassInfo[firstWriteIndex].resourceCreateList[type].Add(i);
var latestValidReadIndex = GetLatestValidReadIndex(resourceInfo);
var latestValidWriteIndex = GetLatestValidWriteIndex(resourceInfo);
// Sometimes, a texture can be written by a pass after the last pass that reads it.
// In this case, we need to extend its lifetime to this pass otherwise the pass would get an invalid texture.
// This is exhibited in cases where a pass might produce more than one output and one of them isn't used.
// Ex: Transparent pass in HDRP that writes to the color buffer and motion vectors.
// If TAA/MotionBlur aren't used, the movecs are never read after the transparent pass and it would raise this error.
// Because of that, it's hard to make this an actual error.
// Commented out code to check such cases if needed.
//if (latestValidReadIndex != -1 && (latestValidWriteIndex > latestValidReadIndex))
//{
// var name = m_Resources.GetRenderGraphResourceName((RenderGraphResourceType)type, i);
// var lastPassReadName = m_CompiledPassInfos[latestValidReadIndex].pass.name;
// var lastPassWriteName = m_CompiledPassInfos[latestValidWriteIndex].pass.name;
// Debug.LogError($"Resource {name} is written again after the last pass that reads it.\nLast pass read: {lastPassReadName}\nLast pass write: {lastPassWriteName}");
//}
// For not imported resources, make sure we don't try to release them if they were never created (due to culling).
bool shouldRelease = !(firstWriteIndex == -1 && !resourceInfo.imported);
int lastReadPassIndex = shouldRelease ? Math.Max(latestValidWriteIndex, latestValidReadIndex) : -1;
// Texture release
if (lastReadPassIndex != -1)
{
// In case of async passes, we need to extend lifetime of resource to the first pass on the graphics pipeline that wait for async passes to be over.
// Otherwise, if we freed the resource right away during an async pass, another non async pass could reuse the resource even though the async pipe is not done.
if (compiledPassInfo[lastReadPassIndex].enableAsyncCompute)
{
int currentPassIndex = lastReadPassIndex;
int firstWaitingPassIndex = compiledPassInfo[currentPassIndex].syncFromPassIndex;
// Find the first async pass that is synchronized by the graphics pipeline (ie: passInfo.syncFromPassIndex != -1)
while (firstWaitingPassIndex == -1 && currentPassIndex++ < compiledPassInfo.size - 1)
{
if (compiledPassInfo[currentPassIndex].enableAsyncCompute)
firstWaitingPassIndex = compiledPassInfo[currentPassIndex].syncFromPassIndex;
}
// Fail safe in case render graph is badly formed.
if (currentPassIndex == compiledPassInfo.size)
{
// This is not true with passes with side effect as they are writing to a resource that may not be read by the render graph this frame and to no other resource.
// In this case we extend the lifetime of resources to the end of the frame. It's not idea but it should not be the majority of cases.
if (compiledPassInfo[lastReadPassIndex].hasSideEffect)
{
firstWaitingPassIndex = currentPassIndex;
}
else
{
RenderGraphPass invalidPass = m_RenderPasses[lastReadPassIndex];
var resName = "<unknown>";
#if DEVELOPMENT_BUILD || UNITY_EDITOR
resName = m_Resources.GetRenderGraphResourceName((RenderGraphResourceType)type, i);
#endif
var msg = $"{(RenderGraphResourceType)type} resource '{resName}' in asynchronous pass '{invalidPass.name}' is missing synchronization on the graphics pipeline.";
throw new InvalidOperationException(msg);
}
}
// Finally add the release command to the pass before the first pass that waits for the compute pipe.
var releasePassIndex = Math.Max(0, firstWaitingPassIndex - 1);
// Check to ensure that we do not release resources on a culled pass (causes a leak otherwise).
while (compiledPassInfo[releasePassIndex].culled)
releasePassIndex = Math.Max(0, releasePassIndex - 1);
ref CompiledPassInfo passInfo = ref compiledPassInfo[releasePassIndex];
passInfo.resourceReleaseList[type].Add(i);
}
else
{
ref CompiledPassInfo passInfo = ref compiledPassInfo[lastReadPassIndex];
passInfo.resourceReleaseList[type].Add(i);
}
}
if (sharedResource && (firstWriteIndex != -1 || lastReadPassIndex != -1)) // A shared resource is considered used if it's either read or written at any pass.
{
m_Resources.UpdateSharedResourceLastFrameIndex(type, i);
}
}
}
}
void UpdateAllSharedResourceLastFrameIndex()
{
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resourceInfos = m_CurrentCompiledGraph.compiledResourcesInfos[type];
var sharedResourceCount = m_Resources.GetSharedResourceCount((RenderGraphResourceType)type);
for (int i = 1; i <= sharedResourceCount; ++i) // 0 == null resource skip it
{
CompiledResourceInfo resourceInfo = resourceInfos[i];
var latestValidReadIndex = GetLatestValidReadIndex(resourceInfo);
int firstWriteIndex = GetFirstValidWriteIndex(resourceInfo);
if ((firstWriteIndex != -1 || latestValidReadIndex != -1)) // A shared resource is considered used if it's either read or written at any pass.
{
m_Resources.UpdateSharedResourceLastFrameIndex(type, i);
}
}
}
}
bool AreRendererListsEmpty(List<RendererListHandle> rendererLists)
{
// Anything related to renderer lists needs a real context to be able to use/test it
Debug.Assert(m_RenderGraphContext.contextlessTesting == false);
foreach (RendererListHandle handle in rendererLists)
{
var rendererList = m_Resources.GetRendererList(handle);
if (m_RenderGraphContext.renderContext.QueryRendererListStatus(rendererList) == RendererListStatus.kRendererListPopulated)
{
return false;
}
}
// If the list of RendererLists is empty, then the default behavior is to not cull, so return false.
return rendererLists.Count > 0 ? true : false;
}
void TryCullPassAtIndex(int passIndex)
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
ref var compiledPass = ref compiledPassInfo[passIndex];
var pass = m_RenderPasses[passIndex];
if (!compiledPass.culled &&
pass.allowPassCulling &&
pass.allowRendererListCulling &&
!compiledPass.hasSideEffect)
{
if (AreRendererListsEmpty(pass.usedRendererListList))
{
//Debug.Log($"Culling pass <color=red> {pass.name} </color>");
compiledPass.culled = compiledPass.culledByRendererList = true;
}
}
}
void CullRendererLists()
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
{
var compiledPass = compiledPassInfo[passIndex];
if (!compiledPass.culled && !compiledPass.hasSideEffect)
{
var pass = m_RenderPasses[passIndex];
if (pass.usedRendererListList.Count > 0)
{
TryCullPassAtIndex(passIndex);
}
}
}
}
bool UpdateCurrentCompiledGraph(int graphHash, bool forceNoCaching = false)
{
bool cached = false;
if (m_EnableCompilationCaching && !forceNoCaching)
cached = m_CompilationCache.GetCompilationCache(graphHash, m_ExecutionCount, out m_CurrentCompiledGraph);
else
m_CurrentCompiledGraph = m_DefaultCompiledGraph;
return cached;
}
// Internal visibility for testing purpose only
// Traverse the render graph:
// - Determines when resources are created/released
// - Determines async compute pass synchronization
// - Cull unused render passes.
internal void CompileRenderGraph(int graphHash)
{
using (new ProfilingScope(m_RenderGraphContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.CompileRenderGraph)))
{
bool compilationIsCached = UpdateCurrentCompiledGraph(graphHash);
if (!compilationIsCached)
{
m_CurrentCompiledGraph.Clear();
m_CurrentCompiledGraph.InitializeCompilationData(m_RenderPasses, m_Resources);
CountReferences();
// First cull all passes that produce unused output
CullUnusedPasses();
}
// Create the renderer lists of the remaining passes
CreateRendererLists();
if (!compilationIsCached)
{
// Cull dynamically the graph passes based on the renderer list visibility
if (m_RendererListCulling)
CullRendererLists();
// After all culling passes, allocate the resources for this frame
UpdateResourceAllocationAndSynchronization();
}
else
{
// We need to update all shared resource frame index usage otherwise they might not be in a valid state.
// Otherwise it's done in UpdateResourceAllocationAndSynchronization().
UpdateAllSharedResourceLastFrameIndex();
}
LogRendererListsCreation();
}
}
ref CompiledPassInfo CompilePassImmediatly(RenderGraphPass pass)
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
// If we don't have enough pre allocated elements we double the size.
// It's pretty aggressive but the immediate mode is only for debug purpose so it should be fine.
if (m_CurrentImmediatePassIndex >= compiledPassInfo.size)
compiledPassInfo.Resize(compiledPassInfo.size * 2);
ref CompiledPassInfo passInfo = ref compiledPassInfo[m_CurrentImmediatePassIndex++];
passInfo.Reset(pass, m_CurrentImmediatePassIndex - 1);
// In immediate mode we don't have proper information to generate synchronization so we disable async compute.
passInfo.enableAsyncCompute = false;
// In immediate mode, we don't have any resource usage information so we'll just create resources whenever they are written to if not already alive.
// We will release all resources at the end of the render graph execution.
for (int iType = 0; iType < (int)RenderGraphResourceType.Count; ++iType)
{
foreach (var res in pass.transientResourceList[iType])
{
passInfo.resourceCreateList[iType].Add(res.index);
passInfo.resourceReleaseList[iType].Add(res.index);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
passInfo.debugResourceWrites[iType].Add(m_Resources.GetRenderGraphResourceName(res));
passInfo.debugResourceReads[iType].Add(m_Resources.GetRenderGraphResourceName(res));
#endif
}
foreach (var res in pass.resourceWriteLists[iType])
{
if (pass.transientResourceList[iType].Contains(res))
continue; // Prevent registering writes to transient texture twice
if (!m_Resources.IsGraphicsResourceCreated(res))
{
passInfo.resourceCreateList[iType].Add(res.index);
m_ImmediateModeResourceList[iType].Add(res.index);
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
passInfo.debugResourceWrites[iType].Add(m_Resources.GetRenderGraphResourceName(res));
#endif
}
foreach (var res in pass.resourceReadLists[iType])
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
passInfo.debugResourceReads[iType].Add(m_Resources.GetRenderGraphResourceName(res));
#endif
}
}
// Create the necessary renderer lists
foreach (var rl in pass.usedRendererListList)
{
if (!m_Resources.IsRendererListCreated(rl))
m_RendererLists.Add(rl);
}
// Anything related to renderer lists needs a real context to be able to use/test it
Debug.Assert(m_RenderGraphContext.contextlessTesting == false);
m_Resources.CreateRendererLists(m_RendererLists, m_RenderGraphContext.renderContext);
m_RendererLists.Clear();
return ref passInfo;
}
void ExecutePassImmediately(RenderGraphPass pass)
{
ExecuteCompiledPass(ref CompilePassImmediatly(pass));
}
void ExecuteCompiledPass(ref CompiledPassInfo passInfo)
{
if (passInfo.culled)
return;
var pass = m_RenderPasses[passInfo.index];
if (!pass.HasRenderFunc())
throw new InvalidOperationException($"RenderPass {pass.name} was not provided with an execute function.");
try
{
using (new ProfilingScope(m_RenderGraphContext.cmd, pass.customSampler))
{
LogRenderPassBegin(passInfo);
using (new RenderGraphLogIndent(m_FrameInformationLogger))
{
m_RenderGraphContext.executingPass = pass;
PreRenderPassExecute(passInfo, pass, m_RenderGraphContext);
pass.Execute(m_RenderGraphContext);
PostRenderPassExecute(ref passInfo, pass, m_RenderGraphContext);
}
}
}
catch (Exception e)
{
// Dont log errors during tests
if (m_RenderGraphContext.contextlessTesting == false)
{
// Log exception from the pass that raised it to have improved error logging quality for users
m_ExecutionExceptionWasRaised = true;
Debug.LogError($"Render Graph execution error at pass '{pass.name}' ({passInfo.index})");
Debug.LogException(e);
}
throw;
}
}
// Execute the compiled render graph
void ExecuteRenderGraph()
{
using (new ProfilingScope(m_RenderGraphContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.ExecuteRenderGraph)))
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
{
ExecuteCompiledPass(ref compiledPassInfo[passIndex]);
}
}
}
void PreRenderPassSetRenderTargets(in CompiledPassInfo passInfo, RenderGraphPass pass, InternalRenderGraphContext rgContext)
{
var depthBufferIsValid = pass.depthAccess.textureHandle.IsValid();
if (depthBufferIsValid || pass.colorBufferMaxIndex != -1)
{
var colorBufferAccess = pass.colorBufferAccess;
if (pass.colorBufferMaxIndex > 0)
{
var mrtArray = m_TempMRTArrays[pass.colorBufferMaxIndex];
for (int i = 0; i <= pass.colorBufferMaxIndex; ++i)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (!colorBufferAccess[i].textureHandle.IsValid())
throw new InvalidOperationException("MRT setup is invalid. Some indices are not used.");
#endif
mrtArray[i] = m_Resources.GetTexture(colorBufferAccess[i].textureHandle);
}
if (depthBufferIsValid)
{
CoreUtils.SetRenderTarget(rgContext.cmd, mrtArray, m_Resources.GetTexture(pass.depthAccess.textureHandle));
}
else
{
throw new InvalidOperationException("Setting MRTs without a depth buffer is not supported.");
}
}
else
{
if (depthBufferIsValid)
{
if (pass.colorBufferMaxIndex > -1)
{
CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.colorBufferAccess[0].textureHandle),
m_Resources.GetTexture(pass.depthAccess.textureHandle));
}
else
{
CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.depthAccess.textureHandle));
}
}
else
{
if (pass.colorBufferAccess[0].textureHandle.IsValid())
{
CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.colorBufferAccess[0].textureHandle));
}
else
throw new InvalidOperationException("Neither Depth nor color render targets are correctly setup at pass " + pass.name + ".");
}
}
}
}
void PreRenderPassExecute(in CompiledPassInfo passInfo, RenderGraphPass pass, InternalRenderGraphContext rgContext)
{
// Need to save the command buffer to restore it later as the one in the context can changed if running a pass async.
m_PreviousCommandBuffer = rgContext.cmd;
bool executedWorkDuringResourceCreation = false;
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
foreach (int resource in passInfo.resourceCreateList[type])
{
executedWorkDuringResourceCreation |= m_Resources.CreatePooledResource(rgContext, type, resource);
}
}
if (passInfo.enableFoveatedRasterization)
rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Enabled);
PreRenderPassSetRenderTargets(passInfo, pass, rgContext);
if (passInfo.enableAsyncCompute)
{
GraphicsFence previousFence = new GraphicsFence();
if (executedWorkDuringResourceCreation)
{
previousFence = rgContext.cmd.CreateGraphicsFence(GraphicsFenceType.AsyncQueueSynchronisation, SynchronisationStageFlags.AllGPUOperations);
}
// Flush current command buffer on the render context before enqueuing async commands.
if (rgContext.contextlessTesting == false)
rgContext.renderContext.ExecuteCommandBuffer(rgContext.cmd);
rgContext.cmd.Clear();
CommandBuffer asyncCmd = CommandBufferPool.Get(pass.name);
asyncCmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute);
rgContext.cmd = asyncCmd;
if (executedWorkDuringResourceCreation)
{
rgContext.cmd.WaitOnAsyncGraphicsFence(previousFence);
}
}
// Synchronize with graphics or compute pipe if needed.
if (passInfo.syncToPassIndex != -1)
{
rgContext.cmd.WaitOnAsyncGraphicsFence(m_CurrentCompiledGraph.compiledPassInfos[passInfo.syncToPassIndex].fence);
}
}
void PostRenderPassExecute(ref CompiledPassInfo passInfo, RenderGraphPass pass, InternalRenderGraphContext rgContext)
{
foreach (var tex in pass.setGlobalsList)
{
rgContext.cmd.SetGlobalTexture(tex.Item2, tex.Item1);
}
if (passInfo.needGraphicsFence)
passInfo.fence = rgContext.cmd.CreateAsyncGraphicsFence();
if (passInfo.enableAsyncCompute)
{
// Anything related to async command buffer execution needs a real context to be able to use/test it
// As the async will likely be waited for but never finish if there is no real context
Debug.Assert(m_RenderGraphContext.contextlessTesting == false);
// The command buffer has been filled. We can kick the async task.
rgContext.renderContext.ExecuteCommandBufferAsync(rgContext.cmd, ComputeQueueType.Background);
CommandBufferPool.Release(rgContext.cmd);
rgContext.cmd = m_PreviousCommandBuffer; // Restore the main command buffer.
}
if (passInfo.enableFoveatedRasterization)
rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Disabled);
m_RenderGraphPool.ReleaseAllTempAlloc();
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
foreach (var resource in passInfo.resourceReleaseList[type])
{
m_Resources.ReleasePooledResource(rgContext, type, resource);
}
}
}
void ClearRenderPasses()
{
foreach (var pass in m_RenderPasses)
pass.Release(m_RenderGraphPool);
m_RenderPasses.Clear();
}
void ReleaseImmediateModeResources()
{
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
foreach (var resource in m_ImmediateModeResourceList[type])
{
m_Resources.ReleasePooledResource(m_RenderGraphContext, type, resource);
}
}
}
void LogFrameInformation()
{
if (m_DebugParameters.enableLogging)
{
m_FrameInformationLogger.LogLine($"==== Staring render graph frame for: {m_CurrentExecutionName} ====");
if (!m_DebugParameters.immediateMode)
m_FrameInformationLogger.LogLine("Number of passes declared: {0}\n", m_RenderPasses.Count);
}
}
void LogRendererListsCreation()
{
if (m_DebugParameters.enableLogging)
{
m_FrameInformationLogger.LogLine("Number of renderer lists created: {0}\n", m_RendererLists.Count);
}
}
void LogRenderPassBegin(in CompiledPassInfo passInfo)
{
if (m_DebugParameters.enableLogging)
{
RenderGraphPass pass = m_RenderPasses[passInfo.index];
m_FrameInformationLogger.LogLine("[{0}][{1}] \"{2}\"", pass.index, pass.enableAsyncCompute ? "Compute" : "Graphics", pass.name);
using (new RenderGraphLogIndent(m_FrameInformationLogger))
{
if (passInfo.syncToPassIndex != -1)
m_FrameInformationLogger.LogLine("Synchronize with [{0}]", passInfo.syncToPassIndex);
}
}
}
void LogCulledPasses()
{
if (m_DebugParameters.enableLogging)
{
m_FrameInformationLogger.LogLine("Pass Culling Report:");
using (new RenderGraphLogIndent(m_FrameInformationLogger))
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
for (int i = 0; i < compiledPassInfo.size; ++i)
{
if (compiledPassInfo[i].culled)
{
var pass = m_RenderPasses[i];
m_FrameInformationLogger.LogLine("[{0}] {1}", pass.index, pass.name);
}
}
m_FrameInformationLogger.LogLine("\n");
}
}
}
ProfilingSampler GetDefaultProfilingSampler(string name)
{
// In non-dev builds, ProfilingSampler.Get returns null, so we'd always end up executing this.
// To avoid that we also ifdef the code out here.
#if DEVELOPMENT_BUILD || UNITY_EDITOR
int hash = name.GetHashCode();
if (!m_DefaultProfilingSamplers.TryGetValue(hash, out var sampler))
{
sampler = new ProfilingSampler(name);
m_DefaultProfilingSamplers.Add(hash, sampler);
}
return sampler;
#else
return null;
#endif
}
void UpdateImportedResourceLifeTime(ref DebugData.ResourceData data, List<int> passList)
{
foreach (var pass in passList)
{
if (data.creationPassIndex == -1)
data.creationPassIndex = pass;
else
data.creationPassIndex = Math.Min(data.creationPassIndex, pass);
if (data.releasePassIndex == -1)
data.releasePassIndex = pass;
else
data.releasePassIndex = Math.Max(data.releasePassIndex, pass);
}
}
void GenerateDebugData()
{
if (m_ExecutionExceptionWasRaised)
return;
if (!isRenderGraphViewerActive)
{
CleanupDebugData();
return;
}
if (!m_DebugData.TryGetValue(m_CurrentExecutionName, out var debugData))
{
onExecutionRegistered?.Invoke(this, m_CurrentExecutionName);
debugData = new DebugData();
m_DebugData.Add(m_CurrentExecutionName, debugData);
return; // Generate the debug data on the next frame, because script metadata is collected during recording step
}
// Only generate debug data on request
if (m_CaptureDebugDataForExecution == null || !m_CaptureDebugDataForExecution.Equals(m_CurrentExecutionName))
return;
debugData.Clear();
if (nativeRenderPassesEnabled)
nativeCompiler.GenerateNativeCompilerDebugData(ref debugData);
else
GenerateCompilerDebugData(ref debugData);
onDebugDataCaptured?.Invoke();
m_CaptureDebugDataForExecution = null;
ClearPassDebugMetadata();
}
void GenerateCompilerDebugData(ref DebugData debugData)
{
var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
var compiledResourceInfo = m_CurrentCompiledGraph.compiledResourcesInfos;
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
for (int i = 0; i < compiledResourceInfo[type].size; ++i)
{
ref var resourceInfo = ref compiledResourceInfo[type][i];
DebugData.ResourceData newResource = new DebugData.ResourceData();
if (i != 0)
{
var resName = m_Resources.GetRenderGraphResourceName((RenderGraphResourceType)type, i);
newResource.name = !string.IsNullOrEmpty(resName) ? resName : "(unnamed)";
newResource.imported = m_Resources.IsRenderGraphResourceImported((RenderGraphResourceType)type, i);
}
else
{
// The above functions will throw exceptions when used with the null argument so just use a dummy instead
newResource.name = "<null>";
newResource.imported = true;
}
newResource.creationPassIndex = -1;
newResource.releasePassIndex = -1;
RenderGraphResourceType resourceType = (RenderGraphResourceType) type;
var handle = new ResourceHandle(i, resourceType, false);
if (i != 0 && handle.IsValid())
{
if (resourceType == RenderGraphResourceType.Texture)
{
m_Resources.GetRenderTargetInfo(handle, out var renderTargetInfo);
var textureData = new DebugData.TextureResourceData();
textureData.width = renderTargetInfo.width;
textureData.height = renderTargetInfo.height;
textureData.depth = renderTargetInfo.volumeDepth;
textureData.samples = renderTargetInfo.msaaSamples;
textureData.format = renderTargetInfo.format;
newResource.textureData = textureData;
}
else if (resourceType == RenderGraphResourceType.Buffer)
{
var bufferDesc = m_Resources.GetBufferResourceDesc(handle, true);
var bufferData = new DebugData.BufferResourceData();
bufferData.count = bufferDesc.count;
bufferData.stride = bufferDesc.stride;
bufferData.target = bufferDesc.target;
bufferData.usage = bufferDesc.usageFlags;
newResource.bufferData = bufferData;
}
}
newResource.consumerList = new List<int>(resourceInfo.consumers);
newResource.producerList = new List<int>(resourceInfo.producers);
if (newResource.imported)
{
UpdateImportedResourceLifeTime(ref newResource, newResource.consumerList);
UpdateImportedResourceLifeTime(ref newResource, newResource.producerList);
}
debugData.resourceLists[type].Add(newResource);
}
}
for (int i = 0; i < compiledPassInfo.size; ++i)
{
ref CompiledPassInfo passInfo = ref compiledPassInfo[i];
RenderGraphPass pass = m_RenderPasses[passInfo.index];
DebugData.PassData newPass = new DebugData.PassData();
newPass.name = pass.name;
newPass.type = pass.type;
newPass.culled = passInfo.culled;
newPass.async = passInfo.enableAsyncCompute;
newPass.generateDebugData = pass.generateDebugData;
newPass.resourceReadLists = new List<int>[(int)RenderGraphResourceType.Count];
newPass.resourceWriteLists = new List<int>[(int)RenderGraphResourceType.Count];
newPass.syncFromPassIndex = passInfo.syncFromPassIndex;
newPass.syncToPassIndex = passInfo.syncToPassIndex;
DebugData.s_PassScriptMetadata.TryGetValue(pass.name, out newPass.scriptInfo);
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
newPass.resourceReadLists[type] = new List<int>();
newPass.resourceWriteLists[type] = new List<int>();
foreach (var resourceRead in pass.resourceReadLists[type])
newPass.resourceReadLists[type].Add(resourceRead.index);
foreach (var resourceWrite in pass.resourceWriteLists[type])
newPass.resourceWriteLists[type].Add(resourceWrite.index);
foreach (var resourceCreate in passInfo.resourceCreateList[type])
{
var res = debugData.resourceLists[type][resourceCreate];
if (res.imported)
continue;
res.creationPassIndex = i;
debugData.resourceLists[type][resourceCreate] = res;
}
foreach (var resourceRelease in passInfo.resourceReleaseList[type])
{
var res = debugData.resourceLists[type][resourceRelease];
if (res.imported)
continue;
res.releasePassIndex = i;
debugData.resourceLists[type][resourceRelease] = res;
}
}
debugData.passList.Add(newPass);
}
}
void CleanupDebugData()
{
foreach (var kvp in m_DebugData)
{
onExecutionUnregistered?.Invoke(this, kvp.Key);
}
m_DebugData.Clear();
}
#endregion
Dictionary<int, TextureHandle> registeredGlobals = new Dictionary<int, TextureHandle>();
internal void SetGlobal(TextureHandle h, int globalPropertyId)
{
if (!h.IsValid())
throw new ArgumentException("Attempting to register an invalid texture handle as a global");
registeredGlobals[globalPropertyId] = h;
}
internal bool IsGlobal(int globalPropertyId)
{
return registeredGlobals.ContainsKey(globalPropertyId);
}
internal Dictionary<int, TextureHandle>.ValueCollection AllGlobals()
{
return registeredGlobals.Values;
}
internal TextureHandle GetGlobal(int globalPropertyId)
{
TextureHandle h;
registeredGlobals.TryGetValue(globalPropertyId, out h);
return h;
}
/// <summary>
/// Clears the shader bindings associated with the registered globals in the graph
///
/// This prevents later rendering logic from accidentally relying on stale shader bindings that were set
/// earlier during graph execution.
/// </summary>
internal void ClearGlobalBindings()
{
// Set all the global texture shader bindings to the default black texture.
// This doesn't technically "clear" the shader bindings, but it's the closest we can do.
foreach (var globalTex in registeredGlobals)
{
m_RenderGraphContext.cmd.SetGlobalTexture(globalTex.Key, defaultResources.blackTexture);
}
}
}
/// <summary>
/// Render Graph Scoped Profiling markers
/// </summary>
[MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
public struct RenderGraphProfilingScope : IDisposable
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
ProfilingSampler m_Sampler;
RenderGraph m_RenderGraph;
bool m_Disposed;
#endif
/// <summary>
/// Profiling Scope constructor
/// </summary>
/// <param name="renderGraph">Render Graph used for this scope.</param>
/// <param name="sampler">Profiling Sampler to be used for this scope.</param>
public RenderGraphProfilingScope(RenderGraph renderGraph, ProfilingSampler sampler)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_RenderGraph = renderGraph;
m_Sampler = sampler;
m_Disposed = false;
renderGraph.BeginProfilingSampler(sampler);
#endif
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
public void Dispose()
{
Dispose(true);
}
// Protected implementation of Dispose pattern.
void Dispose(bool disposing)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (m_Disposed)
return;
// As this is a struct, it could have been initialized using an empty constructor so we
// need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix
// this but will generate garbage on every frame (and this struct is used quite a lot).
if (disposing)
{
m_RenderGraph.EndProfilingSampler(m_Sampler);
}
m_Disposed = true;
#endif
}
}
}