Skip to content

Commit 0a12d40

Browse files
RoseHirigoyenEvergreen
authored andcommitted
UUM-65454 MaxSubpass discrepancy with RenderPassSetup and NativePassCompiler
JIRA: https://jira.unity3d.com/browse/UUM-65454 When merging subpasses together in a NativePass, we don't check if there will be more than 8 native render subpasses. This PR adds a check for this when verifying if a certain graph pass can be merged into a Native Pass, as well as a test. If merging the graph pass would result in too many native subpasses, we simply create a new Native Pass
1 parent f8b3cb2 commit 0a12d40

File tree

3 files changed

+210
-8
lines changed

3 files changed

+210
-8
lines changed

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/NativePassCompiler.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ internal struct RenderGraphInputInfo
2727

2828
internal const int k_EstimatedPassCount = 100;
2929
internal const int k_EstimatedResourceCountPerType = 50;
30+
internal const int k_MaxSubpass = 8; // Needs to match with RenderPassSetup.h
3031

3132
NativeList<AttachmentDescriptor> m_BeginRenderPassAttachments;
3233

@@ -662,7 +663,7 @@ void DetectMemoryLessResources()
662663
}
663664
}
664665

665-
private bool IsSameNativeSubPass(ref SubPassDescriptor a, ref SubPassDescriptor b)
666+
internal static bool IsSameNativeSubPass(ref SubPassDescriptor a, ref SubPassDescriptor b)
666667
{
667668
if (a.flags != b.flags
668669
|| a.colorOutputs.Length != b.colorOutputs.Length
@@ -1250,7 +1251,7 @@ internal unsafe void ExecuteBeginRenderPass(InternalRenderGraphContext rgContext
12501251
var graphPassNamesForDebugSpan = ReadOnlySpan<byte>.Empty;
12511252
#if DEVELOPMENT_BUILD || UNITY_EDITOR
12521253
if(RenderGraph.enableValidityChecks)
1253-
{
1254+
{
12541255
graphPassNamesForDebug.Clear();
12551256

12561257
nativePass.GetGraphPassNames(contextData, graphPassNamesForDebug);

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/PassesData.cs

Lines changed: 179 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal struct PassInputData
1515
}
1616

1717
// Per pass info on outputs to the pass
18-
[DebuggerDisplay("PassOutputData: Res({resource.index})")]
18+
[DebuggerDisplay("PassOutputData: Res({resource.index})")]
1919
internal struct PassOutputData
2020
{
2121
public ResourceHandle resource;
@@ -101,7 +101,7 @@ internal struct PassData
101101
public int fragmentInfoHeight;
102102
public int fragmentInfoVolumeDepth;
103103
public int fragmentInfoSamples;
104-
104+
105105
public int waitOnGraphicsFencePassId; // -1 if no fence wait is needed, otherwise the passId to wait on
106106

107107
public bool asyncCompute;
@@ -460,6 +460,7 @@ internal enum PassBreakReason
460460
NonRasterPass, // The next pass is a non-raster pass
461461
DifferentDepthTextures, // The next pass uses a different depth texture (and we only allow one in a whole NRP)
462462
AttachmentLimitReached, // Adding the next pass would have used more attachments than allowed
463+
SubPassLimitReached, // Addind the next pass would have generated more subpasses than allowed
463464
EndOfGraph, // The last pass in the graph was reached
464465
FRStateMismatch, // One pass is using foveated rendering and the other not
465466
Merged, // I actually got merged
@@ -491,6 +492,7 @@ public PassBreakAudit(PassBreakReason reason, int breakPass)
491492
"The next pass is not a raster render pass.",
492493
"The next pass uses a different depth buffer. All passes in the native render pass need to use the same depth buffer.",
493494
$"The limit of {FixedAttachmentArray<PassFragmentData>.MaxAttachments} native pass attachments would be exceeded when merging with the next pass.",
495+
$"The limit of {NativePassCompiler.k_MaxSubpass} native subpasses would be exceeded when merging with the next pass.",
494496
"This is the last pass in the graph, there are no other passes to merge.",
495497
"The the next pass uses a different foveated rendering state",
496498
"The next pass got merged into this pass.",
@@ -536,7 +538,7 @@ public NativePassData(ref PassData pass, CompilerContextData ctx)
536538
samples = pass.fragmentInfoSamples;
537539
hasDepth = pass.fragmentInfoHasDepth;
538540
hasFoveatedRasterization = pass.hasFoveatedRasterization;
539-
541+
540542
loadAudit = new FixedAttachmentArray<LoadAudit>();
541543
storeAudit = new FixedAttachmentArray<StoreAudit>();
542544
breakAudit = new PassBreakAudit(PassBreakReason.NotOptimized, -1);
@@ -651,7 +653,7 @@ public static PassBreakAudit CanMerge(CompilerContextData contextData, int activ
651653
// Gather which attachments to add to the current renderpass
652654
var attachmentsToTryAdding = new FixedAttachmentArray<PassFragmentData>();
653655

654-
// We can't have more than the maximum amout of attachments in a given native renderpass
656+
// We can't have more than the maximum amount of attachments in a given native renderpass
655657
int currAvailableAttachmentSlots = FixedAttachmentArray<PassFragmentData>.MaxAttachments - nativePass.fragments.size;
656658

657659
foreach (ref readonly var fragment in passToMerge.Fragments(contextData))
@@ -714,10 +716,183 @@ public static PassBreakAudit CanMerge(CompilerContextData contextData, int activ
714716
}
715717
}
716718

719+
if (CountNativePassesAfterMerge(contextData, nativePass, passToMerge) > NativePassCompiler.k_MaxSubpass)
720+
{
721+
return new PassBreakAudit(PassBreakReason.SubPassLimitReached, passIdToMerge);
722+
}
723+
717724
// All is good! Pass can be merged into active native pass
718725
return new PassBreakAudit(PassBreakReason.Merged, passIdToMerge);
719726
}
720727

728+
// This function must follow the implementation of NativePassCompiler.PrepareNativeRenderPass
729+
static int CountNativePassesAfterMerge(CompilerContextData contextData, NativePassData nativePass, PassData passToMerge)
730+
{
731+
var combinedFragmentList = nativePass.fragments;
732+
733+
// Depth needs special handling if the native pass doesn't have depth and merges with a pass that does
734+
// as we require the depth attachment to be at index 0
735+
if (!nativePass.hasDepth && passToMerge.fragmentInfoHasDepth)
736+
{
737+
nativePass.hasDepth = true;
738+
combinedFragmentList.Add(contextData.fragmentData[passToMerge.firstFragment]);
739+
var size = combinedFragmentList.size;
740+
if (size > 1)
741+
(combinedFragmentList[0], combinedFragmentList[size-1]) = (combinedFragmentList[size-1], combinedFragmentList[0]);
742+
}
743+
744+
// Update versions and flags of existing attachments and
745+
// add any new attachments
746+
foreach (ref readonly var newAttach in passToMerge.Fragments(contextData))
747+
{
748+
bool alreadyAttached = false;
749+
750+
for (int i = 0; i < combinedFragmentList.size; ++i)
751+
{
752+
ref var existingAttach = ref combinedFragmentList[i];
753+
if (existingAttach.resource.index == newAttach.resource.index)
754+
{
755+
// Update the attached version access flags and version
756+
existingAttach.accessFlags |= newAttach.accessFlags;
757+
existingAttach.resource.version = newAttach.resource.version;
758+
alreadyAttached = true;
759+
break;
760+
}
761+
}
762+
763+
if (!alreadyAttached)
764+
{
765+
combinedFragmentList.Add(newAttach);
766+
}
767+
}
768+
769+
foreach (ref readonly var newAttach in passToMerge.FragmentInputs(contextData))
770+
{
771+
bool alreadyAttached = false;
772+
773+
for (int i = 0; i < combinedFragmentList.size; ++i)
774+
{
775+
ref var existingAttach = ref combinedFragmentList[i];
776+
if (existingAttach.resource.index == newAttach.resource.index)
777+
{
778+
// Update the attached version access flags and version
779+
existingAttach.accessFlags |= newAttach.accessFlags;
780+
existingAttach.resource.version = newAttach.resource.version;
781+
alreadyAttached = true;
782+
break;
783+
}
784+
}
785+
786+
if (!alreadyAttached)
787+
{
788+
combinedFragmentList.Add(newAttach);
789+
}
790+
}
791+
792+
int numNativeSubpasses = 0;
793+
794+
// Fill out the subpass descriptors for the native renderpasses
795+
// NOTE: Not all graph subpasses get an actual native pass:
796+
// - There could be passes that do only non-raster ops (like setglobal) and have no attachments. They don't get a native pass
797+
// - Renderpasses that use exactly the same rendertargets at the previous pass use the same native pass. This is because
798+
// nextSubpass is expensive on some platforms (even if its' essentially a no-op as it's using the same attachments).
799+
SubPassDescriptor lastPass = new SubPassDescriptor();
800+
801+
for (var graphPassIndex = 0; graphPassIndex < nativePass.numGraphPasses + 1; ++graphPassIndex)
802+
{
803+
SubPassDescriptor desc = new SubPassDescriptor();
804+
var graphPass = graphPassIndex < nativePass.numGraphPasses
805+
? contextData.passData.ElementAt(nativePass.firstGraphPass + graphPassIndex)
806+
: passToMerge;
807+
808+
809+
// We have no output attachments, this is an "empty" raster pass doing only non-rendering command so skip it.
810+
if (graphPass.numFragments == 0)
811+
{
812+
continue;
813+
}
814+
815+
// If depth ends up being bound only because of merging we explicitly say that we will not write to it
816+
// which could have been implied by leaving the flag to None
817+
if (!graphPass.fragmentInfoHasDepth && nativePass.hasDepth)
818+
{
819+
desc.flags = SubPassFlags.ReadOnlyDepth;
820+
}
821+
822+
// MRT attachments
823+
{
824+
int fragmentIdx = 0;
825+
int colorOffset = (graphPass.fragmentInfoHasDepth) ? -1 : 0;
826+
827+
desc.colorOutputs = new AttachmentIndexArray(graphPass.numFragments + colorOffset);
828+
829+
foreach (ref readonly var fragment in graphPass.Fragments(contextData))
830+
{
831+
// Check if we're handling the depth attachment
832+
if (graphPass.fragmentInfoHasDepth && fragmentIdx == 0)
833+
{
834+
desc.flags = (fragment.accessFlags.HasFlag(AccessFlags.Write))
835+
? SubPassFlags.None
836+
: SubPassFlags.ReadOnlyDepth;
837+
}
838+
839+
// It's a color attachment
840+
else
841+
{
842+
// Find the index of this subpass's attachment in the native renderpass attachment list
843+
int colorAttachmentIdx = -1;
844+
for (int fragmentId = 0; fragmentId < combinedFragmentList.size; ++fragmentId)
845+
{
846+
if (combinedFragmentList[fragmentId].resource.index == fragment.resource.index)
847+
{
848+
colorAttachmentIdx = fragmentId;
849+
break;
850+
}
851+
}
852+
853+
// Set up the color indexes
854+
desc.colorOutputs[fragmentIdx + colorOffset] = colorAttachmentIdx;
855+
}
856+
857+
fragmentIdx++;
858+
}
859+
}
860+
861+
// FB-fetch attachments
862+
{
863+
int inputIndex = 0;
864+
865+
desc.inputs = new AttachmentIndexArray(graphPass.numFragmentInputs);
866+
867+
foreach (ref readonly var fragmentInput in graphPass.FragmentInputs(contextData))
868+
{
869+
// Find the index of this subpass's attachment in the native renderpass attachment list
870+
int inputAttachmentIdx = -1;
871+
for (int fragmentId = 0; fragmentId < combinedFragmentList.size; ++fragmentId)
872+
{
873+
if (combinedFragmentList[fragmentId].resource.index == fragmentInput.resource.index)
874+
{
875+
inputAttachmentIdx = fragmentId;
876+
break;
877+
}
878+
}
879+
880+
desc.inputs[inputIndex] = inputAttachmentIdx;
881+
inputIndex++;
882+
}
883+
}
884+
885+
// Check if we can merge the native sub pass with the previous one
886+
if (numNativeSubpasses == 0 || !NativePassCompiler.IsSameNativeSubPass(ref desc, ref lastPass))
887+
{
888+
lastPass = desc;
889+
numNativeSubpasses++;
890+
}
891+
}
892+
893+
return numNativeSubpasses;
894+
}
895+
721896
public static PassBreakAudit TryMerge(CompilerContextData contextData, int activeNativePassId, int passIdToMerge)
722897
{
723898
var passBreakAudit = CanMerge(contextData, activeNativePassId, passIdToMerge);

Packages/com.unity.render-pipelines.core/Tests/Editor/NativePassCompilerRenderGraphTests.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,32 @@ public void TooManyAttachmentsBreaksPass()
411411
Assert.AreEqual(Rendering.RenderGraphModule.NativeRenderPassCompiler.PassBreakReason.AttachmentLimitReached, passes[0].breakAudit.reason);
412412
}
413413

414+
[Test]
415+
public void NativeSubPassesLimitNotExceeded()
416+
{
417+
var g = AllocateRenderGraph();
418+
var buffers = ImportAndCreateBuffers(g);
419+
420+
// Native subpasses limit is 8 so go above
421+
for (int i = 0; i < Rendering.RenderGraphModule.NativeRenderPassCompiler.NativePassCompiler.k_MaxSubpass + 2; i++)
422+
{
423+
using var builder = g.AddRasterRenderPass<RenderGraphTestPassData>($"TestPass_{i}", out var passData);
424+
builder.SetInputAttachment(buffers.extraBuffers[1 - i % 2], 0);
425+
426+
builder.SetRenderAttachmentDepth(buffers.depthBuffer);
427+
builder.SetRenderAttachment(buffers.extraBuffers[i % 2], 1);
428+
429+
builder.SetRenderFunc((RenderGraphTestPassData data, RasterGraphContext context) => { });
430+
}
431+
432+
var result = g.CompileNativeRenderGraph(g.ComputeGraphHash());
433+
var passes = result.contextData.GetNativePasses();
434+
435+
Assert.AreEqual(2, passes.Count);
436+
Assert.AreEqual(Rendering.RenderGraphModule.NativeRenderPassCompiler.NativePassCompiler.k_MaxSubpass, passes[0].numGraphPasses);
437+
Assert.AreEqual(Rendering.RenderGraphModule.NativeRenderPassCompiler.PassBreakReason.SubPassLimitReached, passes[0].breakAudit.reason);
438+
}
439+
414440
[Test]
415441
public void AllocateFreeInMergedPassesWorks()
416442
{
@@ -814,7 +840,7 @@ public void ResolveMSAAImportColor()
814840
importInfoDepth.msaaSamples = 4;
815841
importInfoDepth.volumeDepth = 1;
816842
importInfoDepth.format = GraphicsFormat.D32_SFloat_S8_UInt;
817-
843+
818844
ImportResourceParams importResourceParams = new ImportResourceParams();
819845
importResourceParams.clearOnFirstUse = true;
820846
importResourceParams.discardOnLastUse = true;
@@ -846,7 +872,7 @@ public void ResolveMSAAImportColor()
846872
var result = g.CompileNativeRenderGraph(g.ComputeGraphHash());
847873
var passes = result.contextData.GetNativePasses();
848874

849-
// Validate nr pass
875+
// Validate nr pass
850876
Assert.AreEqual(1, passes.Count);
851877
Assert.AreEqual(2, passes[0].attachments.size);
852878
Assert.AreEqual(1, passes[0].numGraphPasses);

0 commit comments

Comments
 (0)