A game about forced loneliness, made by TACStudios
at master 310 lines 13 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Runtime.CompilerServices; 4using UnityEngine.Rendering; 5using Unity.Collections; 6using Unity.Collections.LowLevel.Unsafe; 7 8namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler 9{ 10 // Wrapper struct to allow storing strings in a DynamicArray which requires a type with a parameterless constructor 11 internal struct Name 12 { 13 public readonly string name; 14 public readonly int utf8ByteCount; 15 public Name(string name, bool computeUTF8ByteCount = false) 16 { 17 this.name = name; 18 this.utf8ByteCount = ((name?.Length > 0) && computeUTF8ByteCount) ? System.Text.Encoding.UTF8.GetByteCount((ReadOnlySpan<char>)name) : 0; 19 } 20 } 21 22 // Helper extensions for NativeList 23 internal static class NativeListExtensions 24 { 25 [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 internal static unsafe ReadOnlySpan<T> MakeReadOnlySpan<T>(this ref NativeList<T> list, int first, int numElements) where T : unmanaged 27 { 28#if UNITY_EDITOR 29 if (first + numElements > list.Length) 30 throw new IndexOutOfRangeException(); 31#endif 32 return new ReadOnlySpan<T>(&list.GetUnsafeReadOnlyPtr()[first], numElements); 33 } 34 35 [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 internal static int LastIndex<T>(this ref NativeList<T> list) where T : unmanaged 37 { 38 return list.Length - 1; 39 } 40 } 41 42 // Note pass=node in the graph, both are sometimes mixed up here 43 // Datastructure that contains passes and dependencies and allow you to iterate and reason on them more like a graph 44 internal class CompilerContextData : IDisposable, RenderGraph.ICompiledGraph 45 { 46 public CompilerContextData(int estimatedNumPasses) 47 { 48 passData = new NativeList<PassData>(estimatedNumPasses, AllocatorManager.Persistent); 49 fences = new Dictionary<int, GraphicsFence>(); 50 passNames = new DynamicArray<Name>(estimatedNumPasses, false); // T in NativeList<T> cannot contain managed types, so the names are stored separately 51 inputData = new NativeList<PassInputData>(estimatedNumPasses * 2, AllocatorManager.Persistent); 52 outputData = new NativeList<PassOutputData>(estimatedNumPasses * 2, AllocatorManager.Persistent); 53 fragmentData = new NativeList<PassFragmentData>(estimatedNumPasses * 4, AllocatorManager.Persistent); 54 randomAccessResourceData = new NativeList<PassRandomWriteData>(4, AllocatorManager.Persistent); // We assume not a lot of passes use random write 55 resources = new ResourcesData(); 56 nativePassData = new NativeList<NativePassData>(estimatedNumPasses, AllocatorManager.Persistent);// assume nothing gets merged 57 nativeSubPassData = new NativeList<SubPassDescriptor>(estimatedNumPasses, AllocatorManager.Persistent);// there should "never" be more subpasses than graph passes 58 createData = new NativeList<ResourceHandle>(estimatedNumPasses * 2, AllocatorManager.Persistent); // assume every pass creates two resources 59 destroyData = new NativeList<ResourceHandle>(estimatedNumPasses * 2, AllocatorManager.Persistent); // assume every pass destroys two resources 60 } 61 62 public void Initialize(RenderGraphResourceRegistry resourceRegistry) 63 { 64 resources.Initialize(resourceRegistry); 65 } 66 67 public void Clear() 68 { 69 passData.Clear(); 70 fences.Clear(); 71 passNames.Clear(); 72 inputData.Clear(); 73 outputData.Clear(); 74 fragmentData.Clear(); 75 randomAccessResourceData.Clear(); 76 resources.Clear(); 77 nativePassData.Clear(); 78 nativeSubPassData.Clear(); 79 createData.Clear(); 80 destroyData.Clear(); 81 } 82 83 public ResourcesData resources; 84 85 [MethodImpl(MethodImplOptions.AggressiveInlining)] 86 public ref ResourceUnversionedData UnversionedResourceData(ResourceHandle h) 87 { 88 return ref resources.unversionedData[h.iType].ElementAt(h.index); 89 } 90 91 [MethodImpl(MethodImplOptions.AggressiveInlining)] 92 public ref ResourceVersionedData VersionedResourceData(ResourceHandle h) 93 { 94 return ref resources[h]; 95 } 96 97 // Iterate over all the readers of a particular resource 98 [MethodImpl(MethodImplOptions.AggressiveInlining)] 99 public ReadOnlySpan<ResourceReaderData> Readers(ResourceHandle h) 100 { 101 int firstReader = ResourcesData.IndexReader(h, 0); 102 int numReaders = resources[h].numReaders; 103 return resources.readerData[h.iType].MakeReadOnlySpan(firstReader, numReaders); 104 } 105 106 // Get the i'th reader of a resource 107 [MethodImpl(MethodImplOptions.AggressiveInlining)] 108 public ref ResourceReaderData ResourceReader(ResourceHandle h, int i) 109 { 110 int numReaders = resources[h].numReaders; 111#if DEVELOPMENT_BUILD || UNITY_EDITOR 112 if (i >= numReaders) 113 { 114 throw new Exception("Invalid reader id"); 115 } 116#endif 117 return ref resources.readerData[h.iType].ElementAt(ResourcesData.IndexReader(h, 0) + i); 118 } 119 120 // Data per graph level renderpass 121 public NativeList<PassData> passData; 122 public Dictionary<int, GraphicsFence> fences; 123 public DynamicArray<Name> passNames; 124 125 // Tightly packed lists all passes, add to these lists then index in it using offset+count 126 public NativeList<PassInputData> inputData; 127 public NativeList<PassOutputData> outputData; 128 public NativeList<PassFragmentData> fragmentData; 129 public NativeList<ResourceHandle> createData; 130 public NativeList<ResourceHandle> destroyData; 131 public NativeList<PassRandomWriteData> randomAccessResourceData; 132 133 // Data per native renderpas 134 public NativeList<NativePassData> nativePassData; 135 public NativeList<SubPassDescriptor> nativeSubPassData; //Tighty packed list of per nrp subpasses 136 137 // resources can be added as fragment both as input and output so make sure not to add them twice (return true upon new addition) 138 public bool AddToFragmentList(TextureAccess access, int listFirstIndex, int numItems) 139 { 140#if DEVELOPMENT_BUILD || UNITY_EDITOR 141 if (access.textureHandle.handle.type != RenderGraphResourceType.Texture) new Exception("Only textures can be used as a fragment attachment."); 142#endif 143 for (var i = listFirstIndex; i < listFirstIndex + numItems; ++i) 144 { 145 ref var fragment = ref fragmentData.ElementAt(i); 146 if (fragment.resource.index == access.textureHandle.handle.index) 147 { 148#if DEVELOPMENT_BUILD || UNITY_EDITOR 149 if (fragment.resource.version != access.textureHandle.handle.version) 150 { 151 //this would mean you're trying to attach say both v1 and v2 of a resource to the same pass as an attachment 152 //this is not allowed 153 throw new Exception("Trying to UseFragment two versions of the same resource"); 154 } 155#endif 156 return false; 157 } 158 } 159 160 // Validate that we're correctly building up the fragment lists we can only append to the last list 161 // not int the middle of lists 162 Debug.Assert(listFirstIndex + numItems == fragmentData.Length); 163 164 fragmentData.Add(new PassFragmentData() 165 { 166 resource = access.textureHandle.handle, 167 accessFlags = access.flags, 168 mipLevel = access.mipLevel, 169 depthSlice = access.depthSlice, 170 }); 171 return true; 172 } 173 174 [MethodImpl(MethodImplOptions.AggressiveInlining)] 175 public Name GetFullPassName(int passId) => passNames[passId]; 176 177 [MethodImpl(MethodImplOptions.AggressiveInlining)] 178 public string GetPassName(int passId) => passNames[passId].name; 179 180 [MethodImpl(MethodImplOptions.AggressiveInlining)] 181 public string GetResourceName(ResourceHandle h) => resources.resourceNames[h.iType][h.index].name; 182 183 [MethodImpl(MethodImplOptions.AggressiveInlining)] 184 public string GetResourceVersionedName(ResourceHandle h) => GetResourceName(h) + " V" + h.version; 185 186 // resources can be added as fragment both as input and output so make sure not to add them twice (return true upon new addition) 187 public bool AddToRandomAccessResourceList(ResourceHandle h, int randomWriteSlotIndex, bool preserveCounterValue, int listFirstIndex, int numItems) 188 { 189 for (var i = listFirstIndex; i < listFirstIndex + numItems; ++i) 190 { 191 if (randomAccessResourceData[i].resource.index == h.index && randomAccessResourceData[i].resource.type == h.type) 192 { 193 if (randomAccessResourceData[i].resource.version != h.version) 194 { 195 //this would mean you're trying to attach say both v1 and v2 of a resource to the same pass as an attachment 196 //this is not allowed 197 throw new Exception("Trying to UseTextureRandomWrite two versions of the same resource"); 198 } 199 return false; 200 } 201 } 202 203 // Validate that we're correctly building up the fragment lists we can only append to the last list 204 // not int the middle of lists 205 Debug.Assert(listFirstIndex + numItems == randomAccessResourceData.Length); 206 207 randomAccessResourceData.Add(new PassRandomWriteData() 208 { 209 resource = h, 210 index = randomWriteSlotIndex, 211 preserveCounterValue = preserveCounterValue 212 }); 213 return true; 214 } 215 216 // Mark all passes as unvisited this is useful for graph algorithms that do something with the tag 217 public void TagAllPasses(int value) 218 { 219 for (int passId = 0; passId < passData.Length; passId++) 220 { 221 passData.ElementAt(passId).tag = value; 222 } 223 } 224 public void CullAllPasses(bool isCulled) 225 { 226 for (int passId = 0; passId < passData.Length; passId++) 227 { 228 passData.ElementAt(passId).culled = isCulled; 229 } 230 } 231 232 // Helper to loop over native passes 233 public struct NativePassIterator 234 { 235 readonly CompilerContextData m_Ctx; 236 int m_Index; 237 238 public NativePassIterator(CompilerContextData ctx) 239 { 240 m_Ctx = ctx; 241 m_Index = -1; 242 } 243 244 public ref readonly NativePassData Current => ref m_Ctx.nativePassData.ElementAt(m_Index); 245 246 public bool MoveNext() 247 { 248 while (true) 249 { 250 m_Index++; 251 bool inRange = m_Index < m_Ctx.nativePassData.Length; 252 if (!inRange || m_Ctx.nativePassData.ElementAt(m_Index).IsValid()) 253 return inRange; 254 } 255 } 256 257 public NativePassIterator GetEnumerator() 258 { 259 return this; 260 } 261 } 262 263 // Iterate only the active native passes 264 // the list may contain empty dummy entries after merging 265 public NativePassIterator NativePasses => new NativePassIterator(this); 266 267 268 // Use for testing only 269 internal List<NativePassData> GetNativePasses() 270 { 271 var result = new List<NativePassData>(); 272 foreach (ref readonly var pass in NativePasses) 273 { 274 result.Add(pass); 275 } 276 return result; 277 } 278 279 // IDisposable implementation 280 281 bool m_Disposed; 282 283 ~CompilerContextData() => Cleanup(); 284 285 public void Dispose() 286 { 287 Cleanup(); 288 GC.SuppressFinalize(this); 289 } 290 291 void Cleanup() 292 { 293 if (!m_Disposed) 294 { 295 resources.Dispose(); 296 297 passData.Dispose(); 298 inputData.Dispose(); 299 outputData.Dispose(); 300 fragmentData.Dispose(); 301 createData.Dispose(); 302 destroyData.Dispose(); 303 randomAccessResourceData.Dispose(); 304 nativePassData.Dispose(); 305 nativeSubPassData.Dispose(); 306 m_Disposed = true; 307 } 308 } 309 } 310}