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.
 
 
 
 

323 lines
13 KiB

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine.Rendering;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler
{
// Wrapper struct to allow storing strings in a DynamicArray which requires a type with a parameterless constructor
internal readonly struct Name
{
public readonly string name;
public readonly int utf8ByteCount;
public Name(string name, bool computeUTF8ByteCount = false)
{
this.name = name;
this.utf8ByteCount = ((name?.Length > 0) && computeUTF8ByteCount) ? System.Text.Encoding.UTF8.GetByteCount((ReadOnlySpan<char>)name) : 0;
}
}
// Helper extensions for NativeList
internal static class NativeListExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe ReadOnlySpan<T> MakeReadOnlySpan<T>(this ref NativeList<T> list, int first, int numElements) where T : unmanaged
{
#if UNITY_EDITOR
if (first + numElements > list.Length)
throw new IndexOutOfRangeException();
#endif
return new ReadOnlySpan<T>(&list.GetUnsafeReadOnlyPtr()[first], numElements);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int LastIndex<T>(this ref NativeList<T> list) where T : unmanaged
{
return list.Length - 1;
}
}
// Note pass=node in the graph, both are sometimes mixed up here
// Datastructure that contains passes and dependencies and allow you to iterate and reason on them more like a graph
internal class CompilerContextData : IDisposable, RenderGraph.ICompiledGraph
{
public CompilerContextData()
{
fences = new Dictionary<int, GraphicsFence>();
resources = new ResourcesData();
passNames = new DynamicArray<Name>(0, false); // T in NativeList<T> cannot contain managed types, so the names are stored separately
}
void AllocateNativeDataStructuresIfNeeded(int estimatedNumPasses)
{
// Only first init or if Dispose() has been called through RenderGraph.Cleanup()
if (!m_AreNativeListsAllocated)
{
// These are risky heuristics that only work because we purposely estimate a very high number of passes
// We need to fix this with a proper size computation
passData = new NativeList<PassData>(estimatedNumPasses, AllocatorManager.Persistent);
inputData = new NativeList<PassInputData>(estimatedNumPasses * 2, AllocatorManager.Persistent);
outputData = new NativeList<PassOutputData>(estimatedNumPasses * 2, AllocatorManager.Persistent);
fragmentData = new NativeList<PassFragmentData>(estimatedNumPasses * 4, AllocatorManager.Persistent);
randomAccessResourceData = new NativeList<PassRandomWriteData>(4, AllocatorManager.Persistent); // We assume not a lot of passes use random write
nativePassData = new NativeList<NativePassData>(estimatedNumPasses, AllocatorManager.Persistent);// assume nothing gets merged
nativeSubPassData = new NativeList<SubPassDescriptor>(estimatedNumPasses, AllocatorManager.Persistent);// there should "never" be more subpasses than graph passes
createData = new NativeList<ResourceHandle>(estimatedNumPasses * 2, AllocatorManager.Persistent); // assume every pass creates two resources
destroyData = new NativeList<ResourceHandle>(estimatedNumPasses * 2, AllocatorManager.Persistent); // assume every pass destroys two resources
m_AreNativeListsAllocated = true;
}
}
public void Initialize(RenderGraphResourceRegistry resourceRegistry, int estimatedNumPasses)
{
resources.Initialize(resourceRegistry);
passNames.Reserve(estimatedNumPasses, false);
AllocateNativeDataStructuresIfNeeded(estimatedNumPasses);
}
public void Clear()
{
passNames.Clear();
resources.Clear();
if (m_AreNativeListsAllocated)
{
passData.Clear();
fences.Clear();
inputData.Clear();
outputData.Clear();
fragmentData.Clear();
randomAccessResourceData.Clear();
nativePassData.Clear();
nativeSubPassData.Clear();
createData.Clear();
destroyData.Clear();
}
}
public ResourcesData resources;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref ResourceUnversionedData UnversionedResourceData(ResourceHandle h)
{
return ref resources.unversionedData[h.iType].ElementAt(h.index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref ResourceVersionedData VersionedResourceData(ResourceHandle h)
{
return ref resources[h];
}
// Iterate over all the readers of a particular resource
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<ResourceReaderData> Readers(ResourceHandle h)
{
int firstReader = resources.IndexReader(h, 0);
int numReaders = resources[h].numReaders;
return resources.readerData[h.iType].MakeReadOnlySpan(firstReader, numReaders);
}
// Get the i'th reader of a resource
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref ResourceReaderData ResourceReader(ResourceHandle h, int i)
{
int numReaders = resources[h].numReaders;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (i >= numReaders)
{
throw new Exception("Invalid reader id");
}
#endif
return ref resources.readerData[h.iType].ElementAt(resources.IndexReader(h, 0) + i);
}
// Data per graph level renderpass
public NativeList<PassData> passData;
public Dictionary<int, GraphicsFence> fences;
public DynamicArray<Name> passNames;
// Tightly packed lists all passes, add to these lists then index in it using offset+count
public NativeList<PassInputData> inputData;
public NativeList<PassOutputData> outputData;
public NativeList<PassFragmentData> fragmentData;
public NativeList<ResourceHandle> createData;
public NativeList<ResourceHandle> destroyData;
public NativeList<PassRandomWriteData> randomAccessResourceData;
// Data per native renderpas
public NativeList<NativePassData> nativePassData;
public NativeList<SubPassDescriptor> nativeSubPassData; //Tighty packed list of per nrp subpasses
// resources can be added as fragment both as input and output so make sure not to add them twice (return true upon new addition)
public bool AddToFragmentList(TextureAccess access, int listFirstIndex, int numItems)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (access.textureHandle.handle.type != RenderGraphResourceType.Texture) new Exception("Only textures can be used as a fragment attachment.");
#endif
for (var i = listFirstIndex; i < listFirstIndex + numItems; ++i)
{
ref var fragment = ref fragmentData.ElementAt(i);
if (fragment.resource.index == access.textureHandle.handle.index)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (fragment.resource.version != access.textureHandle.handle.version)
{
//this would mean you're trying to attach say both v1 and v2 of a resource to the same pass as an attachment
//this is not allowed
throw new Exception("Trying to UseFragment two versions of the same resource");
}
#endif
return false;
}
}
// Validate that we're correctly building up the fragment lists we can only append to the last list
// not int the middle of lists
Debug.Assert(listFirstIndex + numItems == fragmentData.Length);
fragmentData.Add(new PassFragmentData(
access.textureHandle.handle,
access.flags,
access.mipLevel,
access.depthSlice
));
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Name GetFullPassName(int passId) => passNames[passId];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string GetPassName(int passId) => passNames[passId].name;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string GetResourceName(ResourceHandle h) => resources.resourceNames[h.iType][h.index].name;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string GetResourceVersionedName(ResourceHandle h) => GetResourceName(h) + " V" + h.version;
// resources can be added as fragment both as input and output so make sure not to add them twice (return true upon new addition)
public bool AddToRandomAccessResourceList(ResourceHandle h, int randomWriteSlotIndex, bool preserveCounterValue, int listFirstIndex, int numItems)
{
for (var i = listFirstIndex; i < listFirstIndex + numItems; ++i)
{
if (randomAccessResourceData[i].resource.index == h.index && randomAccessResourceData[i].resource.type == h.type)
{
if (randomAccessResourceData[i].resource.version != h.version)
{
//this would mean you're trying to attach say both v1 and v2 of a resource to the same pass as an attachment
//this is not allowed
throw new Exception("Trying to UseTextureRandomWrite two versions of the same resource");
}
return false;
}
}
// Validate that we're correctly building up the fragment lists we can only append to the last list
// not int the middle of lists
Debug.Assert(listFirstIndex + numItems == randomAccessResourceData.Length);
randomAccessResourceData.Add(new PassRandomWriteData(h, randomWriteSlotIndex, preserveCounterValue));
return true;
}
// Mark all passes as unvisited this is useful for graph algorithms that do something with the tag
public void TagAllPasses(int value)
{
for (int passId = 0; passId < passData.Length; passId++)
{
passData.ElementAt(passId).tag = value;
}
}
public void CullAllPasses(bool isCulled)
{
for (int passId = 0; passId < passData.Length; passId++)
{
passData.ElementAt(passId).culled = isCulled;
}
}
// Helper to loop over native passes
public ref struct NativePassIterator
{
readonly CompilerContextData m_Ctx;
int m_Index;
public NativePassIterator(CompilerContextData ctx)
{
m_Ctx = ctx;
m_Index = -1;
}
public ref readonly NativePassData Current => ref m_Ctx.nativePassData.ElementAt(m_Index);
public bool MoveNext()
{
while (true)
{
m_Index++;
bool inRange = m_Index < m_Ctx.nativePassData.Length;
if (!inRange || m_Ctx.nativePassData.ElementAt(m_Index).IsValid())
return inRange;
}
}
public NativePassIterator GetEnumerator()
{
return this;
}
}
// Iterate only the active native passes
// the list may contain empty dummy entries after merging
public NativePassIterator NativePasses => new NativePassIterator(this);
// Use for testing only
internal List<NativePassData> GetNativePasses()
{
var result = new List<NativePassData>();
foreach (ref readonly var pass in NativePasses)
{
result.Add(pass);
}
return result;
}
// IDisposable implementation
bool m_AreNativeListsAllocated = false;
~CompilerContextData() => Cleanup();
public void Dispose()
{
Cleanup();
GC.SuppressFinalize(this);
}
void Cleanup()
{
resources.Dispose();
if (m_AreNativeListsAllocated)
{
passData.Dispose();
inputData.Dispose();
outputData.Dispose();
fragmentData.Dispose();
createData.Dispose();
destroyData.Dispose();
randomAccessResourceData.Dispose();
nativePassData.Dispose();
nativeSubPassData.Dispose();
m_AreNativeListsAllocated = false;
}
}
}
}