You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1010 lines
43 KiB
1010 lines
43 KiB
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.CompilerServices;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using UnityEngine.Rendering;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler
|
|
{
|
|
// Per pass info on inputs to the pass
|
|
[DebuggerDisplay("PassInputData: Res({resource.index})")]
|
|
internal struct PassInputData
|
|
{
|
|
public ResourceHandle resource;
|
|
}
|
|
|
|
// Per pass info on outputs to the pass
|
|
[DebuggerDisplay("PassOutputData: Res({resource.index})")]
|
|
internal struct PassOutputData
|
|
{
|
|
public ResourceHandle resource;
|
|
}
|
|
|
|
// Per pass fragment (attachment) info
|
|
[DebuggerDisplay("PassFragmentData: Res({resource.index}):{accessFlags}")]
|
|
internal struct PassFragmentData
|
|
{
|
|
public ResourceHandle resource;
|
|
public AccessFlags accessFlags;
|
|
public int mipLevel;
|
|
public int depthSlice;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public override int GetHashCode()
|
|
{
|
|
var hash = resource.GetHashCode();
|
|
hash = hash * 23 + accessFlags.GetHashCode();
|
|
hash = hash * 23 + mipLevel.GetHashCode();
|
|
hash = hash * 23 + depthSlice.GetHashCode();
|
|
return hash;
|
|
}
|
|
|
|
public static bool EqualForMerge(PassFragmentData x, PassFragmentData y)
|
|
{
|
|
// We ignore the version for now we assume if one pass writes version x and the next y they can
|
|
// be merged in the same native render pass
|
|
return x.resource.index == y.resource.index && x.accessFlags == y.accessFlags && x.mipLevel == y.mipLevel && x.depthSlice == y.depthSlice;
|
|
}
|
|
}
|
|
|
|
// Per pass random write texture info
|
|
[DebuggerDisplay("PassRandomWriteData: Res({resource.index}):{index}:{preserveCounterValue}")]
|
|
internal struct PassRandomWriteData
|
|
{
|
|
public ResourceHandle resource;
|
|
public int index;
|
|
public bool preserveCounterValue;
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hash = resource.GetHashCode();
|
|
hash = hash * 23 + index.GetHashCode();
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
internal enum PassMergeState
|
|
{
|
|
None = -1, // this pass is "standalone", it will begin and end the NRP
|
|
Begin = 0, // Only Begin NRP is done by this pass, sequential passes will End the NRP (after 0 or more additional subpasses)
|
|
SubPass = 1, // This pass is a subpass only. Begin has been called by a previous pass and end will be called be a pass after this one
|
|
End = 2 // This pass is both a subpass and will end the current NRP
|
|
}
|
|
|
|
// Data per pass
|
|
internal struct PassData
|
|
{
|
|
// Warning, any field must initialized in both constructor and ResetAndInitialize function
|
|
|
|
public int passId; // Index of self in the passData list, can we calculate this somehow in c#? would use offsetof in c++
|
|
public RenderGraphPassType type;
|
|
public bool hasFoveatedRasterization;
|
|
public int tag; // Arbitrary per node int used by various graph analysis tools
|
|
|
|
public PassMergeState mergeState;
|
|
public int nativePassIndex; // Index of the native pass this pass belongs to
|
|
public int nativeSubPassIndex; // Index of the native subpass this pass belongs to
|
|
|
|
public int firstInput; //base+offset in CompilerContextData.inputData (use the InputNodes iterator to iterate this more easily)
|
|
public int numInputs;
|
|
public int firstOutput; //base+offset in CompilerContextData.outputData (use the OutputNodes iterator to iterate this more easily)
|
|
public int numOutputs;
|
|
public int firstFragment; //base+offset in CompilerContextData.fragmentData (use the Fragments iterator to iterate this more easily)
|
|
public int numFragments;
|
|
public int firstFragmentInput; //base+offset in CompilerContextData.fragmentData (use the Fragment inputs iterator to iterate this more easily)
|
|
public int numFragmentInputs;
|
|
public int firstRandomAccessResource; //base+offset in CompilerContextData.randomWriteData (use the Fragment inputs iterator to iterate this more easily)
|
|
public int numRandomAccessResources;
|
|
public int firstCreate; //base+offset in CompilerContextData.createData (use the InputNodes iterator to iterate this more easily)
|
|
public int numCreated;
|
|
public int firstDestroy; //base+offset in CompilerContextData.destroyData (use the InputNodes iterator to iterate this more easily)
|
|
public int numDestroyed;
|
|
|
|
public int fragmentInfoWidth;
|
|
public int fragmentInfoHeight;
|
|
public int fragmentInfoVolumeDepth;
|
|
public int fragmentInfoSamples;
|
|
|
|
public int waitOnGraphicsFencePassId; // -1 if no fence wait is needed, otherwise the passId to wait on
|
|
|
|
public bool asyncCompute;
|
|
public bool hasSideEffects;
|
|
public bool culled;
|
|
public bool beginNativeSubpass; // If true this is the first graph pass of a merged native subpass
|
|
public bool fragmentInfoValid;
|
|
public bool fragmentInfoHasDepth;
|
|
public bool insertGraphicsFence; // Whether this pass should insert a fence into the command buffer
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Name GetName(CompilerContextData ctx) => ctx.GetFullPassName(passId);
|
|
|
|
public PassData(in RenderGraphPass pass, int passIndex)
|
|
{
|
|
passId = passIndex;
|
|
type = pass.type;
|
|
asyncCompute = pass.enableAsyncCompute;
|
|
hasSideEffects = !pass.allowPassCulling;
|
|
hasFoveatedRasterization = pass.enableFoveatedRasterization;
|
|
mergeState = PassMergeState.None;
|
|
nativePassIndex = -1;
|
|
nativeSubPassIndex = -1;
|
|
beginNativeSubpass = false;
|
|
|
|
culled = false;
|
|
tag = 0;
|
|
|
|
firstInput = 0;
|
|
numInputs = 0;
|
|
firstOutput = 0;
|
|
numOutputs = 0;
|
|
firstFragment = 0;
|
|
numFragments = 0;
|
|
firstRandomAccessResource = 0;
|
|
numRandomAccessResources = 0;
|
|
firstFragmentInput = 0;
|
|
numFragmentInputs = 0;
|
|
firstCreate = 0;
|
|
numCreated = 0;
|
|
firstDestroy = 0;
|
|
numDestroyed = 0;
|
|
|
|
fragmentInfoValid = false;
|
|
fragmentInfoWidth = 0;
|
|
fragmentInfoHeight = 0;
|
|
fragmentInfoVolumeDepth = 0;
|
|
fragmentInfoSamples = 0;
|
|
fragmentInfoHasDepth = false;
|
|
|
|
insertGraphicsFence = false;
|
|
waitOnGraphicsFencePassId = -1;
|
|
}
|
|
|
|
// Helper func to reset and initialize existing PassData struct directly in a data container without costly deep copy (~120bytes) when adding it
|
|
public void ResetAndInitialize(in RenderGraphPass pass, int passIndex)
|
|
{
|
|
passId = passIndex;
|
|
type = pass.type;
|
|
asyncCompute = pass.enableAsyncCompute;
|
|
hasSideEffects = !pass.allowPassCulling;
|
|
hasFoveatedRasterization = pass.enableFoveatedRasterization;
|
|
|
|
mergeState = PassMergeState.None;
|
|
nativePassIndex = -1;
|
|
nativeSubPassIndex = -1;
|
|
beginNativeSubpass = false;
|
|
|
|
culled = false;
|
|
tag = 0;
|
|
|
|
firstInput = 0;
|
|
numInputs = 0;
|
|
firstOutput = 0;
|
|
numOutputs = 0;
|
|
firstFragment = 0;
|
|
numFragments = 0;
|
|
firstFragmentInput = 0;
|
|
numFragmentInputs = 0;
|
|
firstRandomAccessResource = 0;
|
|
numRandomAccessResources = 0;
|
|
firstCreate = 0;
|
|
numCreated = 0;
|
|
firstDestroy = 0;
|
|
numDestroyed = 0;
|
|
|
|
fragmentInfoValid = false;
|
|
fragmentInfoWidth = 0;
|
|
fragmentInfoHeight = 0;
|
|
fragmentInfoVolumeDepth = 0;
|
|
fragmentInfoSamples = 0;
|
|
fragmentInfoHasDepth = false;
|
|
|
|
insertGraphicsFence = false;
|
|
waitOnGraphicsFencePassId = -1;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<PassOutputData> Outputs(CompilerContextData ctx)
|
|
=> ctx.outputData.MakeReadOnlySpan(firstOutput, numOutputs);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<PassInputData> Inputs(CompilerContextData ctx)
|
|
=> ctx.inputData.MakeReadOnlySpan(firstInput, numInputs);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<PassFragmentData> Fragments(CompilerContextData ctx)
|
|
=> ctx.fragmentData.MakeReadOnlySpan(firstFragment, numFragments);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<PassFragmentData> FragmentInputs(CompilerContextData ctx)
|
|
=> ctx.fragmentData.MakeReadOnlySpan(firstFragmentInput, numFragmentInputs);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<ResourceHandle> FirstUsedResources(CompilerContextData ctx)
|
|
=> ctx.createData.MakeReadOnlySpan(firstCreate, numCreated);
|
|
|
|
// Loop over this pass's random write textures returned as PassFragmentData
|
|
public ReadOnlySpan<PassRandomWriteData> RandomWriteTextures(CompilerContextData ctx)
|
|
=> ctx.randomAccessResourceData.MakeReadOnlySpan(firstRandomAccessResource, numRandomAccessResources);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<ResourceHandle> LastUsedResources(CompilerContextData ctx)
|
|
=> ctx.destroyData.MakeReadOnlySpan(firstDestroy, numDestroyed);
|
|
|
|
private void SetupAndValidateFragmentInfo(ResourceHandle h, CompilerContextData ctx)
|
|
{
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (h.type != RenderGraphResourceType.Texture) new Exception("Only textures can be used as a fragment attachment.");
|
|
#endif
|
|
|
|
ref readonly var resInfo = ref ctx.UnversionedResourceData(h);
|
|
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (resInfo.width == 0 || resInfo.height == 0 || resInfo.msaaSamples == 0) throw new Exception("GetRenderTargetInfo returned invalid results.");
|
|
#endif
|
|
if (fragmentInfoValid)
|
|
{
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (fragmentInfoWidth != resInfo.width ||
|
|
fragmentInfoHeight != resInfo.height ||
|
|
fragmentInfoVolumeDepth != resInfo.volumeDepth ||
|
|
fragmentInfoSamples != resInfo.msaaSamples)
|
|
throw new Exception("Mismatch in Fragment dimensions");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
fragmentInfoWidth = resInfo.width;
|
|
fragmentInfoHeight = resInfo.height;
|
|
fragmentInfoSamples = resInfo.msaaSamples;
|
|
fragmentInfoVolumeDepth = resInfo.volumeDepth;
|
|
fragmentInfoValid = true;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AddFragment(ResourceHandle h, CompilerContextData ctx)
|
|
{
|
|
SetupAndValidateFragmentInfo(h, ctx);
|
|
numFragments++;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AddFragmentInput(ResourceHandle h, CompilerContextData ctx)
|
|
{
|
|
SetupAndValidateFragmentInfo(h, ctx);
|
|
numFragmentInputs++;
|
|
}
|
|
|
|
internal void AddRandomAccessResource()
|
|
{
|
|
// This function is here for orthogonality with AddFragment/AddFragmentInput
|
|
// Random write textures can be arbitrary sizes and do not need to validate or set-up the fragment info
|
|
numRandomAccessResources++;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AddFirstUse(ResourceHandle h, CompilerContextData ctx)
|
|
{
|
|
// Already registered? Skip it
|
|
foreach (ref readonly var res in FirstUsedResources(ctx))
|
|
{
|
|
if (res.index == h.index && res.type == h.type)
|
|
return;
|
|
}
|
|
|
|
ctx.createData.Add(h);
|
|
int addedIndex = ctx.createData.LastIndex();
|
|
|
|
// First item added, set up firstCreate
|
|
if (numCreated == 0)
|
|
{
|
|
firstCreate = addedIndex;
|
|
}
|
|
|
|
Debug.Assert(addedIndex == firstCreate + numCreated, "you can only incrementally set-up the Creation lists for all passes, AddCreation is called in an arbitrary non-incremental way");
|
|
|
|
numCreated++;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AddLastUse(ResourceHandle h, CompilerContextData ctx)
|
|
{
|
|
// Already registered? Skip it
|
|
foreach (ref readonly var res in LastUsedResources(ctx))
|
|
{
|
|
if (res.index == h.index && res.type == h.type)
|
|
return;
|
|
}
|
|
|
|
ctx.destroyData.Add(h);
|
|
int addedIndex = ctx.destroyData.LastIndex();
|
|
|
|
// First item added, set up firstDestroy
|
|
if (numDestroyed == 0)
|
|
{
|
|
firstDestroy = addedIndex;
|
|
}
|
|
|
|
Debug.Assert(addedIndex == firstDestroy + numDestroyed, "you can only incrementally set-up the Destruction lists for all passes, AddCreation is called in an arbitrary non-incremental way");
|
|
numDestroyed++;
|
|
}
|
|
|
|
// Is the resource used as a fragment this pass.
|
|
// As it is ambiguous if this is an input our output version, the version is ignored
|
|
// This checks use of both MRT attachment as well as input attachment
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal readonly bool IsUsedAsFragment(ResourceHandle h, CompilerContextData ctx)
|
|
{
|
|
//Only textures can be used as a fragment attachment.
|
|
if (h.type != RenderGraphResourceType.Texture) return false;
|
|
|
|
// Only raster passes can have fragment attachments
|
|
if (type != RenderGraphPassType.Raster) return false;
|
|
|
|
foreach (ref readonly var fragment in Fragments(ctx))
|
|
{
|
|
if (fragment.resource.index == h.index)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
foreach (ref readonly var fragmentInput in FragmentInputs(ctx))
|
|
{
|
|
if (fragmentInput.resource.index == h.index)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Data per attachment of a native renderpass
|
|
[DebuggerDisplay("Res({handle.index}) : {loadAction} : {storeAction} : {memoryless}")]
|
|
internal struct NativePassAttachment
|
|
{
|
|
public ResourceHandle handle;
|
|
public UnityEngine.Rendering.RenderBufferLoadAction loadAction;
|
|
public UnityEngine.Rendering.RenderBufferStoreAction storeAction;
|
|
public bool memoryless;
|
|
public int mipLevel;
|
|
public int depthSlice;
|
|
}
|
|
|
|
internal enum LoadReason
|
|
{
|
|
InvalidReason,
|
|
LoadImported,
|
|
LoadPreviouslyWritten,
|
|
ClearImported,
|
|
ClearCreated,
|
|
FullyRewritten,
|
|
|
|
Count
|
|
}
|
|
|
|
[DebuggerDisplay("{reason} : {passId}")]
|
|
internal struct LoadAudit
|
|
{
|
|
public static readonly string[] LoadReasonMessages = {
|
|
"Invalid reason",
|
|
"The resource is imported in the graph and loaded to retrieve the existing buffer contents.",
|
|
"The resource is written by {pass} executed previously in the graph. The data is loaded.",
|
|
"The resource is imported in the graph but was imported with the 'clear on first use' option enabled. The data is cleared.",
|
|
"The resource is created in this pass and cleared on first use.",
|
|
"The pass indicated it will rewrite the full resource contents. Existing contents are not loaded or cleared.",
|
|
};
|
|
|
|
public LoadReason reason;
|
|
public int passId;
|
|
|
|
public LoadAudit(LoadReason setReason, int setPassId = -1)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Debug.Assert(LoadReasonMessages.Length == (int)LoadReason.Count,
|
|
$"Make sure {nameof(LoadReasonMessages)} is in sync with {nameof(LoadReason)}");
|
|
#endif
|
|
|
|
reason = setReason;
|
|
passId = setPassId;
|
|
}
|
|
}
|
|
|
|
internal enum StoreReason
|
|
{
|
|
InvalidReason,
|
|
StoreImported,
|
|
StoreUsedByLaterPass,
|
|
DiscardImported,
|
|
DiscardUnused,
|
|
DiscardBindMs,
|
|
NoMSAABuffer,
|
|
|
|
Count
|
|
}
|
|
|
|
[DebuggerDisplay("{reason} : {passId} / MSAA {msaaReason} : {msaaPassId}")]
|
|
internal struct StoreAudit
|
|
{
|
|
public static readonly string[] StoreReasonMessages = {
|
|
"Invalid reason",
|
|
"The resource is imported in the graph. The data is stored so results are available outside the graph.",
|
|
"The resource is read by pass {pass} executed later in the graph. The data is stored.",
|
|
"The resource is imported but the import was with the 'discard on last use' option enabled. The data is discarded.",
|
|
"The resource is written by this pass but no later passes are using the results. The data is discarded.",
|
|
"The resource was created as MSAA only resource, the data can never be resolved.",
|
|
"The resource is a single sample resource, there is no multi-sample data to handle.",
|
|
};
|
|
|
|
public StoreReason reason;
|
|
public int passId;
|
|
public StoreReason msaaReason;
|
|
public int msaaPassId;
|
|
|
|
public StoreAudit(StoreReason setReason, int setPassId = -1, StoreReason setMsaaReason = StoreReason.NoMSAABuffer, int setMsaaPassId = -1)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Debug.Assert(StoreReasonMessages.Length == (int)StoreReason.Count,
|
|
$"Make sure {nameof(StoreReasonMessages)} is in sync with {nameof(StoreReason)}");
|
|
#endif
|
|
|
|
reason = setReason;
|
|
passId = setPassId;
|
|
msaaReason = setMsaaReason;
|
|
msaaPassId = setMsaaPassId;
|
|
}
|
|
}
|
|
|
|
internal enum PassBreakReason
|
|
{
|
|
NotOptimized, // Optimize never ran on this pass
|
|
TargetSizeMismatch, // Target Sizes or msaa samples don't match
|
|
NextPassReadsTexture, // The next pass reads data written by this pass as a texture
|
|
NonRasterPass, // The next pass is a non-raster pass
|
|
DifferentDepthTextures, // The next pass uses a different depth texture (and we only allow one in a whole NRP)
|
|
AttachmentLimitReached, // Adding the next pass would have used more attachments than allowed
|
|
SubPassLimitReached, // Addind the next pass would have generated more subpasses than allowed
|
|
EndOfGraph, // The last pass in the graph was reached
|
|
FRStateMismatch, // One pass is using foveated rendering and the other not
|
|
Merged, // I actually got merged
|
|
|
|
Count
|
|
}
|
|
|
|
[DebuggerDisplay("{reason} : {breakPass}")]
|
|
internal struct PassBreakAudit
|
|
{
|
|
public PassBreakReason reason;
|
|
public int breakPass;
|
|
|
|
public PassBreakAudit(PassBreakReason reason, int breakPass)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Debug.Assert(BreakReasonMessages.Length == (int)PassBreakReason.Count,
|
|
$"Make sure {nameof(BreakReasonMessages)} is in sync with {nameof(PassBreakReason)}");
|
|
#endif
|
|
|
|
this.reason = reason;
|
|
this.breakPass = breakPass; // This is not so simple as finding the next pass as it might be culled etc, so we store it to be sure we get the right pass
|
|
}
|
|
|
|
public static readonly string[] BreakReasonMessages = {
|
|
"The native render pass optimizer never ran on this pass. Pass is standalone and not merged.",
|
|
"The render target sizes of the next pass do not match.",
|
|
"The next pass reads data output by this pass as a regular texture.",
|
|
"The next pass is not a raster render pass.",
|
|
"The next pass uses a different depth buffer. All passes in the native render pass need to use the same depth buffer.",
|
|
$"The limit of {FixedAttachmentArray<PassFragmentData>.MaxAttachments} native pass attachments would be exceeded when merging with the next pass.",
|
|
$"The limit of {NativePassCompiler.k_MaxSubpass} native subpasses would be exceeded when merging with the next pass.",
|
|
"This is the last pass in the graph, there are no other passes to merge.",
|
|
"The the next pass uses a different foveated rendering state",
|
|
"The next pass got merged into this pass.",
|
|
};
|
|
}
|
|
|
|
// Data per native renderpass
|
|
internal struct NativePassData
|
|
{
|
|
public FixedAttachmentArray<LoadAudit> loadAudit;
|
|
public FixedAttachmentArray<StoreAudit> storeAudit;
|
|
public PassBreakAudit breakAudit;
|
|
|
|
public FixedAttachmentArray<PassFragmentData> fragments;
|
|
public FixedAttachmentArray<NativePassAttachment> attachments;
|
|
|
|
// Index of the first graph pass this native pass encapsulates
|
|
public int firstGraphPass; // Offset+count in context pass array
|
|
public int numGraphPasses;
|
|
|
|
public int firstNativeSubPass; // Offset+count in context subpass array
|
|
public int numNativeSubPasses;
|
|
public int width;
|
|
public int height;
|
|
public int volumeDepth;
|
|
public int samples;
|
|
public bool hasDepth;
|
|
public bool hasFoveatedRasterization;
|
|
|
|
public NativePassData(ref PassData pass, CompilerContextData ctx)
|
|
{
|
|
firstGraphPass = pass.passId;
|
|
numGraphPasses = 1;
|
|
firstNativeSubPass = -1;// Set up during compile
|
|
numNativeSubPasses = 0;
|
|
|
|
fragments = new FixedAttachmentArray<PassFragmentData>();
|
|
attachments = new FixedAttachmentArray<NativePassAttachment>();
|
|
|
|
width = pass.fragmentInfoWidth;
|
|
height = pass.fragmentInfoHeight;
|
|
volumeDepth = pass.fragmentInfoVolumeDepth;
|
|
samples = pass.fragmentInfoSamples;
|
|
hasDepth = pass.fragmentInfoHasDepth;
|
|
hasFoveatedRasterization = pass.hasFoveatedRasterization;
|
|
|
|
loadAudit = new FixedAttachmentArray<LoadAudit>();
|
|
storeAudit = new FixedAttachmentArray<StoreAudit>();
|
|
breakAudit = new PassBreakAudit(PassBreakReason.NotOptimized, -1);
|
|
|
|
foreach (ref readonly var fragment in pass.Fragments(ctx))
|
|
{
|
|
fragments.Add(fragment);
|
|
}
|
|
|
|
foreach (ref readonly var fragment in pass.FragmentInputs(ctx))
|
|
{
|
|
fragments.Add(fragment);
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
firstGraphPass = 0;
|
|
numGraphPasses = 0;
|
|
attachments.Clear();
|
|
fragments.Clear();
|
|
loadAudit.Clear();
|
|
storeAudit.Clear();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly bool IsValid()
|
|
{
|
|
return numGraphPasses > 0;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly ReadOnlySpan<PassData> GraphPasses(CompilerContextData ctx) => ctx.passData.MakeReadOnlySpan(firstGraphPass, numGraphPasses);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly void GetGraphPassNames(CompilerContextData ctx, DynamicArray<Name> dest)
|
|
{
|
|
foreach (ref readonly var pass in GraphPasses(ctx))
|
|
{
|
|
dest.Add(pass.GetName(ctx));
|
|
}
|
|
}
|
|
|
|
// This function does not modify the current render graph state, it only evaluates and returns the correct PassBreakAudit
|
|
public static PassBreakAudit CanMerge(CompilerContextData contextData, int activeNativePassId, int passIdToMerge)
|
|
{
|
|
ref readonly var passToMerge = ref contextData.passData.ElementAt(passIdToMerge);
|
|
|
|
// Non raster passes (low level, compute,...) will break the native pass chain
|
|
// as they may need to do SetRendertarget or non-fragment work
|
|
if (passToMerge.type != RenderGraphPassType.Raster)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.NonRasterPass, passIdToMerge);
|
|
}
|
|
|
|
ref readonly var nativePass = ref contextData.nativePassData.ElementAt(activeNativePassId);
|
|
|
|
// If a pass has no fragment attachments a lot of the tests can be skipped
|
|
// You could argue that a raster pass with no fragments is not allowed but why not?
|
|
// it allow us to set some legacy unity state from fragment passes without breaking NRP
|
|
// Allowing this would means something like
|
|
// Fill Gbuffer - Set Shadow Globals - Do light pass would break as the shadow globals pass
|
|
// is a 0x0 pass with no rendertargets that just sets shadow global texture pointers
|
|
// By allowing this to be merged into the NRP we can actually ensure these passes are merged
|
|
bool hasFragments = (passToMerge.numFragments > 0 || passToMerge.numFragmentInputs > 0);
|
|
if (hasFragments)
|
|
{
|
|
// Easy early outs, sizes mismatch
|
|
if (nativePass.width != passToMerge.fragmentInfoWidth ||
|
|
nativePass.height != passToMerge.fragmentInfoHeight ||
|
|
nativePass.volumeDepth != passToMerge.fragmentInfoVolumeDepth ||
|
|
nativePass.samples != passToMerge.fragmentInfoSamples)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.TargetSizeMismatch, passIdToMerge);
|
|
}
|
|
|
|
// Easy early outs, different depth buffers we only allow a single depth for the whole NRP for now ?!?
|
|
// Depth buffer is by-design always at index 0
|
|
if (nativePass.hasDepth && passToMerge.fragmentInfoHasDepth)
|
|
{
|
|
ref readonly var firstFragment = ref contextData.fragmentData.ElementAt(passToMerge.firstFragment);
|
|
if (nativePass.fragments[0].resource.index != firstFragment.resource.index)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.DifferentDepthTextures, passIdToMerge);
|
|
}
|
|
}
|
|
|
|
// We do not support foveation state changes within the renderpass due to platform limitation
|
|
if (nativePass.hasFoveatedRasterization != passToMerge.hasFoveatedRasterization)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.FRStateMismatch, passIdToMerge);
|
|
}
|
|
}
|
|
|
|
// Check the non-fragment inputs of this pass, if they are generated by the current open native pass we can't merge
|
|
// as we need to commit the pixels to the texture
|
|
foreach (ref readonly var input in passToMerge.Inputs(contextData))
|
|
{
|
|
var inputResource = input.resource;
|
|
var writingPassId = contextData.resources[inputResource].writePassId;
|
|
// Is the writing pass enclosed in the current native renderpass
|
|
if (writingPassId >= nativePass.firstGraphPass && writingPassId < nativePass.firstGraphPass + nativePass.numGraphPasses)
|
|
{
|
|
// If it's not used as a fragment, it's used as some sort of texture read of load so we need so sync it out
|
|
if (!passToMerge.IsUsedAsFragment(inputResource, contextData))
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.NextPassReadsTexture, passIdToMerge);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gather which attachments to add to the current renderpass
|
|
var attachmentsToTryAdding = new FixedAttachmentArray<PassFragmentData>();
|
|
|
|
// We can't have more than the maximum amount of attachments in a given native renderpass
|
|
int currAvailableAttachmentSlots = FixedAttachmentArray<PassFragmentData>.MaxAttachments - nativePass.fragments.size;
|
|
|
|
foreach (ref readonly var fragment in passToMerge.Fragments(contextData))
|
|
{
|
|
bool alreadyAttached = false;
|
|
|
|
for (int i = 0; i < nativePass.fragments.size; ++i)
|
|
{
|
|
if (nativePass.fragments[i].resource.index == fragment.resource.index)
|
|
{
|
|
alreadyAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This fragment is not attached to the native renderpass yet, we will need to attach it
|
|
if (!alreadyAttached)
|
|
{
|
|
// We already reached the maximum amount of attachments in this renderpass
|
|
// We can't add any new attachment, just start a new renderpass
|
|
if (currAvailableAttachmentSlots == 0)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.AttachmentLimitReached, passIdToMerge);
|
|
}
|
|
else
|
|
{
|
|
attachmentsToTryAdding.Add(fragment);
|
|
currAvailableAttachmentSlots--;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (ref readonly var fragmentInput in passToMerge.FragmentInputs(contextData))
|
|
{
|
|
bool alreadyAttached = false;
|
|
|
|
for (int i = 0; i < nativePass.fragments.size; ++i)
|
|
{
|
|
if (nativePass.fragments[i].resource.index == fragmentInput.resource.index)
|
|
{
|
|
alreadyAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This fragment input is not attached to the native renderpass yet, we will need to attach it
|
|
if (!alreadyAttached)
|
|
{
|
|
// We already reached the maximum amount of attachments in this native renderpass
|
|
// We can't add any new attachment, just start a new renderpass
|
|
if (currAvailableAttachmentSlots == 0)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.AttachmentLimitReached, passIdToMerge);
|
|
}
|
|
else
|
|
{
|
|
attachmentsToTryAdding.Add(fragmentInput);
|
|
currAvailableAttachmentSlots--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CountNativePassesAfterMerge(contextData, nativePass, passToMerge) > NativePassCompiler.k_MaxSubpass)
|
|
{
|
|
return new PassBreakAudit(PassBreakReason.SubPassLimitReached, passIdToMerge);
|
|
}
|
|
|
|
// All is good! Pass can be merged into active native pass
|
|
return new PassBreakAudit(PassBreakReason.Merged, passIdToMerge);
|
|
}
|
|
|
|
// This function must follow the implementation of NativePassCompiler.PrepareNativeRenderPass
|
|
static int CountNativePassesAfterMerge(CompilerContextData contextData, NativePassData nativePass, PassData passToMerge)
|
|
{
|
|
var combinedFragmentList = nativePass.fragments;
|
|
|
|
// Depth needs special handling if the native pass doesn't have depth and merges with a pass that does
|
|
// as we require the depth attachment to be at index 0
|
|
if (!nativePass.hasDepth && passToMerge.fragmentInfoHasDepth)
|
|
{
|
|
nativePass.hasDepth = true;
|
|
combinedFragmentList.Add(contextData.fragmentData[passToMerge.firstFragment]);
|
|
var size = combinedFragmentList.size;
|
|
if (size > 1)
|
|
(combinedFragmentList[0], combinedFragmentList[size-1]) = (combinedFragmentList[size-1], combinedFragmentList[0]);
|
|
}
|
|
|
|
// Update versions and flags of existing attachments and
|
|
// add any new attachments
|
|
foreach (ref readonly var newAttach in passToMerge.Fragments(contextData))
|
|
{
|
|
bool alreadyAttached = false;
|
|
|
|
for (int i = 0; i < combinedFragmentList.size; ++i)
|
|
{
|
|
ref var existingAttach = ref combinedFragmentList[i];
|
|
if (existingAttach.resource.index == newAttach.resource.index)
|
|
{
|
|
// Update the attached version access flags and version
|
|
existingAttach.accessFlags |= newAttach.accessFlags;
|
|
existingAttach.resource.version = newAttach.resource.version;
|
|
alreadyAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alreadyAttached)
|
|
{
|
|
combinedFragmentList.Add(newAttach);
|
|
}
|
|
}
|
|
|
|
foreach (ref readonly var newAttach in passToMerge.FragmentInputs(contextData))
|
|
{
|
|
bool alreadyAttached = false;
|
|
|
|
for (int i = 0; i < combinedFragmentList.size; ++i)
|
|
{
|
|
ref var existingAttach = ref combinedFragmentList[i];
|
|
if (existingAttach.resource.index == newAttach.resource.index)
|
|
{
|
|
// Update the attached version access flags and version
|
|
existingAttach.accessFlags |= newAttach.accessFlags;
|
|
existingAttach.resource.version = newAttach.resource.version;
|
|
alreadyAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alreadyAttached)
|
|
{
|
|
combinedFragmentList.Add(newAttach);
|
|
}
|
|
}
|
|
|
|
int numNativeSubpasses = 0;
|
|
|
|
// Fill out the subpass descriptors for the native renderpasses
|
|
// NOTE: Not all graph subpasses get an actual native pass:
|
|
// - There could be passes that do only non-raster ops (like setglobal) and have no attachments. They don't get a native pass
|
|
// - Renderpasses that use exactly the same rendertargets at the previous pass use the same native pass. This is because
|
|
// nextSubpass is expensive on some platforms (even if its' essentially a no-op as it's using the same attachments).
|
|
SubPassDescriptor lastPass = new SubPassDescriptor();
|
|
|
|
for (var graphPassIndex = 0; graphPassIndex < nativePass.numGraphPasses + 1; ++graphPassIndex)
|
|
{
|
|
SubPassDescriptor desc = new SubPassDescriptor();
|
|
var graphPass = graphPassIndex < nativePass.numGraphPasses
|
|
? contextData.passData.ElementAt(nativePass.firstGraphPass + graphPassIndex)
|
|
: passToMerge;
|
|
|
|
|
|
// We have no output attachments, this is an "empty" raster pass doing only non-rendering command so skip it.
|
|
if (graphPass.numFragments == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If depth ends up being bound only because of merging we explicitly say that we will not write to it
|
|
// which could have been implied by leaving the flag to None
|
|
if (!graphPass.fragmentInfoHasDepth && nativePass.hasDepth)
|
|
{
|
|
desc.flags = SubPassFlags.ReadOnlyDepth;
|
|
}
|
|
|
|
// MRT attachments
|
|
{
|
|
int fragmentIdx = 0;
|
|
int colorOffset = (graphPass.fragmentInfoHasDepth) ? -1 : 0;
|
|
|
|
desc.colorOutputs = new AttachmentIndexArray(graphPass.numFragments + colorOffset);
|
|
|
|
foreach (ref readonly var fragment in graphPass.Fragments(contextData))
|
|
{
|
|
// Check if we're handling the depth attachment
|
|
if (graphPass.fragmentInfoHasDepth && fragmentIdx == 0)
|
|
{
|
|
desc.flags = (fragment.accessFlags.HasFlag(AccessFlags.Write))
|
|
? SubPassFlags.None
|
|
: SubPassFlags.ReadOnlyDepth;
|
|
}
|
|
|
|
// It's a color attachment
|
|
else
|
|
{
|
|
// Find the index of this subpass's attachment in the native renderpass attachment list
|
|
int colorAttachmentIdx = -1;
|
|
for (int fragmentId = 0; fragmentId < combinedFragmentList.size; ++fragmentId)
|
|
{
|
|
if (combinedFragmentList[fragmentId].resource.index == fragment.resource.index)
|
|
{
|
|
colorAttachmentIdx = fragmentId;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set up the color indexes
|
|
desc.colorOutputs[fragmentIdx + colorOffset] = colorAttachmentIdx;
|
|
}
|
|
|
|
fragmentIdx++;
|
|
}
|
|
}
|
|
|
|
// FB-fetch attachments
|
|
{
|
|
int inputIndex = 0;
|
|
|
|
desc.inputs = new AttachmentIndexArray(graphPass.numFragmentInputs);
|
|
|
|
foreach (ref readonly var fragmentInput in graphPass.FragmentInputs(contextData))
|
|
{
|
|
// Find the index of this subpass's attachment in the native renderpass attachment list
|
|
int inputAttachmentIdx = -1;
|
|
for (int fragmentId = 0; fragmentId < combinedFragmentList.size; ++fragmentId)
|
|
{
|
|
if (combinedFragmentList[fragmentId].resource.index == fragmentInput.resource.index)
|
|
{
|
|
inputAttachmentIdx = fragmentId;
|
|
break;
|
|
}
|
|
}
|
|
|
|
desc.inputs[inputIndex] = inputAttachmentIdx;
|
|
inputIndex++;
|
|
}
|
|
}
|
|
|
|
// Check if we can merge the native sub pass with the previous one
|
|
if (numNativeSubpasses == 0 || !NativePassCompiler.IsSameNativeSubPass(ref desc, ref lastPass))
|
|
{
|
|
lastPass = desc;
|
|
numNativeSubpasses++;
|
|
}
|
|
}
|
|
|
|
return numNativeSubpasses;
|
|
}
|
|
|
|
public static PassBreakAudit TryMerge(CompilerContextData contextData, int activeNativePassId, int passIdToMerge)
|
|
{
|
|
var passBreakAudit = CanMerge(contextData, activeNativePassId, passIdToMerge);
|
|
|
|
// Pass cannot be merged into active native pass
|
|
if(passBreakAudit.reason != PassBreakReason.Merged)
|
|
return passBreakAudit;
|
|
|
|
ref var passToMerge = ref contextData.passData.ElementAt(passIdToMerge);
|
|
ref var nativePass = ref contextData.nativePassData.ElementAt(activeNativePassId);
|
|
|
|
passToMerge.mergeState = PassMergeState.SubPass;
|
|
if (passToMerge.nativePassIndex >= 0)
|
|
contextData.nativePassData.ElementAt(passToMerge.nativePassIndex).Clear();
|
|
passToMerge.nativePassIndex = activeNativePassId;
|
|
nativePass.numGraphPasses++;
|
|
|
|
// Depth needs special handling if the native pass doesn't have depth and merges with a pass that does
|
|
// as we require the depth attachment to be at index 0
|
|
if (!nativePass.hasDepth && passToMerge.fragmentInfoHasDepth)
|
|
{
|
|
nativePass.hasDepth = true;
|
|
nativePass.fragments.Add(contextData.fragmentData[passToMerge.firstFragment]);
|
|
var size = nativePass.fragments.size;
|
|
if (size > 1)
|
|
(nativePass.fragments[0], nativePass.fragments[size-1]) = (nativePass.fragments[size-1], nativePass.fragments[0]);
|
|
}
|
|
|
|
// Update versions and flags of existing attachments and
|
|
// add any new attachments
|
|
foreach (ref readonly var newAttach in passToMerge.Fragments(contextData))
|
|
{
|
|
bool alreadyAttached = false;
|
|
|
|
for (int i = 0; i < nativePass.fragments.size; ++i)
|
|
{
|
|
ref var existingAttach = ref nativePass.fragments[i];
|
|
if (existingAttach.resource.index == newAttach.resource.index)
|
|
{
|
|
// Update the attached version access flags and version
|
|
existingAttach.accessFlags |= newAttach.accessFlags;
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (existingAttach.resource.version > newAttach.resource.version)
|
|
throw new Exception("Adding an older version while a higher version is already registered with the pass.");
|
|
#endif
|
|
existingAttach.resource.version = newAttach.resource.version;
|
|
alreadyAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alreadyAttached)
|
|
{
|
|
nativePass.fragments.Add(newAttach);
|
|
}
|
|
}
|
|
|
|
foreach (ref readonly var newAttach in passToMerge.FragmentInputs(contextData))
|
|
{
|
|
bool alreadyAttached = false;
|
|
|
|
for (int i = 0; i < nativePass.fragments.size; ++i)
|
|
{
|
|
ref var existingAttach = ref nativePass.fragments[i];
|
|
if (existingAttach.resource.index == newAttach.resource.index)
|
|
{
|
|
// Update the attached version access flags and version
|
|
existingAttach.accessFlags |= newAttach.accessFlags;
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (existingAttach.resource.version > newAttach.resource.version)
|
|
throw new Exception("Adding an older version while a higher version is already registered with the pass.");
|
|
#endif
|
|
existingAttach.resource.version = newAttach.resource.version;
|
|
alreadyAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alreadyAttached)
|
|
{
|
|
nativePass.fragments.Add(newAttach);
|
|
}
|
|
}
|
|
|
|
SetPassStatesForNativePass(contextData, activeNativePassId);
|
|
|
|
return passBreakAudit;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void SetPassStatesForNativePass(CompilerContextData contextData, int nativePassId)
|
|
{
|
|
ref readonly var nativePass = ref contextData.nativePassData.ElementAt(nativePassId);
|
|
if (nativePass.numGraphPasses > 1)
|
|
{
|
|
contextData.passData.ElementAt(nativePass.firstGraphPass).mergeState = PassMergeState.Begin;
|
|
for (int i = 1; i < nativePass.numGraphPasses - 1; i++)
|
|
{
|
|
contextData.passData.ElementAt(nativePass.firstGraphPass + i).mergeState = PassMergeState.SubPass;
|
|
}
|
|
contextData.passData.ElementAt(nativePass.firstGraphPass + nativePass.numGraphPasses - 1).mergeState = PassMergeState.End;
|
|
}
|
|
else
|
|
{
|
|
contextData.passData.ElementAt(nativePass.firstGraphPass).mergeState = PassMergeState.None;
|
|
}
|
|
}
|
|
}
|
|
}
|