@@ -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 ) ;
0 commit comments