A game about forced loneliness, made by TACStudios
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}