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 { /// /// Sets the read and write access for the depth buffer. /// [Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public enum DepthAccess { ///Read Access. Read = 1 << 0, ///Write Access. Write = 1 << 1, ///Read and Write Access. ReadWrite = Read | Write, } /// /// Express the operations the rendergraph pass will do on a resource. /// [Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public enum AccessFlags { ///The pass does not access the resource at all. Calling Use* functions with none has no effect. None = 0, ///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. Read = 1 << 0, ///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. Write = 1 << 1, ///Previous data in the resource is not preserved. The resource will contain undefined data at the beginning of the pass. Discard = 1 << 2, ///All data in the resource will be written by this pass. Data in the resource should never be read. WriteAll = Write | Discard, /// Shortcut for Read | Write ReadWrite = Read | Write } /// /// An object representing the internal context of a rendergraph pass execution. /// This object is public for technical reasons only and should not be used. /// [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 { /// /// This function is only public for techical resons of the c# language and should not be called outside the package. /// /// The context to convert public void FromInternalContext(InternalRenderGraphContext context); } /// /// 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. /// [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public struct RenderGraphContext : IDerivedRendergraphContext { private InternalRenderGraphContext wrappedContext; /// public void FromInternalContext(InternalRenderGraphContext context) { wrappedContext = context; } ///Scriptable Render Context used for rendering. public ScriptableRenderContext renderContext { get => wrappedContext.renderContext; } ///Command Buffer used for rendering. public CommandBuffer cmd { get => wrappedContext.cmd; } ///Render Graph pool used for temporary data. public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } ///Render Graph default resources. public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } } /// /// This class declares the context object passed to the execute function of a raster render pass. /// /// [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public struct RasterGraphContext : IDerivedRendergraphContext { private InternalRenderGraphContext wrappedContext; ///Command Buffer used for rendering. public RasterCommandBuffer cmd; ///Render Graph default resources. public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } ///Render Graph pool used for temporary data. public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } static internal RasterCommandBuffer rastercmd = new RasterCommandBuffer(null, null, false); /// public void FromInternalContext(InternalRenderGraphContext context) { wrappedContext = context; rastercmd.m_WrappedCommandBuffer = wrappedContext.cmd; rastercmd.m_ExecutingPass = context.executingPass; cmd = rastercmd; } } /// /// This class declares the context object passed to the execute function of a compute render pass. /// /// [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public class ComputeGraphContext : IDerivedRendergraphContext { private InternalRenderGraphContext wrappedContext; ///Command Buffer used for rendering. public ComputeCommandBuffer cmd; ///Render Graph default resources. public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } ///Render Graph pool used for temporary data. public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } static internal ComputeCommandBuffer computecmd = new ComputeCommandBuffer(null, null, false); /// public void FromInternalContext(InternalRenderGraphContext context) { wrappedContext = context; computecmd.m_WrappedCommandBuffer = wrappedContext.cmd; computecmd.m_ExecutingPass = context.executingPass; cmd = computecmd; } } /// /// This class declares the context object passed to the execute function of an unsafe render pass. /// /// [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public class UnsafeGraphContext : IDerivedRendergraphContext { private InternalRenderGraphContext wrappedContext; ///Unsafe Command Buffer used for rendering. public UnsafeCommandBuffer cmd; ///Render Graph default resources. public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } ///Render Graph pool used for temporary data. public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } internal static UnsafeCommandBuffer unsCmd = new UnsafeCommandBuffer(null, null, false); /// public void FromInternalContext(InternalRenderGraphContext context) { wrappedContext = context; unsCmd.m_WrappedCommandBuffer = wrappedContext.cmd; unsCmd.m_ExecutingPass = context.executingPass; cmd = unsCmd; } } /// /// This struct contains properties which control the execution of the Render Graph. /// [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public struct RenderGraphParameters { ///Identifier for this render graph execution. public string executionName; ///Index of the current frame being rendered. public int currentFrameIndex; /// Controls whether to enable Renderer List culling or not. public bool rendererListCulling; ///Scriptable Render Context used by the render pipeline. public ScriptableRenderContext scriptableRenderContext; ///Command Buffer used to execute graphic commands. public CommandBuffer commandBuffer; ///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. internal bool invalidContextForTesting; } /// /// The Render Pass rendering delegate to use with typed contexts. /// /// The type of the class used to provide data to the Render Pass. /// The type of the context that will be passed to the render function. /// Render Pass specific data. /// Global Render Graph context. [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public delegate void BaseRenderFunc(PassData data, ContextType renderGraphContext) where PassData : class, new(); /// /// This class is the main entry point of the Render Graph system. /// [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] public partial class RenderGraph { ///Maximum number of MRTs supported by Render Graph. public static readonly int kMaxMRTCount = 8; internal struct CompiledResourceInfo { public List producers; public List consumers; public int refCount; public bool imported; public void Reset() { if (producers == null) producers = new List(); if (consumers == null) consumers = new List(); 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[] resourceCreateList; public List[] resourceReleaseList; public GraphicsFence fence; #if DEVELOPMENT_BUILD || UNITY_EDITOR // This members are only here to ease debugging. public List[] debugResourceReads; public List[] 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)RenderGraphResourceType.Count]; resourceReleaseList = new List[(int)RenderGraphResourceType.Count]; for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) { resourceCreateList[i] = new List(); resourceReleaseList[i] = new List(); } #if DEVELOPMENT_BUILD || UNITY_EDITOR debugResourceReads = new List[(int)RenderGraphResourceType.Count]; debugResourceWrites = new List[(int)RenderGraphResourceType.Count]; for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) { debugResourceReads[i] = new List(); debugResourceWrites[i] = new List(); } #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 } } /// /// 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). /// 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 m_RenderPasses = new List(64); List m_RendererLists = new List(32); RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams(); RenderGraphLogger m_FrameInformationLogger = new RenderGraphLogger(); RenderGraphDefaultResources m_DefaultResources = new RenderGraphDefaultResources(); Dictionary m_DefaultProfilingSamplers = new Dictionary(); InternalRenderGraphContext m_RenderGraphContext = new InternalRenderGraphContext(); CommandBuffer m_PreviousCommandBuffer; List[] m_ImmediateModeResourceList = new List[(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[] compiledResourcesInfos = new DynamicArray[(int)RenderGraphResourceType.Count]; public DynamicArray compiledPassInfos = new DynamicArray(); public int lastExecutionFrame; public CompiledGraph() { for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) { compiledResourcesInfos[i] = new DynamicArray(); } } public void Clear() { for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) compiledResourcesInfos[i].Clear(); compiledPassInfos.Clear(); } void InitResourceInfosData(DynamicArray resourceInfos, int count) { resourceInfos.Resize(count); for (int i = 0; i < resourceInfos.size; ++i) resourceInfos[i].Reset(); } public void InitializeCompilationData(List 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 m_CullingStack = new Stack(); 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 m_DebugData = new Dictionary(); // Global list of living render graphs static List s_RegisteredGraphs = new List(); #region Public Interface /// Name of the Render Graph. public string name { get; private set; } = "RenderGraph"; /// Request debug data be captured for the provided execution on the next frame. internal void RequestCaptureDebugData(string executionName) { m_CaptureDebugDataForExecution = executionName; } /// If true, the Render Graph Viewer is active. public static bool isRenderGraphViewerActive { get; internal set; } /// If true, the Render Graph will run its various validity checks while processing (not considered in release mode). internal static bool enableValidityChecks { get; private set; } /// /// Set of default resources usable in a pass rendering code. /// public RenderGraphDefaultResources defaultResources { get { return m_DefaultResources; } } /// /// Render Graph constructor. /// /// Optional name used to identify the render graph instnace. public RenderGraph(string name = "RenderGraph") { this.name = name; if (GraphicsSettings.TryGetRenderPipelineSettings(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); } /// /// Cleanup the Render Graph. /// 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 GetWidgetList() { return m_DebugParameters.GetWidgetList(name); } internal bool areAnySettingsActive => m_DebugParameters.AreAnySettingsActive; /// /// Register the render graph to the debug window. /// /// Optional debug panel to which the render graph debug parameters will be registered. public void RegisterDebug(DebugUI.Panel panel = null) { m_DebugParameters.RegisterDebug(name, panel); } /// /// Unregister render graph from the debug window. /// public void UnRegisterDebug() { m_DebugParameters.UnRegisterDebug(this.name); } /// /// Get the list of all registered render graphs. /// /// The list of all registered render graphs. public static List GetRegisteredRenderGraphs() { return s_RegisteredGraphs; } /// /// Returns the last rendered frame debug data. Can be null if requireDebugData is set to false. /// /// The last rendered frame debug data internal DebugData GetDebugData(string executionName) { if (m_DebugData.TryGetValue(executionName, out var debugData)) return debugData; return null; } /// /// End frame processing. Purge resources that have been used since last frame and resets internal states. /// This need to be called once per frame. /// 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; } } /// /// 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. /// /// External RTHandle that needs to be imported. /// A new TextureHandle that represents the imported texture in the context of this rendergraph. public TextureHandle ImportTexture(RTHandle rt) { return m_Resources.ImportTexture(rt); } /// /// 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. /// /// External RTHandle that needs to be imported. /// 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. /// A new TextureHandle that represents the imported texture in the context of this rendergraph. public TextureHandle ImportTexture(RTHandle rt, ImportResourceParams importParams ) { return m_Resources.ImportTexture(rt, importParams); } /// /// 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. /// /// External RTHandle that needs to be imported. /// The properties of the passed in RTHandle. /// 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. /// A new TextureHandle that represents the imported texture in the context of this rendergraph. public TextureHandle ImportTexture(RTHandle rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams() ) { return m_Resources.ImportTexture(rt, info, importParams); } /// /// 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. /// internal TextureHandle ImportTexture(RTHandle rt, bool isBuiltin) { return m_Resources.ImportTexture(rt, isBuiltin); } /// /// 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. /// /// Backbuffer render target identifier. /// The properties of the passed in RTHandle. /// 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. /// A new TextureHandle that represents the imported texture in the context of this rendergraph. public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams()) { return m_Resources.ImportBackbuffer(rt, info, importParams); } /// /// Import the final backbuffer to render graph. /// This function can only be used when nativeRenderPassesEnabled is false. /// /// Backbuffer render target identifier. /// A new TextureHandle that represents the imported texture in the context of this rendergraph. 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()); } /// /// Create a new Render Graph Texture resource. /// /// Texture descriptor. /// A new TextureHandle. public TextureHandle CreateTexture(in TextureDesc desc) { return m_Resources.CreateTexture(desc); } /// /// Create a new Render Graph Shared Texture resource. /// This texture will be persistent across render graph executions. /// /// Creation descriptor of the texture. /// 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. /// A new TextureHandle. 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); } /// /// Refresh a shared texture with a new descriptor. /// /// Shared texture that needs to be updated. /// New Descriptor for the texture. public void RefreshSharedTextureDesc(TextureHandle handle, in TextureDesc desc) { m_Resources.RefreshSharedTextureDesc(handle, desc); } /// /// Release a Render Graph shared texture resource. /// /// The handle to the texture that needs to be release. 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); } /// /// Create a new Render Graph Texture resource using the descriptor from another texture. /// /// Texture from which the descriptor should be used. /// A new TextureHandle. public TextureHandle CreateTexture(TextureHandle texture) { return m_Resources.CreateTexture(m_Resources.GetTextureResourceDesc(texture.handle)); } /// /// 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. /// /// Desc used to create the texture. /// Texture from which the descriptor should be used. public void CreateTextureIfInvalid(in TextureDesc desc, ref TextureHandle texture) { if (!texture.IsValid()) texture = m_Resources.CreateTexture(desc); } /// /// Gets the descriptor of the specified Texture resource. /// /// Texture resource from which the descriptor is requested. /// The input texture descriptor. public TextureDesc GetTextureDesc(TextureHandle texture) { return m_Resources.GetTextureResourceDesc(texture.handle); } /// /// Gets the descriptor of the specified Texture resource. /// /// Texture resource from which the descriptor is requested. /// The input texture descriptor. public RenderTargetInfo GetRenderTargetInfo(TextureHandle texture) { RenderTargetInfo info; m_Resources.GetRenderTargetInfo(texture.handle, out info); return info; } /// /// Creates a new Renderer List Render Graph resource. /// /// Renderer List descriptor. /// A new RendererListHandle. public RendererListHandle CreateRendererList(in CoreRendererListDesc desc) { return m_Resources.CreateRendererList(desc); } /// /// Creates a new Renderer List Render Graph resource. /// /// Renderer List descriptor. /// A new RendererListHandle. public RendererListHandle CreateRendererList(in RendererListParams desc) { return m_Resources.CreateRendererList(desc); } /// /// Creates a new Shadow Renderer List Render Graph resource. /// /// DrawSettings that describe the shadow drawcall. /// A new RendererListHandle. public RendererListHandle CreateShadowRendererList(ref ShadowDrawingSettings shadowDrawingSettings) { return m_Resources.CreateShadowRendererList(m_RenderGraphContext.renderContext, ref shadowDrawingSettings); } /// /// Creates a new Gizmo Renderer List Render Graph resource. /// /// The camera that is used for rendering the Gizmo. /// GizmoSubset that specifies whether gizmos render before or after postprocessing for a camera render. /// A new RendererListHandle. public RendererListHandle CreateGizmoRendererList(in Camera camera, in GizmoSubset gizmoSubset) { return m_Resources.CreateGizmoRendererList(m_RenderGraphContext.renderContext, camera, gizmoSubset); } /// /// Creates a new UIOverlay Renderer List Render Graph resource. /// /// The camera that is used for rendering the full UIOverlay. /// A new RendererListHandle. public RendererListHandle CreateUIOverlayRendererList(in Camera camera) { return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, UISubset.All); } /// /// Creates a new UIOverlay Renderer List Render Graph resource. /// /// The camera that is used for rendering some subset of the UIOverlay. /// Enum flag that specifies which subset to render. /// A new RendererListHandle. public RendererListHandle CreateUIOverlayRendererList(in Camera camera, in UISubset uiSubset) { return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, uiSubset); } /// /// Creates a new WireOverlay Renderer List Render Graph resource. /// /// The camera that is used for rendering the WireOverlay. /// A new RendererListHandle. public RendererListHandle CreateWireOverlayRendererList(in Camera camera) { return m_Resources.CreateWireOverlayRendererList(m_RenderGraphContext.renderContext, camera); } /// /// Creates a new Skybox Renderer List Render Graph resource. /// /// The camera that is used for rendering the Skybox. /// A new RendererListHandle. public RendererListHandle CreateSkyboxRendererList(in Camera camera) { return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera); } /// /// Creates a new Skybox Renderer List Render Graph resource. /// /// The camera that is used for rendering the Skybox. /// The projection matrix used during XR rendering of the skybox. /// The view matrix used during XR rendering of the skybox. /// A new RendererListHandle. public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrix, Matrix4x4 viewMatrix) { return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrix, viewMatrix); } /// /// Creates a new Skybox Renderer List Render Graph resource. /// /// The camera that is used for rendering the Skybox. /// The left eye projection matrix used during Legacy single pass XR rendering of the skybox. /// The left eye view matrix used during Legacy single pass XR rendering of the skybox. /// The right eye projection matrix used during Legacy single pass XR rendering of the skybox. /// The right eye view matrix used during Legacy single pass XR rendering of the skybox. /// A new RendererListHandle. 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); } /// /// 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. /// /// External Graphics Buffer that needs to be imported. /// The imported graphics buffer will be released after usage. /// A new GraphicsBufferHandle. public BufferHandle ImportBuffer(GraphicsBuffer graphicsBuffer, bool forceRelease = false) { return m_Resources.ImportBuffer(graphicsBuffer, forceRelease); } /// /// Create a new Render Graph Graphics Buffer resource. /// /// Graphics Buffer descriptor. /// A new GraphicsBufferHandle. public BufferHandle CreateBuffer(in BufferDesc desc) { return m_Resources.CreateBuffer(desc); } /// /// Create a new Render Graph Graphics Buffer resource using the descriptor from another graphics buffer. /// /// Graphics Buffer from which the descriptor should be used. /// A new GraphicsBufferHandle. public BufferHandle CreateBuffer(in BufferHandle graphicsBuffer) { return m_Resources.CreateBuffer(m_Resources.GetBufferResourceDesc(graphicsBuffer.handle)); } /// /// Gets the descriptor of the specified Graphics Buffer resource. /// /// Graphics Buffer resource from which the descriptor is requested. /// The input graphics buffer descriptor. public BufferDesc GetBufferDesc(in BufferHandle graphicsBuffer) { return m_Resources.GetBufferResourceDesc(graphicsBuffer.handle); } /// /// 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. /// /// External RayTracingAccelerationStructure that needs to be imported. /// Optional name for identifying the RayTracingAccelerationStructure in the Render Graph. /// A new RayTracingAccelerationStructureHandle. public RayTracingAccelerationStructureHandle ImportRayTracingAccelerationStructure(in RayTracingAccelerationStructure accelStruct, string name = null) { return m_Resources.ImportRayTracingAccelerationStructure(accelStruct, name); } /// /// 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. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// 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. /// 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. /// A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass. public IRasterRenderGraphBuilder AddRasterRenderPass(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); } /// /// 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. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// Profiling sampler used around the pass. /// 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. /// 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. /// A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass. public IRasterRenderGraphBuilder AddRasterRenderPass(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>(); renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, RenderGraphPassType.Raster, sampler); passData = renderPass.data; m_RenderPasses.Add(renderPass); m_builderInstance.Setup(renderPass, m_Resources, this); return m_builderInstance; } /// /// 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. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// 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. /// 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. /// A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass. public IComputeRenderGraphBuilder AddComputePass(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); } /// /// Add a new Compute Render Pass to the Render Graph. Compute passes can execute compute workloads but cannot do rasterization. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// Profiling sampler used around the pass. /// 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. /// 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. /// A new instance of a IComputeRenderGraphBuilder used to setup the new Compute Render Pass. public IComputeRenderGraphBuilder AddComputePass(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>(); renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, RenderGraphPassType.Compute, sampler); passData = renderPass.data; m_RenderPasses.Add(renderPass); m_builderInstance.Setup(renderPass, m_Resources, this); return m_builderInstance; } /// /// 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. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// 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. /// 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. /// A new instance of a IUnsafeRenderGraphBuilder used to setup the new Unsafe Render Pass. public IUnsafeRenderGraphBuilder AddUnsafePass(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); } /// /// 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. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// Profiling sampler used around the pass. /// 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. /// 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. /// A new instance of a IUnsafeRenderGraphBuilder used to setup the new unsafe Render Pass. public IUnsafeRenderGraphBuilder AddUnsafePass(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>(); renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, RenderGraphPassType.Unsafe, sampler); renderPass.AllowGlobalState(true); passData = renderPass.data; m_RenderPasses.Add(renderPass); m_builderInstance.Setup(renderPass, m_Resources, this); return m_builderInstance; } /// /// Add a new Render Pass to the Render Graph. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// Profiling sampler used around the pass. /// 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. /// 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. /// A new instance of a RenderGraphBuilder used to setup the new Render Pass. public RenderGraphBuilder AddRenderPass(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>(); renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), 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); } /// /// Add a new Render Pass to the Render Graph. /// /// Type of the class to use to provide data to the Render Pass. /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). /// Instance of PassData that is passed to the render function and you must fill. /// 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. /// 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. /// A new instance of a RenderGraphBuilder used to setup the new Render Pass. public RenderGraphBuilder AddRenderPass(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); } /// /// Starts the recording of the render graph. /// This must be called before adding any pass to the render graph. /// /// Parameters necessary for the render graph execution. /// /// Begin recording the Render Graph. /// /// renderGraph.BeginRecording(parameters) /// // Add your render graph passes here. /// renderGraph.EndRecordingAndExecute() /// /// 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(); m_ImmediateModeResourceList[i].Clear(); } m_Resources.BeginExecute(m_CurrentFrameIndex); } } /// /// Ends the recording and executes the render graph. /// This must be called once all passes have been added to the render graph. /// public void EndRecordingAndExecute() { Execute(); } /// /// Execute the Render Graph in its current state. /// 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(); #if RENDER_GRAPH_CLEAR_GLOBALS // Clear the shader bindings for all global textures to make sure bindings don't leak outside the graph ClearGlobalBindings(); #endif } } 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"; /// /// Begin a profiling scope. /// /// Sampler used for profiling. /// 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. /// 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. public void BeginProfilingSampler(ProfilingSampler sampler, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) { if (sampler == null) return; using (var builder = AddRenderPass(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); }); } } /// /// End a profiling scope. /// /// Sampler used for profiling. /// 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. /// 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. public void EndProfilingSampler(ProfilingSampler sampler, [CallerFilePath] string file = "", [CallerLineNumber] int line = 0) { if (sampler == null) return; using (var builder = AddRenderPass(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 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) { ExecutePassImmediatly(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))) { int hash = 0; for (int i = 0; i < m_RenderPasses.Count; ++i) hash = hash * 23 + m_RenderPasses[i].ComputeHash(m_Resources); return hash; } } 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 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 = ""; #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 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 {pass.name} "); 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 ExecutePassImmediatly(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(string.Format("RenderPass {0} was not provided with an execute function.", pass.name)); } 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 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 = ""; 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(resourceInfo.consumers); newResource.producerList = new List(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)RenderGraphResourceType.Count]; newPass.resourceWriteLists = new List[(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(); newPass.resourceWriteLists[type] = new List(); 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 registeredGlobals = new Dictionary(); internal void SetGlobal(TextureHandle h, int globalPropertyId) { registeredGlobals[globalPropertyId] = h; } internal bool IsGlobal(int globalPropertyId) { return registeredGlobals.ContainsKey(globalPropertyId); } internal Dictionary.ValueCollection AllGlobals() { return registeredGlobals.Values; } internal TextureHandle GetGlobal(int globalPropertyId) { TextureHandle h; registeredGlobals.TryGetValue(globalPropertyId, out h); return h; } /// /// 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. /// 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); } } } /// /// Render Graph Scoped Profiling markers /// [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 /// /// Profiling Scope constructor /// /// Render Graph used for this scope. /// Profiling Sampler to be used for this scope. public RenderGraphProfilingScope(RenderGraph renderGraph, ProfilingSampler sampler) { #if DEVELOPMENT_BUILD || UNITY_EDITOR m_RenderGraph = renderGraph; m_Sampler = sampler; m_Disposed = false; renderGraph.BeginProfilingSampler(sampler); #endif } /// /// Dispose pattern implementation /// 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 } } }