A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using System.Collections.Generic;
4using System.Runtime.CompilerServices;
5
6namespace UnityEngine.Rendering.RenderGraphModule
7{
8 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
9 abstract class RenderGraphPass
10 {
11 public abstract void Execute(InternalRenderGraphContext renderGraphContext);
12 public abstract void Release(RenderGraphObjectPool pool);
13 public abstract bool HasRenderFunc();
14 public abstract int GetRenderFuncHash();
15
16 public string name { get; protected set; }
17 public int index { get; protected set; }
18 public RenderGraphPassType type { get; internal set; }
19 public ProfilingSampler customSampler { get; protected set; }
20 public bool enableAsyncCompute { get; protected set; }
21 public bool allowPassCulling { get; protected set; }
22 public bool allowGlobalState { get; protected set; }
23 public bool enableFoveatedRasterization { get; protected set; }
24
25 // Before using the AccessFlags use resourceHandle.isValid()
26 // to make sure that the data in the colorBuffer/fragmentInput/randomAccessResource buffers are up to date
27 public TextureAccess depthAccess { get; protected set; }
28
29 public TextureAccess[] colorBufferAccess { get; protected set; } = new TextureAccess[RenderGraph.kMaxMRTCount];
30 public int colorBufferMaxIndex { get; protected set; } = -1;
31
32 // Used by native pass compiler only
33 public TextureAccess[] fragmentInputAccess { get; protected set; } = new TextureAccess[RenderGraph.kMaxMRTCount];
34 public int fragmentInputMaxIndex { get; protected set; } = -1;
35
36 public struct RandomWriteResourceInfo
37 {
38 public ResourceHandle h;
39 public bool preserveCounterValue;
40 }
41
42 // This list can contain both texture and buffer resources based on their binding index.
43 public RandomWriteResourceInfo[] randomAccessResource { get; protected set; } = new RandomWriteResourceInfo[RenderGraph.kMaxMRTCount];
44 public int randomAccessResourceMaxIndex { get; protected set; } = -1;
45
46 public bool generateDebugData { get; protected set; }
47
48 public bool allowRendererListCulling { get; protected set; }
49
50 public List<ResourceHandle>[] resourceReadLists = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
51 public List<ResourceHandle>[] resourceWriteLists = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
52 public List<ResourceHandle>[] transientResourceList = new List<ResourceHandle>[(int)RenderGraphResourceType.Count];
53
54 public List<RendererListHandle> usedRendererListList = new List<RendererListHandle>();
55
56 public List<ValueTuple<TextureHandle, int>> setGlobalsList = new List<ValueTuple<TextureHandle, int>>();
57 public bool useAllGlobalTextures;
58
59 public List<ResourceHandle> implicitReadsList = new List<ResourceHandle>();
60
61 public RenderGraphPass()
62 {
63 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
64 {
65 resourceReadLists[i] = new List<ResourceHandle>();
66 resourceWriteLists[i] = new List<ResourceHandle>();
67 transientResourceList[i] = new List<ResourceHandle>();
68 }
69 }
70
71 public void Clear()
72 {
73 name = "";
74 index = -1;
75 customSampler = null;
76 for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i)
77 {
78 resourceReadLists[i].Clear();
79 resourceWriteLists[i].Clear();
80 transientResourceList[i].Clear();
81 }
82
83 usedRendererListList.Clear();
84 setGlobalsList.Clear();
85 useAllGlobalTextures = false;
86 implicitReadsList.Clear();
87 enableAsyncCompute = false;
88 allowPassCulling = true;
89 allowRendererListCulling = true;
90 allowGlobalState = false;
91 enableFoveatedRasterization = false;
92 generateDebugData = true;
93
94 // Invalidate the buffers without clearing them, as it is too costly
95 // Use IsValid() to make sure that the data in the colorBuffer/fragmentInput/randomAccessResource buffers are up to date
96 colorBufferMaxIndex = -1;
97 fragmentInputMaxIndex = -1;
98 randomAccessResourceMaxIndex = -1;
99 }
100
101 // Check if the pass has any render targets set-up
102 [MethodImpl(MethodImplOptions.AggressiveInlining)]
103 public bool HasRenderAttachments()
104 {
105 return depthAccess.textureHandle.IsValid() || colorBufferAccess[0].textureHandle.IsValid() || colorBufferMaxIndex > 0;
106 }
107
108 // Checks if the resource is involved in this pass
109 [MethodImpl(MethodImplOptions.AggressiveInlining)]
110 public bool IsTransient(in ResourceHandle res)
111 {
112 return transientResourceList[res.iType].Contains(res);
113 }
114
115 [MethodImpl(MethodImplOptions.AggressiveInlining)]
116 public bool IsWritten(in ResourceHandle res)
117 {
118 // You can only ever write to the latest version so we ignore it when looking in the list
119 for (int i = 0; i < resourceWriteLists[res.iType].Count; i++)
120 {
121 if (resourceWriteLists[res.iType][i].index == res.index)
122 {
123 return true;
124 }
125 }
126 return false;
127 }
128
129 [MethodImpl(MethodImplOptions.AggressiveInlining)]
130 public bool IsRead(in ResourceHandle res)
131 {
132 if (res.IsVersioned)
133 {
134 return resourceReadLists[res.iType].Contains(res);
135 }
136 else
137 {
138 // Just look if we are reading any version of this texture.
139 // Note that in theory this pass could read from several versions of the same texture
140 // e.g. ColorBuffer,v3 and ColorBuffer,v5 so this check is always conservative
141 for (int i = 0; i < resourceReadLists[res.iType].Count; i++)
142 {
143 if (resourceReadLists[res.iType][i].index == res.index)
144 {
145 return true;
146 }
147 }
148 return false;
149 }
150 }
151
152 [MethodImpl(MethodImplOptions.AggressiveInlining)]
153 public bool IsAttachment(in TextureHandle res)
154 {
155 // We ignore the version when checking, if any version is used it is considered a match
156
157 if (depthAccess.textureHandle.IsValid() && depthAccess.textureHandle.handle.index == res.handle.index) return true;
158 for (int i = 0; i < colorBufferAccess.Length; i++)
159 {
160 if (colorBufferAccess[i].textureHandle.IsValid() && colorBufferAccess[i].textureHandle.handle.index == res.handle.index) return true;
161 }
162
163 return false;
164 }
165
166
167 [MethodImpl(MethodImplOptions.AggressiveInlining)]
168 public void AddResourceWrite(in ResourceHandle res)
169 {
170 resourceWriteLists[res.iType].Add(res);
171 }
172
173 [MethodImpl(MethodImplOptions.AggressiveInlining)]
174 public void AddResourceRead(in ResourceHandle res)
175 {
176 resourceReadLists[res.iType].Add(res);
177 }
178
179 [MethodImpl(MethodImplOptions.AggressiveInlining)]
180 public void AddTransientResource(in ResourceHandle res)
181 {
182 transientResourceList[res.iType].Add(res);
183 }
184
185 [MethodImpl(MethodImplOptions.AggressiveInlining)]
186 public void UseRendererList(in RendererListHandle rendererList)
187 {
188 usedRendererListList.Add(rendererList);
189 }
190
191 [MethodImpl(MethodImplOptions.AggressiveInlining)]
192 public void EnableAsyncCompute(bool value)
193 {
194 enableAsyncCompute = value;
195 }
196
197 [MethodImpl(MethodImplOptions.AggressiveInlining)]
198 public void AllowPassCulling(bool value)
199 {
200 allowPassCulling = value;
201 }
202
203 [MethodImpl(MethodImplOptions.AggressiveInlining)]
204 public void EnableFoveatedRasterization(bool value)
205 {
206 enableFoveatedRasterization = value;
207 }
208
209 [MethodImpl(MethodImplOptions.AggressiveInlining)]
210 public void AllowRendererListCulling(bool value)
211 {
212 allowRendererListCulling = value;
213 }
214
215 [MethodImpl(MethodImplOptions.AggressiveInlining)]
216 public void AllowGlobalState(bool value)
217 {
218 allowGlobalState = value;
219 }
220
221 [MethodImpl(MethodImplOptions.AggressiveInlining)]
222 public void GenerateDebugData(bool value)
223 {
224 generateDebugData = value;
225 }
226
227 [MethodImpl(MethodImplOptions.AggressiveInlining)]
228 public void SetColorBuffer(in TextureHandle resource, int index)
229 {
230 Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
231 colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index);
232 colorBufferAccess[index].textureHandle = resource;
233 AddResourceWrite(resource.handle);
234 }
235
236 // Sets up the color buffer for this pass but not any resource Read/Writes for it
237 [MethodImpl(MethodImplOptions.AggressiveInlining)]
238 public void SetColorBufferRaw(in TextureHandle resource, int index, AccessFlags accessFlags, int mipLevel, int depthSlice)
239 {
240 Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
241 if (colorBufferAccess[index].textureHandle.handle.Equals(resource.handle) || !colorBufferAccess[index].textureHandle.IsValid())
242 {
243 colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index);
244 colorBufferAccess[index].textureHandle = resource;
245 colorBufferAccess[index].flags = accessFlags;
246 colorBufferAccess[index].mipLevel = mipLevel;
247 colorBufferAccess[index].depthSlice = depthSlice;
248 }
249 else
250 {
251#if DEVELOPMENT_BUILD || UNITY_EDITOR
252 // You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index
253 throw new InvalidOperationException("You can only bind a single texture to an MRT index. Verify your indexes are correct.");
254#endif
255 }
256 }
257
258 // Sets up the color buffer for this pass but not any resource Read/Writes for it
259 [MethodImpl(MethodImplOptions.AggressiveInlining)]
260 public void SetFragmentInputRaw(in TextureHandle resource, int index, AccessFlags accessFlags, int mipLevel, int depthSlice)
261 {
262 Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
263 if (fragmentInputAccess[index].textureHandle.handle.Equals(resource.handle) || !fragmentInputAccess[index].textureHandle.IsValid())
264 {
265 fragmentInputMaxIndex = Math.Max(fragmentInputMaxIndex, index);
266 fragmentInputAccess[index].textureHandle = resource;
267 fragmentInputAccess[index].flags = accessFlags;
268 fragmentInputAccess[index].mipLevel = mipLevel;
269 fragmentInputAccess[index].depthSlice = depthSlice;
270 }
271 else
272 {
273#if DEVELOPMENT_BUILD || UNITY_EDITOR
274 // You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index
275 throw new InvalidOperationException("You can only bind a single texture to an fragment input index. Verify your indexes are correct.");
276#endif
277 }
278 }
279
280 // Sets up the color buffer for this pass but not any resource Read/Writes for it
281 [MethodImpl(MethodImplOptions.AggressiveInlining)]
282 public void SetRandomWriteResourceRaw(in ResourceHandle resource, int index, bool preserveCounterValue, AccessFlags accessFlags)
283 {
284 Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0);
285 if (randomAccessResource[index].h.Equals(resource) || !randomAccessResource[index].h.IsValid())
286 {
287 randomAccessResourceMaxIndex = Math.Max(randomAccessResourceMaxIndex, index);
288 ref var info = ref randomAccessResource[index];
289 info.h = resource;
290 info.preserveCounterValue = preserveCounterValue;
291 }
292 else
293 {
294 // You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index
295 throw new InvalidOperationException("You can only bind a single texture to an random write input index. Verify your indexes are correct.");
296 }
297 }
298
299
300 [MethodImpl(MethodImplOptions.AggressiveInlining)]
301 public void SetDepthBuffer(in TextureHandle resource, DepthAccess flags)
302 {
303 depthAccess = new TextureAccess(resource, (AccessFlags)flags, 0, 0);
304 if ((flags & DepthAccess.Read) != 0)
305 AddResourceRead(resource.handle);
306 if ((flags & DepthAccess.Write) != 0)
307 AddResourceWrite(resource.handle);
308 }
309
310 // Sets up the depth buffer for this pass but not any resource Read/Writes for it
311 [MethodImpl(MethodImplOptions.AggressiveInlining)]
312 public void SetDepthBufferRaw(in TextureHandle resource, AccessFlags accessFlags, int mipLevel, int depthSlice)
313 {
314 // If no depth buffer yet or if it is the same one as the previous one, allow the call otherwise log an error.
315 if (depthAccess.textureHandle.handle.Equals(resource.handle) || !depthAccess.textureHandle.IsValid())
316 {
317 depthAccess = new TextureAccess(resource, accessFlags, mipLevel, depthSlice);
318 }
319#if DEVELOPMENT_BUILD || UNITY_EDITOR
320 else
321 {
322 throw new InvalidOperationException("You can only set a single depth texture per pass.");
323 }
324#endif
325 }
326
327
328 // Here we want to keep computation to a minimum and only hash what will influence NRP compiler: Pass merging, load/store actions etc.
329 [MethodImpl(MethodImplOptions.AggressiveInlining)]
330 void ComputeTextureHash(ref HashFNV1A32 generator, in ResourceHandle handle, RenderGraphResourceRegistry resources)
331 {
332 if (handle.index == 0)
333 return;
334
335 if (resources.IsRenderGraphResourceImported(handle))
336 {
337 var res = resources.GetTextureResource(handle);
338 if (res.graphicsResource.externalTexture != null) // External texture
339 {
340 var externalTexture = res.graphicsResource.externalTexture;
341 generator.Append((int) externalTexture.graphicsFormat);
342 generator.Append((int) externalTexture.dimension);
343 generator.Append(externalTexture.width);
344 generator.Append(externalTexture.height);
345 if (externalTexture is RenderTexture externalRT)
346 generator.Append(externalRT.antiAliasing);
347 }
348 else if (res.graphicsResource.rt != null) // Regular RTHandle
349 {
350 var rt = res.graphicsResource.rt;
351 generator.Append((int) rt.graphicsFormat);
352 generator.Append((int) rt.dimension);
353 generator.Append(rt.antiAliasing);
354 if (res.graphicsResource.useScaling)
355 if (res.graphicsResource.scaleFunc != null)
356 generator.Append(res.graphicsResource.scaleFunc);
357 else
358 generator.Append(res.graphicsResource.scaleFactor);
359 else
360 {
361 generator.Append(rt.width);
362 generator.Append(rt.height);
363 }
364 }
365 else if (res.graphicsResource.nameID != default) // External RTI
366 {
367 // The only info we have is from the provided desc upon importing.
368 ref var desc = ref res.desc;
369 generator.Append((int) desc.format);
370 generator.Append((int) desc.dimension);
371 generator.Append((int) desc.msaaSamples);
372 generator.Append(desc.width);
373 generator.Append(desc.height);
374 }
375
376 // Add the clear/discard buffer flags to the hash (used in all the cases above)
377 generator.Append(res.desc.clearBuffer);
378 generator.Append(res.desc.discardBuffer);
379 }
380 else
381 {
382 var desc = resources.GetTextureResourceDesc(handle);
383 generator.Append((int) desc.format);
384 generator.Append((int) desc.dimension);
385 generator.Append((int) desc.msaaSamples);
386 generator.Append(desc.clearBuffer);
387 generator.Append(desc.discardBuffer);
388 switch (desc.sizeMode)
389 {
390 case TextureSizeMode.Explicit:
391 generator.Append(desc.width);
392 generator.Append(desc.height);
393 break;
394 case TextureSizeMode.Scale:
395 generator.Append(desc.scale);
396 break;
397 case TextureSizeMode.Functor:
398 generator.Append(desc.func);
399 break;
400 }
401 }
402 }
403
404 // This function is performance sensitive.
405 // Avoid mass function calls to get the hashCode and compute locally instead.
406 public void ComputeHash(ref HashFNV1A32 generator, RenderGraphResourceRegistry resources)
407 {
408 generator.Append((int) type);
409 generator.Append(enableAsyncCompute);
410 generator.Append(allowPassCulling);
411 generator.Append(allowGlobalState);
412 generator.Append(enableFoveatedRasterization);
413
414 var depthHandle = depthAccess.textureHandle.handle;
415 if (depthHandle.IsValid())
416 {
417 ComputeTextureHash(ref generator, depthHandle, resources);
418 ComputeHashForTextureAccess(ref generator, depthHandle, depthAccess);
419 }
420
421 for (int i = 0; i < colorBufferMaxIndex + 1; ++i)
422 {
423 var colorBufferAccessElement = colorBufferAccess[i];
424 var handle = colorBufferAccessElement.textureHandle.handle;
425 if (!handle.IsValid())
426 continue;
427
428 ComputeTextureHash(ref generator, handle, resources);
429 ComputeHashForTextureAccess(ref generator, handle, colorBufferAccessElement);
430 }
431
432 generator.Append(colorBufferMaxIndex);
433
434 for (int i = 0; i < fragmentInputMaxIndex + 1; ++i)
435 {
436 var fragmentInputAccessElement = fragmentInputAccess[i];
437 var handle = fragmentInputAccessElement.textureHandle.handle;
438 if (!handle.IsValid())
439 continue;
440
441 ComputeTextureHash(ref generator, handle, resources);
442 ComputeHashForTextureAccess(ref generator, handle, fragmentInputAccessElement);
443 }
444
445 for (int i = 0; i < randomAccessResourceMaxIndex + 1; ++i)
446 {
447 var rar = randomAccessResource[i];
448 if (!rar.h.IsValid())
449 continue;
450
451 generator.Append(rar.h.index);
452 generator.Append(rar.preserveCounterValue);
453 }
454 generator.Append(randomAccessResourceMaxIndex);
455 generator.Append(fragmentInputMaxIndex);
456 generator.Append(generateDebugData);
457 generator.Append(allowRendererListCulling);
458
459 for (int resType = 0; resType < (int)RenderGraphResourceType.Count; resType++)
460 {
461 var resourceReads = resourceReadLists[resType];
462 for (int i = 0; i < resourceReads.Count; ++i)
463 generator.Append(resourceReads[i].index);
464
465 var resourceWrites = resourceWriteLists[resType];
466 for (int i = 0; i < resourceWrites.Count; ++i)
467 generator.Append(resourceWrites[i].index);
468
469 var resourceTransient = transientResourceList[resType];
470 for (int i = 0; i < resourceTransient.Count; ++i)
471 generator.Append(resourceTransient[i].index);
472 }
473
474 for (int i = 0; i < usedRendererListList.Count; ++i)
475 generator.Append(usedRendererListList[i].handle);
476
477 for (int i = 0; i < setGlobalsList.Count; ++i)
478 {
479 var global = setGlobalsList[i];
480 generator.Append(global.Item1.handle.index);
481 generator.Append(global.Item2);
482 }
483 generator.Append(useAllGlobalTextures);
484
485 for (int i = 0; i < implicitReadsList.Count; ++i)
486 generator.Append(implicitReadsList[i].index);
487
488 generator.Append(GetRenderFuncHash());
489 }
490
491 [MethodImpl(MethodImplOptions.AggressiveInlining)]
492 static void ComputeHashForTextureAccess(ref HashFNV1A32 generator, in ResourceHandle handle, in TextureAccess textureAccess)
493 {
494 generator.Append(handle.index);
495 generator.Append((int) textureAccess.flags);
496 generator.Append(textureAccess.mipLevel);
497 generator.Append(textureAccess.depthSlice);
498 }
499 }
500
501 // This used to have an extra generic argument 'RenderGraphContext' abstracting the context and avoiding
502 // the RenderGraphPass/ComputeRenderGraphPass/RasterRenderGraphPass/UnsafeRenderGraphPass classes below
503 // but this confuses IL2CPP and causes garbage when boxing the context created (even though they are structs)
504 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
505 internal abstract class BaseRenderGraphPass<PassData, TRenderGraphContext> : RenderGraphPass
506 where PassData : class, new()
507 {
508 internal PassData data;
509 internal BaseRenderFunc<PassData, TRenderGraphContext> renderFunc;
510
511 [MethodImpl(MethodImplOptions.AggressiveInlining)]
512 public void Initialize(int passIndex, PassData passData, string passName, RenderGraphPassType passType, ProfilingSampler sampler)
513 {
514 Clear();
515 index = passIndex;
516 data = passData;
517 name = passName;
518 type = passType;
519 customSampler = sampler;
520 }
521
522 [MethodImpl(MethodImplOptions.AggressiveInlining)]
523 public override void Release(RenderGraphObjectPool pool)
524 {
525 pool.Release(data);
526 data = null;
527 renderFunc = null;
528 }
529
530 [MethodImpl(MethodImplOptions.AggressiveInlining)]
531 public override bool HasRenderFunc()
532 {
533 return renderFunc != null;
534 }
535
536 [MethodImpl(MethodImplOptions.AggressiveInlining)]
537 public override int GetRenderFuncHash()
538 {
539 return renderFunc != null ? HashFNV1A32.GetFuncHashCode(renderFunc) : 0;
540 }
541 }
542
543 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
544 internal sealed class RenderGraphPass<PassData> : BaseRenderGraphPass<PassData, RenderGraphContext>
545 where PassData : class, new()
546 {
547 internal static RenderGraphContext c = new RenderGraphContext();
548
549 [MethodImpl(MethodImplOptions.AggressiveInlining)]
550 public override void Execute(InternalRenderGraphContext renderGraphContext)
551 {
552 c.FromInternalContext(renderGraphContext);
553 renderFunc(data, c);
554 }
555
556 [MethodImpl(MethodImplOptions.AggressiveInlining)]
557 public override void Release(RenderGraphObjectPool pool)
558 {
559 base.Release(pool);
560
561 // We need to do the release from here because we need the final type.
562 pool.Release(this);
563 }
564 }
565
566 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
567 internal sealed class ComputeRenderGraphPass<PassData> : BaseRenderGraphPass<PassData, ComputeGraphContext>
568 where PassData : class, new()
569 {
570 internal static ComputeGraphContext c = new ComputeGraphContext();
571
572 [MethodImpl(MethodImplOptions.AggressiveInlining)]
573 public override void Execute(InternalRenderGraphContext renderGraphContext)
574 {
575 c.FromInternalContext(renderGraphContext);
576 renderFunc(data, c);
577 }
578
579 [MethodImpl(MethodImplOptions.AggressiveInlining)]
580 public override void Release(RenderGraphObjectPool pool)
581 {
582 base.Release(pool);
583
584 // We need to do the release from here because we need the final type.
585 pool.Release(this);
586 }
587 }
588
589 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
590 internal sealed class RasterRenderGraphPass<PassData> : BaseRenderGraphPass<PassData, RasterGraphContext>
591 where PassData : class, new()
592 {
593 internal static RasterGraphContext c = new RasterGraphContext();
594
595 [MethodImpl(MethodImplOptions.AggressiveInlining)]
596 public override void Execute(InternalRenderGraphContext renderGraphContext)
597 {
598 c.FromInternalContext(renderGraphContext);
599 renderFunc(data, c);
600 }
601
602 [MethodImpl(MethodImplOptions.AggressiveInlining)]
603 public override void Release(RenderGraphObjectPool pool)
604 {
605 base.Release(pool);
606
607 // We need to do the release from here because we need the final type.
608 pool.Release(this);
609 }
610 }
611
612 [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")]
613 internal sealed class UnsafeRenderGraphPass<PassData> : BaseRenderGraphPass<PassData, UnsafeGraphContext>
614 where PassData : class, new()
615 {
616 internal static UnsafeGraphContext c = new UnsafeGraphContext();
617
618 [MethodImpl(MethodImplOptions.AggressiveInlining)]
619 public override void Execute(InternalRenderGraphContext renderGraphContext)
620 {
621 c.FromInternalContext(renderGraphContext);
622 renderFunc(data, c);
623 }
624
625 [MethodImpl(MethodImplOptions.AggressiveInlining)]
626 public override void Release(RenderGraphObjectPool pool)
627 {
628 base.Release(pool);
629
630 // We need to do the release from here because we need the final type.
631 pool.Release(this);
632 }
633 }
634}