A game about forced loneliness, made by TACStudios
at master 515 lines 24 kB view raw
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}