A game about forced loneliness, made by TACStudios
at master 1481 lines 76 kB view raw
1using System; 2using System.Diagnostics; 3using System.Collections.Generic; 4using Unity.Collections; 5using Unity.Collections.LowLevel.Unsafe; 6using UnityEngine.Rendering; 7 8namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler 9{ 10 internal partial class NativePassCompiler : IDisposable 11 { 12 internal struct RenderGraphInputInfo 13 { 14 public RenderGraphResourceRegistry m_ResourcesForDebugOnly; 15 public List<RenderGraphPass> m_RenderPasses; 16 public string debugName; 17 public bool disableCulling; 18 } 19 20 internal RenderGraphInputInfo graph; 21 internal CompilerContextData contextData = null; 22 internal CompilerContextData defaultContextData; 23 internal CommandBuffer previousCommandBuffer; 24 Stack<int> toVisitPassIds; 25 26 RenderGraphCompilationCache m_CompilationCache; 27 28 internal const int k_EstimatedPassCount = 100; 29 internal const int k_MaxSubpass = 8; // Needs to match with RenderPassSetup.h 30 31 NativeList<AttachmentDescriptor> m_BeginRenderPassAttachments; 32 33 public NativePassCompiler(RenderGraphCompilationCache cache) 34 { 35 m_CompilationCache = cache; 36 defaultContextData = new CompilerContextData(k_EstimatedPassCount); 37 toVisitPassIds = new Stack<int>(k_EstimatedPassCount); 38 m_BeginRenderPassAttachments = new NativeList<AttachmentDescriptor>(FixedAttachmentArray<AttachmentDescriptor>.MaxAttachments, Allocator.Persistent); 39 } 40 41 // IDisposable implementation 42 43 bool m_Disposed; 44 45 ~NativePassCompiler() => Cleanup(); 46 47 public void Dispose() 48 { 49 Cleanup(); 50 GC.SuppressFinalize(this); 51 } 52 53 void Cleanup() 54 { 55 if (!m_Disposed) 56 { 57 m_BeginRenderPassAttachments.Dispose(); 58 m_Disposed = true; 59 } 60 } 61 62 public bool Initialize(RenderGraphResourceRegistry resources, List<RenderGraphPass> renderPasses, bool disableCulling, string debugName, bool useCompilationCaching, int graphHash, int frameIndex) 63 { 64 bool cached = false; 65 if (!useCompilationCaching) 66 contextData = defaultContextData; 67 else 68 cached = m_CompilationCache.GetCompilationCache(graphHash, frameIndex, out contextData); 69 70 graph.m_ResourcesForDebugOnly = resources; 71 graph.m_RenderPasses = renderPasses; 72 graph.disableCulling = disableCulling; 73 graph.debugName = debugName; 74 75 Clear(clearContextData: !useCompilationCaching); 76 77 return cached; 78 } 79 80 public void Compile(RenderGraphResourceRegistry resources) 81 { 82 SetupContextData(resources); 83 84 BuildGraph(); 85 86 CullUnusedRenderPasses(); 87 88 TryMergeNativePasses(); 89 90 FindResourceUsageRanges(); 91 92 DetectMemoryLessResources(); 93 94 PrepareNativeRenderPasses(); 95 } 96 97 public void Clear(bool clearContextData) 98 { 99 if (clearContextData) 100 contextData.Clear(); 101 toVisitPassIds.Clear(); 102 } 103 104 void SetPassStatesForNativePass(int nativePassId) 105 { 106 NativePassData.SetPassStatesForNativePass(contextData, nativePassId); 107 } 108 109 internal enum NativeCompilerProfileId 110 { 111 NRPRGComp_PrepareNativePass, 112 NRPRGComp_SetupContextData, 113 NRPRGComp_BuildGraph, 114 NRPRGComp_CullNodes, 115 NRPRGComp_TryMergeNativePasses, 116 NRPRGComp_FindResourceUsageRanges, 117 NRPRGComp_DetectMemorylessResources, 118 NRPRGComp_ExecuteCreateResources, 119 NRPRGComp_ExecuteBeginRenderpassCommand, 120 NRPRGComp_ExecuteDestroyResources, 121 } 122 123 void SetupContextData(RenderGraphResourceRegistry resources) 124 { 125 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_SetupContextData))) 126 { 127 contextData.Initialize(resources); 128 } 129 } 130 131 void BuildGraph() 132 { 133 var ctx = contextData; 134 List<RenderGraphPass> passes = graph.m_RenderPasses; 135 136 // Not clearing data, we will do it right after in the for loop 137 // This is to prevent unnecessary costly copies of pass struct (128bytes) 138 ctx.passData.ResizeUninitialized(passes.Count); 139 140 // Build up the context graph and keep track of nodes we encounter that can't be culled 141 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_BuildGraph))) 142 { 143 for (int passId = 0; passId < passes.Count; passId++) 144 { 145 var inputPass = passes[passId]; 146 147#if DEVELOPMENT_BUILD || UNITY_EDITOR 148 if (inputPass.type == RenderGraphPassType.Legacy) 149 { 150 throw new Exception("Pass '" + inputPass.name + "' is using the legacy rendergraph API." + 151 " You cannot use legacy passes with the native render pass compiler." + 152 " Please do not use AddPass on the rendergrpah but use one of the more specific pass types such as AddRasterRenderPass"); 153 } 154#endif 155 156 // Accessing already existing passData in place in the container through reference to avoid deep copy 157 // Make sure everything is reset and initialized or we will use obsolete data from previous frame 158 ref var ctxPass = ref ctx.passData.ElementAt(passId); 159 ctxPass.ResetAndInitialize(inputPass, passId); 160 161 ctx.passNames.Add(new Name(inputPass.name, true)); 162 163 if (ctxPass.hasSideEffects) 164 { 165 toVisitPassIds.Push(passId); 166 } 167 168 // Set up the list of fragment attachments for this pass 169 // Note: This doesn't set up the resource reader/writer list as the fragment attachments 170 // will also be in the pass read/write lists accordingly 171 if (ctxPass.type == RenderGraphPassType.Raster) 172 { 173 // Grab offset in context fragment list to begin building the fragment list 174 ctxPass.firstFragment = ctx.fragmentData.Length; 175 176 // Depth attachment is always at index 0 177 if (inputPass.depthAccess.textureHandle.handle.IsValid()) 178 { 179 ctxPass.fragmentInfoHasDepth = true; 180 181 if (ctx.AddToFragmentList(inputPass.depthAccess, ctxPass.firstFragment, ctxPass.numFragments)) 182 { 183 ctxPass.AddFragment(inputPass.depthAccess.textureHandle.handle, ctx); 184 } 185 } 186 187 for (var ci = 0; ci < inputPass.colorBufferMaxIndex + 1; ++ci) 188 { 189 // Skip unused color slots 190 if (!inputPass.colorBufferAccess[ci].textureHandle.handle.IsValid()) continue; 191 192 if (ctx.AddToFragmentList(inputPass.colorBufferAccess[ci], ctxPass.firstFragment, ctxPass.numFragments)) 193 { 194 ctxPass.AddFragment(inputPass.colorBufferAccess[ci].textureHandle.handle, ctx); 195 } 196 } 197 198 // Grab offset in context fragment list to begin building the fragment input list 199 ctxPass.firstFragmentInput = ctx.fragmentData.Length; 200 201 for (var ci = 0; ci < inputPass.fragmentInputMaxIndex + 1; ++ci) 202 { 203 // Skip unused fragment input slots 204 if (!inputPass.fragmentInputAccess[ci].textureHandle.IsValid()) continue; 205 206 var resource = inputPass.fragmentInputAccess[ci].textureHandle; 207 if (ctx.AddToFragmentList(inputPass.fragmentInputAccess[ci], ctxPass.firstFragmentInput, ctxPass.numFragmentInputs)) 208 { 209 ctxPass.AddFragmentInput(inputPass.fragmentInputAccess[ci].textureHandle.handle, ctx); 210 } 211 } 212 213 // Grab offset in context random write list to begin building the per pass random write lists 214 ctxPass.firstRandomAccessResource = ctx.randomAccessResourceData.Length; 215 216 for (var ci = 0; ci < passes[passId].randomAccessResourceMaxIndex + 1; ++ci) 217 { 218 ref var uav = ref passes[passId].randomAccessResource[ci]; 219 220 // Skip unused random write slots 221 if (!uav.h.IsValid()) continue; 222 223 if (ctx.AddToRandomAccessResourceList(uav.h, ci, uav.preserveCounterValue, ctxPass.firstRandomAccessResource, ctxPass.numRandomAccessResources)) 224 { 225 ctxPass.AddRandomAccessResource(); 226 } 227 } 228 229 // This is suspicious, there are frame buffer fetch inputs but nothing is output. We don't allow this for now. 230 // In theory you could fb-fetch inputs and write something to a uav and output nothing? This needs to be investigated 231 // so don't allow it for now. 232 if (ctxPass.numFragments == 0) 233 { 234 Debug.Assert(ctxPass.numFragmentInputs == 0); 235 } 236 } 237 238 // Set up per resource type read/write lists for this pass 239 ctxPass.firstInput = ctx.inputData.Length; // Grab offset in context input list 240 ctxPass.firstOutput = ctx.outputData.Length; // Grab offset in context output list 241 for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) 242 { 243 var resourceWrite = inputPass.resourceWriteLists[type]; 244 var resourceWriteCount = resourceWrite.Count; 245 for (var i = 0; i < resourceWriteCount; ++i) 246 { 247 var resource = resourceWrite[i]; 248 249 // Writing to an imported resource is a side effect so mark the pass if needed 250 ref var resData = ref ctx.UnversionedResourceData(resource); 251 if (resData.isImported) 252 { 253 if (ctxPass.hasSideEffects == false) 254 { 255 ctxPass.hasSideEffects = true; 256 toVisitPassIds.Push(passId); 257 } 258 } 259 260 // Mark this pass as writing to this version of the resource 261 ctx.resources[resource].SetWritingPass(ctx, resource, passId); 262 263 ctx.outputData.Add(new PassOutputData 264 { 265 resource = resource, 266 }); 267 268 ctxPass.numOutputs++; 269 } 270 271 var resourceRead = inputPass.resourceReadLists[type]; 272 var resourceReadCount = resourceRead.Count; 273 for (var i = 0; i < resourceReadCount; ++i) 274 { 275 var resource = resourceRead[i]; 276 277 // Mark this pass as reading from this version of the resource 278 ctx.resources[resource].RegisterReadingPass(ctx, resource, passId, ctxPass.numInputs); 279 280 ctx.inputData.Add(new PassInputData 281 { 282 resource = resource, 283 }); 284 285 ctxPass.numInputs++; 286 } 287 288 var resourceTrans = inputPass.transientResourceList[type]; 289 var resourceTransCount = resourceTrans.Count; 290 291 for (var i = 0; i < resourceTransCount; ++i) 292 { 293 var resource = resourceTrans[i]; 294 295 // Mark this pass as reading from this version of the resource 296 ctx.resources[resource].RegisterReadingPass(ctx, resource, passId, ctxPass.numInputs); 297 298 ctx.inputData.Add(new PassInputData 299 { 300 resource = resource, 301 }); 302 303 ctxPass.numInputs++; 304 305 // Mark this pass as writing to this version of the resource 306 ctx.resources[resource].SetWritingPass(ctx, resource, passId); 307 308 ctx.outputData.Add(new PassOutputData 309 { 310 resource = resource, 311 }); 312 313 ctxPass.numOutputs++; 314 } 315 } 316 } 317 } 318 } 319 320 void CullUnusedRenderPasses() 321 { 322 var ctx = contextData; 323 324 // Source = input of the graph and starting point that takes no inputs itself : e.g. z-prepass 325 // Sink = output of the graph and end point e.g. rendered frame 326 // Usually sinks will have to be pinned or write to an external resource or the whole graph would get culled :-) 327 328 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_CullNodes))) 329 { 330 // No need to go further if we don't enable culling 331 if (graph.disableCulling) 332 return; 333 334 // Cull all passes first 335 ctx.CullAllPasses(true); 336 337 // Flood fill downstream algorithm using BFS, 338 // starting from the pinned nodes to all their dependencies 339 while (toVisitPassIds.Count != 0) 340 { 341 int passId = toVisitPassIds.Pop(); 342 343 ref var passData = ref ctx.passData.ElementAt(passId); 344 345 // We already found this node through another dependency chain 346 if (!passData.culled) continue; 347 348 // Flow upstream from this node 349 foreach (ref readonly var input in passData.Inputs(ctx)) 350 { 351 int inputPassIndex = ctx.resources[input.resource].writePassId; 352 toVisitPassIds.Push(inputPassIndex); 353 } 354 355 // We need this node, don't cull it 356 passData.culled = false; 357 } 358 359 // Update graph based on freshly culled nodes, remove any connection to them 360 var numPasses = ctx.passData.Length; 361 for (int passIndex = 0; passIndex < numPasses; passIndex++) 362 { 363 ref readonly var pass = ref ctx.passData.ElementAt(passIndex); 364 365 // Remove the connections from the list so they won't be visited again 366 if (pass.culled) 367 { 368 foreach (ref readonly var input in pass.Inputs(ctx)) 369 { 370 var inputResource = input.resource; 371 ctx.resources[inputResource].RemoveReadingPass(ctx, inputResource, pass.passId); 372 } 373 } 374 } 375 } 376 } 377 378 void TryMergeNativePasses() 379 { 380 var ctx = contextData; 381 382 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_TryMergeNativePasses))) 383 { 384 // Try to merge raster passes into the currently active native pass. This will not do pass reordering yet 385 // so it is simply greedy trying to add the next pass to a currently active one. 386 // In the future we want to try adding any available (i.e. that has no data dependencies on any future results) future pass 387 // and allow merging that into the pass thus greedily reordering passes. But reordering requires a lot of API validation 388 // that ensures rendering behaves accordingly with reordered passes so we don't allow that for now. 389 390 // !!! Compilation caching warning !!! 391 // Merging of passes is highly dependent on render texture properties. 392 // When caching the render graph compilation, we hash a subset of those render texture properties to make sure we recompile the graph if needed. 393 // We only hash a subset for performance reason so if you add logic here that will change the behavior of pass merging, 394 // make sure that the relevant properties are hashed properly. See RenderGraphPass.ComputeHash() 395 396 int activeNativePassId = -1; 397 398 for (var passIdx = 0; passIdx < ctx.passData.Length; ++passIdx) 399 { 400 ref var passToAdd = ref ctx.passData.ElementAt(passIdx); 401 402 // If the pass has been culled, just ignore it 403 if (passToAdd.culled) 404 { 405 continue; 406 } 407 408 // If no active pass, there is nothing to merge... 409 if (activeNativePassId == -1) 410 { 411 //If raster, start a new native pass with the current pass 412 if (passToAdd.type == RenderGraphPassType.Raster) 413 { 414 // Allocate a stand-alone native renderpass based on the current pass 415 ctx.nativePassData.Add(new NativePassData(ref passToAdd, ctx)); 416 passToAdd.nativePassIndex = ctx.nativePassData.LastIndex(); 417 activeNativePassId = passToAdd.nativePassIndex; 418 } 419 } 420 // There is an native pass currently open, try to add the current graph pass to it 421 else 422 { 423 var mergeTestResult = NativePassData.TryMerge(contextData, activeNativePassId, passIdx); 424 425 // Merge failed, close current native render pass and create a new one 426 if (mergeTestResult.reason != PassBreakReason.Merged) 427 { 428 SetPassStatesForNativePass(activeNativePassId); 429#if UNITY_EDITOR 430 ref var nativePassData = ref contextData.nativePassData.ElementAt(activeNativePassId); 431 nativePassData.breakAudit = mergeTestResult; 432#endif 433 if (mergeTestResult.reason == PassBreakReason.NonRasterPass) 434 { 435 // Non-raster pass, no active native pass at all 436 activeNativePassId = -1; 437 } 438 else 439 { 440 // Raster but cannot be merged, allocate a new stand-alone native renderpass based on the current pass 441 ctx.nativePassData.Add(new NativePassData(ref passToAdd, ctx)); 442 passToAdd.nativePassIndex = ctx.nativePassData.LastIndex(); 443 activeNativePassId = passToAdd.nativePassIndex; 444 } 445 } 446 } 447 } 448 449 if (activeNativePassId >= 0) 450 { 451 // "Close" the last native pass by marking the last graph pass as end 452 SetPassStatesForNativePass(activeNativePassId); 453#if UNITY_EDITOR 454 ref var nativePassData = ref contextData.nativePassData.ElementAt(activeNativePassId); 455 nativePassData.breakAudit = new PassBreakAudit(PassBreakReason.EndOfGraph, -1); 456#endif 457 } 458 } 459 } 460 461 void FindResourceUsageRanges() 462 { 463 var ctx = contextData; 464 465 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_FindResourceUsageRanges))) 466 { 467 // Find the passes that last use a resource 468 // First do a forward-pass increasing the refcount 469 // followed by another forward-pass decreasing it again (-> if we get 0, we must be the last ones using it) 470 471 // TODO: I have a feeling this can be done more optimally by walking the graph instead of looping over all the passes twice 472 // to be investigated 473 // It also won't work if we start reordering passes. 474 for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++) 475 { 476 ref var pass = ref ctx.passData.ElementAt(passIndex); 477 if (pass.culled) 478 continue; 479 480 // Loop over all the resources this pass needs (=inputs) 481 foreach (ref readonly var input in pass.Inputs(ctx)) 482 { 483 var inputResource = input.resource; 484 ref var pointTo = ref ctx.UnversionedResourceData(inputResource); 485 pointTo.lastUsePassID = -1; 486 487 // If we use version 0 and nobody else is using it yet, 488 // mark this pass as the first using the resource. 489 // It can happen that two passes use v0, e.g.: 490 // pass1.UseTex(v0,Read) -> this will clear the pass but keep it at v0 491 // pass2.UseTex(v0,Read) -> "reads" v0 492 if (inputResource.version == 0) 493 { 494 if (pointTo.firstUsePassID < 0) 495 { 496 pointTo.firstUsePassID = pass.passId; 497 pass.AddFirstUse(inputResource, ctx); 498 } 499 } 500 501 // This pass uses the last version of a resource increase the ref count of this resource 502 var last = pointTo.latestVersionNumber; 503 if (last == inputResource.version) 504 { 505 pointTo.tag++; //Refcount of how many passes are using the last version of a resource 506 } 507 } 508 509 //Also look at outputs (but with version 1) for edge case were we do a Write (but no read) to a texture and the pass is manually excluded from culling 510 //As it isn't read it won't be in the inputs array with V0 511 512 foreach (ref readonly var output in pass.Outputs(ctx)) 513 { 514 var outputResource = output.resource; 515 ref var pointTo = ref ctx.UnversionedResourceData(outputResource); 516 if (outputResource.version == 1) 517 { 518 // If we use version 0 and nobody else is using it yet, 519 // Mark this pass as the first using the resource. 520 // It can happen that two passes use v0, e.g.: 521 // pass1.UseTex(v0,Read) -> this will clear the pass but keep it at v0 522 // pass3.UseTex(v0,Read/Write) -> wites v0, brings it to v1 from here on 523 if (pointTo.firstUsePassID < 0) 524 { 525 pointTo.firstUsePassID = pass.passId; 526 pass.AddFirstUse(outputResource, ctx); 527 } 528 } 529 530 // This pass outputs the last version of a resource track that 531 var last = pointTo.latestVersionNumber; 532 if (last == outputResource.version) 533 { 534 Debug.Assert(pointTo.lastWritePassID == -1); // Only one can be the last writer 535 pointTo.lastWritePassID = pass.passId; 536 } 537 } 538 } 539 540 for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++) 541 { 542 ref var pass = ref ctx.passData.ElementAt(passIndex); 543 if (pass.culled) 544 continue; 545 546 pass.waitOnGraphicsFencePassId = -1; 547 pass.insertGraphicsFence = false; 548 549 foreach (ref readonly var input in pass.Inputs(ctx)) 550 { 551 var inputResource = input.resource; 552 ref var pointTo = ref ctx.UnversionedResourceData(inputResource); 553 var last = pointTo.latestVersionNumber; 554 if (last == inputResource.version) 555 { 556 var refC = pointTo.tag - 1;//Decrease refcount this pass is done using it 557 if (refC == 0)// We're the last pass done using it, this pass should destroy it. 558 { 559 pointTo.lastUsePassID = pass.passId; 560 pass.AddLastUse(inputResource, ctx); 561 } 562 563 pointTo.tag = refC; 564 } 565 566 // Resolve if this pass needs to wait on a fence due to its inputs 567 if (pass.waitOnGraphicsFencePassId == -1) 568 { 569 ref var pointToVer = ref ctx.VersionedResourceData(inputResource); 570 ref var wPass = ref ctx.passData.ElementAt(pointToVer.writePassId); 571 if (wPass.asyncCompute != pass.asyncCompute) 572 { 573 pass.waitOnGraphicsFencePassId = wPass.passId; 574 } 575 } 576 } 577 578 // We're outputting a resource that is never used. 579 // This can happen if this pass has multiple outputs and only a portion of them are used 580 // as some are used, the whole pass is not culled but the unused output still should be freed 581 foreach (ref readonly var output in pass.Outputs(ctx)) 582 { 583 var outputResource = output.resource; 584 ref var pointTo = ref ctx.UnversionedResourceData(outputResource); 585 ref var pointToVer = ref ctx.VersionedResourceData(outputResource); 586 var last = pointTo.latestVersionNumber; 587 if (last == outputResource.version && pointToVer.numReaders == 0) 588 { 589 pointTo.lastUsePassID = pass.passId; 590 pass.AddLastUse(outputResource, ctx); 591 } 592 593 // Resolve if this pass should insert a fence for its outputs 594 var numReaders = pointToVer.numReaders; 595 for (var i = 0; i < numReaders; ++i) 596 { 597 var depIdx = ResourcesData.IndexReader(outputResource, i); 598 ref var dep = ref ctx.resources.readerData[outputResource.iType].ElementAt(depIdx); 599 ref var depPass = ref ctx.passData.ElementAt(dep.passId); 600 if (pass.asyncCompute != depPass.asyncCompute) 601 { 602 pass.insertGraphicsFence = true; 603 break; 604 } 605 } 606 } 607 } 608 } 609 } 610 611 void PrepareNativeRenderPasses() 612 { 613 // Prepare all native render pass execution info: 614 for (var passIdx = 0; passIdx < contextData.nativePassData.Length; ++passIdx) 615 { 616 ref var nativePassData = ref contextData.nativePassData.ElementAt(passIdx); 617 DetermineLoadStoreActions(ref nativePassData); 618 } 619 } 620 621 static bool IsGlobalTextureInPass(RenderGraphPass pass, ResourceHandle handle) 622 { 623 foreach (var g in pass.setGlobalsList) 624 { 625 if (g.Item1.handle.index == handle.index) 626 { 627 return true; 628 } 629 } 630 return false; 631 } 632 633 void DetectMemoryLessResources() 634 { 635 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_DetectMemorylessResources))) 636 { 637 // Native renderpasses and create/destroy lists have now been set-up. Detect memoryless resources, i.e resources that are created/destroyed 638 // within the scope of an nrp 639 foreach (ref readonly var nativePass in contextData.NativePasses) 640 { 641 // Loop over all created resources by this nrp 642 var graphPasses = nativePass.GraphPasses(contextData); 643 foreach (ref readonly var subPass in graphPasses) 644 { 645 foreach (ref readonly var createdRes in subPass.FirstUsedResources(contextData)) 646 { 647 ref var createInfo = ref contextData.UnversionedResourceData(createdRes); 648 if (createdRes.type == RenderGraphResourceType.Texture && createInfo.isImported == false) 649 { 650 bool isGlobal = IsGlobalTextureInPass(graph.m_RenderPasses[subPass.passId], createdRes); 651 652 // Note: You could think but what if the texture is used as a regular non-frambuffer attachment texture 653 // surely it can't be memoryless then? 654 // That is true, but it can never happen as the fact these passes got merged means the textures cannot be used 655 // as regular textures halfway through a pass. If that were the case they would never have been merged in the first place. 656 // Except! If the pass consists of a single pass, in that case a texture could be allocated and freed within the single pass 657 // This is a somewhat degenerate case (e.g. a pass with culling forced off doing a uav write that is never used anywhere) 658 // But to avoid execution errors we still need to create the resource in this case. 659 660 // Check if it is in the destroy list of any of the subpasses > if yes > memoryless 661 foreach (ref readonly var subPass2 in graphPasses) 662 { 663 foreach (ref readonly var destroyedRes in subPass2.LastUsedResources(contextData)) 664 { 665 ref var destInfo = ref contextData.UnversionedResourceData(destroyedRes); 666 if (destroyedRes.type == RenderGraphResourceType.Texture && destInfo.isImported == false) 667 { 668 if (createdRes.index == destroyedRes.index && !isGlobal) 669 { 670 // If a single pass in the native pass we need to check fragment attachment otherwise we're good 671 // we could always check this in theory but it's an optimization not to check it. 672 if (nativePass.numNativeSubPasses > 1 || subPass2.IsUsedAsFragment(createdRes, contextData)) 673 { 674 createInfo.memoryLess = true; 675 destInfo.memoryLess = true; 676 } 677 } 678 } 679 } 680 } 681 } 682 } 683 } 684 } 685 } 686 } 687 688 internal static bool IsSameNativeSubPass(ref SubPassDescriptor a, ref SubPassDescriptor b) 689 { 690 if (a.flags != b.flags 691 || a.colorOutputs.Length != b.colorOutputs.Length 692 || a.inputs.Length != b.inputs.Length) 693 { 694 return false; 695 } 696 697 for (int i = 0; i < a.colorOutputs.Length; i++) 698 { 699 if (a.colorOutputs[i] != b.colorOutputs[i]) 700 { 701 return false; 702 } 703 } 704 705 for (int i = 0; i < a.inputs.Length; i++) 706 { 707 if (a.inputs[i] != b.inputs[i]) 708 { 709 return false; 710 } 711 } 712 713 return true; 714 } 715 716 717 private void ExecuteCreateRessource(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, in PassData pass) 718 { 719 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_ExecuteCreateResources))) 720 { 721 resources.forceManualClearOfResource = true; 722 723 // For raster passes we need to create resources for all the subpasses at the beginning of the native renderpass 724 if (pass.type == RenderGraphPassType.Raster && pass.nativePassIndex >= 0) 725 { 726 if (pass.mergeState == PassMergeState.Begin || pass.mergeState == PassMergeState.None) 727 { 728 ref var nativePass = ref contextData.nativePassData.ElementAt(pass.nativePassIndex); 729 foreach (ref readonly var subPass in nativePass.GraphPasses(contextData)) 730 { 731 foreach (ref readonly var res in subPass.FirstUsedResources(contextData)) 732 { 733 ref readonly var resInfo = ref contextData.UnversionedResourceData(res); 734 if (resInfo.isImported == false && resInfo.memoryLess == false) 735 { 736 bool usedAsFragmentThisPass = subPass.IsUsedAsFragment(res, contextData); 737 738 // This resource is read for the first time as a regular texture and not as a framebuffer attachment 739 // so we need to explicitly clear it, as loadAction.clear only works on framebuffer attachments 740 // TODO: Should this be a performance warning?? Maybe rare enough in practice? 741 resources.forceManualClearOfResource = !usedAsFragmentThisPass; 742 resources.CreatePooledResource(rgContext, res.iType, res.index); 743 } 744 } 745 } 746 } 747 } 748 // Other passes just create them at the beginning of the individual pass 749 else 750 { 751 foreach (ref readonly var create in pass.FirstUsedResources(contextData)) 752 { 753 ref readonly var pointTo = ref contextData.UnversionedResourceData(create); 754 if (pointTo.isImported == false) 755 { 756 resources.CreatePooledResource(rgContext, create.iType, create.index); 757 } 758 } 759 } 760 761 resources.forceManualClearOfResource = true; 762 } 763 } 764 765 766 void DetermineLoadStoreActions(ref NativePassData nativePass) 767 { 768 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_PrepareNativePass))) 769 { 770 ref readonly var firstGraphPass = ref contextData.passData.ElementAt(nativePass.firstGraphPass); 771 ref readonly var lastGraphPass = ref contextData.passData.ElementAt(nativePass.lastGraphPass); 772 773 // Some passes don't do any rendering only state changes so just skip them 774 // If these passes trigger any drawing the raster command buffer will warn users no render targets are set-up for their rendering 775 if (nativePass.fragments.size <= 0) 776 { 777 return; 778 } 779 780 // Some sanity checks, these should not happen 781 Debug.Assert(firstGraphPass.mergeState == PassMergeState.Begin || firstGraphPass.mergeState == PassMergeState.None); 782 Debug.Assert(lastGraphPass.mergeState == PassMergeState.End || lastGraphPass.mergeState == PassMergeState.None); 783 ref readonly var fragmentList = ref nativePass.fragments; 784 785 // determine load store actions 786 // This pass also contains the latest versions used within this pass 787 // As we have no pass reordering for now the merged passes are always a consecutive list and we can simply do a range 788 // check on the create/destroy passid to see if it's allocated/freed in this native renderpass 789 for (int fragmentId = 0; fragmentId < fragmentList.size; ++fragmentId) 790 { 791 ref readonly var fragment = ref fragmentList[fragmentId]; 792 793 int idx = nativePass.attachments.Add(new NativePassAttachment()); 794 ref var currAttachment = ref nativePass.attachments[idx]; 795 796#if UNITY_EDITOR 797 nativePass.loadAudit.Add(new LoadAudit(LoadReason.FullyRewritten)); 798 ref var currLoadAudit = ref nativePass.loadAudit[idx]; 799 800 nativePass.storeAudit.Add(new StoreAudit(StoreReason.DiscardUnused)); 801 ref var currStoreAudit = ref nativePass.storeAudit[idx]; 802#endif 803 currAttachment.handle = fragment.resource; 804 currAttachment.mipLevel = fragment.mipLevel; 805 currAttachment.depthSlice = fragment.depthSlice; 806 807 // Don't care by default 808 currAttachment.loadAction = UnityEngine.Rendering.RenderBufferLoadAction.DontCare; 809 currAttachment.storeAction = UnityEngine.Rendering.RenderBufferStoreAction.DontCare; 810 811 // Writing by-default has to preserve the contents, think rendering only a few small triangles on top of a big framebuffer 812 // So it means we need to load/clear contents potentially. 813 // If a user pass knows it will write all pixels in a buffer (like a blit) it can use the WriteAll/Discard usage to indicate this to the graph 814 bool partialWrite = fragment.accessFlags.HasFlag(AccessFlags.Write) && !fragment.accessFlags.HasFlag(AccessFlags.Discard); 815 816 ref readonly var resourceData = ref contextData.UnversionedResourceData(fragment.resource); 817 bool isImported = resourceData.isImported; 818 819 int destroyPassID = resourceData.lastUsePassID; 820 bool usedAfterThisNativePass = (destroyPassID >= (nativePass.lastGraphPass + 1)); 821 822 if (fragment.accessFlags.HasFlag(AccessFlags.Read) || partialWrite) 823 { 824 // The resource is already allocated before this pass so we need to load it 825 if (resourceData.firstUsePassID < nativePass.firstGraphPass) 826 { 827 currAttachment.loadAction = UnityEngine.Rendering.RenderBufferLoadAction.Load; 828#if UNITY_EDITOR 829 currLoadAudit = new LoadAudit(LoadReason.LoadPreviouslyWritten, resourceData.firstUsePassID); 830#endif 831 832 // Once we decide to load a resource, we must default to the Store action if the resource is used after the current native pass. 833 // If we were to use the DontCare action in this case, the driver would be effectively be allowed to discard the 834 // contents of the resource. This is true even when we're only performing reads on it. 835 if (usedAfterThisNativePass) 836 { 837 currAttachment.storeAction = RenderBufferStoreAction.Store; 838#if UNITY_EDITOR 839 currStoreAudit = new StoreAudit(StoreReason.StoreUsedByLaterPass, destroyPassID); 840#endif 841 } 842 843 } 844 // It's first used this native pass so we need to clear it so reads/partial writes return the correct clear value 845 // the clear colors are part of the resource description and set-up when executing the graph we don't need to care about that here. 846 else 847 { 848 if (isImported) 849 { 850 // Check if the user indicated he wanted clearing of his imported resource on it's first use by the graph 851 if (resourceData.clear) 852 { 853 currAttachment.loadAction = UnityEngine.Rendering.RenderBufferLoadAction.Clear; 854#if UNITY_EDITOR 855 currLoadAudit = new LoadAudit(LoadReason.ClearImported); 856#endif 857 } 858 else 859 { 860 currAttachment.loadAction = UnityEngine.Rendering.RenderBufferLoadAction.Load; 861#if UNITY_EDITOR 862 currLoadAudit = new LoadAudit(LoadReason.LoadImported); 863#endif 864 } 865 } 866 else 867 { 868 // Created by the graph internally clear on first read 869 currAttachment.loadAction = UnityEngine.Rendering.RenderBufferLoadAction.Clear; 870#if UNITY_EDITOR 871 currLoadAudit = new LoadAudit(LoadReason.ClearCreated); 872#endif 873 } 874 } 875 } 876 877 if (fragment.accessFlags.HasFlag(AccessFlags.Write)) 878 { 879 // Simple non-msaa case 880 if (nativePass.samples <= 1) 881 { 882 if (usedAfterThisNativePass) 883 { 884 // The resource is still used after this native pass so we need to store it. 885 currAttachment.storeAction = RenderBufferStoreAction.Store; 886#if UNITY_EDITOR 887 currStoreAudit = new StoreAudit(StoreReason.StoreUsedByLaterPass, destroyPassID); 888#endif 889 } 890 else 891 { 892 // This is the last native pass that uses the resource. 893 // If it's imported, we store it because its contents may be used outside the graph. 894 // Otherwise, we can safely discard its contents. 895 // 896 // The one exception to this, is the user declared discard flag which allows us to assume an imported 897 // resource is not used outside the graph. 898 if (isImported) 899 { 900 if (resourceData.discard) 901 { 902 currAttachment.storeAction = RenderBufferStoreAction.DontCare; 903#if UNITY_EDITOR 904 currStoreAudit = new StoreAudit(StoreReason.DiscardImported); 905#endif 906 } 907 else 908 { 909 currAttachment.storeAction = RenderBufferStoreAction.Store; 910#if UNITY_EDITOR 911 currStoreAudit = new StoreAudit(StoreReason.StoreImported); 912#endif 913 } 914 } 915 else 916 { 917 currAttachment.storeAction = RenderBufferStoreAction.DontCare; 918#if UNITY_EDITOR 919 currStoreAudit = new StoreAudit(StoreReason.DiscardUnused); 920#endif 921 } 922 } 923 } 924 // Complex msaa case 925 else 926 { 927 // The resource is still used after this native pass so we need to store it. 928 // as we don't know what happens with them and assume the contents are somewhow used outside the graph 929 // With MSAA we may access the resolved data for longer than the MSAA data so we track the destroyPass and lastPassThatNeedsUnresolved separately 930 // In theory the opposite could also be true (use MSAA after resolve data is no longer needed) but we consider it sufficiently strange to not 931 // consider it here. 932 933 currAttachment.storeAction = RenderBufferStoreAction.DontCare; 934 935 //Check if we're the last pass writing it by checking the output version of the current pass is the higherst version the resource will reach 936 bool lastWriter = (resourceData.latestVersionNumber == fragment.resource.version); // Cheaper but same? = resourceData.lastWritePassID >= pass.firstGraphPass && resourceData.lastWritePassID < pass.firstGraphPass + pass.numSubPasses; 937 bool isImportedLastWriter = isImported && lastWriter; 938 939 // Used outside this native render pass, we need to store something 940 if (destroyPassID >= nativePass.firstGraphPass + nativePass.numGraphPasses) 941 { 942 // Assume nothing is needed unless we are an imported texture (which doesn't require discarding) and we're the last ones writing it 943 bool needsMSAASamples = isImportedLastWriter && !resourceData.discard; 944 bool needsResolvedData = isImportedLastWriter && (resourceData.bindMS == false);//bindMS never resolves 945 int userPassID = 0; 946 int msaaUserPassID = 0; 947 948 // Check if we need msaa/resolved data by checking all the passes using this buffer 949 // Partial writes will register themselves as readers so this should be adequate 950 foreach (ref readonly var reader in contextData.Readers(fragment.resource)) 951 { 952 ref var readerPass = ref contextData.passData.ElementAt(reader.passId); 953 bool isFragment = readerPass.IsUsedAsFragment(fragment.resource, contextData); 954 955 // Unsafe pass - we cannot know how it is used, so we need to both store and resolve 956 if (readerPass.type == RenderGraphPassType.Unsafe) 957 { 958 needsMSAASamples = true; 959 needsResolvedData = !resourceData.bindMS; 960 msaaUserPassID = reader.passId; 961 userPassID = reader.passId; 962 break; 963 } 964 // A fragment attachment use we need the msaa samples 965 else if (isFragment) 966 { 967 needsMSAASamples = true; 968 msaaUserPassID = reader.passId; 969 } 970 else 971 { 972 // Used as a multisample-texture we need the msaa samples 973 if (resourceData.bindMS) 974 { 975 needsMSAASamples = true; 976 msaaUserPassID = reader.passId; 977 } 978 // Used as a regular non-multisample texture we need resolved data 979 else 980 { 981 needsResolvedData = true; 982 userPassID = reader.passId; 983 } 984 } 985 } 986 987 if (needsMSAASamples && needsResolvedData) 988 { 989 currAttachment.storeAction = RenderBufferStoreAction.StoreAndResolve; 990#if UNITY_EDITOR 991 currStoreAudit = new StoreAudit( 992 (isImportedLastWriter) ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass, userPassID, 993 (isImportedLastWriter) ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass, msaaUserPassID); 994#endif 995 } 996 else if (needsResolvedData) 997 { 998 currAttachment.storeAction = RenderBufferStoreAction.Resolve; 999#if UNITY_EDITOR 1000 currStoreAudit = new StoreAudit((isImportedLastWriter) ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass, userPassID, StoreReason.DiscardUnused); 1001#endif 1002 } 1003 else if (needsMSAASamples) 1004 { 1005 currAttachment.storeAction = RenderBufferStoreAction.Store; 1006#if UNITY_EDITOR 1007 currStoreAudit = new StoreAudit( 1008 (resourceData.bindMS) ? StoreReason.DiscardBindMs : StoreReason.DiscardUnused, -1, 1009 (isImportedLastWriter) ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass, msaaUserPassID); 1010#endif 1011 } 1012 else 1013 { 1014 Debug.Assert(false, "Resource was not destroyed but nobody seems to be using it!??"); 1015 } 1016 } 1017 else if (isImportedLastWriter) 1018 { 1019 //It's an imported texture and we're the last ones writing it make sure to store the results 1020 1021 // Used as a multisample-texture, we need the msaa samples only 1022 if (resourceData.bindMS) 1023 { 1024 if (resourceData.discard) 1025 { 1026 currAttachment.storeAction = RenderBufferStoreAction.DontCare; 1027#if UNITY_EDITOR 1028 currStoreAudit = new StoreAudit(StoreReason.DiscardImported); 1029#endif 1030 } 1031 else 1032 { 1033 currAttachment.storeAction = RenderBufferStoreAction.Store; 1034#if UNITY_EDITOR 1035 currStoreAudit = new StoreAudit( 1036 StoreReason.DiscardBindMs, -1, StoreReason.StoreImported); 1037#endif 1038 } 1039 } 1040 // Used as a regular non-multisample texture, we need samples as resolved data 1041 // we have no idea which one of them will be needed by the external users 1042 else 1043 { 1044 if (resourceData.discard) 1045 { 1046 // Depth attachment always comes first if existing 1047 bool isDepthAttachment = (nativePass.hasDepth && idx == 0); 1048 1049 // For color attachment, we only discard the MSAA buffers and keep the resolve texture 1050 // This is a design decision due to the restrictive ImportResourceParams API, it could be revised later 1051 currAttachment.storeAction = isDepthAttachment ? RenderBufferStoreAction.DontCare : RenderBufferStoreAction.Resolve; 1052#if UNITY_EDITOR 1053 currStoreAudit = new StoreAudit( 1054 StoreReason.DiscardImported, -1, StoreReason.DiscardImported); 1055#endif 1056 } 1057 else 1058 { 1059 currAttachment.storeAction = RenderBufferStoreAction.StoreAndResolve; 1060#if UNITY_EDITOR 1061 currStoreAudit = new StoreAudit( 1062 StoreReason.StoreImported, -1, StoreReason.StoreImported); 1063#endif 1064 } 1065 } 1066 } 1067 } 1068 } 1069 1070 if (resourceData.memoryLess) 1071 { 1072 currAttachment.memoryless = true; 1073#if DEVELOPMENT_BUILD || UNITY_EDITOR 1074 // Ensure load/store actions are actually valid for memory less 1075 if (currAttachment.loadAction == RenderBufferLoadAction.Load) throw new Exception("Resource was marked as memoryless but is trying to load."); 1076 if (currAttachment.storeAction != RenderBufferStoreAction.DontCare) throw new Exception("Resource was marked as memoryless but is trying to store or resolve."); 1077#endif 1078 } 1079 } 1080 } 1081 } 1082 1083 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] 1084 private void ValidateNativePass(in NativePassData nativePass, int width, int height, int depth, int samples, int attachmentCount) 1085 { 1086 if (RenderGraph.enableValidityChecks) 1087 { 1088 if (nativePass.attachments.size == 0 || nativePass.numNativeSubPasses == 0) 1089 throw new Exception("Empty render pass"); 1090 1091 if (width == 0 || height == 0 || depth == 0 || samples == 0 || nativePass.numNativeSubPasses == 0 || attachmentCount == 0) 1092 throw new Exception("Invalid render pass properties. One or more properties are zero."); 1093 } 1094 } 1095 1096 [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] 1097 private void ValidateAttachmentRenderTarget(in RenderTargetInfo attRenderTargetInfo, RenderGraphResourceRegistry resources, int nativePassWidth, int nativePassHeight, int nativePassMSAASamples) 1098 { 1099 if(RenderGraph.enableValidityChecks) 1100 { 1101 if (attRenderTargetInfo.width != nativePassWidth || attRenderTargetInfo.height != nativePassHeight || attRenderTargetInfo.msaaSamples != nativePassMSAASamples) 1102 throw new Exception("Low level rendergraph error: Attachments in renderpass do not match!"); 1103 } 1104 } 1105 1106 internal unsafe void ExecuteBeginRenderPass(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, ref NativePassData nativePass) 1107 { 1108 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_ExecuteBeginRenderpassCommand))) 1109 { 1110 ref var attachments = ref nativePass.attachments; 1111 var attachmentCount = attachments.size; 1112 1113 ref readonly var firstGraphPass = ref contextData.passData.ElementAt(nativePass.firstGraphPass); 1114 var w = firstGraphPass.fragmentInfoWidth; 1115 var h = firstGraphPass.fragmentInfoHeight; 1116 var d = firstGraphPass.fragmentInfoVolumeDepth; 1117 var s = firstGraphPass.fragmentInfoSamples; 1118 1119 ValidateNativePass(nativePass, w, h, d, s, attachmentCount); 1120 1121 ref var nativeSubPasses = ref contextData.nativeSubPassData; 1122 NativeArray<SubPassDescriptor> nativeSubPassArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<SubPassDescriptor>(nativeSubPasses.GetUnsafeReadOnlyPtr<SubPassDescriptor>() + nativePass.firstNativeSubPass, nativePass.numNativeSubPasses, Allocator.None); 1123 1124#if ENABLE_UNITY_COLLECTIONS_CHECKS 1125 var safetyHandle = AtomicSafetyHandle.Create(); 1126 AtomicSafetyHandle.SetAllowReadOrWriteAccess(safetyHandle, true); 1127 NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeSubPassArray, safetyHandle); 1128#endif 1129 1130 if (nativePass.hasFoveatedRasterization) 1131 { 1132 rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Enabled); 1133 } 1134 1135 // Filling the attachments array to be sent to the rendering command buffer 1136 m_BeginRenderPassAttachments.Resize(attachmentCount, NativeArrayOptions.UninitializedMemory); 1137 for (var i = 0; i < attachmentCount; ++i) 1138 { 1139 ref var currAttachmentHandle = ref attachments[i].handle; 1140 1141 resources.GetRenderTargetInfo(currAttachmentHandle, out var renderTargetInfo); 1142 ValidateAttachmentRenderTarget(renderTargetInfo, resources, w, h, s); 1143 1144 ref var currBeginAttachment = ref m_BeginRenderPassAttachments.ElementAt(i); 1145 currBeginAttachment = new AttachmentDescriptor(renderTargetInfo.format); 1146 1147 // Set up the RT pointers 1148 if (attachments[i].memoryless == false) 1149 { 1150 var rtHandle = resources.GetTexture(currAttachmentHandle.index); 1151 1152 //HACK: Always set the loadstore target even if StoreAction == DontCare or Resolve 1153 //and LoadAction == Clear or DontCare 1154 //in these cases you could argue setting the loadStoreTarget to NULL and only set the resolveTarget 1155 //but this confuses the backend (on vulkan) and in general is not how the lower level APIs tend to work. 1156 //because of the RenderTexture duality where we always bundle store+resolve targets as one RTex 1157 //it does become impossible to have a memoryless loadStore texture with a memoryfull resolve 1158 //but that is why we mark this as a hack and future work to fix. 1159 //The proper (and planned) solution would be to move away from the render texture duality. 1160 RenderTargetIdentifier rtidAllSlices = rtHandle; 1161 currBeginAttachment.loadStoreTarget = new RenderTargetIdentifier(rtidAllSlices, attachments[i].mipLevel, CubemapFace.Unknown, attachments[i].depthSlice); 1162 1163 if (attachments[i].storeAction == RenderBufferStoreAction.Resolve || 1164 attachments[i].storeAction == RenderBufferStoreAction.StoreAndResolve) 1165 { 1166 currBeginAttachment.resolveTarget = rtHandle; 1167 } 1168 } 1169 // In the memoryless case it's valid to not set both loadStoreTarget/and resolveTarget as the backend will allocate a transient one 1170 1171 currBeginAttachment.loadAction = attachments[i].loadAction; 1172 currBeginAttachment.storeAction = attachments[i].storeAction; 1173 1174 // Set up clear colors if we have a clear load action 1175 if (attachments[i].loadAction == RenderBufferLoadAction.Clear) 1176 { 1177 currBeginAttachment.clearColor = Color.red; 1178 currBeginAttachment.clearDepth = 1.0f; 1179 currBeginAttachment.clearStencil = 0; 1180 var desc = resources.GetTextureResourceDesc(currAttachmentHandle, true); 1181 if (i == 0 && nativePass.hasDepth) 1182 { 1183 // TODO: There seems to be no clear depth specified ?!?! 1184 currBeginAttachment.clearDepth = 1.0f;// desc.clearDepth; 1185 } 1186 else 1187 { 1188 currBeginAttachment.clearColor = desc.clearColor; 1189 } 1190 } 1191 } 1192 1193 NativeArray<AttachmentDescriptor> attachmentDescArray = m_BeginRenderPassAttachments.AsArray(); 1194 1195 var depthAttachmentIndex = nativePass.hasDepth ? 0 : -1; 1196 1197 var graphPassNamesForDebugSpan = ReadOnlySpan<byte>.Empty; 1198#if DEVELOPMENT_BUILD || UNITY_EDITOR 1199 if(RenderGraph.enableValidityChecks) 1200 { 1201 graphPassNamesForDebug.Clear(); 1202 1203 nativePass.GetGraphPassNames(contextData, graphPassNamesForDebug); 1204 1205 int utf8CStrDebugNameLength = 0; 1206 foreach (ref readonly Name graphPassName in graphPassNamesForDebug) 1207 { 1208 utf8CStrDebugNameLength += graphPassName.utf8ByteCount + 1; // +1 to add '/' between passes or the null terminator at the end 1209 } 1210 1211 var nameBytes = stackalloc byte[utf8CStrDebugNameLength]; 1212 if (utf8CStrDebugNameLength > 0) 1213 { 1214 int startStr = 0; 1215 foreach (ref readonly var graphPassName in graphPassNamesForDebug) 1216 { 1217 int strByteCount = graphPassName.utf8ByteCount; 1218 System.Text.Encoding.UTF8.GetBytes(graphPassName.name.AsSpan(), new Span<byte>(nameBytes + startStr, strByteCount)); 1219 startStr += strByteCount; 1220 // Adding '/' in UTF8 1221 nameBytes[startStr++] = (byte)(0x2F); 1222 } 1223 // Rewriting last '/' to be the null terminator 1224 nameBytes[utf8CStrDebugNameLength - 1] = (byte)0; 1225 } 1226 1227 graphPassNamesForDebugSpan = new ReadOnlySpan<byte>(nameBytes, utf8CStrDebugNameLength); 1228 } 1229#endif 1230 1231 rgContext.cmd.BeginRenderPass(w, h, d, s, attachmentDescArray, depthAttachmentIndex, nativeSubPassArray, graphPassNamesForDebugSpan); 1232 1233#if ENABLE_UNITY_COLLECTIONS_CHECKS 1234 AtomicSafetyHandle.Release(safetyHandle); 1235#endif 1236 CommandBuffer.ThrowOnSetRenderTarget = true; 1237 } 1238 } 1239 1240 const int ArbitraryMaxNbMergedPasses = 16; 1241 DynamicArray<Name> graphPassNamesForDebug = new DynamicArray<Name>(ArbitraryMaxNbMergedPasses); 1242 1243 private void ExecuteDestroyResource(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, ref PassData pass) 1244 { 1245 using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_ExecuteDestroyResources))) 1246 { 1247 if (pass.type == RenderGraphPassType.Raster && pass.nativePassIndex >= 0) 1248 { 1249 // For raster passes we need to destroy resources after all the subpasses at the end of the native renderpass 1250 if (pass.mergeState == PassMergeState.End || pass.mergeState == PassMergeState.None) 1251 { 1252 ref var nativePass = ref contextData.nativePassData.ElementAt(pass.nativePassIndex); 1253 foreach (ref readonly var subPass in nativePass.GraphPasses(contextData)) 1254 { 1255 foreach (ref readonly var res in subPass.LastUsedResources(contextData)) 1256 { 1257 ref readonly var resInfo = ref contextData.UnversionedResourceData(res); 1258 if (resInfo.isImported == false && resInfo.memoryLess == false) 1259 { 1260 resources.ReleasePooledResource(rgContext, res.iType, res.index); 1261 } 1262 } 1263 } 1264 } 1265 } 1266 else 1267 { 1268 foreach (ref readonly var destroy in pass.LastUsedResources(contextData)) 1269 { 1270 ref readonly var pointTo = ref contextData.UnversionedResourceData(destroy); 1271 if (pointTo.isImported == false) 1272 { 1273 resources.ReleasePooledResource(rgContext, destroy.iType, destroy.index); 1274 } 1275 } 1276 } 1277 } 1278 } 1279 1280 internal unsafe void SetRandomWriteTarget(in CommandBuffer cmd, RenderGraphResourceRegistry resources, int index, ResourceHandle resource, bool preserveCounterValue = true) 1281 { 1282 if (resource.type == RenderGraphResourceType.Texture) 1283 { 1284 var tex = resources.GetTexture(resource.index); 1285 cmd.SetRandomWriteTarget(index, tex); 1286 } 1287 else if (resource.type == RenderGraphResourceType.Buffer) 1288 { 1289 var buff = resources.GetBuffer(resource.index); 1290 // Default is to preserve the value 1291 if (preserveCounterValue) 1292 { 1293 cmd.SetRandomWriteTarget(index, buff); 1294 } 1295 else 1296 { 1297 cmd.SetRandomWriteTarget(index, buff, false); 1298 } 1299 1300 } 1301 else 1302 { 1303 throw new Exception($"Invalid resource type {resource.type}, expected texture or buffer"); 1304 } 1305 } 1306 1307 internal void ExecuteGraphNode(ref InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, RenderGraphPass pass) 1308 { 1309#if THROW_ON_SETRENDERTARGET_DEBUG 1310 if (pass.type == RenderGraphPassType.Raster) 1311 { 1312 CommandBuffer.ThrowOnSetRenderTarget = true; 1313 } 1314#endif 1315 try 1316 { 1317 rgContext.executingPass = pass; 1318 1319 if (!pass.HasRenderFunc()) 1320 { 1321 throw new InvalidOperationException(string.Format("RenderPass {0} was not provided with an execute function.", pass.name)); 1322 } 1323 1324 using (new ProfilingScope(rgContext.cmd, pass.customSampler)) 1325 { 1326 pass.Execute(rgContext); 1327 1328 foreach (var tex in pass.setGlobalsList) 1329 { 1330 rgContext.cmd.SetGlobalTexture(tex.Item2, tex.Item1); 1331 } 1332 } 1333 } 1334 finally 1335 { 1336#if THROW_ON_SETRENDERTARGET_DEBUG 1337 if (pass.type == RenderGraphPassType.Raster) 1338 { 1339 CommandBuffer.ThrowOnSetRenderTarget = false; 1340 } 1341#endif 1342 } 1343 } 1344 1345 public void ExecuteGraph(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, in List<RenderGraphPass> passes) 1346 { 1347 bool inRenderPass = false; 1348 previousCommandBuffer = rgContext.cmd; 1349 1350 // Having random access targets bound leads to all sorts of weird behavior so we clear them before executing the graph. 1351 rgContext.cmd.ClearRandomWriteTargets(); 1352 1353 for (int passIndex = 0; passIndex < contextData.passData.Length; passIndex++) 1354 { 1355 ref var pass = ref contextData.passData.ElementAt(passIndex); 1356 1357 //Fix low level passes being merged into nrp giving errors 1358 //because of the "isRaster" check below 1359 1360 if (pass.culled) 1361 continue; 1362 1363 var isRaster = pass.type == RenderGraphPassType.Raster; 1364 1365 ExecuteCreateRessource(rgContext, resources, pass); 1366 1367 var isAsyncCompute = pass.type == RenderGraphPassType.Compute && pass.asyncCompute == true; 1368 if (isAsyncCompute) 1369 { 1370 if (rgContext.contextlessTesting == false) 1371 rgContext.renderContext.ExecuteCommandBuffer(rgContext.cmd); 1372 rgContext.cmd.Clear(); 1373 1374 var asyncCmd = CommandBufferPool.Get("async cmd"); 1375 asyncCmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute); 1376 rgContext.cmd = asyncCmd; 1377 } 1378 1379 // also make sure to insert fence=waits for multiple queue syncs 1380 if (pass.waitOnGraphicsFencePassId != -1) 1381 { 1382 var fence = contextData.fences[pass.waitOnGraphicsFencePassId]; 1383 rgContext.cmd.WaitOnAsyncGraphicsFence(fence); 1384 } 1385 1386 var nrpBegan = false; 1387 if (isRaster == true && pass.mergeState <= PassMergeState.Begin) 1388 { 1389 if (pass.nativePassIndex >= 0) 1390 { 1391 ref var nativePass = ref contextData.nativePassData.ElementAt(pass.nativePassIndex); 1392 if (nativePass.fragments.size > 0) 1393 { 1394 ExecuteBeginRenderPass(rgContext, resources, ref nativePass); 1395 nrpBegan = true; 1396 inRenderPass = true; 1397 } 1398 } 1399 } 1400 1401 if (pass.mergeState >= PassMergeState.SubPass) 1402 { 1403 if (pass.beginNativeSubpass) 1404 { 1405 if (!inRenderPass) 1406 { 1407 throw new Exception("Compiler error: Pass is marked as beginning a native sub pass but no pass is currently active."); 1408 } 1409 rgContext.cmd.NextSubPass(); 1410 } 1411 } 1412 1413 if (pass.numRandomAccessResources > 0) 1414 { 1415 foreach (var randomWriteAttachment in pass.RandomWriteTextures(contextData)) 1416 { 1417 SetRandomWriteTarget(rgContext.cmd, resources, randomWriteAttachment.index, randomWriteAttachment.resource); 1418 } 1419 } 1420 1421#if THROW_ON_SETRENDERTARGET_DEBUG 1422 if (passes[pass.passId].type == RenderGraphPassType.Raster) 1423 { 1424 CommandBuffer.ThrowOnSetRenderTarget = true; 1425 } 1426#endif 1427 1428 ExecuteGraphNode(ref rgContext, resources, passes[pass.passId]); 1429 1430 // If we set any uavs clear them again so they are local to the pass 1431 if (pass.numRandomAccessResources > 0) 1432 { 1433 rgContext.cmd.ClearRandomWriteTargets(); 1434 } 1435 1436 // should we insert a fence to sync between difference queues? 1437 if (pass.insertGraphicsFence) 1438 { 1439 var fence = rgContext.cmd.CreateAsyncGraphicsFence(); 1440 contextData.fences[pass.passId] = fence; 1441 } 1442 1443 if (isRaster) 1444 { 1445 if ((pass.mergeState == PassMergeState.None && nrpBegan) 1446 || pass.mergeState == PassMergeState.End) 1447 { 1448 if (pass.nativePassIndex >= 0) 1449 { 1450 ref var nativePass = ref contextData.nativePassData.ElementAt(pass.nativePassIndex); 1451 if (nativePass.fragments.size > 0) 1452 { 1453 if (!inRenderPass) 1454 { 1455 throw new Exception("Compiler error: Generated a subpass pass but no pass is currently active."); 1456 } 1457 1458 if (nativePass.hasFoveatedRasterization) 1459 { 1460 rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Disabled); 1461 } 1462 1463 rgContext.cmd.EndRenderPass(); 1464 CommandBuffer.ThrowOnSetRenderTarget = false; 1465 inRenderPass = false; 1466 } 1467 } 1468 } 1469 } 1470 else if (isAsyncCompute) 1471 { 1472 rgContext.renderContext.ExecuteCommandBufferAsync(rgContext.cmd, ComputeQueueType.Background); 1473 CommandBufferPool.Release(rgContext.cmd); 1474 rgContext.cmd = previousCommandBuffer; 1475 } 1476 1477 ExecuteDestroyResource(rgContext, resources, ref pass); 1478 } 1479 } 1480 } 1481}