-
Notifications
You must be signed in to change notification settings - Fork 869
Expand file tree
/
Copy pathNativePassCompiler.cs
More file actions
2130 lines (1843 loc) · 111 KB
/
NativePassCompiler.cs
File metadata and controls
2130 lines (1843 loc) · 111 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler
{
internal partial class NativePassCompiler : IDisposable
{
internal struct RenderGraphInputInfo
{
public RenderGraphResourceRegistry m_ResourcesForDebugOnly;
public List<RenderGraphPass> m_RenderPasses;
public string debugName;
public bool disablePassCulling;
public bool disablePassMerging;
public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy;
}
internal RenderGraphInputInfo graph;
internal CompilerContextData contextData = null;
internal CompilerContextData defaultContextData;
internal CommandBuffer previousCommandBuffer;
Stack<int> m_HasSideEffectPassIdCullingStack;
List<Stack<ResourceHandle>> m_UnusedVersionedResourceIdCullingStacks;
Dictionary<int, List<ResourceHandle>> m_DelayedLastUseListPerPassMap;
RenderGraphCompilationCache m_CompilationCache;
RenderTargetIdentifier[][] m_TempMRTArrays = null;
internal const int k_EstimatedPassCount = 100;
internal const int k_MaxSubpass = 8; // Needs to match with RenderPassSetup.h
NativeList<AttachmentDescriptor> m_BeginRenderPassAttachments;
internal static bool s_ForceGenerateAuditsForTests = false;
public NativePassCompiler(RenderGraphCompilationCache cache)
{
m_CompilationCache = cache;
defaultContextData = new CompilerContextData();
m_HasSideEffectPassIdCullingStack = new Stack<int>(k_EstimatedPassCount);
m_UnusedVersionedResourceIdCullingStacks = new List<Stack<ResourceHandle>>();
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
m_UnusedVersionedResourceIdCullingStacks.Add(new Stack<ResourceHandle>());
m_DelayedLastUseListPerPassMap = new Dictionary<int, List<ResourceHandle>>(k_EstimatedPassCount);
for (int passId = 0; passId < k_EstimatedPassCount; ++passId)
m_DelayedLastUseListPerPassMap.Add(passId, new List<ResourceHandle>());
m_TempMRTArrays = new RenderTargetIdentifier[RenderGraph.kMaxMRTCount][];
for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i)
m_TempMRTArrays[i] = new RenderTargetIdentifier[i + 1];
}
// IDisposable implementation
~NativePassCompiler() => Cleanup();
public void Dispose()
{
Cleanup();
GC.SuppressFinalize(this);
}
public void Cleanup()
{
// If caching enabled, the two can be different
contextData?.Dispose();
defaultContextData?.Dispose();
if (m_BeginRenderPassAttachments.IsCreated)
{
m_BeginRenderPassAttachments.Dispose();
}
}
public bool Initialize(RenderGraphResourceRegistry resources, List<RenderGraphPass> renderPasses, RenderGraphDebugParams debugParams, string debugName, bool useCompilationCaching,
int graphHash, int frameIndex, RenderTextureUVOriginStrategy renderTextureUVOriginStrategy)
{
bool cached = false;
if (!useCompilationCaching)
contextData = defaultContextData;
else
cached = m_CompilationCache.GetCompilationCache(graphHash, frameIndex, out contextData);
graph.m_ResourcesForDebugOnly = resources;
graph.m_RenderPasses = renderPasses;
graph.disablePassCulling = debugParams.disablePassCulling;
graph.disablePassMerging = debugParams.disablePassMerging;
graph.debugName = debugName;
graph.renderTextureUVOriginStrategy = renderTextureUVOriginStrategy;
Clear(clearContextData: !useCompilationCaching);
return cached;
}
void HandleExtendedFeatureFlags()
{
for (int nativePassIndex = 0; nativePassIndex < contextData.nativePassData.Length; nativePassIndex++)
{
int firstNativeSubPass = contextData.nativePassData[nativePassIndex].firstNativeSubPass;
// Does this native pass have any sub passes.
if (firstNativeSubPass >= 0)
{
int firstGraphPass = contextData.nativePassData[nativePassIndex].firstGraphPass;
int graphPassIndex = 0;
for (int nativeSubPassIndex = 0; nativeSubPassIndex < contextData.nativePassData[nativePassIndex].numNativeSubPasses; nativeSubPassIndex++)
{
// Start with the MVPVV compatible flag set so that it can be used for the & operation later
SubPassFlags extendedSubPassFlags = SubPassFlags.MultiviewRenderRegionsCompatible;
// Iterate over all graph passes that got merged into this sub pass
while ((graphPassIndex < contextData.nativePassData[nativePassIndex].numGraphPasses) && (contextData.passData[graphPassIndex + firstGraphPass].nativeSubPassIndex == nativeSubPassIndex))
{
if (contextData.passData[graphPassIndex + firstGraphPass].extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.TileProperties))
{
extendedSubPassFlags |= SubPassFlags.TileProperties;
}
// A native sub pass is MultiviewRenderRegionsCompatible only if all of its graph passes are compatible
if (!contextData.passData[graphPassIndex + firstGraphPass].extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.MultiviewRenderRegionsCompatible))
{
extendedSubPassFlags &= ~SubPassFlags.MultiviewRenderRegionsCompatible;
}
graphPassIndex++;
}
contextData.nativeSubPassData.ElementAt(firstNativeSubPass + nativeSubPassIndex).flags |= extendedSubPassFlags;
}
}
}
}
public void Compile(RenderGraphResourceRegistry resources)
{
ValidatePasses();
SetupContextData(resources);
BuildGraph();
CullUnusedRenderGraphPasses();
TryMergeNativePasses();
HandleExtendedFeatureFlags();
FindResourceUsageRangeAndSynchronization();
DetectMemoryLessResources();
PrepareNativeRenderPasses();
if (graph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation)
PropagateTextureUVOrigin();
}
public void Clear(bool clearContextData)
{
if (clearContextData)
contextData.Clear();
m_HasSideEffectPassIdCullingStack.Clear();
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
m_UnusedVersionedResourceIdCullingStacks[type].Clear();
foreach (var resListPerPassId in m_DelayedLastUseListPerPassMap)
resListPerPassId.Value.Clear();
m_DelayedLastUseListPerPassMap.Clear();
}
void SetPassStatesForNativePass(int nativePassId)
{
NativePassData.SetPassStatesForNativePass(contextData, nativePassId);
}
internal enum NativeCompilerProfileId
{
NRPRGComp_PrepareNativePass,
NRPRGComp_SetupContextData,
NRPRGComp_BuildGraph,
NRPRGComp_CullNodes,
NRPRGComp_TryMergeNativePasses,
NRPRGComp_FindResourceUsageRanges,
NRPRGComp_DetectMemorylessResources,
NRPRGComp_PropagateTextureUVOrigin,
NRPRGComp_ExecuteInitializeResources,
NRPRGComp_ExecuteBeginRenderpassCommand,
NRPRGComp_ExecuteDestroyResources,
}
[Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
void ValidatePasses()
{
if (RenderGraph.enableValidityChecks)
{
int tilePropertiesPassIndex = -1;
for (int passId = 0; passId < graph.m_RenderPasses.Count; passId++)
{
if (graph.m_RenderPasses[passId].extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.TileProperties))
{
if (tilePropertiesPassIndex > -1)
{
throw new Exception($"ExtendedFeatureFlags.TileProperties can only be set once per render graph (render graph {graph.debugName}, pass {graph.m_RenderPasses[passId].name}), previously set at (pass {graph.m_RenderPasses[tilePropertiesPassIndex].name}).");
}
tilePropertiesPassIndex = passId;
}
}
}
}
void SetupContextData(RenderGraphResourceRegistry resources)
{
using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_SetupContextData)))
{
contextData.Initialize(resources, k_EstimatedPassCount);
}
}
// Returns true if the RasterFragmentList is successfully set up
bool TrySetupRasterFragmentList(ref PassData ctxPass, ref RenderGraphPass inputPass, out string errorMessage)
{
errorMessage = null;
var ctx = contextData;
// Grab offset in context fragment list to begin building the fragment list
ctxPass.firstFragment = ctx.fragmentData.Length;
// Depth attachment is always at index 0
if (inputPass.depthAccess.textureHandle.handle.IsValid())
{
ctxPass.fragmentInfoHasDepth = true;
if (ctx.TryAddToFragmentList(inputPass.depthAccess, ctxPass.firstFragment, ctxPass.numFragments, out errorMessage))
{
ctxPass.TryAddFragment(inputPass.depthAccess.textureHandle.handle, ctx, out errorMessage);
}
if (errorMessage != null)
{
errorMessage =
$"when trying to add depth attachment of type {inputPass.depthAccess.textureHandle.handle.type} at index {inputPass.depthAccess.textureHandle.handle.index} - {errorMessage}";
return false;
}
}
for (var ci = 0; ci < inputPass.colorBufferMaxIndex + 1; ++ci)
{
// Skip unused color slots
if (!inputPass.colorBufferAccess[ci].textureHandle.handle.IsValid()) continue;
if (ctx.TryAddToFragmentList(inputPass.colorBufferAccess[ci], ctxPass.firstFragment, ctxPass.numFragments, out errorMessage))
{
ctxPass.TryAddFragment(inputPass.colorBufferAccess[ci].textureHandle.handle, ctx, out errorMessage);
}
if (errorMessage != null)
{
errorMessage =
$"when trying to add render attachment of type {inputPass.colorBufferAccess[ci].textureHandle.handle.type} at index {inputPass.colorBufferAccess[ci].textureHandle.handle.index} - {errorMessage}";
return false;
}
}
// shading rate image - this is a specific type of attachment (more of an image resource that can't be sampled, only used by the rasterizer)
if (inputPass.hasShadingRateImage && inputPass.shadingRateAccess.textureHandle.handle.IsValid())
{
if (ctx.TryAddToFragmentList(inputPass.shadingRateAccess, ctxPass.firstFragment, ctxPass.numFragments, out errorMessage))
{
ctxPass.shadingRateImageIndex = ctx.fragmentData.Length - 1;
}
if (errorMessage != null)
{
errorMessage =
$"when trying to add VRS attachment of type {inputPass.shadingRateAccess.textureHandle.handle.type} at index {inputPass.shadingRateAccess.textureHandle.handle.index} - {errorMessage}";
return false;
}
}
// Grab offset in context fragment list to begin building the fragment input list
ctxPass.firstFragmentInput = ctx.fragmentData.Length;
for (var ci = 0; ci < inputPass.fragmentInputMaxIndex + 1; ++ci)
{
// Skip unused fragment input slots
if (!inputPass.fragmentInputAccess[ci].textureHandle.IsValid()) continue;
if (ctx.TryAddToFragmentList(inputPass.fragmentInputAccess[ci], ctxPass.firstFragmentInput, ctxPass.numFragmentInputs, out errorMessage))
{
ctxPass.TryAddFragmentInput(inputPass.fragmentInputAccess[ci].textureHandle.handle, ctx, out errorMessage);
}
if (errorMessage != null)
{
errorMessage =
$"when trying to add input attachment of type {inputPass.fragmentInputAccess[ci].textureHandle.handle.type} at index {inputPass.fragmentInputAccess[ci].textureHandle.handle.index} - {errorMessage}";
return false;
}
}
// Grab offset in context random write list to begin building the per pass random write lists
ctxPass.firstRandomAccessResource = ctx.randomAccessResourceData.Length;
for (var ci = 0; ci < inputPass.randomAccessResourceMaxIndex + 1; ++ci)
{
ref var uav = ref inputPass.randomAccessResource[ci];
// Skip unused random write slots
if (!uav.h.IsValid()) continue;
if (ctx.TryAddToRandomAccessResourceList(uav.h, ci, uav.preserveCounterValue, ctxPass.firstRandomAccessResource, ctxPass.numRandomAccessResources, out errorMessage))
{
ctxPass.AddRandomAccessResource();
}
if (errorMessage != null)
{
errorMessage = $"when trying to add random access attachment of type {uav.h.type} at index {uav.h.index} - {errorMessage}";
return false;
}
}
// This is suspicious, there are frame buffer fetch inputs but nothing is output. We don't allow this for now.
// In theory you could fb-fetch inputs and write something to a uav and output nothing? This needs to be investigated
// so don't allow it for now.
if (ctxPass.numFragments == 0)
{
Debug.Assert(ctxPass.numFragmentInputs == 0);
}
return true;
}
void BuildGraph()
{
var ctx = contextData;
List<RenderGraphPass> passes = graph.m_RenderPasses;
// Not clearing data, we will do it right after in the for loop
// This is to prevent unnecessary costly copies of pass struct (128bytes)
ctx.passData.ResizeUninitialized(passes.Count);
// Build up the context graph and keep track of nodes we encounter that can't be culled
using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_BuildGraph)))
{
for (int passId = 0; passId < passes.Count; passId++)
{
var inputPass = passes[passId];
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (inputPass.type == RenderGraphPassType.Legacy)
{
throw new Exception(RenderGraph.RenderGraphExceptionMessages.UsingLegacyRenderGraph(inputPass.name));
}
#endif
// Accessing already existing passData in place in the container through reference to avoid deep copy
// Make sure everything is reset and initialized or we will use obsolete data from previous frame
ref var ctxPass = ref ctx.passData.ElementAt(passId);
ctxPass.ResetAndInitialize(inputPass, passId);
ctx.passNames.Add(new Name(inputPass.name, true));
if (ctxPass.hasSideEffects)
{
m_HasSideEffectPassIdCullingStack.Push(passId);
}
// Set up the list of fragment attachments for this pass
// Note: This doesn't set up the resource reader/writer list as the fragment attachments
// will also be in the pass read/write lists accordingly
if (ctxPass.type == RenderGraphPassType.Raster)
{
if (!TrySetupRasterFragmentList(ref ctxPass, ref inputPass, out var errorMessage))
{
throw new Exception($"In pass '{inputPass.name}', {errorMessage}");
}
}
// Set up per resource type read/write lists for this pass
ctxPass.firstInput = ctx.inputData.Length; // Grab offset in context input list
ctxPass.firstOutput = ctx.outputData.Length; // Grab offset in context output list
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var resourceWrite = inputPass.resourceWriteLists[type];
var resourceWriteCount = resourceWrite.Count;
for (var i = 0; i < resourceWriteCount; ++i)
{
var resource = resourceWrite[i];
// Writing to an imported resource is a side effect so mark the pass if needed
ref var resData = ref ctx.UnversionedResourceData(resource);
if (resData.isImported)
{
if (!ctxPass.hasSideEffects)
{
ctxPass.hasSideEffects = true;
m_HasSideEffectPassIdCullingStack.Push(passId);
}
}
// Mark this pass as writing to this version of the resource
ctx.resources[resource].SetWritingPass(ctx, resource, passId);
ctx.outputData.Add(new PassOutputData(resource));
ctxPass.numOutputs++;
}
var resourceRead = inputPass.resourceReadLists[type];
var resourceReadCount = resourceRead.Count;
for (var i = 0; i < resourceReadCount; ++i)
{
var resource = resourceRead[i];
// Mark this pass as reading from this version of the resource
ctx.resources[resource].RegisterReadingPass(ctx, resource, passId, ctxPass.numInputs);
ctx.inputData.Add(new PassInputData(resource));
ctxPass.numInputs++;
}
var resourceTrans = inputPass.transientResourceList[type];
var resourceTransCount = resourceTrans.Count;
for (var i = 0; i < resourceTransCount; ++i)
{
var resource = resourceTrans[i];
// Mark this pass as reading from this version of the resource
ctx.resources[resource].RegisterReadingPass(ctx, resource, passId, ctxPass.numInputs);
ctx.inputData.Add(new PassInputData(resource));
ctxPass.numInputs++;
// Mark this pass as writing to this version of the resource
ctx.resources[resource].SetWritingPass(ctx, resource, passId);
ctx.outputData.Add(new PassOutputData(resource));
ctxPass.numOutputs++;
}
// For raster passes, we do an extra step to monitor textures sampled in the pass
// It can be a breaking change reason later on when building a native render pass
if (type == (int)RenderGraphResourceType.Texture && ctxPass.type == RenderGraphPassType.Raster)
{
ctxPass.firstSampledOnlyRaster = ctx.sampledData.Length;
foreach (ref readonly var input in ctxPass.Inputs(ctx))
{
// Check if this input is the shading rate image
if (!ctxPass.IsUsedAsFragment(input.resource, ctx))
{
ctx.sampledData.Add(input.resource);
ctxPass.numSampledOnlyRaster++;
}
}
}
}
}
}
}
void CullUnusedRenderGraphPasses()
{
using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_CullNodes)))
{
if (graph.disablePassCulling)
return;
// Must come first
// TODO make another subfunction CullRenderGraphPassesWithNoSideEffect() for this first step of the culling stage
var ctx = contextData;
// Cull all passes first
ctx.CullAllPasses(true);
// Flood fill downstream algorithm using BFS,
// starting from the passes with side effects (writting to imported texture, not allowed to be culled, globals modification...)
// to all their dependencies
while (m_HasSideEffectPassIdCullingStack.Count != 0)
{
int passId = m_HasSideEffectPassIdCullingStack.Pop();
ref var passData = ref ctx.passData.ElementAt(passId);
// We already found this node through another dependency chain
if (!passData.culled) continue;
// Flow upstream from this node
foreach (ref readonly var input in passData.Inputs(ctx))
{
ref var inputVersionedDataRes = ref ctx.resources[input.resource];
if (inputVersionedDataRes.written)
{
m_HasSideEffectPassIdCullingStack.Push(inputVersionedDataRes.writePassId);
}
}
// We need this node, don't cull it
passData.culled = false;
}
// Update graph based on freshly culled nodes, remove any connection to them
// We start from the latest passes to the first ones as we might need to decrement the version number of unwritten resources
var numPasses = ctx.passData.Length;
for (int passIndex = numPasses - 1; passIndex >= 0; passIndex--)
{
ref readonly var pass = ref ctx.passData.ElementAt(passIndex);
// Remove the connections from the list so they won't be visited again
if (pass.culled)
{
pass.DisconnectFromResources(ctx);
}
}
// Second step of the algorithm, must come after
// TODO: The resources culling step is currently disabled due to an issue: https://jira.unity3d.com/projects/SRP/issues/SRP-897
// Renabled the resource culling step after addressing the depth attachment problem above.
// Renabled the relevent tests
// CullRenderGraphPassesWritingOnlyUnusedResources();
}
}
void CullRenderGraphPassesWritingOnlyUnusedResources()
{
var ctx = contextData;
var numPasses = ctx.passData.Length;
for (int passIndex = 0; passIndex < numPasses; passIndex++)
{
ref var passData = ref ctx.passData.ElementAt(passIndex);
// Use the generic tag to monitor the number of written resources that are used
passData.tag = passData.numOutputs;
// Find all resources that are written by a pass but not read at all and add them to the stacks
foreach (ref readonly var output in passData.Outputs(ctx))
{
ref readonly var outputResource = ref output.resource;
ref var outputVersionedDataRes = ref ctx.resources[outputResource];
if (outputVersionedDataRes.numReaders == 0)
m_UnusedVersionedResourceIdCullingStacks[outputResource.iType].Push(outputResource);
}
}
// Go through each stack of unused resources and try to cull their producer
for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type)
{
var unusedVersionedResourceIdCullingStack = m_UnusedVersionedResourceIdCullingStacks[type];
// Goal is to find the producers of the unused resources and culled them if they only write to unused resources
while (unusedVersionedResourceIdCullingStack.Count != 0)
{
var unusedResource = unusedVersionedResourceIdCullingStack.Pop();
ref var unusedUnversionedDataRes = ref ctx.resources.unversionedData[type].ElementAt(unusedResource.index);
if (unusedUnversionedDataRes.isImported) continue; // Not always unused as someone can read it outside the graph
ref var unusedVersionedDataRes = ref ctx.resources[unusedResource];
ref var producerData = ref ctx.passData.ElementAt(unusedVersionedDataRes.writePassId);
if (producerData.culled) continue; // Producer has been culled already
// Decrement the number of written resources that are used for this pass
producerData.tag--;
Debug.Assert(producerData.tag >= 0);
// Producer is not necessary anymore, as it only writes to unused resources and has no side effects
if (producerData.tag == 0 && !producerData.hasSideEffects)
{
producerData.culled = true;
producerData.DisconnectFromResources(ctx, unusedVersionedResourceIdCullingStack, type);
}
else // Producer is (still) necessary, but we might need to remove the read of the previous version coming implicitly with the write of the current version
{
// We always add written resource to the stack so versionedIndex > 0
var prevVersionedRes = new ResourceHandle(unusedResource, unusedResource.version - 1);
// If no explicit read is requested by the user (AccessFlag.Write only), we need to remove the implicit read
// so that we cut cleanly the connection between previous version of the resource and current producer
bool isImplicitRead = graph.m_RenderPasses[producerData.passId].implicitReadsList.Contains(prevVersionedRes);
if (isImplicitRead)
{
ref var prevVersionedDataRes = ref ctx.resources[prevVersionedRes];
// Notify the previous version of this resource that it is not read anymore by this pass
prevVersionedDataRes.RemoveReadingPass(ctx, prevVersionedRes, producerData.passId);
// We also need to add the previous version of the resource to the stack IF no other pass than current producer needed it
if (prevVersionedDataRes.written && prevVersionedDataRes.numReaders == 0)
{
unusedVersionedResourceIdCullingStack.Push(prevVersionedRes);
}
}
}
}
}
}
void TryMergeNativePasses()
{
var ctx = contextData;
using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_TryMergeNativePasses)))
{
// Try to merge raster passes into the currently active native pass. This will not do pass reordering yet
// so it is simply greedy trying to add the next pass to a currently active one.
// In the future we want to try adding any available (i.e. that has no data dependencies on any future results) future pass
// and allow merging that into the pass thus greedily reordering passes. But reordering requires a lot of API validation
// that ensures rendering behaves accordingly with reordered passes so we don't allow that for now.
// !!! Compilation caching warning !!!
// Merging of passes is highly dependent on render texture properties.
// When caching the render graph compilation, we hash a subset of those render texture properties to make sure we recompile the graph if needed.
// We only hash a subset for performance reason so if you add logic here that will change the behavior of pass merging,
// make sure that the relevant properties are hashed properly. See RenderGraphPass.ComputeHash()
int activeNativePassId = -1;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
bool generatePassBreakAudits = RenderGraphDebugSession.hasActiveDebugSession || s_ForceGenerateAuditsForTests;
#endif
for (var passIdx = 0; passIdx < ctx.passData.Length; ++passIdx)
{
ref var passToAdd = ref ctx.passData.ElementAt(passIdx);
// If the pass has been culled, just ignore it
if (passToAdd.culled)
{
continue;
}
// If no active pass, there is nothing to merge...
if (activeNativePassId == -1)
{
//If raster, start a new native pass with the current pass
if (passToAdd.type == RenderGraphPassType.Raster)
{
// Allocate a stand-alone native renderpass based on the current pass
ctx.nativePassData.Add(new NativePassData(ref passToAdd, ctx));
passToAdd.nativePassIndex = ctx.nativePassData.LastIndex();
activeNativePassId = passToAdd.nativePassIndex;
}
}
// There is an native pass currently open, try to add the current graph pass to it
else
{
var mergeTestResult = graph.disablePassMerging ? new PassBreakAudit(PassBreakReason.PassMergingDisabled, passIdx)
: NativePassData.TryMerge(contextData, activeNativePassId, passIdx);
// Merge failed, close current native render pass and create a new one
if (mergeTestResult.reason != PassBreakReason.Merged)
{
SetPassStatesForNativePass(activeNativePassId);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (generatePassBreakAudits)
{
ref var nativePassData = ref contextData.nativePassData.ElementAt(activeNativePassId);
nativePassData.breakAudit = mergeTestResult;
}
#endif
if (mergeTestResult.reason == PassBreakReason.NonRasterPass)
{
// Non-raster pass, no active native pass at all
activeNativePassId = -1;
}
else
{
// Raster but cannot be merged, allocate a new stand-alone native renderpass based on the current pass
ctx.nativePassData.Add(new NativePassData(ref passToAdd, ctx));
passToAdd.nativePassIndex = ctx.nativePassData.LastIndex();
activeNativePassId = passToAdd.nativePassIndex;
}
}
}
}
if (activeNativePassId >= 0)
{
// "Close" the last native pass by marking the last graph pass as end
SetPassStatesForNativePass(activeNativePassId);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (generatePassBreakAudits)
{
ref var nativePassData = ref contextData.nativePassData.ElementAt(activeNativePassId);
nativePassData.breakAudit = new PassBreakAudit(PassBreakReason.EndOfGraph, -1);
}
#endif
}
}
}
bool FindFirstPassIdOnGraphicsQueueAwaitingFenceGoingForward(ref PassData startAsyncPass, out int firstPassIdAwaiting)
{
var ctx = contextData;
Debug.Assert(startAsyncPass.asyncCompute && !startAsyncPass.culled);
firstPassIdAwaiting = startAsyncPass.awaitingMyGraphicsFencePassId;
// This async pass has no one waiting for it, try the next async passes
if (firstPassIdAwaiting == -1)
{
var nextPassIndex = startAsyncPass.passId + 1;
var lastPassIndex = ctx.passData.Length - 1;
// Find the first async pass that is synchronized by the graphics queue
while (firstPassIdAwaiting == -1 && nextPassIndex <= lastPassIndex)
{
ref var nextPass = ref ctx.passData.ElementAt(nextPassIndex);
if (nextPass.asyncCompute && !nextPass.culled)
firstPassIdAwaiting = nextPass.awaitingMyGraphicsFencePassId;
nextPassIndex++;
}
// We didn't find any fence, this should not happen?
if (nextPassIndex > lastPassIndex)
{
// For now we fallback to the last pass of the graph
firstPassIdAwaiting = lastPassIndex;
return false;
}
}
// Found a pass awaiting a fence
return true;
}
int FindFirstNonCulledPassIdGoingBackward(int startPassId, bool startPassIsIncluded)
{
var ctx = contextData;
Debug.Assert(startPassId >= 0 && startPassId < ctx.passData.Length);
var currPassId = startPassIsIncluded ? startPassId : Math.Max(0, startPassId - 1);
ref var currPass = ref ctx.passData.ElementAt(currPassId);
// If this pre pass is culled, fallback to the first previous one not culled
while (currPass.culled && currPassId > 0)
{
currPass = ref ctx.passData.ElementAt(--currPassId);
}
return currPass.passId;
}
void FindResourceUsageRangeAndSynchronization()
{
var ctx = contextData;
using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_FindResourceUsageRanges)))
{
// Algorithm is in two steps, traversing the list of passes twice
// First forward traversal:
// - we find the passes that first use a resource
// - we increase the refcount of the last version for each resource used
// - we find where fences must be added in case of async compute/gfx queues and the related async pass dependencies
for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++)
{
ref var pass = ref ctx.passData.ElementAt(passIndex);
if (pass.culled)
continue;
// In case of an async pass, we need to extend the lifetime of the resource to the first pass on the graphics queue that waits for this async pass to be completed.
// By doing so, we ensure that the resource will not be released back to the pool right after adding the async pass commands to the async queue,
// as it could create a data race condition if a non async pass reuses the resource from the pool while the async pass is still processing it
// As contextData must be filled incrementally per pass, we store temporarily these delayed releases in a list.
// Here we clear this list before using it later in the second foward traversal.
ClearDelayedLastUseListAtPass(passIndex);
pass.waitOnGraphicsFencePassId = -1;
pass.awaitingMyGraphicsFencePassId = -1;
pass.insertGraphicsFence = false;
// Loop over all the resources this pass needs (=inputs)
foreach (ref readonly var input in pass.Inputs(ctx))
{
var inputResource = input.resource;
ref var pointTo = ref ctx.UnversionedResourceData(inputResource);
ref var pointToVer = ref ctx.VersionedResourceData(inputResource);
pointTo.lastUsePassID = -1;
// If nobody else is using it yet,
// mark this pass as the first using the resource.
// It can happen that two passes use v0, e.g.:
// pass1.UseTex(v0,Read) -> this will clear the pass but keep it at v0
// pass2.UseTex(v0,Read) -> "reads" v0
if (pointTo.firstUsePassID < 0)
{
pointTo.firstUsePassID = pass.passId;
pass.AddFirstUse(inputResource, ctx);
}
// This pass uses the last version of a resource increase the ref count of this resource
var last = pointTo.latestVersionNumber;
if (last == inputResource.version)
{
pointTo.tag++; //Refcount of how many passes are using the last version of a resource
}
// Verify if this pass needs to wait on a fence due to its inputs
// If no RG pass writes to the resource, no need to wait for anyone
if (pointToVer.written)
{
ref var writingPass = ref ctx.passData.ElementAt(pointToVer.writePassId);
if (writingPass.asyncCompute != pass.asyncCompute)
{
// Find the last pass on the opposite queue that the current pass must wait on
var currWaitForPassId = pass.waitOnGraphicsFencePassId;
pass.waitOnGraphicsFencePassId = Math.Max(writingPass.passId, currWaitForPassId);
}
}
}
//Also look at outputs (but with version 1) for edge case were we do a Write (but no read) to a texture and the pass is manually excluded from culling
//As it isn't read it won't be in the inputs array with V0
foreach (ref readonly var output in pass.Outputs(ctx))
{
var outputResource = output.resource;
ref var pointTo = ref ctx.UnversionedResourceData(outputResource);
ref var pointToVer = ref ctx.VersionedResourceData(outputResource);
// If nobody else is using it yet (no explicit read),
// Mark this pass as the first using the resource.
// It can happen that two passes use v0, e.g.:
// pass1.UseTex(v0, Write) -> implicit read of v0, writes v1 - culled because none explicitly reads v1
// pass3.UseTex(v1, Write) -> implicit read of v1, writes v2 - not culled because of unrelated reason
if (pointTo.firstUsePassID < 0)
{
pointTo.firstUsePassID = pass.passId;
pass.AddFirstUse(outputResource, ctx);
}
// This pass outputs the last version of a resource track that
var last = pointTo.latestVersionNumber;
if (last == outputResource.version)
{
Debug.Assert(pointTo.lastWritePassID == -1); // Only one can be the last writer
pointTo.lastWritePassID = pass.passId;
}
// Resolve if this pass should insert a fence for its outputs
var numReaders = pointToVer.numReaders;
for (var i = 0; i < numReaders; ++i)
{
var readerIndex = ctx.resources.IndexReader(outputResource, i);
ref var readerData = ref ctx.resources.readerData[outputResource.iType].ElementAt(readerIndex);
ref var readerPass = ref ctx.passData.ElementAt(readerData.passId);
if (pass.asyncCompute != readerPass.asyncCompute)
{
// A subsequent pass on the opposite queue will read this resource written by the current pass,
// so this subsequent pass needs to wait for the completion of the current pass
// to do so, the current pass will insert a fence on its queue after its execution
pass.insertGraphicsFence = true;
// Different async passes can wait for different resources
// Find the first pass on the opposite queue that will wait for this fence
var currFirstPassId = pass.awaitingMyGraphicsFencePassId;
pass.awaitingMyGraphicsFencePassId = currFirstPassId == -1 ? readerData.passId : Math.Min(currFirstPassId, readerData.passId);
}
}
}
}
// Second forward traversal:
// - we decrease the refcount to detect which is the last pass using the last version of a resource, i.e when we can release it
// - in case of async processing, we must delay the release to the first pass on gfx queue waiting for a fence
for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++)
{
ref var pass = ref ctx.passData.ElementAt(passIndex);
if (pass.culled)
continue;
bool isAsync = pass.asyncCompute;
foreach (ref readonly var input in pass.Inputs(ctx))
{
var inputResource = input.resource;
ref var pointTo = ref ctx.UnversionedResourceData(inputResource);
var last = pointTo.latestVersionNumber;
if (last == inputResource.version)
{
var refC = pointTo.tag - 1; //Decrease refcount this pass is done using it
if (refC == 0) // We're the last pass done using it, this pass should destroy it.
{
if (isAsync)
{
// If no fence found, we fallback to the last non culled pass of the graph on graphics queue, not ideal but safe
bool foundFence = FindFirstPassIdOnGraphicsQueueAwaitingFenceGoingForward(ref pass, out int firstWaitingOrLastPassId);
var delayLastUsedPassId = FindFirstNonCulledPassIdGoingBackward(firstWaitingOrLastPassId, !foundFence);
pointTo.lastUsePassID = delayLastUsedPassId;
AddDelayedLastUseToPass(inputResource, delayLastUsedPassId);
}
else
{
pointTo.lastUsePassID = pass.passId;
pass.AddLastUse(inputResource, ctx);
}
}
pointTo.tag = refC;
}
}
// We're outputting a resource that is never used.
// This can happen if this pass has multiple outputs and only a portion of them are used
// as some are used, the whole pass is not culled but the unused output still should be freed
foreach (ref readonly var output in pass.Outputs(ctx))
{
var outputResource = output.resource;
ref var pointTo = ref ctx.UnversionedResourceData(outputResource);
ref var pointToVer = ref ctx.VersionedResourceData(outputResource);
var last = pointTo.latestVersionNumber;
if (last == outputResource.version && pointToVer.numReaders == 0)
{
if (isAsync)
{
// If no fence found, we fallback to the last non culled pass of the graph, not ideal but safe
bool foundFence = FindFirstPassIdOnGraphicsQueueAwaitingFenceGoingForward(ref pass, out int firstWaitingOrLastPassId);
var delayLastUsedPassId = FindFirstNonCulledPassIdGoingBackward(firstWaitingOrLastPassId, !foundFence);
pointTo.lastUsePassID = delayLastUsedPassId;
AddDelayedLastUseToPass(outputResource, delayLastUsedPassId);
}
else
{
pointTo.lastUsePassID = pass.passId;
pass.AddLastUse(outputResource, ctx);
}
}
}
// Add any potential delayed resource releases to the contextData
AddLastUseFromDelayedList(ref pass);
}
}
}
void ClearDelayedLastUseListAtPass(int passId)
{
if (m_DelayedLastUseListPerPassMap.TryGetValue(passId, out var lastUseListForPassId))
{
lastUseListForPassId.Clear();
}
}
void AddDelayedLastUseToPass(in ResourceHandle releaseResource, int passId)
{
if (!m_DelayedLastUseListPerPassMap.TryGetValue(passId, out var lastUseListForPassId))
{
lastUseListForPassId = new List<ResourceHandle>();
m_DelayedLastUseListPerPassMap.Add(passId, lastUseListForPassId);
}
lastUseListForPassId.Add(releaseResource);
}
public void AddLastUseFromDelayedList(ref PassData passData)
{
if (m_DelayedLastUseListPerPassMap.TryGetValue(passData.passId, out var lastUseListForPassId))
{
foreach (var resource in lastUseListForPassId)
{
passData.AddLastUse(resource, contextData);
}
lastUseListForPassId.Clear();
}
}
void PrepareNativeRenderPasses()
{
// Prepare all native render pass execution info:
for (var passIdx = 0; passIdx < contextData.nativePassData.Length; ++passIdx)
{
ref var nativePassData = ref contextData.nativePassData.ElementAt(passIdx);
DetermineLoadStoreActions(ref nativePassData);
}
}
void PropagateTextureUVOrigin()
{
using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_PropagateTextureUVOrigin)))
{