A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using UnityEngine.Experimental.Rendering;
4
5namespace UnityEngine.Rendering.RenderGraphModule
6{
7 // This is a class making it a struct wouldn't help as we pas it around as an interface which means it would be boxed/unboxed anyway
8 // Publicly this class has different faces to help the users with different pass types through type safety but internally
9 // we just have a single implementation for all builders
10 internal class RenderGraphBuilders : IBaseRenderGraphBuilder, IComputeRenderGraphBuilder, IRasterRenderGraphBuilder, IUnsafeRenderGraphBuilder
11 {
12 RenderGraphPass m_RenderPass;
13 RenderGraphResourceRegistry m_Resources;
14 RenderGraph m_RenderGraph;
15 bool m_Disposed;
16
17
18 public RenderGraphBuilders()
19 {
20 m_RenderPass = null;
21 m_Resources = null;
22 m_RenderGraph = null;
23 m_Disposed = true;
24 }
25
26 public void Setup(RenderGraphPass renderPass, RenderGraphResourceRegistry resources, RenderGraph renderGraph)
27 {
28#if DEVELOPMENT_BUILD || UNITY_EDITOR
29 // If the object is not disposed yet this is an error as the pass is not finished (only in the dispose we register it with the rendergraph)
30 // This is likely cause by a user not doing a clean using and then forgetting to manually dispose the object.
31 if (m_Disposed != true)
32 {
33 throw new Exception("Please finish building the previous pass first by disposing the pass builder object before adding a new pass.");
34 }
35#endif
36 m_RenderPass = renderPass;
37 m_Resources = resources;
38 m_RenderGraph = renderGraph;
39 m_Disposed = false;
40
41 renderPass.useAllGlobalTextures = false;
42
43 if (renderPass.type == RenderGraphPassType.Raster)
44 {
45 CommandBuffer.ThrowOnSetRenderTarget = true;
46 }
47 }
48
49
50 public void EnableAsyncCompute(bool value)
51 {
52 m_RenderPass.EnableAsyncCompute(value);
53 }
54
55 public void AllowPassCulling(bool value)
56 {
57 // This pass cannot be culled if it allows global state modifications
58 if (value && m_RenderPass.allowGlobalState)
59 return;
60
61 m_RenderPass.AllowPassCulling(value);
62 }
63
64 public void AllowGlobalStateModification(bool value)
65 {
66 m_RenderPass.AllowGlobalState(value);
67
68 // This pass cannot be culled if it allows global state modifications
69 if (value)
70 {
71 AllowPassCulling(false);
72 }
73 }
74
75 /// <summary>
76 /// Enable foveated rendering for this pass.
77 /// </summary>
78 /// <param name="value">True to enable foveated rendering.</param>
79 public void EnableFoveatedRasterization(bool value)
80 {
81 m_RenderPass.EnableFoveatedRasterization(value);
82 }
83
84 public BufferHandle CreateTransientBuffer(in BufferDesc desc)
85 {
86 var result = m_Resources.CreateBuffer(desc, m_RenderPass.index);
87 UseResource(result.handle, AccessFlags.Write | AccessFlags.Read, isTransient: true);
88 return result;
89 }
90
91 public BufferHandle CreateTransientBuffer(in BufferHandle computebuffer)
92 {
93 var desc = m_Resources.GetBufferResourceDesc(computebuffer.handle);
94 return CreateTransientBuffer(desc);
95 }
96
97 public TextureHandle CreateTransientTexture(in TextureDesc desc)
98 {
99 var result = m_Resources.CreateTexture(desc, m_RenderPass.index);
100 UseResource(result.handle, AccessFlags.Write | AccessFlags.Read, isTransient: true);
101 return result;
102 }
103
104 public TextureHandle CreateTransientTexture(in TextureHandle texture)
105 {
106 var desc = m_Resources.GetTextureResourceDesc(texture.handle);
107 return CreateTransientTexture(desc);
108 }
109
110 public void Dispose()
111 {
112 Dispose(true);
113 }
114
115 protected virtual void Dispose(bool disposing)
116 {
117 if (m_Disposed)
118 return;
119
120 try
121 {
122
123 if (disposing)
124 {
125 // Use all globals simply means this... we do a UseTexture on all globals so the pass has the correct dependencies.
126 // This of course goes to show how bad an idea shader-system wide globals really are dependency/lifetime tracking wise :-)
127 if (m_RenderPass.useAllGlobalTextures)
128 {
129 foreach (var texture in m_RenderGraph.AllGlobals())
130 {
131 this.UseTexture(texture, AccessFlags.Read);
132 }
133 }
134
135 // Set globals on the graph fronted side so subsequent passes can have pass dependencies on these global texture handles
136 foreach (var t in m_RenderPass.setGlobalsList)
137 {
138 m_RenderGraph.SetGlobal(t.Item1, t.Item2);
139 }
140
141 m_RenderGraph.OnPassAdded(m_RenderPass);
142 }
143 }
144 finally
145 {
146 if (m_RenderPass.type == RenderGraphPassType.Raster)
147 {
148 CommandBuffer.ThrowOnSetRenderTarget = false;
149 }
150
151 m_RenderPass = null;
152 m_Resources = null;
153 m_RenderGraph = null;
154 m_Disposed = true;
155 }
156 }
157
158 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
159 private void ValidateWriteTo(in ResourceHandle handle)
160 {
161 if (RenderGraph.enableValidityChecks)
162 {
163 // Write by design generates a new version of the resource. However
164 // you could in theory write to v2 of the resource while there is already
165 // a v3 so this write would then introduce a new v4 of the resource.
166 // This would mean a divergence in the versioning history subsequent versions based on v2 and other subsequent versions based on v3
167 // this would be very confusing as they are all still refered to by the same texture just different versions
168 // so we decide to disallow this. It can always be (at zero cost) handled by using a "Move" pass to move the divergent version
169 // so it's own texture resource which again has it's own single history of versions.
170 if (handle.IsVersioned)
171 {
172 var name = m_Resources.GetRenderGraphResourceName(handle);
173 throw new InvalidOperationException($"Trying to write to a versioned resource handle. You can only write to unversioned resource handles to avoid branches in the resource history. (pass {m_RenderPass.name} resource{name}).");
174 }
175
176 if (m_RenderPass.IsWritten(handle))
177 {
178 // We bump the version and write count if you call USeResource with writing flags. So calling this several times
179 // would lead to the side effect of new versions for every call to UseResource leading to incorrect versions.
180 // In theory we could detect and ignore the second UseResource but we decided to just disallow is as it might also
181 // Stem from user confusion.
182 // It seems the most likely cause of such a situation would be something like:
183 // TextureHandle b = a;
184 // ...
185 // much much code in between, user lost track that a=b
186 // ...
187 // builder.WriteTexture(a)
188 // builder.WriteTexture(b)
189 // > Get this error they were probably thinking they were writing two separate outputs... but they are just two versions of resource 'a'
190 // where they can only differ between by careful management of versioned resources.
191 var name = m_Resources.GetRenderGraphResourceName(handle);
192 throw new InvalidOperationException($"Trying to write a resource twice in a pass. You can only write the same resource once within a pass (pass {m_RenderPass.name} resource{name}).");
193 }
194 }
195 }
196
197 private ResourceHandle UseResource(in ResourceHandle handle, AccessFlags flags, bool isTransient = false)
198 {
199 CheckResource(handle);
200
201 // If we are not discarding the resource, add a "read" dependency on the current version
202 // this "Read" is a bit of a misnomer it really means more like "Preserve existing content or read"
203 if ((flags & AccessFlags.Discard) == 0)
204 {
205 ResourceHandle versioned;
206 if (!handle.IsVersioned)
207 {
208 versioned = m_Resources.GetLatestVersionHandle(handle);
209 }
210 else
211 {
212 versioned = handle;
213 }
214
215 if (isTransient)
216 {
217 m_RenderPass.AddTransientResource(versioned);
218 return GetLatestVersionHandle(handle);
219 }
220
221 m_RenderPass.AddResourceRead(versioned);
222
223 if ((flags & AccessFlags.Read) == 0)
224 {
225 // Flag the resource as being an "implicit read" so that we can distinguish it from a user-specified read
226 m_RenderPass.implicitReadsList.Add(versioned);
227 }
228 }
229 else
230 {
231 // We are discarding it but we still read it, so we add a dependency on version "0" of this resource
232 if ((flags & AccessFlags.Read) != 0)
233 {
234 m_RenderPass.AddResourceRead(m_Resources.GetZeroVersionedHandle(handle));
235 }
236 }
237
238 if ((flags & AccessFlags.Write) != 0)
239 {
240 ValidateWriteTo(handle);
241 m_RenderPass.AddResourceWrite(m_Resources.GetNewVersionedHandle(handle));
242 m_Resources.IncrementWriteCount(handle);
243 }
244
245 return GetLatestVersionHandle(handle);
246 }
247
248 public BufferHandle UseBuffer(in BufferHandle input, AccessFlags flags)
249 {
250 UseResource(input.handle, flags);
251 return input;
252 }
253
254 // UseTexture and SetRenderAttachment are currently forced to be mutually exclusive in the same pass
255 // check this.
256 // We currently ignore the version. In theory there might be some cases that are actually allowed with versioning
257 // for ample UseTexture(myTexV1, read) UseFragment(myTexV2, ReadWrite) as they are different versions
258 // but for now we don't allow any of that.
259 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
260 private void CheckNotUseFragment(TextureHandle tex)
261 {
262 if(RenderGraph.enableValidityChecks)
263 {
264 bool usedAsFragment = false;
265 usedAsFragment = (m_RenderPass.depthAccess.textureHandle.IsValid() && m_RenderPass.depthAccess.textureHandle.handle.index == tex.handle.index);
266 if (!usedAsFragment)
267 {
268 for (int i = 0; i <= m_RenderPass.colorBufferMaxIndex; i++)
269 {
270 if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid() && m_RenderPass.colorBufferAccess[i].textureHandle.handle.index == tex.handle.index)
271 {
272 usedAsFragment = true;
273 break;
274 }
275 }
276 }
277
278 if (usedAsFragment)
279 {
280 var name = m_Resources.GetRenderGraphResourceName(tex.handle);
281 throw new ArgumentException($"Trying to UseTexture on a texture that is already used through SetRenderAttachment. Consider updating your code. (pass {m_RenderPass.name} resource{name}).");
282 }
283 }
284 }
285
286 public void UseTexture(in TextureHandle input, AccessFlags flags)
287 {
288 CheckNotUseFragment(input);
289 UseResource(input.handle, flags);
290 }
291
292 public void UseGlobalTexture(int propertyId, AccessFlags flags)
293 {
294 var h = m_RenderGraph.GetGlobal(propertyId);
295 if (h.IsValid())
296 {
297 UseTexture(h, flags);
298 }
299 else
300 {
301 throw new ArgumentException($"Trying to read global texture property {propertyId} but no previous pass in the graph assigned a value to this global.");
302 }
303 }
304
305 public void UseAllGlobalTextures(bool enable)
306 {
307 m_RenderPass.useAllGlobalTextures = enable;
308 }
309
310 public void SetGlobalTextureAfterPass(in TextureHandle input, int propertyId)
311 {
312 m_RenderPass.setGlobalsList.Add(ValueTuple.Create(input, propertyId));
313 }
314
315 // Shared validation between SetRenderAttachment/SetRenderAttachmentDepth
316 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
317 private void CheckUseFragment(TextureHandle tex, bool isDepth)
318 {
319 if(RenderGraph.enableValidityChecks)
320 {
321 // We ignore the version as we don't allow mixing UseTexture/UseFragment between different versions
322 // even though it should theoretically work (and we might do so in the future) for now we're overly strict.
323 bool alreadyUsed = false;
324
325 //TODO: Check grab textures here and allow if it's grabbed. For now
326 // SetRenderAttachment()
327 // UseTexture(grab)
328 // will work but not the other way around
329 for (int i = 0; i < m_RenderPass.resourceReadLists[tex.handle.iType].Count; i++)
330 {
331 if (m_RenderPass.resourceReadLists[tex.handle.iType][i].index == tex.handle.index)
332 {
333 alreadyUsed = true;
334 break;
335 }
336 }
337
338 for (int i = 0; i < m_RenderPass.resourceWriteLists[tex.handle.iType].Count; i++)
339 {
340 if (m_RenderPass.resourceWriteLists[tex.handle.iType][i].index == tex.handle.index)
341 {
342 alreadyUsed = true;
343 break;
344 }
345 }
346
347 if (alreadyUsed)
348 {
349 var name = m_Resources.GetRenderGraphResourceName(tex.handle);
350 throw new InvalidOperationException($"Trying to SetRenderAttachment on a texture that is already used through UseTexture/SetRenderAttachment. Consider updating your code. (pass '{m_RenderPass.name}' resource '{name}').");
351 }
352
353 m_Resources.GetRenderTargetInfo(tex.handle, out var info);
354
355 // The old path is full of invalid uses that somehow work (or seemt to work) so we skip the tests if not using actual native renderpass
356 if (m_RenderGraph.nativeRenderPassesEnabled)
357 {
358 if (isDepth)
359 {
360 if (!GraphicsFormatUtility.IsDepthFormat(info.format))
361 {
362 var name = m_Resources.GetRenderGraphResourceName(tex.handle);
363 throw new InvalidOperationException($"Trying to SetRenderAttachmentDepth on a texture that has a color format {info.format}. Use a texture with a depth format instead. (pass '{m_RenderPass.name}' resource '{name}').");
364 }
365 }
366 else
367 {
368 if (GraphicsFormatUtility.IsDepthFormat(info.format))
369 {
370 var name = m_Resources.GetRenderGraphResourceName(tex.handle);
371 throw new InvalidOperationException($"Trying to SetRenderAttachment on a texture that has a depth format. Use a texture with a color format instead. (pass '{m_RenderPass.name}' resource '{name}').");
372 }
373 }
374 }
375
376 foreach (var globalTex in m_RenderPass.setGlobalsList)
377 {
378 if (globalTex.Item1.handle.index == tex.handle.index)
379 {
380 throw new InvalidOperationException("Trying to SetRenderAttachment on a texture that is currently set on a global texture slot. Shaders might be using the texture using samplers. You should ensure textures are not set as globals when using them as fragment attachments.");
381 }
382 }
383 }
384 }
385
386 public void SetRenderAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice)
387 {
388 CheckUseFragment(tex, false);
389 ResourceHandle result = UseResource(tex.handle, flags);
390 // Note the version for the attachments is a bit arbitrary so we just use the latest for now
391 // it doesn't really matter as it's really the Read/Write lists that determine that
392 // This is just to keep track of the handle->mrt index mapping
393 var th = new TextureHandle();
394 th.handle = result;
395 m_RenderPass.SetColorBufferRaw(th, index, flags, mipLevel, depthSlice);
396 }
397
398 public void SetInputAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice)
399 {
400 CheckUseFragment(tex, false);
401 ResourceHandle result = UseResource(tex.handle, flags);
402 // Note the version for the attachments is a bit arbitrary so we just use the latest for now
403 // it doesn't really matter as it's really the Read/Write lists that determine that
404 // This is just to keep track of the handle->mrt index mapping
405 var th = new TextureHandle();
406 th.handle = result;
407 m_RenderPass.SetFragmentInputRaw(th, index, flags, mipLevel, depthSlice);
408 }
409
410 public void SetRenderAttachmentDepth(TextureHandle tex, AccessFlags flags, int mipLevel, int depthSlice)
411 {
412 CheckUseFragment(tex, true);
413 ResourceHandle result = UseResource(tex.handle, flags);
414 // Note the version for the attachments is a bit arbitrary so we just use the latest for now
415 // it doesn't really matter as it's really the Read/Write lists that determine that
416 // This is just to keep track to bind this handle as a depth texture.
417 var th = new TextureHandle();
418 th.handle = result;
419 m_RenderPass.SetDepthBufferRaw(th, flags, mipLevel, depthSlice);
420 }
421
422 public TextureHandle SetRandomAccessAttachment(TextureHandle input, int index, AccessFlags flags = AccessFlags.Read)
423 {
424 CheckNotUseFragment(input);
425 ResourceHandle result = UseResource(input.handle, flags);
426
427 // Note the version for the attachments is a bit arbitrary so we just use the latest for now
428 // it doesn't really matter as it's really the Read/Write lists that determine that
429 // This is just to keep track of the resources to bind before execution
430 var th = new TextureHandle();
431 th.handle = result;
432 m_RenderPass.SetRandomWriteResourceRaw(th.handle, index, false, flags);
433 return input;
434 }
435
436 public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, AccessFlags flags = AccessFlags.Read)
437 {
438 var h = UseBuffer(input, flags);
439
440 // Note the version for the attachments is a bit arbitrary so we just use the latest for now
441 // it doesn't really matter as it's really the Read/Write lists that determine that
442 // This is just to keep track of the resources to bind before execution
443 m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, true, flags);
444 return input;
445 }
446
447 public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, bool preserveCounterValue, AccessFlags flags = AccessFlags.Read)
448 {
449 var h = UseBuffer(input, flags);
450
451 // Note the version for the attachments is a bit arbitrary so we just use the latest for now
452 // it doesn't really matter as it's really the Read/Write lists that determine that
453 // This is just to keep track of the resources to bind before execution
454 m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, preserveCounterValue, flags);
455 return input;
456 }
457
458 public void SetRenderFunc<PassData>(BaseRenderFunc<PassData, ComputeGraphContext> renderFunc) where PassData : class, new()
459 {
460 ((ComputeRenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
461 }
462
463 public void SetRenderFunc<PassData>(BaseRenderFunc<PassData, RasterGraphContext> renderFunc) where PassData : class, new()
464 {
465 ((RasterRenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
466 }
467
468 public void SetRenderFunc<PassData>(BaseRenderFunc<PassData, UnsafeGraphContext> renderFunc) where PassData : class, new()
469 {
470 ((UnsafeRenderGraphPass<PassData>)m_RenderPass).renderFunc = renderFunc;
471 }
472
473 public void UseRendererList(in RendererListHandle input)
474 {
475 m_RenderPass.UseRendererList(input);
476 }
477
478 private ResourceHandle GetLatestVersionHandle(in ResourceHandle handle)
479 {
480 // Transient resources can't be used outside the pass so can never be versioned
481 if (m_Resources.GetRenderGraphResourceTransientIndex(handle) >= 0)
482 {
483 return handle;
484 }
485
486 return m_Resources.GetLatestVersionHandle(handle);
487 }
488
489 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
490 void CheckResource(in ResourceHandle res, bool checkTransientReadWrite = false)
491 {
492 if(RenderGraph.enableValidityChecks)
493 {
494 if (res.IsValid())
495 {
496 int transientIndex = m_Resources.GetRenderGraphResourceTransientIndex(res);
497 // We have dontCheckTransientReadWrite here because users may want to use UseColorBuffer/UseDepthBuffer API to benefit from render target auto binding. In this case we don't want to raise the error.
498 if (transientIndex == m_RenderPass.index && checkTransientReadWrite)
499 {
500 Debug.LogError($"Trying to read or write a transient resource at pass {m_RenderPass.name}.Transient resource are always assumed to be both read and written.");
501 }
502
503 if (transientIndex != -1 && transientIndex != m_RenderPass.index)
504 {
505 throw new ArgumentException($"Trying to use a transient {res.type} (pass index {transientIndex}) in a different pass (pass index {m_RenderPass.index}).");
506 }
507 }
508 else
509 {
510 throw new ArgumentException($"Trying to use an invalid resource (pass {m_RenderPass.name}).");
511 }
512 }
513 }
514 }
515}