A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Runtime.CompilerServices;
5using UnityEngine.Experimental.Rendering;
6using UnityEngine.Scripting.APIUpdating;
7// Typedef for the in-engine RendererList API (to avoid conflicts with the experimental version)
8using CoreRendererListDesc = UnityEngine.Rendering.RendererUtils.RendererListDesc;
9
10namespace UnityEngine.Rendering.RenderGraphModule
11{
12 /// <summary>
13 /// Sets the read and write access for the depth buffer.
14 /// </summary>
15 [Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
16 public enum DepthAccess
17 {
18 ///<summary>Read Access.</summary>
19 Read = 1 << 0,
20 ///<summary>Write Access.</summary>
21 Write = 1 << 1,
22 ///<summary>Read and Write Access.</summary>
23 ReadWrite = Read | Write,
24 }
25
26 /// <summary>
27 /// Express the operations the rendergraph pass will do on a resource.
28 /// </summary>
29 [Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
30 public enum AccessFlags
31 {
32 ///<summary>The pass does not access the resource at all. Calling Use* functions with none has no effect.</summary>
33 None = 0,
34
35 ///<summary>This pass will read data the resource. Data in the resource should never be written unless one of the write flags is also present. Writing to a read-only resource may lead to undefined results, significant performance penaties, and GPU crashes.</summary>
36 Read = 1 << 0,
37
38 ///<summary>This pass will at least write some data to the resource. Data in the resource should never be read unless one of the read flags is also present. Reading from a write-only resource may lead to undefined results, significant performance penaties, and GPU crashes.</summary>
39 Write = 1 << 1,
40
41 ///<summary>Previous data in the resource is not preserved. The resource will contain undefined data at the beginning of the pass.</summary>
42 Discard = 1 << 2,
43
44 ///<summary>All data in the resource will be written by this pass. Data in the resource should never be read.</summary>
45 WriteAll = Write | Discard,
46
47 ///<summary> Shortcut for Read | Write</summary>
48 ReadWrite = Read | Write
49 }
50
51 /// <summary>
52 /// An object representing the internal context of a rendergraph pass execution.
53 /// This object is public for technical reasons only and should not be used.
54 /// </summary>
55 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
56 public class InternalRenderGraphContext
57 {
58 internal ScriptableRenderContext renderContext;
59 internal CommandBuffer cmd;
60 internal RenderGraphObjectPool renderGraphPool;
61 internal RenderGraphDefaultResources defaultResources;
62 internal RenderGraphPass executingPass;
63 internal bool contextlessTesting;
64 }
65
66 // This whole thing is a bit of a mess InternalRenderGraphContext is public (but all members are internal)
67 // just because the C# standard says that all interface member function implementations should be public.
68 // So below in for example the RasterGraphContext we can't implement the (internal) interface as
69 // internal void FromInternalContext(InternalRenderGraphContext context) { ... }
70 // So we have to make FromInternalContext public so InternalRenderGraphContext also becomes public.
71 // This seems an oversight in c# where Interfaces used as Generic constraints could very well be useful
72 // with internal only functions.
73
74 internal interface IDerivedRendergraphContext
75 {
76 /// <summary>
77 /// This function is only public for techical resons of the c# language and should not be called outside the package.
78 /// </summary>
79 /// <param name="context">The context to convert</param>
80 public void FromInternalContext(InternalRenderGraphContext context);
81 }
82
83 /// <summary>
84 /// This class specifies the context given to every render pass. This context type passes a generic
85 /// command buffer that can be used to schedule all commands. This will eventually be deprecated
86 /// in favor of more specific contexts that have more specific command buffer types.
87 /// </summary>
88 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
89 public struct RenderGraphContext : IDerivedRendergraphContext
90 {
91 private InternalRenderGraphContext wrappedContext;
92
93 /// <inheritdoc />
94 public void FromInternalContext(InternalRenderGraphContext context)
95 {
96 wrappedContext = context;
97 }
98
99 ///<summary>Scriptable Render Context used for rendering.</summary>
100 public ScriptableRenderContext renderContext { get => wrappedContext.renderContext; }
101 ///<summary>Command Buffer used for rendering.</summary>
102 public CommandBuffer cmd { get => wrappedContext.cmd; }
103 ///<summary>Render Graph pool used for temporary data.</summary>
104 public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
105 ///<summary>Render Graph default resources.</summary>
106 public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
107 }
108
109 /// <summary>
110 /// This class declares the context object passed to the execute function of a raster render pass.
111 /// <see cref="RenderGraph.AddRasterRenderPass"/>
112 /// </summary>
113 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
114 public struct RasterGraphContext : IDerivedRendergraphContext
115 {
116 private InternalRenderGraphContext wrappedContext;
117
118 ///<summary>Command Buffer used for rendering.</summary>
119 public RasterCommandBuffer cmd;
120
121 ///<summary>Render Graph default resources.</summary>
122 public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
123
124 ///<summary>Render Graph pool used for temporary data.</summary>
125 public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
126
127 static internal RasterCommandBuffer rastercmd = new RasterCommandBuffer(null, null, false);
128 /// <inheritdoc />
129 public void FromInternalContext(InternalRenderGraphContext context)
130 {
131 wrappedContext = context;
132 rastercmd.m_WrappedCommandBuffer = wrappedContext.cmd;
133 rastercmd.m_ExecutingPass = context.executingPass;
134 cmd = rastercmd;
135 }
136 }
137
138 /// <summary>
139 /// This class declares the context object passed to the execute function of a compute render pass.
140 /// <see cref="RenderGraph.AddComputePass"/>
141 /// </summary>
142 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
143 public class ComputeGraphContext : IDerivedRendergraphContext
144 {
145 private InternalRenderGraphContext wrappedContext;
146
147 ///<summary>Command Buffer used for rendering.</summary>
148 public ComputeCommandBuffer cmd;
149
150 ///<summary>Render Graph default resources.</summary>
151 public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
152
153 ///<summary>Render Graph pool used for temporary data.</summary>
154 public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
155
156 static internal ComputeCommandBuffer computecmd = new ComputeCommandBuffer(null, null, false);
157
158 /// <inheritdoc />
159 public void FromInternalContext(InternalRenderGraphContext context)
160 {
161 wrappedContext = context;
162 computecmd.m_WrappedCommandBuffer = wrappedContext.cmd;
163 computecmd.m_ExecutingPass = context.executingPass;
164 cmd = computecmd;
165 }
166 }
167
168 /// <summary>
169 /// This class declares the context object passed to the execute function of an unsafe render pass.
170 /// <see cref="RenderGraph.AddUnsafePass"/>
171 /// </summary>
172 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
173 public class UnsafeGraphContext : IDerivedRendergraphContext
174 {
175 private InternalRenderGraphContext wrappedContext;
176
177 ///<summary>Unsafe Command Buffer used for rendering.</summary>
178 public UnsafeCommandBuffer cmd;
179
180 ///<summary>Render Graph default resources.</summary>
181 public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; }
182
183 ///<summary>Render Graph pool used for temporary data.</summary>
184 public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; }
185
186 internal static UnsafeCommandBuffer unsCmd = new UnsafeCommandBuffer(null, null, false);
187 /// <inheritdoc />
188 public void FromInternalContext(InternalRenderGraphContext context)
189 {
190 wrappedContext = context;
191 unsCmd.m_WrappedCommandBuffer = wrappedContext.cmd;
192 unsCmd.m_ExecutingPass = context.executingPass;
193 cmd = unsCmd;
194 }
195 }
196
197 /// <summary>
198 /// This struct contains properties which control the execution of the Render Graph.
199 /// </summary>
200 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
201 public struct RenderGraphParameters
202 {
203 ///<summary>Identifier for this render graph execution.</summary>
204 public string executionName;
205 ///<summary>Index of the current frame being rendered.</summary>
206 public int currentFrameIndex;
207 ///<summary> Controls whether to enable Renderer List culling or not.</summary>
208 public bool rendererListCulling;
209 ///<summary>Scriptable Render Context used by the render pipeline.</summary>
210 public ScriptableRenderContext scriptableRenderContext;
211 ///<summary>Command Buffer used to execute graphic commands.</summary>
212 public CommandBuffer commandBuffer;
213 ///<summary>When running tests indicate the context is intentionally invalid and all calls on it should just do nothing.
214 ///This allows you to run tests that rely on code execution the way to the pass render functions
215 ///This also changes some behaviours with exception handling and error logging so the test framework can act on exceptions to validate behaviour better.</summary>
216 internal bool invalidContextForTesting;
217 }
218
219 /// <summary>
220 /// The Render Pass rendering delegate to use with typed contexts.
221 /// </summary>
222 /// <typeparam name="PassData">The type of the class used to provide data to the Render Pass.</typeparam>
223 /// <typeparam name="ContextType">The type of the context that will be passed to the render function.</typeparam>
224 /// <param name="data">Render Pass specific data.</param>
225 /// <param name="renderGraphContext">Global Render Graph context.</param>
226 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
227 public delegate void BaseRenderFunc<PassData, ContextType>(PassData data, ContextType renderGraphContext) where PassData : class, new();
228
229
230 /// <summary>
231 /// This class is the main entry point of the Render Graph system.
232 /// </summary>
233 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
234 public partial class RenderGraph
235 {
236 ///<summary>Maximum number of MRTs supported by Render Graph.</summary>
237 public static readonly int kMaxMRTCount = 8;
238
239 internal struct CompiledResourceInfo
240 {
241 public List<int> producers;
242 public List<int> consumers;
243 public int refCount;
244 public bool imported;
245
246 public void Reset()
247 {
248 if (producers == null)
249 producers = new List<int>();
250 if (consumers == null)
251 consumers = new List<int>();
252
253 producers.Clear();
254 consumers.Clear();
255 refCount = 0;
256 imported = false;
257 }
258 }
259
260 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
261 internal struct CompiledPassInfo
262 {
263 public string name;
264 public int index;
265
266 public List<int>[] resourceCreateList;
267 public List<int>[] resourceReleaseList;
268 public GraphicsFence fence;
269
270#if DEVELOPMENT_BUILD || UNITY_EDITOR
271 // This members are only here to ease debugging.
272 public List<string>[] debugResourceReads;
273 public List<string>[] debugResourceWrites;
274#endif
275
276 public int refCount;
277 public int syncToPassIndex; // Index of the pass that needs to be waited for.
278 public int syncFromPassIndex; // Smaller pass index that waits for this pass.
279
280 public bool enableAsyncCompute;
281 public bool allowPassCulling;
282 public bool needGraphicsFence;
283 public bool culled;
284 public bool culledByRendererList;
285 public bool hasSideEffect;
286 public bool enableFoveatedRasterization;
287
288 public void Reset(RenderGraphPass pass, int index)
289 {
290 name = pass.name;
291 this.index = index;
292
293 enableAsyncCompute = pass.enableAsyncCompute;
294 allowPassCulling = pass.allowPassCulling;
295 enableFoveatedRasterization = pass.enableFoveatedRasterization;
296
297 if (resourceCreateList == null)
298 {
299 resourceCreateList = new List<int>[(int)RenderGraphResourceType.Count];
300 resourceReleaseList = new List<int>[(int)RenderGraphResourceType.Count];
301 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
302 {
303 resourceCreateList[i] = new List<int>();
304 resourceReleaseList[i] = new List<int>();
305 }
306
307#if DEVELOPMENT_BUILD || UNITY_EDITOR
308 debugResourceReads = new List<string>[(int)RenderGraphResourceType.Count];
309 debugResourceWrites = new List<string>[(int)RenderGraphResourceType.Count];
310 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
311 {
312 debugResourceReads[i] = new List<string>();
313 debugResourceWrites[i] = new List<string>();
314 }
315#endif
316 }
317
318 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
319 {
320 resourceCreateList[i].Clear();
321 resourceReleaseList[i].Clear();
322 }
323
324 refCount = 0;
325 culled = false;
326 culledByRendererList = false;
327 hasSideEffect = false;
328 syncToPassIndex = -1;
329 syncFromPassIndex = -1;
330 needGraphicsFence = false;
331
332#if DEVELOPMENT_BUILD || UNITY_EDITOR
333 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
334 {
335 debugResourceReads[i].Clear();
336 debugResourceWrites[i].Clear();
337 }
338#endif
339 }
340 }
341
342 /// <summary>
343 /// Enable the use of the render pass API by the graph instead of traditional SetRenderTarget. This is an advanced
344 /// feature and users have to be aware of the specific impact it has on rendergraph/graphics APIs below.
345 ///
346 /// When enabled, the render graph try to use render passes and supasses instead of relying on SetRendertarget. It
347 /// will try to aggressively optimize the number of BeginRenderPass+EndRenderPass calls as well as calls to NextSubPass.
348 /// This with the aim to maximize the time spent "on chip" on tile based renderers.
349 ///
350 /// The Graph will automatically determine when to break render passes as well as the load and store actions to apply to these render passes.
351 /// 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
352 /// 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
353 /// a later pass this render target will be stored (and possibly resolved) and the render pass will be broken up.
354 ///
355 /// 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
356 /// native render pases. In particular:
357 /// - The ImportBackbuffer overload without a RenderTargetInfo argument.
358 /// - Any AddRenderPass overloads. The more specific AddRasterRenderPass/AddComputePass/AddUnsafePass functions should be used to register passes.
359 ///
360 /// 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
361 /// to new errors when using existing render graph code with nativeRenderPassesEnabled.
362 ///
363 /// Note: that CommandBuffer.BeginRenderPass/EndRenderPass calls are different by design from SetRenderTarget so this could also have
364 /// 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).
365 /// </summary>
366 public bool nativeRenderPassesEnabled
367 {
368 get; set;
369 }
370
371 internal/*for tests*/ RenderGraphResourceRegistry m_Resources;
372 RenderGraphObjectPool m_RenderGraphPool = new RenderGraphObjectPool();
373 RenderGraphBuilders m_builderInstance = new RenderGraphBuilders();
374 internal/*for tests*/ List<RenderGraphPass> m_RenderPasses = new List<RenderGraphPass>(64);
375 List<RendererListHandle> m_RendererLists = new List<RendererListHandle>(32);
376 RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams();
377 RenderGraphLogger m_FrameInformationLogger = new RenderGraphLogger();
378 RenderGraphDefaultResources m_DefaultResources = new RenderGraphDefaultResources();
379 Dictionary<int, ProfilingSampler> m_DefaultProfilingSamplers = new Dictionary<int, ProfilingSampler>();
380 InternalRenderGraphContext m_RenderGraphContext = new InternalRenderGraphContext();
381 CommandBuffer m_PreviousCommandBuffer;
382 List<int>[] m_ImmediateModeResourceList = new List<int>[(int)RenderGraphResourceType.Count];
383 RenderGraphCompilationCache m_CompilationCache;
384
385 RenderTargetIdentifier[][] m_TempMRTArrays = null;
386
387 internal interface ICompiledGraph
388 {
389 public void Clear();
390 }
391
392 // Compiled Render Graph info.
393 internal class CompiledGraph : ICompiledGraph
394 {
395 // This is a 1:1 mapping on the resource handle indexes, this means the first element with index 0 will represent the "null" handle
396 public DynamicArray<CompiledResourceInfo>[] compiledResourcesInfos = new DynamicArray<CompiledResourceInfo>[(int)RenderGraphResourceType.Count];
397 public DynamicArray<CompiledPassInfo> compiledPassInfos = new DynamicArray<CompiledPassInfo>();
398 public int lastExecutionFrame;
399
400 public CompiledGraph()
401 {
402 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
403 {
404 compiledResourcesInfos[i] = new DynamicArray<CompiledResourceInfo>();
405 }
406 }
407
408 public void Clear()
409 {
410 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
411 compiledResourcesInfos[i].Clear();
412 compiledPassInfos.Clear();
413 }
414
415 void InitResourceInfosData(DynamicArray<CompiledResourceInfo> resourceInfos, int count)
416 {
417 resourceInfos.Resize(count);
418 for (int i = 0; i < resourceInfos.size; ++i)
419 resourceInfos[i].Reset();
420 }
421
422 public void InitializeCompilationData(List<RenderGraphPass> passes, RenderGraphResourceRegistry resources)
423 {
424 InitResourceInfosData(compiledResourcesInfos[(int)RenderGraphResourceType.Texture], resources.GetTextureResourceCount());
425 InitResourceInfosData(compiledResourcesInfos[(int)RenderGraphResourceType.Buffer], resources.GetBufferResourceCount());
426 InitResourceInfosData(compiledResourcesInfos[(int)RenderGraphResourceType.AccelerationStructure], resources.GetRayTracingAccelerationStructureResourceCount());
427
428 compiledPassInfos.Resize(passes.Count);
429 for (int i = 0; i < compiledPassInfos.size; ++i)
430 compiledPassInfos[i].Reset(passes[i], i);
431 }
432 }
433
434 Stack<int> m_CullingStack = new Stack<int>();
435
436 string m_CurrentExecutionName;
437 int m_ExecutionCount;
438 int m_CurrentFrameIndex;
439 int m_CurrentImmediatePassIndex;
440 bool m_ExecutionExceptionWasRaised;
441 bool m_HasRenderGraphBegun;
442 bool m_RendererListCulling;
443 bool m_EnableCompilationCaching;
444 CompiledGraph m_DefaultCompiledGraph = new();
445 CompiledGraph m_CurrentCompiledGraph;
446 string m_CaptureDebugDataForExecution; // Null unless debug data has been requested
447
448 Dictionary<string, DebugData> m_DebugData = new Dictionary<string, DebugData>();
449
450 // Global list of living render graphs
451 static List<RenderGraph> s_RegisteredGraphs = new List<RenderGraph>();
452
453 #region Public Interface
454 /// <summary>Name of the Render Graph.</summary>
455 public string name { get; private set; } = "RenderGraph";
456
457 /// <summary>Request debug data be captured for the provided execution on the next frame.</summary>
458 internal void RequestCaptureDebugData(string executionName)
459 {
460 m_CaptureDebugDataForExecution = executionName;
461 }
462
463 /// <summary>If true, the Render Graph Viewer is active.</summary>
464 public static bool isRenderGraphViewerActive { get; internal set; }
465
466 /// <summary>If true, the Render Graph will run its various validity checks while processing (not considered in release mode).</summary>
467 internal static bool enableValidityChecks { get; private set; }
468
469 /// <summary>
470 /// Set of default resources usable in a pass rendering code.
471 /// </summary>
472 public RenderGraphDefaultResources defaultResources
473 {
474 get
475 {
476 return m_DefaultResources;
477 }
478 }
479
480 /// <summary>
481 /// Render Graph constructor.
482 /// </summary>
483 /// <param name="name">Optional name used to identify the render graph instnace.</param>
484 public RenderGraph(string name = "RenderGraph")
485 {
486 this.name = name;
487 if (GraphicsSettings.TryGetRenderPipelineSettings<RenderGraphGlobalSettings>(out var renderGraphGlobalSettings))
488 {
489 m_EnableCompilationCaching = renderGraphGlobalSettings.enableCompilationCaching;
490 if (m_EnableCompilationCaching)
491 m_CompilationCache = new RenderGraphCompilationCache();
492
493 enableValidityChecks = renderGraphGlobalSettings.enableValidityChecks;
494 }
495 else // No SRP pipeline is present/active, it can happen with unit tests
496 {
497 enableValidityChecks = true;
498 }
499
500 m_TempMRTArrays = new RenderTargetIdentifier[kMaxMRTCount][];
501 for (int i = 0; i < kMaxMRTCount; ++i)
502 m_TempMRTArrays[i] = new RenderTargetIdentifier[i + 1];
503
504 m_Resources = new RenderGraphResourceRegistry(m_DebugParameters, m_FrameInformationLogger);
505 s_RegisteredGraphs.Add(this);
506 onGraphRegistered?.Invoke(this);
507 }
508
509 /// <summary>
510 /// Cleanup the Render Graph.
511 /// </summary>
512 public void Cleanup()
513 {
514 m_Resources.Cleanup();
515 m_DefaultResources.Cleanup();
516 m_RenderGraphPool.Cleanup();
517
518 s_RegisteredGraphs.Remove(this);
519 onGraphUnregistered?.Invoke(this);
520
521 nativeCompiler?.contextData?.Dispose();
522
523 m_CompilationCache?.Clear();
524 }
525
526 internal RenderGraphDebugParams debugParams => m_DebugParameters;
527
528 internal List<DebugUI.Widget> GetWidgetList()
529 {
530 return m_DebugParameters.GetWidgetList(name);
531 }
532
533 internal bool areAnySettingsActive => m_DebugParameters.AreAnySettingsActive;
534
535 /// <summary>
536 /// Register the render graph to the debug window.
537 /// </summary>
538 /// <param name="panel">Optional debug panel to which the render graph debug parameters will be registered.</param>
539 public void RegisterDebug(DebugUI.Panel panel = null)
540 {
541 m_DebugParameters.RegisterDebug(name, panel);
542 }
543
544 /// <summary>
545 /// Unregister render graph from the debug window.
546 /// </summary>
547 public void UnRegisterDebug()
548 {
549 m_DebugParameters.UnRegisterDebug(this.name);
550 }
551
552 /// <summary>
553 /// Get the list of all registered render graphs.
554 /// </summary>
555 /// <returns>The list of all registered render graphs.</returns>
556 public static List<RenderGraph> GetRegisteredRenderGraphs()
557 {
558 return s_RegisteredGraphs;
559 }
560
561 /// <summary>
562 /// Returns the last rendered frame debug data. Can be null if requireDebugData is set to false.
563 /// </summary>
564 /// <returns>The last rendered frame debug data</returns>
565 internal DebugData GetDebugData(string executionName)
566 {
567 if (m_DebugData.TryGetValue(executionName, out var debugData))
568 return debugData;
569
570 return null;
571 }
572
573 /// <summary>
574 /// End frame processing. Purge resources that have been used since last frame and resets internal states.
575 /// This need to be called once per frame.
576 /// </summary>
577 public void EndFrame()
578 {
579 m_Resources.PurgeUnusedGraphicsResources();
580
581 if (m_DebugParameters.logFrameInformation)
582 {
583 Debug.Log(m_FrameInformationLogger.GetAllLogs());
584 m_DebugParameters.logFrameInformation = false;
585 }
586 if (m_DebugParameters.logResources)
587 {
588 m_Resources.FlushLogs();
589 m_DebugParameters.logResources = false;
590 }
591 }
592
593 /// <summary>
594 /// Import an external texture to the Render Graph.
595 /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled.
596 /// </summary>
597 /// <param name="rt">External RTHandle that needs to be imported.</param>
598 /// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
599 public TextureHandle ImportTexture(RTHandle rt)
600 {
601 return m_Resources.ImportTexture(rt);
602 }
603
604 /// <summary>
605 /// Import an external texture to the Render Graph.
606 /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled.
607 ///
608 /// Note: RTHandles that wrap RenderTargetIdentifier will fail to import using this overload as render graph can't derive the render texture's properties.
609 /// In that case the overload taking a RenderTargetInfo argument should be used instead.
610 /// </summary>
611 /// <param name="rt">External RTHandle that needs to be imported.</param>
612 /// <param name="importParams">Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware.</param>
613 /// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
614 public TextureHandle ImportTexture(RTHandle rt, ImportResourceParams importParams )
615 {
616 return m_Resources.ImportTexture(rt, importParams);
617 }
618
619
620 /// <summary>
621 /// Import an external texture to the Render Graph. This overload should be used for RTHandles wrapping a RenderTargetIdentifier.
622 /// 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.
623 ///
624 /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled.
625 ///
626 /// 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.
627 /// If this is not the case, the overload of ImportTexture without a RenderTargetInfo argument should be used instead.
628 /// </summary>
629 /// <param name="rt">External RTHandle that needs to be imported.</param>
630 /// <param name="info">The properties of the passed in RTHandle.</param>
631 /// <param name="importParams">Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware.</param>
632 /// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
633 public TextureHandle ImportTexture(RTHandle rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams() )
634 {
635 return m_Resources.ImportTexture(rt, info, importParams);
636 }
637
638 /// <summary>
639 /// Import an external texture to the Render Graph and set the handle as builtin handle. This can only happen from within the graph module
640 /// so it is internal.
641 /// </summary>
642 internal TextureHandle ImportTexture(RTHandle rt, bool isBuiltin)
643 {
644 return m_Resources.ImportTexture(rt, isBuiltin);
645 }
646
647 /// <summary>
648 /// 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.
649 /// </summary>
650 /// <param name="rt">Backbuffer render target identifier.</param>
651 /// <param name="info">The properties of the passed in RTHandle.</param>
652 /// <param name="importParams">Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware.</param>
653 /// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
654 public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams())
655 {
656 return m_Resources.ImportBackbuffer(rt, info, importParams);
657 }
658
659 /// <summary>
660 /// Import the final backbuffer to render graph.
661 /// This function can only be used when nativeRenderPassesEnabled is false.
662 /// </summary>
663 /// <param name="rt">Backbuffer render target identifier.</param>
664 /// <returns>A new TextureHandle that represents the imported texture in the context of this rendergraph.</returns>
665 public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt)
666 {
667 RenderTargetInfo dummy = new RenderTargetInfo();
668 dummy.width = dummy.height = dummy.volumeDepth = dummy.msaaSamples = 1;
669 dummy.format = GraphicsFormat.R8G8B8A8_SRGB;
670 return m_Resources.ImportBackbuffer(rt, dummy, new ImportResourceParams());
671 }
672
673 /// <summary>
674 /// Create a new Render Graph Texture resource.
675 /// </summary>
676 /// <param name="desc">Texture descriptor.</param>
677 /// <returns>A new TextureHandle.</returns>
678 public TextureHandle CreateTexture(in TextureDesc desc)
679 {
680 return m_Resources.CreateTexture(desc);
681 }
682
683 /// <summary>
684 /// Create a new Render Graph Shared Texture resource.
685 /// This texture will be persistent across render graph executions.
686 /// </summary>
687 /// <param name="desc">Creation descriptor of the texture.</param>
688 /// <param name="explicitRelease">Set to true if you want to manage the lifetime of the resource yourself. Otherwise the resource will be released automatically if unused for a time.</param>
689 /// <returns>A new TextureHandle.</returns>
690 public TextureHandle CreateSharedTexture(in TextureDesc desc, bool explicitRelease = false)
691 {
692 if (m_HasRenderGraphBegun)
693 throw new InvalidOperationException("A shared texture can only be created outside of render graph execution.");
694
695 return m_Resources.CreateSharedTexture(desc, explicitRelease);
696 }
697
698 /// <summary>
699 /// Refresh a shared texture with a new descriptor.
700 /// </summary>
701 /// <param name="handle">Shared texture that needs to be updated.</param>
702 /// <param name="desc">New Descriptor for the texture.</param>
703 public void RefreshSharedTextureDesc(TextureHandle handle, in TextureDesc desc)
704 {
705 m_Resources.RefreshSharedTextureDesc(handle, desc);
706 }
707
708 /// <summary>
709 /// Release a Render Graph shared texture resource.
710 /// </summary>
711 /// <param name="texture">The handle to the texture that needs to be release.</param>
712 public void ReleaseSharedTexture(TextureHandle texture)
713 {
714 if (m_HasRenderGraphBegun)
715 throw new InvalidOperationException("A shared texture can only be release outside of render graph execution.");
716
717 m_Resources.ReleaseSharedTexture(texture);
718 }
719
720 /// <summary>
721 /// Create a new Render Graph Texture resource using the descriptor from another texture.
722 /// </summary>
723 /// <param name="texture">Texture from which the descriptor should be used.</param>
724 /// <returns>A new TextureHandle.</returns>
725 public TextureHandle CreateTexture(TextureHandle texture)
726 {
727 return m_Resources.CreateTexture(m_Resources.GetTextureResourceDesc(texture.handle));
728 }
729
730 /// <summary>
731 /// Create a new Render Graph Texture if the passed handle is invalid and use said handle as output.
732 /// If the passed handle is valid, no texture is created.
733 /// </summary>
734 /// <param name="desc">Desc used to create the texture.</param>
735 /// <param name="texture">Texture from which the descriptor should be used.</param>
736 public void CreateTextureIfInvalid(in TextureDesc desc, ref TextureHandle texture)
737 {
738 if (!texture.IsValid())
739 texture = m_Resources.CreateTexture(desc);
740 }
741
742 /// <summary>
743 /// Gets the descriptor of the specified Texture resource.
744 /// </summary>
745 /// <param name="texture">Texture resource from which the descriptor is requested.</param>
746 /// <returns>The input texture descriptor.</returns>
747 public TextureDesc GetTextureDesc(TextureHandle texture)
748 {
749 return m_Resources.GetTextureResourceDesc(texture.handle);
750 }
751
752 /// <summary>
753 /// Gets the descriptor of the specified Texture resource.
754 /// </summary>
755 /// <param name="texture">Texture resource from which the descriptor is requested.</param>
756 /// <returns>The input texture descriptor.</returns>
757 public RenderTargetInfo GetRenderTargetInfo(TextureHandle texture)
758 {
759 RenderTargetInfo info;
760 m_Resources.GetRenderTargetInfo(texture.handle, out info);
761 return info;
762 }
763
764
765 /// <summary>
766 /// Creates a new Renderer List Render Graph resource.
767 /// </summary>
768 /// <param name="desc">Renderer List descriptor.</param>
769 /// <returns>A new RendererListHandle.</returns>
770 public RendererListHandle CreateRendererList(in CoreRendererListDesc desc)
771 {
772 return m_Resources.CreateRendererList(desc);
773 }
774
775 /// <summary>
776 /// Creates a new Renderer List Render Graph resource.
777 /// </summary>
778 /// <param name="desc">Renderer List descriptor.</param>
779 /// <returns>A new RendererListHandle.</returns>
780 public RendererListHandle CreateRendererList(in RendererListParams desc)
781 {
782 return m_Resources.CreateRendererList(desc);
783 }
784
785 /// <summary>
786 /// Creates a new Shadow Renderer List Render Graph resource.
787 /// </summary>
788 /// <param name="shadowDrawingSettings">DrawSettings that describe the shadow drawcall.</param>
789 /// <returns>A new RendererListHandle.</returns>
790 public RendererListHandle CreateShadowRendererList(ref ShadowDrawingSettings shadowDrawingSettings)
791 {
792 return m_Resources.CreateShadowRendererList(m_RenderGraphContext.renderContext, ref shadowDrawingSettings);
793 }
794
795 /// <summary>
796 /// Creates a new Gizmo Renderer List Render Graph resource.
797 /// </summary>
798 /// <param name="camera">The camera that is used for rendering the Gizmo.</param>
799 /// <param name="gizmoSubset">GizmoSubset that specifies whether gizmos render before or after postprocessing for a camera render. </param>
800 /// <returns>A new RendererListHandle.</returns>
801 public RendererListHandle CreateGizmoRendererList(in Camera camera, in GizmoSubset gizmoSubset)
802 {
803 return m_Resources.CreateGizmoRendererList(m_RenderGraphContext.renderContext, camera, gizmoSubset);
804 }
805
806 /// <summary>
807 /// Creates a new UIOverlay Renderer List Render Graph resource.
808 /// </summary>
809 /// <param name="camera">The camera that is used for rendering the full UIOverlay.</param>
810 /// <returns>A new RendererListHandle.</returns>
811 public RendererListHandle CreateUIOverlayRendererList(in Camera camera)
812 {
813 return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, UISubset.All);
814 }
815
816 /// <summary>
817 /// Creates a new UIOverlay Renderer List Render Graph resource.
818 /// </summary>
819 /// <param name="camera">The camera that is used for rendering some subset of the UIOverlay.</param>
820 /// <param name="uiSubset">Enum flag that specifies which subset to render.</param>
821 /// <returns>A new RendererListHandle.</returns>
822 public RendererListHandle CreateUIOverlayRendererList(in Camera camera, in UISubset uiSubset)
823 {
824 return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, uiSubset);
825 }
826
827 /// <summary>
828 /// Creates a new WireOverlay Renderer List Render Graph resource.
829 /// </summary>
830 /// <param name="camera">The camera that is used for rendering the WireOverlay.</param>
831 /// <returns>A new RendererListHandle.</returns>
832 public RendererListHandle CreateWireOverlayRendererList(in Camera camera)
833 {
834 return m_Resources.CreateWireOverlayRendererList(m_RenderGraphContext.renderContext, camera);
835 }
836
837 /// <summary>
838 /// Creates a new Skybox Renderer List Render Graph resource.
839 /// </summary>
840 /// <param name="camera">The camera that is used for rendering the Skybox.</param>
841 /// <returns>A new RendererListHandle.</returns>
842 public RendererListHandle CreateSkyboxRendererList(in Camera camera)
843 {
844 return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera);
845 }
846
847 /// <summary>
848 /// Creates a new Skybox Renderer List Render Graph resource.
849 /// </summary>
850 /// <param name="camera">The camera that is used for rendering the Skybox.</param>
851 /// <param name="projectionMatrix">The projection matrix used during XR rendering of the skybox.</param>
852 /// <param name="viewMatrix">The view matrix used during XR rendering of the skybox.</param>
853 /// <returns>A new RendererListHandle.</returns>
854 public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrix, Matrix4x4 viewMatrix)
855 {
856 return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrix, viewMatrix);
857 }
858
859 /// <summary>
860 /// Creates a new Skybox Renderer List Render Graph resource.
861 /// </summary>
862 /// <param name="camera">The camera that is used for rendering the Skybox.</param>
863 /// <param name="projectionMatrixL">The left eye projection matrix used during Legacy single pass XR rendering of the skybox.</param>
864 /// <param name="viewMatrixL">The left eye view matrix used during Legacy single pass XR rendering of the skybox.</param>
865 /// <param name="projectionMatrixR">The right eye projection matrix used during Legacy single pass XR rendering of the skybox.</param>
866 /// <param name="viewMatrixR">The right eye view matrix used during Legacy single pass XR rendering of the skybox.</param>
867 /// <returns>A new RendererListHandle.</returns>
868 public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrixL, Matrix4x4 viewMatrixL, Matrix4x4 projectionMatrixR, Matrix4x4 viewMatrixR)
869 {
870 return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrixL, viewMatrixL, projectionMatrixR, viewMatrixR);
871 }
872
873 /// <summary>
874 /// Import an external Graphics Buffer to the Render Graph.
875 /// Any pass writing to an imported graphics buffer will be considered having side effects and can't be automatically culled.
876 /// </summary>
877 /// <param name="graphicsBuffer">External Graphics Buffer that needs to be imported.</param>
878 /// <param name="forceRelease">The imported graphics buffer will be released after usage.</param>
879 /// <returns>A new GraphicsBufferHandle.</returns>
880 public BufferHandle ImportBuffer(GraphicsBuffer graphicsBuffer, bool forceRelease = false)
881 {
882 return m_Resources.ImportBuffer(graphicsBuffer, forceRelease);
883 }
884
885 /// <summary>
886 /// Create a new Render Graph Graphics Buffer resource.
887 /// </summary>
888 /// <param name="desc">Graphics Buffer descriptor.</param>
889 /// <returns>A new GraphicsBufferHandle.</returns>
890 public BufferHandle CreateBuffer(in BufferDesc desc)
891 {
892 return m_Resources.CreateBuffer(desc);
893 }
894
895 /// <summary>
896 /// Create a new Render Graph Graphics Buffer resource using the descriptor from another graphics buffer.
897 /// </summary>
898 /// <param name="graphicsBuffer">Graphics Buffer from which the descriptor should be used.</param>
899 /// <returns>A new GraphicsBufferHandle.</returns>
900 public BufferHandle CreateBuffer(in BufferHandle graphicsBuffer)
901 {
902 return m_Resources.CreateBuffer(m_Resources.GetBufferResourceDesc(graphicsBuffer.handle));
903 }
904
905 /// <summary>
906 /// Gets the descriptor of the specified Graphics Buffer resource.
907 /// </summary>
908 /// <param name="graphicsBuffer">Graphics Buffer resource from which the descriptor is requested.</param>
909 /// <returns>The input graphics buffer descriptor.</returns>
910 public BufferDesc GetBufferDesc(in BufferHandle graphicsBuffer)
911 {
912 return m_Resources.GetBufferResourceDesc(graphicsBuffer.handle);
913 }
914
915 /// <summary>
916 /// Import an external RayTracingAccelerationStructure to the Render Graph.
917 /// Any pass writing to (building) an imported RayTracingAccelerationStructure will be considered having side effects and can't be automatically culled.
918 /// </summary>
919 /// <param name="accelStruct">External RayTracingAccelerationStructure that needs to be imported.</param>
920 /// <param name="name">Optional name for identifying the RayTracingAccelerationStructure in the Render Graph.</param>
921 /// <returns>A new RayTracingAccelerationStructureHandle.</returns>
922 public RayTracingAccelerationStructureHandle ImportRayTracingAccelerationStructure(in RayTracingAccelerationStructure accelStruct, string name = null)
923 {
924 return m_Resources.ImportRayTracingAccelerationStructure(accelStruct, name);
925 }
926
927 /// <summary>
928 /// 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.
929 /// </summary>
930 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
931 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
932 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
933 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
934 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
935 /// <returns>A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass.</returns>
936 public IRasterRenderGraphBuilder AddRasterRenderPass<PassData>(string passName, out PassData passData
937#if !CORE_PACKAGE_DOCTOOLS
938 , [CallerFilePath] string file = "",
939 [CallerLineNumber] int line = 0) where PassData : class, new()
940#endif
941 {
942 return AddRasterRenderPass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
943 }
944
945 /// <summary>
946 /// 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.
947 /// </summary>
948 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
949 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
950 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
951 /// <param name="sampler">Profiling sampler used around the pass.</param>
952 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
953 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
954 /// <returns>A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass.</returns>
955 public IRasterRenderGraphBuilder AddRasterRenderPass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
956#if !CORE_PACKAGE_DOCTOOLS
957 ,[CallerFilePath] string file = "",
958 [CallerLineNumber] int line = 0) where PassData : class, new()
959#endif
960 {
961 AddPassDebugMetadata(passName, file, line);
962
963 var renderPass = m_RenderGraphPool.Get<RasterRenderGraphPass<PassData>>();
964 renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Raster, sampler);
965
966 passData = renderPass.data;
967
968 m_RenderPasses.Add(renderPass);
969
970 m_builderInstance.Setup(renderPass, m_Resources, this);
971 return m_builderInstance;
972 }
973
974 /// <summary>
975 /// 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.
976 /// </summary>
977 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
978 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
979 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
980 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
981 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
982 /// <returns>A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass.</returns>
983 public IComputeRenderGraphBuilder AddComputePass<PassData>(string passName, out PassData passData
984#if !CORE_PACKAGE_DOCTOOLS
985 , [CallerFilePath] string file = "",
986 [CallerLineNumber] int line = 0) where PassData : class, new()
987#endif
988 {
989 return AddComputePass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
990 }
991
992 /// <summary>
993 /// Add a new Compute Render Pass to the Render Graph. Compute passes can execute compute workloads but cannot do rasterization.
994 /// </summary>
995 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
996 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
997 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
998 /// <param name="sampler">Profiling sampler used around the pass.</param>
999 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1000 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1001 /// <returns>A new instance of a IComputeRenderGraphBuilder used to setup the new Compute Render Pass.</returns>
1002 public IComputeRenderGraphBuilder AddComputePass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
1003#if !CORE_PACKAGE_DOCTOOLS
1004 ,[CallerFilePath] string file = "",
1005 [CallerLineNumber] int line = 0) where PassData : class, new()
1006#endif
1007 {
1008 AddPassDebugMetadata(passName, file, line);
1009
1010 var renderPass = m_RenderGraphPool.Get<ComputeRenderGraphPass<PassData>>();
1011 renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Compute, sampler);
1012
1013 passData = renderPass.data;
1014
1015 m_RenderPasses.Add(renderPass);
1016
1017 m_builderInstance.Setup(renderPass, m_Resources, this);
1018 return m_builderInstance;
1019 }
1020
1021
1022 /// <summary>
1023 /// Add a new Unsafe Render Pass to the Render Graph. Unsafe passes can do certain operations compute/raster render passes cannot do and have
1024 /// access to the full command buffer API. The unsafe API should be used sparingly as it has the following downsides:
1025 /// - Limited automatic validation of the commands and resource dependencies. The user is responsible to ensure that all dependencies are correctly declared.
1026 /// - All native render passes will be serialized out.
1027 /// - In the future the render graph compiler may generate a sub-optimal command stream for unsafe passes.
1028 /// When using a unsafe pass the graph will also not automatically set up graphics state like rendertargets. The pass should do this itself
1029 /// using cmd.SetRenderTarget and related commands.
1030 /// </summary>
1031 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
1032 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
1033 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
1034 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1035 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1036 /// <returns>A new instance of a IUnsafeRenderGraphBuilder used to setup the new Unsafe Render Pass.</returns>
1037 public IUnsafeRenderGraphBuilder AddUnsafePass<PassData>(string passName, out PassData passData
1038#if !CORE_PACKAGE_DOCTOOLS
1039 , [CallerFilePath] string file = "",
1040 [CallerLineNumber] int line = 0) where PassData : class, new()
1041#endif
1042 {
1043 return AddUnsafePass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
1044 }
1045
1046
1047 /// <summary>
1048 /// Add a new unsafe Render Pass to the Render Graph. Unsafe passes can do certain operations compute/raster render passes cannot do and have
1049 /// access to the full command buffer API. The unsafe API should be used sparingly as it has the following downsides:
1050 /// - Limited automatic validation of the commands and resource dependencies. The user is responsible to ensure that all dependencies are correctly declared.
1051 /// - All native render passes will be serialized out.
1052 /// - In the future the render graph compiler may generate a sub-optimal command stream for unsafe passes.
1053 /// When using an unsafe pass the graph will also not automatically set up graphics state like rendertargets. The pass should do this itself
1054 /// using cmd.SetRenderTarget and related commands.
1055 /// </summary>
1056 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
1057 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
1058 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
1059 /// <param name="sampler">Profiling sampler used around the pass.</param>
1060 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1061 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1062 /// <returns>A new instance of a IUnsafeRenderGraphBuilder used to setup the new unsafe Render Pass.</returns>
1063 public IUnsafeRenderGraphBuilder AddUnsafePass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
1064#if !CORE_PACKAGE_DOCTOOLS
1065 , [CallerFilePath] string file = "",
1066 [CallerLineNumber] int line = 0) where PassData : class, new()
1067#endif
1068 {
1069 AddPassDebugMetadata(passName, file, line);
1070
1071 var renderPass = m_RenderGraphPool.Get<UnsafeRenderGraphPass<PassData>>();
1072 renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Unsafe, sampler);
1073 renderPass.AllowGlobalState(true);
1074
1075 passData = renderPass.data;
1076
1077 m_RenderPasses.Add(renderPass);
1078
1079 m_builderInstance.Setup(renderPass, m_Resources, this);
1080 return m_builderInstance;
1081 }
1082
1083 /// <summary>
1084 /// Add a new Render Pass to the Render Graph.
1085 /// </summary>
1086 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
1087 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
1088 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
1089 /// <param name="sampler">Profiling sampler used around the pass.</param>
1090 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1091 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1092 /// <returns>A new instance of a RenderGraphBuilder used to setup the new Render Pass.</returns>
1093 public RenderGraphBuilder AddRenderPass<PassData>(string passName, out PassData passData, ProfilingSampler sampler
1094#if !CORE_PACKAGE_DOCTOOLS
1095 ,[CallerFilePath] string file = "",
1096 [CallerLineNumber] int line = 0) where PassData : class, new()
1097#endif
1098 {
1099 AddPassDebugMetadata(passName, file, line);
1100
1101 var renderPass = m_RenderGraphPool.Get<RenderGraphPass<PassData>>();
1102 renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get<PassData>(), passName, RenderGraphPassType.Legacy, sampler);
1103 renderPass.AllowGlobalState(true);// Old pass types allow global state by default as HDRP relies on it
1104
1105 passData = renderPass.data;
1106
1107 m_RenderPasses.Add(renderPass);
1108
1109 return new RenderGraphBuilder(renderPass, m_Resources, this);
1110 }
1111
1112 /// <summary>
1113 /// Add a new Render Pass to the Render Graph.
1114 /// </summary>
1115 /// <typeparam name="PassData">Type of the class to use to provide data to the Render Pass.</typeparam>
1116 /// <param name="passName">Name of the new Render Pass (this is also be used to generate a GPU profiling marker).</param>
1117 /// <param name="passData">Instance of PassData that is passed to the render function and you must fill.</param>
1118 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1119 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1120 /// <returns>A new instance of a RenderGraphBuilder used to setup the new Render Pass.</returns>
1121 public RenderGraphBuilder AddRenderPass<PassData>(string passName, out PassData passData
1122#if !CORE_PACKAGE_DOCTOOLS
1123 ,[CallerFilePath] string file = "",
1124 [CallerLineNumber] int line = 0) where PassData : class, new()
1125#endif
1126 {
1127 return AddRenderPass(passName, out passData, GetDefaultProfilingSampler(passName), file, line);
1128 }
1129
1130 /// <summary>
1131 /// Starts the recording of the render graph.
1132 /// This must be called before adding any pass to the render graph.
1133 /// </summary>
1134 /// <param name="parameters">Parameters necessary for the render graph execution.</param>
1135 /// <example>
1136 /// <para>Begin recording the Render Graph.</para>
1137 /// <code>
1138 /// renderGraph.BeginRecording(parameters)
1139 /// // Add your render graph passes here.
1140 /// renderGraph.EndRecordingAndExecute()
1141 /// </code>
1142 /// </example>
1143 public void BeginRecording(in RenderGraphParameters parameters)
1144 {
1145 m_CurrentFrameIndex = parameters.currentFrameIndex;
1146 m_CurrentExecutionName = parameters.executionName != null ? parameters.executionName : "RenderGraphExecution";
1147 m_HasRenderGraphBegun = true;
1148 // Cannot do renderer list culling with compilation caching because it happens after compilation is done so it can lead to discrepancies.
1149 m_RendererListCulling = parameters.rendererListCulling && !m_EnableCompilationCaching;
1150
1151 m_Resources.BeginRenderGraph(m_ExecutionCount++);
1152
1153 if (m_DebugParameters.enableLogging)
1154 {
1155 m_FrameInformationLogger.Initialize(m_CurrentExecutionName);
1156 }
1157
1158 m_DefaultResources.InitializeForRendering(this);
1159
1160 m_RenderGraphContext.cmd = parameters.commandBuffer;
1161 m_RenderGraphContext.renderContext = parameters.scriptableRenderContext;
1162 m_RenderGraphContext.contextlessTesting = parameters.invalidContextForTesting;
1163 m_RenderGraphContext.renderGraphPool = m_RenderGraphPool;
1164 m_RenderGraphContext.defaultResources = m_DefaultResources;
1165
1166 if (m_DebugParameters.immediateMode)
1167 {
1168 UpdateCurrentCompiledGraph(graphHash: -1, forceNoCaching: true);
1169
1170 LogFrameInformation();
1171
1172 // Prepare the list of compiled pass info for immediate mode.
1173 // Conservative resize because we don't know how many passes there will be.
1174 // We might still need to grow the array later on anyway if it's not enough.
1175 m_CurrentCompiledGraph.compiledPassInfos.Resize(m_CurrentCompiledGraph.compiledPassInfos.capacity);
1176 m_CurrentImmediatePassIndex = 0;
1177
1178 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
1179 {
1180 if (m_ImmediateModeResourceList[i] == null)
1181 m_ImmediateModeResourceList[i] = new List<int>();
1182
1183 m_ImmediateModeResourceList[i].Clear();
1184 }
1185
1186 m_Resources.BeginExecute(m_CurrentFrameIndex);
1187 }
1188 }
1189
1190 /// <summary>
1191 /// Ends the recording and executes the render graph.
1192 /// This must be called once all passes have been added to the render graph.
1193 /// </summary>
1194 public void EndRecordingAndExecute()
1195 {
1196 Execute();
1197 }
1198
1199 /// <summary>
1200 /// Execute the Render Graph in its current state.
1201 /// </summary>
1202 internal void Execute()
1203 {
1204 m_ExecutionExceptionWasRaised = false;
1205
1206 try
1207 {
1208#if DEVELOPMENT_BUILD || UNITY_EDITOR
1209 if (m_RenderGraphContext.cmd == null)
1210 throw new InvalidOperationException("RenderGraph.BeginRecording was not called before executing the render graph.");
1211#endif
1212 if (!m_DebugParameters.immediateMode)
1213 {
1214 LogFrameInformation();
1215
1216 int graphHash = 0;
1217 if (m_EnableCompilationCaching)
1218 graphHash = ComputeGraphHash();
1219
1220 if (nativeRenderPassesEnabled)
1221 CompileNativeRenderGraph(graphHash);
1222 else
1223 CompileRenderGraph(graphHash);
1224
1225 m_Resources.BeginExecute(m_CurrentFrameIndex);
1226
1227#if UNITY_EDITOR
1228 // Feeding Render Graph Viewer before resource deallocation at pass execution
1229 GenerateDebugData();
1230#endif
1231
1232 if (nativeRenderPassesEnabled)
1233 ExecuteNativeRenderGraph();
1234 else
1235 ExecuteRenderGraph();
1236
1237 // Clear the shader bindings for all global textures to make sure bindings don't leak outside the graph
1238 ClearGlobalBindings();
1239 }
1240 }
1241 catch (Exception e)
1242 {
1243 if (m_RenderGraphContext.contextlessTesting)
1244 {
1245 // Throw it for the tests to handle
1246 throw;
1247 }
1248 else
1249 {
1250 // If we're not testing log the exception and swallow it.
1251 // TODO: Do we really want to swallow exceptions here? Not a very c# thing to do.
1252 Debug.LogError("Render Graph Execution error");
1253 if (!m_ExecutionExceptionWasRaised) // Already logged. TODO: There is probably a better way in C# to handle that.
1254 Debug.LogException(e);
1255 m_ExecutionExceptionWasRaised = true;
1256 }
1257 }
1258 finally
1259 {
1260 if (m_DebugParameters.immediateMode)
1261 ReleaseImmediateModeResources();
1262
1263 ClearCompiledGraph(m_CurrentCompiledGraph, m_EnableCompilationCaching);
1264
1265 m_Resources.EndExecute();
1266
1267 InvalidateContext();
1268
1269 m_HasRenderGraphBegun = false;
1270 }
1271 }
1272
1273 class ProfilingScopePassData
1274 {
1275 public ProfilingSampler sampler;
1276 }
1277
1278 const string k_BeginProfilingSamplerPassName = "BeginProfile";
1279 const string k_EndProfilingSamplerPassName = "EndProfile";
1280
1281 /// <summary>
1282 /// Begin a profiling scope.
1283 /// </summary>
1284 /// <param name="sampler">Sampler used for profiling.</param>
1285 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1286 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1287 public void BeginProfilingSampler(ProfilingSampler sampler,
1288 [CallerFilePath] string file = "",
1289 [CallerLineNumber] int line = 0)
1290 {
1291 if (sampler == null)
1292 return;
1293
1294 using (var builder = AddRenderPass<ProfilingScopePassData>(k_BeginProfilingSamplerPassName, out var passData, (ProfilingSampler)null, file, line))
1295 {
1296 passData.sampler = sampler;
1297 builder.AllowPassCulling(false);
1298 builder.GenerateDebugData(false);
1299 builder.SetRenderFunc((ProfilingScopePassData data, RenderGraphContext ctx) =>
1300 {
1301 data.sampler.Begin(ctx.cmd);
1302 });
1303 }
1304 }
1305
1306 /// <summary>
1307 /// End a profiling scope.
1308 /// </summary>
1309 /// <param name="sampler">Sampler used for profiling.</param>
1310 /// <param name="file">File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1311 /// <param name="line">File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it.</param>
1312 public void EndProfilingSampler(ProfilingSampler sampler,
1313 [CallerFilePath] string file = "",
1314 [CallerLineNumber] int line = 0)
1315 {
1316 if (sampler == null)
1317 return;
1318
1319 using (var builder = AddRenderPass<ProfilingScopePassData>(k_EndProfilingSamplerPassName, out var passData, (ProfilingSampler)null, file, line))
1320 {
1321 passData.sampler = sampler;
1322 builder.AllowPassCulling(false);
1323 builder.GenerateDebugData(false);
1324 builder.SetRenderFunc((ProfilingScopePassData data, RenderGraphContext ctx) =>
1325 {
1326 data.sampler.End(ctx.cmd);
1327 });
1328 }
1329 }
1330
1331 #endregion
1332
1333 #region Internal Interface
1334 // Internal for testing purpose only
1335 internal DynamicArray<CompiledPassInfo> GetCompiledPassInfos() { return m_CurrentCompiledGraph.compiledPassInfos; }
1336
1337 // Internal for testing purpose only
1338 internal void ClearCompiledGraph()
1339 {
1340 ClearCompiledGraph(m_CurrentCompiledGraph, false);
1341 }
1342
1343 void ClearCompiledGraph(CompiledGraph compiledGraph, bool useCompilationCaching)
1344 {
1345 ClearRenderPasses();
1346 m_Resources.Clear(m_ExecutionExceptionWasRaised);
1347 m_RendererLists.Clear();
1348 registeredGlobals.Clear();
1349
1350 // When using compilation caching, we need to keep alive the result of the compiled graph.
1351 if (!useCompilationCaching)
1352 {
1353 if (!nativeRenderPassesEnabled)
1354 compiledGraph?.Clear();
1355 }
1356 }
1357
1358 void InvalidateContext()
1359 {
1360 m_RenderGraphContext.cmd = null;
1361 m_RenderGraphContext.renderGraphPool = null;
1362 m_RenderGraphContext.defaultResources = null;
1363 }
1364
1365 internal void OnPassAdded(RenderGraphPass pass)
1366 {
1367 if (m_DebugParameters.immediateMode)
1368 {
1369 ExecutePassImmediately(pass);
1370 }
1371 }
1372
1373 internal delegate void OnGraphRegisteredDelegate(RenderGraph graph);
1374 internal static event OnGraphRegisteredDelegate onGraphRegistered;
1375 internal static event OnGraphRegisteredDelegate onGraphUnregistered;
1376 internal delegate void OnExecutionRegisteredDelegate(RenderGraph graph, string executionName);
1377 internal static event OnExecutionRegisteredDelegate onExecutionRegistered;
1378 internal static event OnExecutionRegisteredDelegate onExecutionUnregistered;
1379 internal static event Action onDebugDataCaptured;
1380
1381 #endregion
1382
1383 #region Private Interface
1384
1385 // Internal for testing purpose only.
1386 internal int ComputeGraphHash()
1387 {
1388 using (new ProfilingScope(ProfilingSampler.Get(RenderGraphProfileId.ComputeHashRenderGraph)))
1389 {
1390 var hash128 = HashFNV1A32.Create();
1391 for (int i = 0; i < m_RenderPasses.Count; ++i)
1392 m_RenderPasses[i].ComputeHash(ref hash128, m_Resources);
1393
1394 return hash128.value;
1395 }
1396 }
1397
1398 void CountReferences()
1399 {
1400 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1401 var compiledResourceInfo = m_CurrentCompiledGraph.compiledResourcesInfos;
1402
1403 for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
1404 {
1405 RenderGraphPass pass = m_RenderPasses[passIndex];
1406 ref CompiledPassInfo passInfo = ref compiledPassInfo[passIndex];
1407
1408 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
1409 {
1410 var resourceRead = pass.resourceReadLists[type];
1411 foreach (var resource in resourceRead)
1412 {
1413 ref CompiledResourceInfo info = ref compiledResourceInfo[type][resource.index];
1414 info.imported = m_Resources.IsRenderGraphResourceImported(resource);
1415 info.consumers.Add(passIndex);
1416 info.refCount++;
1417
1418#if DEVELOPMENT_BUILD || UNITY_EDITOR
1419 passInfo.debugResourceReads[type].Add(m_Resources.GetRenderGraphResourceName(resource));
1420#endif
1421 }
1422
1423 var resourceWrite = pass.resourceWriteLists[type];
1424 foreach (var resource in resourceWrite)
1425 {
1426 ref CompiledResourceInfo info = ref compiledResourceInfo[type][resource.index];
1427 info.imported = m_Resources.IsRenderGraphResourceImported(resource);
1428 info.producers.Add(passIndex);
1429
1430 // 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.
1431 passInfo.hasSideEffect = info.imported;
1432 passInfo.refCount++;
1433
1434#if DEVELOPMENT_BUILD || UNITY_EDITOR
1435 passInfo.debugResourceWrites[type].Add(m_Resources.GetRenderGraphResourceName(resource));
1436#endif
1437 }
1438
1439 foreach (var resource in pass.transientResourceList[type])
1440 {
1441 ref CompiledResourceInfo info = ref compiledResourceInfo[type][resource.index];
1442 info.refCount++;
1443 info.consumers.Add(passIndex);
1444 info.producers.Add(passIndex);
1445 }
1446 }
1447 }
1448 }
1449
1450 void CullUnusedPasses()
1451 {
1452 if (m_DebugParameters.disablePassCulling)
1453 {
1454 if (m_DebugParameters.enableLogging)
1455 {
1456 m_FrameInformationLogger.LogLine("- Pass Culling Disabled -\n");
1457 }
1458 return;
1459 }
1460
1461 // This will cull all passes that produce resource that are never read.
1462 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
1463 {
1464 DynamicArray<CompiledResourceInfo> resourceUsageList = m_CurrentCompiledGraph.compiledResourcesInfos[type];
1465
1466 // Gather resources that are never read.
1467 m_CullingStack.Clear();
1468 for (int i = 1; i < resourceUsageList.size; ++i) // 0 == null resource skip it
1469 {
1470 if (resourceUsageList[i].refCount == 0)
1471 {
1472 m_CullingStack.Push(i);
1473 }
1474 }
1475
1476 while (m_CullingStack.Count != 0)
1477 {
1478 var unusedResource = resourceUsageList[m_CullingStack.Pop()];
1479 foreach (var producerIndex in unusedResource.producers)
1480 {
1481 ref var producerInfo = ref m_CurrentCompiledGraph.compiledPassInfos[producerIndex];
1482 var producerPass = m_RenderPasses[producerIndex];
1483 producerInfo.refCount--;
1484 if (producerInfo.refCount == 0 && !producerInfo.hasSideEffect && producerInfo.allowPassCulling)
1485 {
1486 // Producer is not necessary anymore as it produces zero resources
1487 // Cull it and decrement refCount of all the textures it reads.
1488 producerInfo.culled = true;
1489
1490 foreach (var resource in producerPass.resourceReadLists[type])
1491 {
1492 ref CompiledResourceInfo resourceInfo = ref resourceUsageList[resource.index];
1493 resourceInfo.refCount--;
1494 // If a resource is not used anymore, add it to the stack to be processed in subsequent iteration.
1495 if (resourceInfo.refCount == 0)
1496 m_CullingStack.Push(resource.index);
1497 }
1498 }
1499 }
1500 }
1501 }
1502
1503 LogCulledPasses();
1504 }
1505
1506 void UpdatePassSynchronization(ref CompiledPassInfo currentPassInfo, ref CompiledPassInfo producerPassInfo, int currentPassIndex, int lastProducer, ref int intLastSyncIndex)
1507 {
1508 // Current pass needs to wait for pass index lastProducer
1509 currentPassInfo.syncToPassIndex = lastProducer;
1510 // Update latest pass waiting for the other pipe.
1511 intLastSyncIndex = lastProducer;
1512
1513 // Producer will need a graphics fence that this pass will wait on.
1514 producerPassInfo.needGraphicsFence = true;
1515 // We update the producer pass with the index of the smallest pass waiting for it.
1516 // This will be used to "lock" resource from being reused until the pipe has been synchronized.
1517 if (producerPassInfo.syncFromPassIndex == -1)
1518 producerPassInfo.syncFromPassIndex = currentPassIndex;
1519 }
1520
1521 void UpdateResourceSynchronization(ref int lastGraphicsPipeSync, ref int lastComputePipeSync, int currentPassIndex, in CompiledResourceInfo resource)
1522 {
1523 int lastProducer = GetLatestProducerIndex(currentPassIndex, resource);
1524 if (lastProducer != -1)
1525 {
1526 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1527 ref CompiledPassInfo currentPassInfo = ref compiledPassInfo[currentPassIndex];
1528
1529 //If the passes are on different pipes, we need synchronization.
1530 if (m_CurrentCompiledGraph.compiledPassInfos[lastProducer].enableAsyncCompute != currentPassInfo.enableAsyncCompute)
1531 {
1532 // Pass is on compute pipe, need sync with graphics pipe.
1533 if (currentPassInfo.enableAsyncCompute)
1534 {
1535 if (lastProducer > lastGraphicsPipeSync)
1536 {
1537 UpdatePassSynchronization(ref currentPassInfo, ref compiledPassInfo[lastProducer], currentPassIndex, lastProducer, ref lastGraphicsPipeSync);
1538 }
1539 }
1540 else
1541 {
1542 if (lastProducer > lastComputePipeSync)
1543 {
1544 UpdatePassSynchronization(ref currentPassInfo, ref compiledPassInfo[lastProducer], currentPassIndex, lastProducer, ref lastComputePipeSync);
1545 }
1546 }
1547 }
1548 }
1549 }
1550
1551 int GetFirstValidConsumerIndex(int passIndex, in CompiledResourceInfo info)
1552 {
1553 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1554 // We want to know the lowest pass index after the current pass that reads from the resource.
1555 foreach (int consumer in info.consumers)
1556 {
1557 // consumers are by construction in increasing order.
1558 if (consumer > passIndex && !compiledPassInfo[consumer].culled)
1559 return consumer;
1560 }
1561
1562 return -1;
1563 }
1564
1565 int FindTextureProducer(int consumerPass, in CompiledResourceInfo info, out int index)
1566 {
1567 // We check all producers before the consumerPass. The first one not culled will be the one allocating the resource
1568 // If they are all culled, we need to get the one right before the consumer, it will allocate or reuse the resource
1569 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1570 int previousPass = 0;
1571 for (index = 0; index < info.producers.Count; index++)
1572 {
1573 int currentPass = info.producers[index];
1574 // We found a valid producer - he will allocate the texture
1575 if (!compiledPassInfo[currentPass].culled)
1576 return currentPass;
1577 // We reached consumer pass, return last producer even if it's culled
1578 if (currentPass >= consumerPass)
1579 return previousPass;
1580 previousPass = currentPass;
1581 }
1582
1583 return previousPass;
1584 }
1585
1586 int GetLatestProducerIndex(int passIndex, in CompiledResourceInfo info)
1587 {
1588 // We want to know the highest pass index below the current pass that writes to the resource.
1589 int result = -1;
1590 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1591 foreach (var producer in info.producers)
1592 {
1593 var producerPassInfo = compiledPassInfo[producer];
1594 // producers are by construction in increasing order.
1595 // 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).
1596 if (producer < passIndex && !(producerPassInfo.culled || producerPassInfo.culledByRendererList))
1597 result = producer;
1598 else
1599 return result;
1600 }
1601
1602 return result;
1603 }
1604
1605 int GetLatestValidReadIndex(in CompiledResourceInfo info)
1606 {
1607 if (info.consumers.Count == 0)
1608 return -1;
1609
1610 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1611 var consumers = info.consumers;
1612 for (int i = consumers.Count - 1; i >= 0; --i)
1613 {
1614 if (!compiledPassInfo[consumers[i]].culled)
1615 return consumers[i];
1616 }
1617
1618 return -1;
1619 }
1620
1621 int GetFirstValidWriteIndex(in CompiledResourceInfo info)
1622 {
1623 if (info.producers.Count == 0)
1624 return -1;
1625
1626 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1627 var producers = info.producers;
1628 for (int i = 0; i < producers.Count; i++)
1629 {
1630 if (!compiledPassInfo[producers[i]].culled)
1631 return producers[i];
1632 }
1633
1634 return -1;
1635 }
1636
1637 int GetLatestValidWriteIndex(in CompiledResourceInfo info)
1638 {
1639 if (info.producers.Count == 0)
1640 return -1;
1641
1642 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1643 var producers = info.producers;
1644 for (int i = producers.Count - 1; i >= 0; --i)
1645 {
1646 if (!compiledPassInfo[producers[i]].culled)
1647 return producers[i];
1648 }
1649
1650 return -1;
1651 }
1652
1653 void CreateRendererLists()
1654 {
1655 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1656 for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
1657 {
1658 ref CompiledPassInfo passInfo = ref compiledPassInfo[passIndex];
1659
1660 if (passInfo.culled)
1661 continue;
1662
1663 // Gather all renderer lists
1664 m_RendererLists.AddRange(m_RenderPasses[passInfo.index].usedRendererListList);
1665 }
1666
1667 // Anything related to renderer lists needs a real context to be able to use/test it
1668 Debug.Assert(m_RendererLists.Count == 0 || m_RenderGraphContext.contextlessTesting == false);
1669
1670 // Creates all renderer lists
1671 m_Resources.CreateRendererLists(m_RendererLists, m_RenderGraphContext.renderContext, m_RendererListCulling);
1672 }
1673
1674 internal bool GetImportedFallback(TextureDesc desc, out TextureHandle fallback)
1675 {
1676 fallback = TextureHandle.nullHandle;
1677
1678 // We don't have any fallback texture with MSAA
1679 if (!desc.bindTextureMS)
1680 {
1681 if (desc.depthBufferBits != DepthBits.None)
1682 {
1683 fallback = defaultResources.whiteTexture;
1684 }
1685 else if (desc.clearColor == Color.black || desc.clearColor == default)
1686 {
1687 if (desc.dimension == TextureXR.dimension)
1688 fallback = defaultResources.blackTextureXR;
1689 else if (desc.dimension == TextureDimension.Tex3D)
1690 fallback = defaultResources.blackTexture3DXR;
1691 else if (desc.dimension == TextureDimension.Tex2D)
1692 fallback = defaultResources.blackTexture;
1693 }
1694 else if (desc.clearColor == Color.white)
1695 {
1696 if (desc.dimension == TextureXR.dimension)
1697 fallback = defaultResources.whiteTextureXR;
1698 else if (desc.dimension == TextureDimension.Tex2D)
1699 fallback = defaultResources.whiteTexture;
1700 }
1701 }
1702
1703 return fallback.IsValid();
1704 }
1705
1706 void AllocateCulledPassResources(ref CompiledPassInfo passInfo)
1707 {
1708 var passIndex = passInfo.index;
1709 var pass = m_RenderPasses[passIndex];
1710
1711 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
1712 {
1713 var resourcesInfo = m_CurrentCompiledGraph.compiledResourcesInfos[type];
1714 foreach (var resourceHandle in pass.resourceWriteLists[type])
1715 {
1716 ref var compiledResource = ref resourcesInfo[resourceHandle.index];
1717
1718 // Check if there is a valid consumer and no other valid producer
1719 int consumerPass = GetFirstValidConsumerIndex(passIndex, compiledResource);
1720 int producerPass = FindTextureProducer(consumerPass, compiledResource, out int index);
1721 if (consumerPass != -1 && passIndex == producerPass)
1722 {
1723 if (type == (int)RenderGraphResourceType.Texture)
1724 {
1725 // Try to transform into an imported resource - for some textures, this will save an allocation
1726 // We have a way to disable the fallback, because we can't fallback to RenderTexture and sometimes it's necessary (eg. SampleCopyChannel_xyzw2x)
1727 var textureResource = m_Resources.GetTextureResource(resourceHandle);
1728 if (!textureResource.desc.disableFallBackToImportedTexture && GetImportedFallback(textureResource.desc, out var fallback))
1729 {
1730 compiledResource.imported = true;
1731 textureResource.imported = true;
1732 textureResource.graphicsResource = m_Resources.GetTexture(fallback);
1733 continue;
1734 }
1735
1736 textureResource.desc.sizeMode = TextureSizeMode.Explicit;
1737 textureResource.desc.width = 1;
1738 textureResource.desc.height = 1;
1739 textureResource.desc.clearBuffer = true;
1740 }
1741
1742 // Delegate resource allocation to the consumer
1743 compiledResource.producers[index - 1] = consumerPass;
1744 }
1745 }
1746 }
1747 }
1748
1749 void UpdateResourceAllocationAndSynchronization()
1750 {
1751 int lastGraphicsPipeSync = -1;
1752 int lastComputePipeSync = -1;
1753
1754 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1755 var compiledResourceInfo = m_CurrentCompiledGraph.compiledResourcesInfos;
1756
1757 // First go through all passes.
1758 // - Update the last pass read index for each resource.
1759 // - Add texture to creation list for passes that first write to a texture.
1760 // - Update synchronization points for all resources between compute and graphics pipes.
1761 for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
1762 {
1763 ref CompiledPassInfo passInfo = ref compiledPassInfo[passIndex];
1764
1765 // If this pass is culled, we need to make sure that any texture read by a later pass is still allocated
1766 // We also try to find an imported fallback to save an allocation
1767 if (passInfo.culledByRendererList)
1768 AllocateCulledPassResources(ref passInfo);
1769
1770 if (passInfo.culled)
1771 continue;
1772
1773 var pass = m_RenderPasses[passInfo.index];
1774
1775 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
1776 {
1777 var resourcesInfo = compiledResourceInfo[type];
1778 foreach (var resource in pass.resourceReadLists[type])
1779 {
1780 UpdateResourceSynchronization(ref lastGraphicsPipeSync, ref lastComputePipeSync, passIndex, resourcesInfo[resource.index]);
1781 }
1782
1783 foreach (var resource in pass.resourceWriteLists[type])
1784 {
1785 UpdateResourceSynchronization(ref lastGraphicsPipeSync, ref lastComputePipeSync, passIndex, resourcesInfo[resource.index]);
1786 }
1787 }
1788 }
1789
1790 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
1791 {
1792 var resourceInfos = compiledResourceInfo[type];
1793
1794 // Now push resources to the release list of the pass that reads it last.
1795 for (int i = 1; i < resourceInfos.size; ++i) // 0 == null resource skip it
1796 {
1797 CompiledResourceInfo resourceInfo = resourceInfos[i];
1798
1799 bool sharedResource = m_Resources.IsRenderGraphResourceShared((RenderGraphResourceType)type, i);
1800 bool forceRelease = m_Resources.IsRenderGraphResourceForceReleased((RenderGraphResourceType) type, i);
1801
1802 // Imported resource needs neither creation nor release.
1803 if (resourceInfo.imported && !sharedResource && !forceRelease)
1804 continue;
1805
1806 // Resource creation
1807 int firstWriteIndex = GetFirstValidWriteIndex(resourceInfo);
1808 // 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)
1809 // Or when the only pass that was writting to this resource was culled dynamically by renderer lists
1810 if (firstWriteIndex != -1)
1811 compiledPassInfo[firstWriteIndex].resourceCreateList[type].Add(i);
1812
1813 var latestValidReadIndex = GetLatestValidReadIndex(resourceInfo);
1814 var latestValidWriteIndex = GetLatestValidWriteIndex(resourceInfo);
1815
1816 // Sometimes, a texture can be written by a pass after the last pass that reads it.
1817 // In this case, we need to extend its lifetime to this pass otherwise the pass would get an invalid texture.
1818 // This is exhibited in cases where a pass might produce more than one output and one of them isn't used.
1819 // Ex: Transparent pass in HDRP that writes to the color buffer and motion vectors.
1820 // If TAA/MotionBlur aren't used, the movecs are never read after the transparent pass and it would raise this error.
1821 // Because of that, it's hard to make this an actual error.
1822 // Commented out code to check such cases if needed.
1823 //if (latestValidReadIndex != -1 && (latestValidWriteIndex > latestValidReadIndex))
1824 //{
1825 // var name = m_Resources.GetRenderGraphResourceName((RenderGraphResourceType)type, i);
1826 // var lastPassReadName = m_CompiledPassInfos[latestValidReadIndex].pass.name;
1827 // var lastPassWriteName = m_CompiledPassInfos[latestValidWriteIndex].pass.name;
1828 // Debug.LogError($"Resource {name} is written again after the last pass that reads it.\nLast pass read: {lastPassReadName}\nLast pass write: {lastPassWriteName}");
1829 //}
1830
1831 // For not imported resources, make sure we don't try to release them if they were never created (due to culling).
1832 bool shouldRelease = !(firstWriteIndex == -1 && !resourceInfo.imported);
1833 int lastReadPassIndex = shouldRelease ? Math.Max(latestValidWriteIndex, latestValidReadIndex) : -1;
1834
1835 // Texture release
1836 if (lastReadPassIndex != -1)
1837 {
1838 // 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.
1839 // 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.
1840 if (compiledPassInfo[lastReadPassIndex].enableAsyncCompute)
1841 {
1842 int currentPassIndex = lastReadPassIndex;
1843 int firstWaitingPassIndex = compiledPassInfo[currentPassIndex].syncFromPassIndex;
1844 // Find the first async pass that is synchronized by the graphics pipeline (ie: passInfo.syncFromPassIndex != -1)
1845 while (firstWaitingPassIndex == -1 && currentPassIndex++ < compiledPassInfo.size - 1)
1846 {
1847 if (compiledPassInfo[currentPassIndex].enableAsyncCompute)
1848 firstWaitingPassIndex = compiledPassInfo[currentPassIndex].syncFromPassIndex;
1849 }
1850
1851 // Fail safe in case render graph is badly formed.
1852 if (currentPassIndex == compiledPassInfo.size)
1853 {
1854 // 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.
1855 // 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.
1856 if (compiledPassInfo[lastReadPassIndex].hasSideEffect)
1857 {
1858 firstWaitingPassIndex = currentPassIndex;
1859 }
1860 else
1861 {
1862 RenderGraphPass invalidPass = m_RenderPasses[lastReadPassIndex];
1863
1864 var resName = "<unknown>";
1865#if DEVELOPMENT_BUILD || UNITY_EDITOR
1866 resName = m_Resources.GetRenderGraphResourceName((RenderGraphResourceType)type, i);
1867#endif
1868 var msg = $"{(RenderGraphResourceType)type} resource '{resName}' in asynchronous pass '{invalidPass.name}' is missing synchronization on the graphics pipeline.";
1869 throw new InvalidOperationException(msg);
1870 }
1871 }
1872
1873 // Finally add the release command to the pass before the first pass that waits for the compute pipe.
1874 var releasePassIndex = Math.Max(0, firstWaitingPassIndex - 1);
1875
1876 // Check to ensure that we do not release resources on a culled pass (causes a leak otherwise).
1877 while (compiledPassInfo[releasePassIndex].culled)
1878 releasePassIndex = Math.Max(0, releasePassIndex - 1);
1879
1880 ref CompiledPassInfo passInfo = ref compiledPassInfo[releasePassIndex];
1881 passInfo.resourceReleaseList[type].Add(i);
1882 }
1883 else
1884 {
1885 ref CompiledPassInfo passInfo = ref compiledPassInfo[lastReadPassIndex];
1886 passInfo.resourceReleaseList[type].Add(i);
1887 }
1888 }
1889
1890 if (sharedResource && (firstWriteIndex != -1 || lastReadPassIndex != -1)) // A shared resource is considered used if it's either read or written at any pass.
1891 {
1892 m_Resources.UpdateSharedResourceLastFrameIndex(type, i);
1893 }
1894 }
1895 }
1896 }
1897
1898 void UpdateAllSharedResourceLastFrameIndex()
1899 {
1900 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
1901 {
1902 var resourceInfos = m_CurrentCompiledGraph.compiledResourcesInfos[type];
1903 var sharedResourceCount = m_Resources.GetSharedResourceCount((RenderGraphResourceType)type);
1904
1905 for (int i = 1; i <= sharedResourceCount; ++i) // 0 == null resource skip it
1906 {
1907 CompiledResourceInfo resourceInfo = resourceInfos[i];
1908 var latestValidReadIndex = GetLatestValidReadIndex(resourceInfo);
1909 int firstWriteIndex = GetFirstValidWriteIndex(resourceInfo);
1910
1911 if ((firstWriteIndex != -1 || latestValidReadIndex != -1)) // A shared resource is considered used if it's either read or written at any pass.
1912 {
1913 m_Resources.UpdateSharedResourceLastFrameIndex(type, i);
1914 }
1915 }
1916 }
1917 }
1918
1919 bool AreRendererListsEmpty(List<RendererListHandle> rendererLists)
1920 {
1921 // Anything related to renderer lists needs a real context to be able to use/test it
1922 Debug.Assert(m_RenderGraphContext.contextlessTesting == false);
1923
1924 foreach (RendererListHandle handle in rendererLists)
1925 {
1926 var rendererList = m_Resources.GetRendererList(handle);
1927 if (m_RenderGraphContext.renderContext.QueryRendererListStatus(rendererList) == RendererListStatus.kRendererListPopulated)
1928 {
1929 return false;
1930 }
1931 }
1932
1933 // If the list of RendererLists is empty, then the default behavior is to not cull, so return false.
1934 return rendererLists.Count > 0 ? true : false;
1935 }
1936
1937 void TryCullPassAtIndex(int passIndex)
1938 {
1939 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1940 ref var compiledPass = ref compiledPassInfo[passIndex];
1941 var pass = m_RenderPasses[passIndex];
1942 if (!compiledPass.culled &&
1943 pass.allowPassCulling &&
1944 pass.allowRendererListCulling &&
1945 !compiledPass.hasSideEffect)
1946 {
1947 if (AreRendererListsEmpty(pass.usedRendererListList))
1948 {
1949 //Debug.Log($"Culling pass <color=red> {pass.name} </color>");
1950 compiledPass.culled = compiledPass.culledByRendererList = true;
1951 }
1952 }
1953 }
1954
1955 void CullRendererLists()
1956 {
1957 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
1958 for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
1959 {
1960 var compiledPass = compiledPassInfo[passIndex];
1961
1962 if (!compiledPass.culled && !compiledPass.hasSideEffect)
1963 {
1964 var pass = m_RenderPasses[passIndex];
1965 if (pass.usedRendererListList.Count > 0)
1966 {
1967 TryCullPassAtIndex(passIndex);
1968 }
1969 }
1970 }
1971 }
1972
1973 bool UpdateCurrentCompiledGraph(int graphHash, bool forceNoCaching = false)
1974 {
1975 bool cached = false;
1976 if (m_EnableCompilationCaching && !forceNoCaching)
1977 cached = m_CompilationCache.GetCompilationCache(graphHash, m_ExecutionCount, out m_CurrentCompiledGraph);
1978 else
1979 m_CurrentCompiledGraph = m_DefaultCompiledGraph;
1980
1981 return cached;
1982 }
1983
1984 // Internal visibility for testing purpose only
1985 // Traverse the render graph:
1986 // - Determines when resources are created/released
1987 // - Determines async compute pass synchronization
1988 // - Cull unused render passes.
1989 internal void CompileRenderGraph(int graphHash)
1990 {
1991 using (new ProfilingScope(m_RenderGraphContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.CompileRenderGraph)))
1992 {
1993 bool compilationIsCached = UpdateCurrentCompiledGraph(graphHash);
1994
1995 if (!compilationIsCached)
1996 {
1997 m_CurrentCompiledGraph.Clear();
1998 m_CurrentCompiledGraph.InitializeCompilationData(m_RenderPasses, m_Resources);
1999 CountReferences();
2000
2001 // First cull all passes that produce unused output
2002 CullUnusedPasses();
2003 }
2004
2005 // Create the renderer lists of the remaining passes
2006 CreateRendererLists();
2007
2008 if (!compilationIsCached)
2009 {
2010 // Cull dynamically the graph passes based on the renderer list visibility
2011 if (m_RendererListCulling)
2012 CullRendererLists();
2013
2014 // After all culling passes, allocate the resources for this frame
2015 UpdateResourceAllocationAndSynchronization();
2016 }
2017 else
2018 {
2019 // We need to update all shared resource frame index usage otherwise they might not be in a valid state.
2020 // Otherwise it's done in UpdateResourceAllocationAndSynchronization().
2021 UpdateAllSharedResourceLastFrameIndex();
2022 }
2023
2024 LogRendererListsCreation();
2025 }
2026 }
2027
2028 ref CompiledPassInfo CompilePassImmediatly(RenderGraphPass pass)
2029 {
2030 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
2031 // If we don't have enough pre allocated elements we double the size.
2032 // It's pretty aggressive but the immediate mode is only for debug purpose so it should be fine.
2033 if (m_CurrentImmediatePassIndex >= compiledPassInfo.size)
2034 compiledPassInfo.Resize(compiledPassInfo.size * 2);
2035
2036 ref CompiledPassInfo passInfo = ref compiledPassInfo[m_CurrentImmediatePassIndex++];
2037 passInfo.Reset(pass, m_CurrentImmediatePassIndex - 1);
2038 // In immediate mode we don't have proper information to generate synchronization so we disable async compute.
2039 passInfo.enableAsyncCompute = false;
2040
2041 // 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.
2042 // We will release all resources at the end of the render graph execution.
2043 for (int iType = 0; iType < (int)RenderGraphResourceType.Count; ++iType)
2044 {
2045 foreach (var res in pass.transientResourceList[iType])
2046 {
2047 passInfo.resourceCreateList[iType].Add(res.index);
2048 passInfo.resourceReleaseList[iType].Add(res.index);
2049
2050#if DEVELOPMENT_BUILD || UNITY_EDITOR
2051 passInfo.debugResourceWrites[iType].Add(m_Resources.GetRenderGraphResourceName(res));
2052 passInfo.debugResourceReads[iType].Add(m_Resources.GetRenderGraphResourceName(res));
2053#endif
2054 }
2055
2056 foreach (var res in pass.resourceWriteLists[iType])
2057 {
2058 if (pass.transientResourceList[iType].Contains(res))
2059 continue; // Prevent registering writes to transient texture twice
2060
2061 if (!m_Resources.IsGraphicsResourceCreated(res))
2062 {
2063 passInfo.resourceCreateList[iType].Add(res.index);
2064 m_ImmediateModeResourceList[iType].Add(res.index);
2065 }
2066
2067#if DEVELOPMENT_BUILD || UNITY_EDITOR
2068 passInfo.debugResourceWrites[iType].Add(m_Resources.GetRenderGraphResourceName(res));
2069#endif
2070 }
2071
2072 foreach (var res in pass.resourceReadLists[iType])
2073 {
2074#if DEVELOPMENT_BUILD || UNITY_EDITOR
2075 passInfo.debugResourceReads[iType].Add(m_Resources.GetRenderGraphResourceName(res));
2076#endif
2077 }
2078 }
2079
2080 // Create the necessary renderer lists
2081 foreach (var rl in pass.usedRendererListList)
2082 {
2083 if (!m_Resources.IsRendererListCreated(rl))
2084 m_RendererLists.Add(rl);
2085 }
2086 // Anything related to renderer lists needs a real context to be able to use/test it
2087 Debug.Assert(m_RenderGraphContext.contextlessTesting == false);
2088 m_Resources.CreateRendererLists(m_RendererLists, m_RenderGraphContext.renderContext);
2089 m_RendererLists.Clear();
2090
2091 return ref passInfo;
2092 }
2093
2094 void ExecutePassImmediately(RenderGraphPass pass)
2095 {
2096 ExecuteCompiledPass(ref CompilePassImmediatly(pass));
2097 }
2098
2099 void ExecuteCompiledPass(ref CompiledPassInfo passInfo)
2100 {
2101 if (passInfo.culled)
2102 return;
2103
2104 var pass = m_RenderPasses[passInfo.index];
2105
2106 if (!pass.HasRenderFunc())
2107 throw new InvalidOperationException($"RenderPass {pass.name} was not provided with an execute function.");
2108
2109 try
2110 {
2111 using (new ProfilingScope(m_RenderGraphContext.cmd, pass.customSampler))
2112 {
2113 LogRenderPassBegin(passInfo);
2114 using (new RenderGraphLogIndent(m_FrameInformationLogger))
2115 {
2116 m_RenderGraphContext.executingPass = pass;
2117 PreRenderPassExecute(passInfo, pass, m_RenderGraphContext);
2118 pass.Execute(m_RenderGraphContext);
2119 PostRenderPassExecute(ref passInfo, pass, m_RenderGraphContext);
2120 }
2121 }
2122 }
2123 catch (Exception e)
2124 {
2125 // Dont log errors during tests
2126 if (m_RenderGraphContext.contextlessTesting == false)
2127 {
2128 // Log exception from the pass that raised it to have improved error logging quality for users
2129 m_ExecutionExceptionWasRaised = true;
2130 Debug.LogError($"Render Graph execution error at pass '{pass.name}' ({passInfo.index})");
2131 Debug.LogException(e);
2132 }
2133 throw;
2134 }
2135 }
2136
2137 // Execute the compiled render graph
2138 void ExecuteRenderGraph()
2139 {
2140 using (new ProfilingScope(m_RenderGraphContext.cmd, ProfilingSampler.Get(RenderGraphProfileId.ExecuteRenderGraph)))
2141 {
2142 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
2143 for (int passIndex = 0; passIndex < compiledPassInfo.size; ++passIndex)
2144 {
2145 ExecuteCompiledPass(ref compiledPassInfo[passIndex]);
2146 }
2147 }
2148 }
2149
2150 void PreRenderPassSetRenderTargets(in CompiledPassInfo passInfo, RenderGraphPass pass, InternalRenderGraphContext rgContext)
2151 {
2152 var depthBufferIsValid = pass.depthAccess.textureHandle.IsValid();
2153 if (depthBufferIsValid || pass.colorBufferMaxIndex != -1)
2154 {
2155 var colorBufferAccess = pass.colorBufferAccess;
2156 if (pass.colorBufferMaxIndex > 0)
2157 {
2158 var mrtArray = m_TempMRTArrays[pass.colorBufferMaxIndex];
2159
2160 for (int i = 0; i <= pass.colorBufferMaxIndex; ++i)
2161 {
2162#if DEVELOPMENT_BUILD || UNITY_EDITOR
2163 if (!colorBufferAccess[i].textureHandle.IsValid())
2164 throw new InvalidOperationException("MRT setup is invalid. Some indices are not used.");
2165#endif
2166 mrtArray[i] = m_Resources.GetTexture(colorBufferAccess[i].textureHandle);
2167 }
2168
2169 if (depthBufferIsValid)
2170 {
2171 CoreUtils.SetRenderTarget(rgContext.cmd, mrtArray, m_Resources.GetTexture(pass.depthAccess.textureHandle));
2172 }
2173 else
2174 {
2175 throw new InvalidOperationException("Setting MRTs without a depth buffer is not supported.");
2176 }
2177 }
2178 else
2179 {
2180 if (depthBufferIsValid)
2181 {
2182 if (pass.colorBufferMaxIndex > -1)
2183 {
2184 CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.colorBufferAccess[0].textureHandle),
2185 m_Resources.GetTexture(pass.depthAccess.textureHandle));
2186 }
2187 else
2188 {
2189 CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.depthAccess.textureHandle));
2190 }
2191 }
2192 else
2193 {
2194 if (pass.colorBufferAccess[0].textureHandle.IsValid())
2195 {
2196 CoreUtils.SetRenderTarget(rgContext.cmd, m_Resources.GetTexture(pass.colorBufferAccess[0].textureHandle));
2197 }
2198 else
2199 throw new InvalidOperationException("Neither Depth nor color render targets are correctly setup at pass " + pass.name + ".");
2200 }
2201 }
2202 }
2203 }
2204
2205 void PreRenderPassExecute(in CompiledPassInfo passInfo, RenderGraphPass pass, InternalRenderGraphContext rgContext)
2206 {
2207 // Need to save the command buffer to restore it later as the one in the context can changed if running a pass async.
2208 m_PreviousCommandBuffer = rgContext.cmd;
2209
2210 bool executedWorkDuringResourceCreation = false;
2211 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
2212 {
2213 foreach (int resource in passInfo.resourceCreateList[type])
2214 {
2215 executedWorkDuringResourceCreation |= m_Resources.CreatePooledResource(rgContext, type, resource);
2216 }
2217 }
2218
2219 if (passInfo.enableFoveatedRasterization)
2220 rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Enabled);
2221
2222 PreRenderPassSetRenderTargets(passInfo, pass, rgContext);
2223
2224 if (passInfo.enableAsyncCompute)
2225 {
2226 GraphicsFence previousFence = new GraphicsFence();
2227 if (executedWorkDuringResourceCreation)
2228 {
2229 previousFence = rgContext.cmd.CreateGraphicsFence(GraphicsFenceType.AsyncQueueSynchronisation, SynchronisationStageFlags.AllGPUOperations);
2230 }
2231
2232 // Flush current command buffer on the render context before enqueuing async commands.
2233 if (rgContext.contextlessTesting == false)
2234 rgContext.renderContext.ExecuteCommandBuffer(rgContext.cmd);
2235 rgContext.cmd.Clear();
2236
2237 CommandBuffer asyncCmd = CommandBufferPool.Get(pass.name);
2238 asyncCmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute);
2239 rgContext.cmd = asyncCmd;
2240
2241 if (executedWorkDuringResourceCreation)
2242 {
2243 rgContext.cmd.WaitOnAsyncGraphicsFence(previousFence);
2244 }
2245 }
2246
2247 // Synchronize with graphics or compute pipe if needed.
2248 if (passInfo.syncToPassIndex != -1)
2249 {
2250 rgContext.cmd.WaitOnAsyncGraphicsFence(m_CurrentCompiledGraph.compiledPassInfos[passInfo.syncToPassIndex].fence);
2251 }
2252 }
2253
2254 void PostRenderPassExecute(ref CompiledPassInfo passInfo, RenderGraphPass pass, InternalRenderGraphContext rgContext)
2255 {
2256 foreach (var tex in pass.setGlobalsList)
2257 {
2258 rgContext.cmd.SetGlobalTexture(tex.Item2, tex.Item1);
2259 }
2260
2261 if (passInfo.needGraphicsFence)
2262 passInfo.fence = rgContext.cmd.CreateAsyncGraphicsFence();
2263
2264 if (passInfo.enableAsyncCompute)
2265 {
2266 // Anything related to async command buffer execution needs a real context to be able to use/test it
2267 // As the async will likely be waited for but never finish if there is no real context
2268 Debug.Assert(m_RenderGraphContext.contextlessTesting == false);
2269
2270 // The command buffer has been filled. We can kick the async task.
2271 rgContext.renderContext.ExecuteCommandBufferAsync(rgContext.cmd, ComputeQueueType.Background);
2272 CommandBufferPool.Release(rgContext.cmd);
2273 rgContext.cmd = m_PreviousCommandBuffer; // Restore the main command buffer.
2274 }
2275
2276 if (passInfo.enableFoveatedRasterization)
2277 rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Disabled);
2278
2279 m_RenderGraphPool.ReleaseAllTempAlloc();
2280
2281 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
2282 {
2283 foreach (var resource in passInfo.resourceReleaseList[type])
2284 {
2285 m_Resources.ReleasePooledResource(rgContext, type, resource);
2286 }
2287 }
2288 }
2289
2290 void ClearRenderPasses()
2291 {
2292 foreach (var pass in m_RenderPasses)
2293 pass.Release(m_RenderGraphPool);
2294 m_RenderPasses.Clear();
2295 }
2296
2297 void ReleaseImmediateModeResources()
2298 {
2299 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
2300 {
2301 foreach (var resource in m_ImmediateModeResourceList[type])
2302 {
2303 m_Resources.ReleasePooledResource(m_RenderGraphContext, type, resource);
2304 }
2305 }
2306 }
2307
2308 void LogFrameInformation()
2309 {
2310 if (m_DebugParameters.enableLogging)
2311 {
2312 m_FrameInformationLogger.LogLine($"==== Staring render graph frame for: {m_CurrentExecutionName} ====");
2313
2314 if (!m_DebugParameters.immediateMode)
2315 m_FrameInformationLogger.LogLine("Number of passes declared: {0}\n", m_RenderPasses.Count);
2316 }
2317 }
2318
2319 void LogRendererListsCreation()
2320 {
2321 if (m_DebugParameters.enableLogging)
2322 {
2323 m_FrameInformationLogger.LogLine("Number of renderer lists created: {0}\n", m_RendererLists.Count);
2324 }
2325 }
2326
2327 void LogRenderPassBegin(in CompiledPassInfo passInfo)
2328 {
2329 if (m_DebugParameters.enableLogging)
2330 {
2331 RenderGraphPass pass = m_RenderPasses[passInfo.index];
2332
2333 m_FrameInformationLogger.LogLine("[{0}][{1}] \"{2}\"", pass.index, pass.enableAsyncCompute ? "Compute" : "Graphics", pass.name);
2334 using (new RenderGraphLogIndent(m_FrameInformationLogger))
2335 {
2336 if (passInfo.syncToPassIndex != -1)
2337 m_FrameInformationLogger.LogLine("Synchronize with [{0}]", passInfo.syncToPassIndex);
2338 }
2339 }
2340 }
2341
2342 void LogCulledPasses()
2343 {
2344 if (m_DebugParameters.enableLogging)
2345 {
2346 m_FrameInformationLogger.LogLine("Pass Culling Report:");
2347 using (new RenderGraphLogIndent(m_FrameInformationLogger))
2348 {
2349 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
2350 for (int i = 0; i < compiledPassInfo.size; ++i)
2351 {
2352 if (compiledPassInfo[i].culled)
2353 {
2354 var pass = m_RenderPasses[i];
2355 m_FrameInformationLogger.LogLine("[{0}] {1}", pass.index, pass.name);
2356 }
2357 }
2358 m_FrameInformationLogger.LogLine("\n");
2359 }
2360 }
2361 }
2362
2363 ProfilingSampler GetDefaultProfilingSampler(string name)
2364 {
2365 // In non-dev builds, ProfilingSampler.Get returns null, so we'd always end up executing this.
2366 // To avoid that we also ifdef the code out here.
2367#if DEVELOPMENT_BUILD || UNITY_EDITOR
2368 int hash = name.GetHashCode();
2369 if (!m_DefaultProfilingSamplers.TryGetValue(hash, out var sampler))
2370 {
2371 sampler = new ProfilingSampler(name);
2372 m_DefaultProfilingSamplers.Add(hash, sampler);
2373 }
2374
2375 return sampler;
2376#else
2377 return null;
2378#endif
2379 }
2380
2381 void UpdateImportedResourceLifeTime(ref DebugData.ResourceData data, List<int> passList)
2382 {
2383 foreach (var pass in passList)
2384 {
2385 if (data.creationPassIndex == -1)
2386 data.creationPassIndex = pass;
2387 else
2388 data.creationPassIndex = Math.Min(data.creationPassIndex, pass);
2389
2390 if (data.releasePassIndex == -1)
2391 data.releasePassIndex = pass;
2392 else
2393 data.releasePassIndex = Math.Max(data.releasePassIndex, pass);
2394 }
2395 }
2396
2397 void GenerateDebugData()
2398 {
2399 if (m_ExecutionExceptionWasRaised)
2400 return;
2401
2402 if (!isRenderGraphViewerActive)
2403 {
2404 CleanupDebugData();
2405 return;
2406 }
2407
2408 if (!m_DebugData.TryGetValue(m_CurrentExecutionName, out var debugData))
2409 {
2410 onExecutionRegistered?.Invoke(this, m_CurrentExecutionName);
2411 debugData = new DebugData();
2412 m_DebugData.Add(m_CurrentExecutionName, debugData);
2413 return; // Generate the debug data on the next frame, because script metadata is collected during recording step
2414 }
2415
2416 // Only generate debug data on request
2417 if (m_CaptureDebugDataForExecution == null || !m_CaptureDebugDataForExecution.Equals(m_CurrentExecutionName))
2418 return;
2419
2420 debugData.Clear();
2421
2422 if (nativeRenderPassesEnabled)
2423 nativeCompiler.GenerateNativeCompilerDebugData(ref debugData);
2424 else
2425 GenerateCompilerDebugData(ref debugData);
2426
2427 onDebugDataCaptured?.Invoke();
2428
2429 m_CaptureDebugDataForExecution = null;
2430
2431 ClearPassDebugMetadata();
2432 }
2433
2434 void GenerateCompilerDebugData(ref DebugData debugData)
2435 {
2436 var compiledPassInfo = m_CurrentCompiledGraph.compiledPassInfos;
2437 var compiledResourceInfo = m_CurrentCompiledGraph.compiledResourcesInfos;
2438
2439 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
2440 {
2441 for (int i = 0; i < compiledResourceInfo[type].size; ++i)
2442 {
2443 ref var resourceInfo = ref compiledResourceInfo[type][i];
2444 DebugData.ResourceData newResource = new DebugData.ResourceData();
2445 if (i != 0)
2446 {
2447 var resName = m_Resources.GetRenderGraphResourceName((RenderGraphResourceType)type, i);
2448 newResource.name = !string.IsNullOrEmpty(resName) ? resName : "(unnamed)";
2449 newResource.imported = m_Resources.IsRenderGraphResourceImported((RenderGraphResourceType)type, i);
2450 }
2451 else
2452 {
2453 // The above functions will throw exceptions when used with the null argument so just use a dummy instead
2454 newResource.name = "<null>";
2455 newResource.imported = true;
2456 }
2457
2458 newResource.creationPassIndex = -1;
2459 newResource.releasePassIndex = -1;
2460
2461 RenderGraphResourceType resourceType = (RenderGraphResourceType) type;
2462 var handle = new ResourceHandle(i, resourceType, false);
2463 if (i != 0 && handle.IsValid())
2464 {
2465 if (resourceType == RenderGraphResourceType.Texture)
2466 {
2467 m_Resources.GetRenderTargetInfo(handle, out var renderTargetInfo);
2468
2469 var textureData = new DebugData.TextureResourceData();
2470 textureData.width = renderTargetInfo.width;
2471 textureData.height = renderTargetInfo.height;
2472 textureData.depth = renderTargetInfo.volumeDepth;
2473 textureData.samples = renderTargetInfo.msaaSamples;
2474 textureData.format = renderTargetInfo.format;
2475
2476 newResource.textureData = textureData;
2477 }
2478 else if (resourceType == RenderGraphResourceType.Buffer)
2479 {
2480 var bufferDesc = m_Resources.GetBufferResourceDesc(handle, true);
2481
2482 var bufferData = new DebugData.BufferResourceData();
2483 bufferData.count = bufferDesc.count;
2484 bufferData.stride = bufferDesc.stride;
2485 bufferData.target = bufferDesc.target;
2486 bufferData.usage = bufferDesc.usageFlags;
2487
2488 newResource.bufferData = bufferData;
2489 }
2490 }
2491
2492 newResource.consumerList = new List<int>(resourceInfo.consumers);
2493 newResource.producerList = new List<int>(resourceInfo.producers);
2494
2495 if (newResource.imported)
2496 {
2497 UpdateImportedResourceLifeTime(ref newResource, newResource.consumerList);
2498 UpdateImportedResourceLifeTime(ref newResource, newResource.producerList);
2499 }
2500
2501 debugData.resourceLists[type].Add(newResource);
2502 }
2503 }
2504
2505 for (int i = 0; i < compiledPassInfo.size; ++i)
2506 {
2507 ref CompiledPassInfo passInfo = ref compiledPassInfo[i];
2508 RenderGraphPass pass = m_RenderPasses[passInfo.index];
2509
2510 DebugData.PassData newPass = new DebugData.PassData();
2511 newPass.name = pass.name;
2512 newPass.type = pass.type;
2513 newPass.culled = passInfo.culled;
2514 newPass.async = passInfo.enableAsyncCompute;
2515 newPass.generateDebugData = pass.generateDebugData;
2516 newPass.resourceReadLists = new List<int>[(int)RenderGraphResourceType.Count];
2517 newPass.resourceWriteLists = new List<int>[(int)RenderGraphResourceType.Count];
2518 newPass.syncFromPassIndex = passInfo.syncFromPassIndex;
2519 newPass.syncToPassIndex = passInfo.syncToPassIndex;
2520
2521 DebugData.s_PassScriptMetadata.TryGetValue(pass.name, out newPass.scriptInfo);
2522
2523 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
2524 {
2525 newPass.resourceReadLists[type] = new List<int>();
2526 newPass.resourceWriteLists[type] = new List<int>();
2527
2528 foreach (var resourceRead in pass.resourceReadLists[type])
2529 newPass.resourceReadLists[type].Add(resourceRead.index);
2530 foreach (var resourceWrite in pass.resourceWriteLists[type])
2531 newPass.resourceWriteLists[type].Add(resourceWrite.index);
2532
2533 foreach (var resourceCreate in passInfo.resourceCreateList[type])
2534 {
2535 var res = debugData.resourceLists[type][resourceCreate];
2536 if (res.imported)
2537 continue;
2538 res.creationPassIndex = i;
2539 debugData.resourceLists[type][resourceCreate] = res;
2540 }
2541
2542 foreach (var resourceRelease in passInfo.resourceReleaseList[type])
2543 {
2544 var res = debugData.resourceLists[type][resourceRelease];
2545 if (res.imported)
2546 continue;
2547 res.releasePassIndex = i;
2548 debugData.resourceLists[type][resourceRelease] = res;
2549 }
2550 }
2551
2552 debugData.passList.Add(newPass);
2553 }
2554 }
2555
2556 void CleanupDebugData()
2557 {
2558 foreach (var kvp in m_DebugData)
2559 {
2560 onExecutionUnregistered?.Invoke(this, kvp.Key);
2561 }
2562
2563 m_DebugData.Clear();
2564 }
2565
2566 #endregion
2567
2568
2569 Dictionary<int, TextureHandle> registeredGlobals = new Dictionary<int, TextureHandle>();
2570
2571 internal void SetGlobal(TextureHandle h, int globalPropertyId)
2572 {
2573 if (!h.IsValid())
2574 throw new ArgumentException("Attempting to register an invalid texture handle as a global");
2575
2576 registeredGlobals[globalPropertyId] = h;
2577 }
2578
2579 internal bool IsGlobal(int globalPropertyId)
2580 {
2581 return registeredGlobals.ContainsKey(globalPropertyId);
2582 }
2583
2584 internal Dictionary<int, TextureHandle>.ValueCollection AllGlobals()
2585 {
2586 return registeredGlobals.Values;
2587 }
2588
2589 internal TextureHandle GetGlobal(int globalPropertyId)
2590 {
2591 TextureHandle h;
2592 registeredGlobals.TryGetValue(globalPropertyId, out h);
2593 return h;
2594 }
2595
2596 /// <summary>
2597 /// Clears the shader bindings associated with the registered globals in the graph
2598 ///
2599 /// This prevents later rendering logic from accidentally relying on stale shader bindings that were set
2600 /// earlier during graph execution.
2601 /// </summary>
2602 internal void ClearGlobalBindings()
2603 {
2604 // Set all the global texture shader bindings to the default black texture.
2605 // This doesn't technically "clear" the shader bindings, but it's the closest we can do.
2606 foreach (var globalTex in registeredGlobals)
2607 {
2608 m_RenderGraphContext.cmd.SetGlobalTexture(globalTex.Key, defaultResources.blackTexture);
2609 }
2610 }
2611 }
2612
2613 /// <summary>
2614 /// Render Graph Scoped Profiling markers
2615 /// </summary>
2616 [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")]
2617 public struct RenderGraphProfilingScope : IDisposable
2618 {
2619#if DEVELOPMENT_BUILD || UNITY_EDITOR
2620 ProfilingSampler m_Sampler;
2621 RenderGraph m_RenderGraph;
2622 bool m_Disposed;
2623#endif
2624
2625 /// <summary>
2626 /// Profiling Scope constructor
2627 /// </summary>
2628 /// <param name="renderGraph">Render Graph used for this scope.</param>
2629 /// <param name="sampler">Profiling Sampler to be used for this scope.</param>
2630 public RenderGraphProfilingScope(RenderGraph renderGraph, ProfilingSampler sampler)
2631 {
2632#if DEVELOPMENT_BUILD || UNITY_EDITOR
2633 m_RenderGraph = renderGraph;
2634 m_Sampler = sampler;
2635 m_Disposed = false;
2636 renderGraph.BeginProfilingSampler(sampler);
2637#endif
2638 }
2639
2640 /// <summary>
2641 /// Dispose pattern implementation
2642 /// </summary>
2643 public void Dispose()
2644 {
2645 Dispose(true);
2646 }
2647
2648 // Protected implementation of Dispose pattern.
2649 void Dispose(bool disposing)
2650 {
2651#if DEVELOPMENT_BUILD || UNITY_EDITOR
2652 if (m_Disposed)
2653 return;
2654
2655 // As this is a struct, it could have been initialized using an empty constructor so we
2656 // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix
2657 // this but will generate garbage on every frame (and this struct is used quite a lot).
2658 if (disposing)
2659 {
2660 m_RenderGraph.EndProfilingSampler(m_Sampler);
2661 }
2662
2663 m_Disposed = true;
2664#endif
2665 }
2666 }
2667}