// Copyright Epic Games, Inc. All Rights Reserved. #include "RenderGraphBuilder.h" #include "RenderGraphPrivate.h" #include "RenderGraphTrace.h" #include "RenderTargetPool.h" #include "RenderGraphResourcePool.h" #include "VisualizeTexture.h" #include "ProfilingDebugging/CsvProfiler.h" template inline void EnumeratePassUAVs(const FRDGPass* Pass, FunctionType Function) { Pass->GetParameters().Enumerate([&](FRDGParameter Parameter) { if (Parameter.IsUAV()) { if (FRDGUnorderedAccessViewRef UAV = Parameter.GetAsUAV()) { Function(UAV->GetRHI()); } } }); } inline void BeginUAVOverlap(const FRDGPass* Pass, FRHIComputeCommandList& RHICmdList) { #if ENABLE_RHI_VALIDATION TArray> UAVs; EnumeratePassUAVs(Pass, [&](FRHIUnorderedAccessView* UAV) { UAVs.Add(UAV); }); RHICmdList.BeginUAVOverlap(UAVs); #endif } inline void EndUAVOverlap(const FRDGPass* Pass, FRHIComputeCommandList& RHICmdList) { #if ENABLE_RHI_VALIDATION TArray> UAVs; EnumeratePassUAVs(Pass, [&](FRHIUnorderedAccessView* UAV) { UAVs.Add(UAV); }); RHICmdList.EndUAVOverlap(UAVs); #endif } inline ERHIAccess MakeValidAccess(ERHIAccess Access) { // If we find any write states in the access mask, remove all read-only states. This mainly exists // to allow RDG uniform buffers to contain read-only parameters which are also bound for write on the // pass. Often times these uniform buffers are created and only relevant things are accessed. If an // invalid access does occur, the RHI validation layer will catch it. return IsWritableAccess(Access) ? (Access & ~ERHIAccess::ReadOnlyExclusiveMask) : Access; } inline void GetPassAccess(ERDGPassFlags PassFlags, ERHIAccess& SRVAccess, ERHIAccess& UAVAccess) { SRVAccess = ERHIAccess::Unknown; UAVAccess = ERHIAccess::Unknown; if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster)) { SRVAccess |= ERHIAccess::SRVCompute | ERHIAccess::SRVGraphics; UAVAccess |= ERHIAccess::UAVGraphics; } if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::AsyncCompute | ERDGPassFlags::Compute)) { SRVAccess |= ERHIAccess::SRVCompute; UAVAccess |= ERHIAccess::UAVCompute; } if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Copy)) { SRVAccess |= ERHIAccess::CopySrc; } } /** Enumerates all texture accesses and provides the access and subresource range info. This results in * multiple invocations of the same resource, but with different access / subresource range. */ template void EnumerateTextureAccess(FRDGParameterStruct PassParameters, ERDGPassFlags PassFlags, TAccessFunction AccessFunction) { ERHIAccess SRVAccess, UAVAccess; GetPassAccess(PassFlags, SRVAccess, UAVAccess); PassParameters.EnumerateTextures([&](FRDGParameter Parameter) { switch (Parameter.GetType()) { case UBMT_RDG_TEXTURE: if (FRDGTextureRef Texture = Parameter.GetAsTexture()) { AccessFunction(nullptr, Texture, SRVAccess, Texture->GetSubresourceRangeSRV()); } break; case UBMT_RDG_TEXTURE_ACCESS: { if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess()) { AccessFunction(nullptr, TextureAccess.GetTexture(), TextureAccess.GetAccess(), TextureAccess.GetTexture()->GetSubresourceRange()); } } break; case UBMT_RDG_TEXTURE_SRV: if (FRDGTextureSRVRef SRV = Parameter.GetAsTextureSRV()) { AccessFunction(SRV, SRV->GetParent(), SRVAccess, SRV->GetSubresourceRange()); } break; case UBMT_RDG_TEXTURE_UAV: if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV()) { AccessFunction(UAV, UAV->GetParent(), UAVAccess, UAV->GetSubresourceRange()); } break; case UBMT_RENDER_TARGET_BINDING_SLOTS: { const ERHIAccess RTVAccess = ERHIAccess::RTV; const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots(); RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget) { FRDGTextureRef Texture = RenderTarget.GetTexture(); FRDGTextureRef ResolveTexture = RenderTarget.GetResolveTexture(); FRDGTextureSubresourceRange Range(Texture->GetSubresourceRange()); Range.MipIndex = RenderTarget.GetMipIndex(); Range.NumMips = 1; if (RenderTarget.GetArraySlice() != -1) { Range.ArraySlice = RenderTarget.GetArraySlice(); Range.NumArraySlices = 1; } AccessFunction(nullptr, Texture, RTVAccess, Range); if (ResolveTexture && ResolveTexture != Texture) { // Resolve targets must use the RTV|ResolveDst flag combination when the resolve is performed through the render // pass. The ResolveDst flag must be used alone only when the resolve is performed using RHICopyToResolveTarget. AccessFunction(nullptr, ResolveTexture, ERHIAccess::RTV | ERHIAccess::ResolveDst, Range); } }); const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil; if (FRDGTextureRef Texture = DepthStencil.GetTexture()) { DepthStencil.GetDepthStencilAccess().EnumerateSubresources([&](ERHIAccess NewAccess, uint32 PlaneSlice) { FRDGTextureSubresourceRange Range = Texture->GetSubresourceRange(); // Adjust the range to use a single plane slice if not using of them all. if (PlaneSlice != FRHITransitionInfo::kAllSubresources) { Range.PlaneSlice = PlaneSlice; Range.NumPlaneSlices = 1; } AccessFunction(nullptr, Texture, NewAccess, Range); }); } } break; } }); } /** Enumerates all texture pass parameters and calls the provided function. The input function must accept an FRDGResource*, * since either views or textures are provided. */ template void EnumerateTextureParameters(FRDGParameterStruct PassParameters, TParameterFunction ParameterFunction) { PassParameters.EnumerateTextures([&](FRDGParameter Parameter) { switch (Parameter.GetType()) { case UBMT_RDG_TEXTURE: case UBMT_RDG_TEXTURE_ACCESS: if (FRDGTextureRef Texture = Parameter.GetAsTexture()) { ParameterFunction(Texture); } break; case UBMT_RDG_TEXTURE_SRV: if (FRDGTextureSRVRef SRV = Parameter.GetAsTextureSRV()) { ParameterFunction(SRV); } break; case UBMT_RDG_TEXTURE_UAV: if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV()) { ParameterFunction(UAV); } break; case UBMT_RENDER_TARGET_BINDING_SLOTS: { const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots(); RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget) { ParameterFunction(RenderTarget.GetTexture()); if (RenderTarget.GetResolveTexture()) { ParameterFunction(RenderTarget.GetResolveTexture()); } }); if (FRDGTextureRef Texture = RenderTargets.DepthStencil.GetTexture()) { ParameterFunction(Texture); } } break; } }); } /** Enumerates all buffer accesses and provides the access info. */ template void EnumerateBufferAccess(FRDGParameterStruct PassParameters, ERDGPassFlags PassFlags, TAccessFunction AccessFunction) { ERHIAccess SRVAccess, UAVAccess; GetPassAccess(PassFlags, SRVAccess, UAVAccess); PassParameters.EnumerateBuffers([&](FRDGParameter Parameter) { switch (Parameter.GetType()) { case UBMT_RDG_BUFFER: if (FRDGBufferRef Buffer = Parameter.GetAsBuffer()) { ERHIAccess BufferAccess = SRVAccess; if (EnumHasAnyFlags(Buffer->Desc.Usage, BUF_DrawIndirect)) { BufferAccess |= ERHIAccess::IndirectArgs; } AccessFunction(nullptr, Buffer, BufferAccess); } break; case UBMT_RDG_BUFFER_ACCESS: if (FRDGBufferAccess BufferAccess = Parameter.GetAsBufferAccess()) { AccessFunction(nullptr, BufferAccess.GetBuffer(), BufferAccess.GetAccess()); } break; case UBMT_RDG_BUFFER_SRV: if (FRDGBufferSRVRef SRV = Parameter.GetAsBufferSRV()) { AccessFunction(SRV, SRV->GetParent(), SRVAccess); } break; case UBMT_RDG_BUFFER_UAV: if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV()) { AccessFunction(UAV, UAV->GetParent(), UAVAccess); } break; } }); } /** Enumerates all buffer pass parameters and calls the provided function. The input function must accept an FRDGResource*, * since either views or textures are provided. */ template void EnumerateBufferParameters(FRDGParameterStruct PassParameters, TParameterFunction ParameterFunction) { PassParameters.EnumerateBuffers([&](FRDGParameter Parameter) { switch (Parameter.GetType()) { case UBMT_RDG_BUFFER: case UBMT_RDG_BUFFER_ACCESS: if (FRDGBufferRef Buffer = Parameter.GetAsBuffer()) { ParameterFunction(Buffer); } break; case UBMT_RDG_BUFFER_SRV: if (FRDGBufferSRVRef SRV = Parameter.GetAsBufferSRV()) { ParameterFunction(SRV); } break; case UBMT_RDG_BUFFER_UAV: if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV()) { ParameterFunction(UAV); } break; } }); } inline FRDGViewHandle GetHandleIfNoUAVBarrier(FRDGViewRef Resource) { if (Resource && (Resource->Type == ERDGViewType::BufferUAV || Resource->Type == ERDGViewType::TextureUAV)) { if (EnumHasAnyFlags(static_cast(Resource)->Flags, ERDGUnorderedAccessViewFlags::SkipBarrier)) { return Resource->GetHandle(); } } return FRDGViewHandle::Null; } inline EResourceTransitionFlags GetTextureViewTransitionFlags(FRDGViewRef Resource, FRDGTextureRef Texture) { if (Resource) { switch (Resource->Type) { case ERDGViewType::TextureUAV: { FRDGTextureUAVRef UAV = static_cast(Resource); if (UAV->Desc.MetaData != ERDGTextureMetaDataAccess::None) { return EResourceTransitionFlags::MaintainCompression; } } break; case ERDGViewType::TextureSRV: { FRDGTextureSRVRef SRV = static_cast(Resource); if (SRV->Desc.MetaData != ERDGTextureMetaDataAccess::None) { return EResourceTransitionFlags::MaintainCompression; } } break; } } else { if (EnumHasAnyFlags(Texture->Flags, ERDGTextureFlags::MaintainCompression)) { return EResourceTransitionFlags::MaintainCompression; } } return EResourceTransitionFlags::None; } void FRDGBuilder::TickPoolElements() { GRenderGraphResourcePool.TickPoolElements(); #if RDG_ENABLE_DEBUG if (GRDGDumpGraph) { --GRDGDumpGraph; } if (GRDGTransitionLog > 0) { --GRDGTransitionLog; } GRDGDumpGraphUnknownCount = 0; #endif #if STATS SET_DWORD_STAT(STAT_RDG_PassCount, GRDGStatPassCount); SET_DWORD_STAT(STAT_RDG_PassWithParameterCount, GRDGStatPassWithParameterCount); SET_DWORD_STAT(STAT_RDG_PassCullCount, GRDGStatPassCullCount); SET_DWORD_STAT(STAT_RDG_RenderPassMergeCount, GRDGStatRenderPassMergeCount); SET_DWORD_STAT(STAT_RDG_PassDependencyCount, GRDGStatPassDependencyCount); SET_DWORD_STAT(STAT_RDG_TextureCount, GRDGStatTextureCount); SET_DWORD_STAT(STAT_RDG_BufferCount, GRDGStatBufferCount); SET_DWORD_STAT(STAT_RDG_TransitionCount, GRDGStatTransitionCount); SET_DWORD_STAT(STAT_RDG_TransitionBatchCount, GRDGStatTransitionBatchCount); SET_MEMORY_STAT(STAT_RDG_MemoryWatermark, int64(GRDGStatMemoryWatermark)); GRDGStatPassCount = 0; GRDGStatPassWithParameterCount = 0; GRDGStatPassCullCount = 0; GRDGStatRenderPassMergeCount = 0; GRDGStatPassDependencyCount = 0; GRDGStatTextureCount = 0; GRDGStatBufferCount = 0; GRDGStatTransitionCount = 0; GRDGStatTransitionBatchCount = 0; GRDGStatMemoryWatermark = 0; #endif } ERDGPassFlags FRDGBuilder::OverridePassFlags(const TCHAR* PassName, ERDGPassFlags PassFlags, bool bAsyncComputeSupported) { const bool bDebugAllowedForPass = #if RDG_ENABLE_DEBUG IsDebugAllowedForPass(PassName); #else true; #endif const bool bGlobalForceAsyncCompute = (GRDGAsyncCompute == RDG_ASYNC_COMPUTE_FORCE_ENABLED && !GRDGImmediateMode && bDebugAllowedForPass); bAsyncComputeSupported &= !EnumHasAnyFlags(PassFlags, ERDGPassFlags::UntrackedAccess); if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Compute) && (bGlobalForceAsyncCompute)) { PassFlags &= ~ERDGPassFlags::Compute; PassFlags |= ERDGPassFlags::AsyncCompute; } if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::AsyncCompute) && (GRDGAsyncCompute == RDG_ASYNC_COMPUTE_DISABLED || GRDGImmediateMode != 0 || !bAsyncComputeSupported)) { PassFlags &= ~ERDGPassFlags::AsyncCompute; PassFlags |= ERDGPassFlags::Compute; } return PassFlags; } const char* const FRDGBuilder::kDefaultUnaccountedCSVStat = "RDG_Pass"; FRDGBuilder::FRDGBuilder(FRHICommandListImmediate& InRHICmdList, FRDGEventName InName) : RHICmdList(InRHICmdList) , Blackboard(Allocator) , RHICmdListAsyncCompute(FRHICommandListExecutor::GetImmediateAsyncComputeCommandList()) , BuilderName(InName) #if RDG_CPU_SCOPES , CPUScopeStacks(RHICmdList, kDefaultUnaccountedCSVStat) #endif #if RDG_GPU_SCOPES , GPUScopeStacks(RHICmdList, RHICmdListAsyncCompute) #endif #if RDG_ENABLE_DEBUG , UserValidation(Allocator) , BarrierValidation(&Passes, BuilderName) #endif { ProloguePass = Passes.Allocate(Allocator, RDG_EVENT_NAME("Graph Prologue")); SetupEmptyPass(ProloguePass); } void FRDGBuilder::PreallocateBuffer(FRDGBufferRef Buffer) { if (!Buffer->bExternal) { Buffer->bExternal = 1; Buffer->AccessFinal = kDefaultAccessFinal; BeginResourceRHI(GetProloguePassHandle(), Buffer); ExternalBuffers.Add(Buffer->PooledBuffer, Buffer); } } void FRDGBuilder::PreallocateTexture(FRDGTextureRef Texture) { if (!Texture->bExternal) { Texture->bExternal = 1; Texture->AccessFinal = kDefaultAccessFinal; BeginResourceRHI(GetProloguePassHandle(), Texture); ExternalTextures.Add(Texture->GetRHIUnchecked(), Texture); } } FRDGTextureRef FRDGBuilder::RegisterExternalTexture( const TRefCountPtr& ExternalPooledTexture, ERenderTargetTexture RenderTargetTexture, ERDGTextureFlags Flags) { #if RDG_ENABLE_DEBUG checkf(ExternalPooledTexture.IsValid(), TEXT("Attempted to register NULL external texture.")); #endif const TCHAR* Name = ExternalPooledTexture->GetDesc().DebugName; if (!Name) { Name = TEXT("External"); } return RegisterExternalTexture(ExternalPooledTexture, Name, RenderTargetTexture, Flags); } FRDGTextureRef FRDGBuilder::RegisterExternalTexture( const TRefCountPtr& ExternalPooledTexture, const TCHAR* Name, ERenderTargetTexture RenderTargetTexture, ERDGTextureFlags Flags) { IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalTexture(ExternalPooledTexture, Name, RenderTargetTexture, Flags)); FRHITexture* ExternalTextureRHI = ExternalPooledTexture->GetRenderTargetItem().GetRHI(RenderTargetTexture); IF_RDG_ENABLE_DEBUG(checkf(ExternalTextureRHI, TEXT("Attempted to register texture %s, but its RHI texture is null."), Name)); if (FRDGTextureRef FoundTexture = FindExternalTexture(ExternalTextureRHI)) { return FoundTexture; } FRDGTextureRef Texture = Textures.Allocate(Allocator, Name, Translate(ExternalPooledTexture->GetDesc(), RenderTargetTexture), Flags, RenderTargetTexture); FRDGTextureRef PreviousOwner = nullptr; Texture->SetRHI(static_cast(ExternalPooledTexture.GetReference()), PreviousOwner); checkf(!PreviousOwner, TEXT("Externally registered texture '%s' has a previous RDG texture owner '%s'. This can happen if two RDG builder instances register the same resource."), Name, PreviousOwner->Name); const ERHIAccess AccessInitial = kDefaultAccessInitial; Texture->bExternal = true; Texture->AccessInitial = AccessInitial; Texture->AccessFinal = kDefaultAccessFinal; Texture->AcquirePass = GetProloguePassHandle(); FRDGTextureSubresourceState& TextureState = Texture->GetState(); checkf(IsWholeResource(TextureState) && GetWholeResource(TextureState).Access == ERHIAccess::Unknown, TEXT("Externally registered texture '%s' has known RDG state. This means the graph did not sanitize it correctly, or ") TEXT("an IPooledRenderTarget reference was improperly held within a pass."), Texture->Name); { FRDGSubresourceState SubresourceState; SubresourceState.Access = AccessInitial; InitAsWholeResource(TextureState, SubresourceState); } ExternalTextures.Add(Texture->GetRHIUnchecked(), Texture); IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalTexture(Texture)); IF_RDG_ENABLE_TRACE(Trace.AddResource(Texture)); return Texture; } FRDGBufferRef FRDGBuilder::RegisterExternalBuffer(const TRefCountPtr& ExternalPooledBuffer, ERDGBufferFlags Flags) { #if RDG_ENABLE_DEBUG checkf(ExternalPooledBuffer.IsValid(), TEXT("Attempted to register NULL external buffer.")); #endif const TCHAR* Name = ExternalPooledBuffer->Name; if (!Name) { Name = TEXT("External"); } return RegisterExternalBuffer(ExternalPooledBuffer, Name, Flags); } FRDGBufferRef FRDGBuilder::RegisterExternalBuffer( const TRefCountPtr& ExternalPooledBuffer, const TCHAR* Name, ERDGBufferFlags Flags) { IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalBuffer(ExternalPooledBuffer, Name, Flags)); if (FRDGBufferRef* FoundBufferPtr = ExternalBuffers.Find(ExternalPooledBuffer.GetReference())) { return *FoundBufferPtr; } FRDGBufferRef Buffer = Buffers.Allocate(Allocator, Name, ExternalPooledBuffer->Desc, Flags); FRDGBufferRef PreviousOwner = nullptr; Buffer->SetRHI(ExternalPooledBuffer, PreviousOwner); checkf(!PreviousOwner, TEXT("Externally registered buffer '%s' has a previous RDG buffer owner '%s'. This can happen if two RDG builder instances register the same resource."), Name, PreviousOwner->Name); const ERHIAccess AccessInitial = kDefaultAccessInitial; Buffer->bExternal = true; Buffer->AccessInitial = AccessInitial; Buffer->AccessFinal = kDefaultAccessFinal; Buffer->AcquirePass = GetProloguePassHandle(); FRDGSubresourceState& BufferState = Buffer->GetState(); checkf(BufferState.Access == ERHIAccess::Unknown, TEXT("Externally registered buffer '%s' has known RDG state. This means the graph did not sanitize it correctly, or ") TEXT("an FRDGPooledBuffer reference was improperly held within a pass."), Buffer->Name); BufferState.Access = AccessInitial; ExternalBuffers.Add(ExternalPooledBuffer, Buffer); IF_RDG_ENABLE_DEBUG(UserValidation.ValidateRegisterExternalBuffer(Buffer)); IF_RDG_ENABLE_TRACE(Trace.AddResource(Buffer)); return Buffer; } void FRDGBuilder::AddPassDependency(FRDGPassHandle ProducerHandle, FRDGPassHandle ConsumerHandle) { checkf(ProducerHandle.IsValid(), TEXT("AddPassDependency called with null producer.")); checkf(ConsumerHandle.IsValid(), TEXT("AddPassDependency called with null consumer.")); FRDGPass* Consumer = Passes[ConsumerHandle]; auto& Producers = Consumer->Producers; if (Producers.Find(ProducerHandle) == INDEX_NONE) { Producers.Add(ProducerHandle); #if STATS GRDGStatPassDependencyCount++; #endif } }; void FRDGBuilder::Compile() { SCOPE_CYCLE_COUNTER(STAT_RDG_CompileTime); CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDG_Compile, GRDGVerboseCSVStats != 0); SCOPED_NAMED_EVENT(FRDGBuilder_Compile, FColor::Emerald); uint32 RasterPassCount = 0; uint32 AsyncComputePassCount = 0; FRDGPassBitArray PassesOnAsyncCompute(false, Passes.Num()); FRDGPassBitArray PassesOnRaster(false, Passes.Num()); FRDGPassBitArray PassesWithUntrackedOutputs(false, Passes.Num()); FRDGPassBitArray PassesToNeverCull(false, Passes.Num()); const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle(); const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle(); const auto IsCrossPipeline = [&](FRDGPassHandle A, FRDGPassHandle B) { return PassesOnAsyncCompute[A] != PassesOnAsyncCompute[B]; }; const auto IsSortedBefore = [&](FRDGPassHandle A, FRDGPassHandle B) { return A < B; }; const auto IsSortedAfter = [&](FRDGPassHandle A, FRDGPassHandle B) { return A > B; }; // Build producer / consumer dependencies across the graph and construct packed bit-arrays of metadata // for better cache coherency when searching for passes meeting specific criteria. Search roots are also // identified for culling. Passes with untracked RHI output (e.g. SHADER_PARAMETER_{BUFFER, TEXTURE}_UAV) // cannot be culled, nor can any pass which writes to an external resource. Resource extractions extend the // lifetime to the epilogue pass which is always a root of the graph. The prologue and epilogue are helper // passes and therefore never culled. { SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Culling_Dependencies, FColor::Emerald); const auto AddCullingDependency = [&](FRDGProducerStatesByPipeline& LastProducers, const FRDGProducerState& NextState, ERHIPipeline NextPipeline) { if (NextState.Access == ERHIAccess::Unknown) { return; } for (ERHIPipeline LastPipeline : GetRHIPipelines()) { FRDGProducerState& LastProducer = LastProducers[LastPipeline]; if (LastProducer.Access == ERHIAccess::Unknown) { continue; } if (FRDGProducerState::IsDependencyRequired(LastProducer, LastPipeline, NextState, NextPipeline)) { AddPassDependency(LastProducer.PassHandle, NextState.PassHandle); } } if (IsWritableAccess(NextState.Access)) { LastProducers[NextPipeline] = NextState; } }; for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { FRDGPass* Pass = Passes[PassHandle]; const ERHIPipeline PassPipeline = Pass->GetPipeline(); bool bUntrackedOutputs = Pass->GetParameters().HasExternalOutputs(); for (auto& TexturePair : Pass->TextureStates) { FRDGTextureRef Texture = TexturePair.Key; auto& LastProducers = Texture->LastProducers; auto& PassState = TexturePair.Value.State; const bool bWholePassState = IsWholeResource(PassState); const bool bWholeProducers = IsWholeResource(LastProducers); checkf(!bWholeProducers || bWholePassState == bWholeProducers, TEXT("The producer array needs to be at least as large as the pass state array.")); for (uint32 Index = 0, Count = LastProducers.Num(); Index < Count; ++Index) { const auto& SubresourceState = PassState[bWholePassState ? 0 : Index]; FRDGProducerState ProducerState; ProducerState.Access = SubresourceState.Access; ProducerState.PassHandle = PassHandle; ProducerState.NoUAVBarrierHandle = SubresourceState.NoUAVBarrierFilter.GetUniqueHandle(); AddCullingDependency(LastProducers[Index], ProducerState, PassPipeline); } bUntrackedOutputs |= Texture->bExternal; } for (auto& BufferPair : Pass->BufferStates) { FRDGBufferRef Buffer = BufferPair.Key; const auto& SubresourceState = BufferPair.Value.State; FRDGProducerState ProducerState; ProducerState.Access = SubresourceState.Access; ProducerState.PassHandle = PassHandle; ProducerState.NoUAVBarrierHandle = SubresourceState.NoUAVBarrierFilter.GetUniqueHandle(); AddCullingDependency(Buffer->LastProducer, ProducerState, PassPipeline); bUntrackedOutputs |= Buffer->bExternal; } const ERDGPassFlags PassFlags = Pass->GetFlags(); const bool bAsyncCompute = EnumHasAnyFlags(PassFlags, ERDGPassFlags::AsyncCompute); const bool bRaster = EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster); const bool bNeverCull = EnumHasAnyFlags(PassFlags, ERDGPassFlags::NeverCull); PassesOnRaster[PassHandle] = bRaster; PassesOnAsyncCompute[PassHandle] = bAsyncCompute; PassesToNeverCull[PassHandle] = bNeverCull; PassesWithUntrackedOutputs[PassHandle] = bUntrackedOutputs; AsyncComputePassCount += bAsyncCompute ? 1 : 0; RasterPassCount += bRaster ? 1 : 0; } // The prologue / epilogue is responsible for external resource import / export, respectively. PassesWithUntrackedOutputs[ProloguePassHandle] = true; PassesWithUntrackedOutputs[EpiloguePassHandle] = true; for (const auto& Query : ExtractedTextures) { FRDGTextureRef Texture = Query.Key; for (auto& LastProducer : Texture->LastProducers) { FRDGProducerState StateFinal; StateFinal.Access = Texture->AccessFinal; StateFinal.PassHandle = EpiloguePassHandle; AddCullingDependency(LastProducer, StateFinal, ERHIPipeline::Graphics); } Texture->ReferenceCount++; } for (const auto& Query : ExtractedBuffers) { FRDGBufferRef Buffer = Query.Key; FRDGProducerState StateFinal; StateFinal.Access = Buffer->AccessFinal; StateFinal.PassHandle = EpiloguePassHandle; AddCullingDependency(Buffer->LastProducer, StateFinal, ERHIPipeline::Graphics); Buffer->ReferenceCount++; } } // All dependencies in the raw graph have been specified; if enabled, all passes are marked as culled and a // depth first search is employed to find reachable regions of the graph. Roots of the search are those passes // with outputs leaving the graph or those marked to never cull. if (GRDGCullPasses) { SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Cull_Passes, FColor::Emerald); TArray> PassStack; PassesToCull.Init(true, Passes.Num()); #if STATS GRDGStatPassCullCount += Passes.Num(); #endif for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (PassesWithUntrackedOutputs[PassHandle] || PassesToNeverCull[PassHandle]) { PassStack.Add(PassHandle); } } while (PassStack.Num()) { const FRDGPassHandle PassHandle = PassStack.Pop(); if (PassesToCull[PassHandle]) { PassesToCull[PassHandle] = false; PassStack.Append(Passes[PassHandle]->Producers); #if STATS --GRDGStatPassCullCount; #endif } } } else { PassesToCull.Init(false, Passes.Num()); } // Walk the culled graph and compile barriers for each subresource. Certain transitions are redundant; read-to-read, for example. // We can avoid them by traversing and merging compatible states together. The merging states removes a transition, but the merging // heuristic is conservative and choosing not to merge doesn't necessarily mean a transition is performed. They are two distinct steps. // Merged states track the first and last pass interval. Pass references are also accumulated onto each resource. This must happen // after culling since culled passes can't contribute references. { SCOPED_NAMED_EVENT(FRDGBuilder_Compile_Barriers, FColor::Emerald); for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (PassesToCull[PassHandle] || PassesWithEmptyParameters[PassHandle]) { continue; } const bool bAsyncComputePass = PassesOnAsyncCompute[PassHandle]; FRDGPass* Pass = Passes[PassHandle]; const ERHIPipeline PassPipeline = Pass->GetPipeline(); const auto MergeSubresourceStates = [&](ERDGParentResourceType ResourceType, FRDGSubresourceState*& PassMergeState, FRDGSubresourceState*& ResourceMergeState, const FRDGSubresourceState& PassState) { if (PassState.Access == ERHIAccess::Unknown) { return; } if (!ResourceMergeState || !FRDGSubresourceState::IsMergeAllowed(ResourceType, *ResourceMergeState, PassState)) { // Cross-pipeline, non-mergable state changes require a new pass dependency for fencing purposes. if (ResourceMergeState) { for (ERHIPipeline Pipeline : GetRHIPipelines()) { if (Pipeline != PassPipeline && ResourceMergeState->LastPass[Pipeline].IsValid()) { // Add a dependency from the other pipe to this pass to join back. AddPassDependency(ResourceMergeState->LastPass[Pipeline], PassHandle); } } } // Allocate a new pending merge state and assign it to the pass state. ResourceMergeState = AllocSubresource(PassState); } else { // Merge the pass state into the merged state. ResourceMergeState->Access |= PassState.Access; FRDGPassHandle& FirstPassHandle = ResourceMergeState->FirstPass[PassPipeline]; if (FirstPassHandle.IsNull()) { FirstPassHandle = PassHandle; } ResourceMergeState->LastPass[PassPipeline] = PassHandle; } PassMergeState = ResourceMergeState; }; for (auto& TexturePair : Pass->TextureStates) { FRDGTextureRef Texture = TexturePair.Key; auto& PassState = TexturePair.Value; Texture->ReferenceCount += PassState.ReferenceCount; Texture->bUsedByAsyncComputePass |= bAsyncComputePass; // For simplicity, the merge / pass state dimensionality should match. if (Texture->MergeState.Num() != PassState.State.Num()) { check(IsSubresources(Texture->MergeState)); InitAsSubresources(PassState.State, Texture->Layout, GetWholeResource(PassState.State)); } const uint32 SubresourceCount = PassState.State.Num(); check(Texture->MergeState.Num() == SubresourceCount); PassState.MergeState.SetNum(SubresourceCount); for (uint32 Index = 0; Index < SubresourceCount; ++Index) { MergeSubresourceStates(ERDGParentResourceType::Texture, PassState.MergeState[Index], Texture->MergeState[Index], PassState.State[Index]); } } for (auto& BufferPair : Pass->BufferStates) { FRDGBufferRef Buffer = BufferPair.Key; auto& PassState = BufferPair.Value; Buffer->ReferenceCount += PassState.ReferenceCount; Buffer->bUsedByAsyncComputePass |= bAsyncComputePass; MergeSubresourceStates(ERDGParentResourceType::Buffer, PassState.MergeState, Buffer->MergeState, PassState.State); } } } if (AsyncComputePassCount > 0) { SCOPED_NAMED_EVENT(FRDGBuilder_Compile_AsyncCompute, FColor::Emerald); // Traverse the active passes in execution order to find latest cross-pipeline producer and the earliest // cross-pipeline consumer for each pass. This helps narrow the search space later when building async // compute overlap regions. FRDGPassBitArray PassesWithCrossPipelineProducer(false, Passes.Num()); FRDGPassBitArray PassesWithCrossPipelineConsumer(false, Passes.Num()); for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (PassesToCull[PassHandle] || PassesWithEmptyParameters[PassHandle]) { continue; } FRDGPass* Pass = Passes[PassHandle]; for (FRDGPassHandle ProducerHandle : Pass->GetProducers()) { const FRDGPassHandle ConsumerHandle = PassHandle; if (!IsCrossPipeline(ProducerHandle, ConsumerHandle)) { continue; } FRDGPass* Consumer = Pass; FRDGPass* Producer = Passes[ProducerHandle]; // Finds the earliest consumer on the other pipeline for the producer. if (Producer->CrossPipelineConsumer.IsNull() || IsSortedBefore(ConsumerHandle, Producer->CrossPipelineConsumer)) { Producer->CrossPipelineConsumer = PassHandle; PassesWithCrossPipelineConsumer[ProducerHandle] = true; } // Finds the latest producer on the other pipeline for the consumer. if (Consumer->CrossPipelineProducer.IsNull() || IsSortedAfter(ProducerHandle, Consumer->CrossPipelineProducer)) { Consumer->CrossPipelineProducer = ProducerHandle; PassesWithCrossPipelineProducer[ConsumerHandle] = true; } } } // Establishes fork / join overlap regions for async compute. This is used for fencing as well as resource // allocation / deallocation. Async compute passes can't allocate / release their resource references until // the fork / join is complete, since the two pipes run in parallel. Therefore, all resource lifetimes on // async compute are extended to cover the full async region. const auto IsCrossPipelineProducer = [&](FRDGPassHandle A) { return PassesWithCrossPipelineConsumer[A]; }; const auto IsCrossPipelineConsumer = [&](FRDGPassHandle A) { return PassesWithCrossPipelineProducer[A]; }; const auto FindCrossPipelineProducer = [&](FRDGPassHandle PassHandle) { check(PassHandle != ProloguePassHandle); FRDGPassHandle LatestProducerHandle = ProloguePassHandle; FRDGPassHandle ConsumerHandle = PassHandle; // We want to find the latest producer on the other pipeline in order to establish a fork point. // Since we could be consuming N resources with N producer passes, we only care about the last one. while (ConsumerHandle != Passes.Begin()) { if (!PassesToCull[ConsumerHandle] && !IsCrossPipeline(ConsumerHandle, PassHandle) && IsCrossPipelineConsumer(ConsumerHandle)) { const FRDGPass* Consumer = Passes[ConsumerHandle]; if (IsSortedAfter(Consumer->CrossPipelineProducer, LatestProducerHandle)) { LatestProducerHandle = Consumer->CrossPipelineProducer; } } --ConsumerHandle; } return LatestProducerHandle; }; const auto FindCrossPipelineConsumer = [&](FRDGPassHandle PassHandle) { check(PassHandle != EpiloguePassHandle); FRDGPassHandle EarliestConsumerHandle = EpiloguePassHandle; FRDGPassHandle ProducerHandle = PassHandle; // We want to find the earliest consumer on the other pipeline, as this establishes a join point // between the pipes. Since we could be producing for N consumers on the other pipeline, we only // care about the first one to execute. while (ProducerHandle != Passes.End()) { if (!PassesToCull[ProducerHandle] && !IsCrossPipeline(ProducerHandle, PassHandle) && IsCrossPipelineProducer(ProducerHandle)) { const FRDGPass* Producer = Passes[ProducerHandle]; if (IsSortedBefore(Producer->CrossPipelineConsumer, EarliestConsumerHandle)) { EarliestConsumerHandle = Producer->CrossPipelineConsumer; } } ++ProducerHandle; } return EarliestConsumerHandle; }; const auto InsertGraphicsToAsyncComputeFork = [&](FRDGPass* GraphicsPass, FRDGPass* AsyncComputePass) { FRDGBarrierBatchBegin& EpilogueBarriersToBeginForAsyncCompute = GraphicsPass->GetEpilogueBarriersToBeginForAsyncCompute(Allocator); GraphicsPass->bGraphicsFork = 1; EpilogueBarriersToBeginForAsyncCompute.SetUseCrossPipelineFence(); AsyncComputePass->bAsyncComputeBegin = 1; AsyncComputePass->GetPrologueBarriersToEnd(Allocator).AddDependency(&EpilogueBarriersToBeginForAsyncCompute); }; const auto InsertAsyncToGraphicsComputeJoin = [&](FRDGPass* AsyncComputePass, FRDGPass* GraphicsPass) { FRDGBarrierBatchBegin& EpilogueBarriersToBeginForGraphics = AsyncComputePass->GetEpilogueBarriersToBeginForGraphics(Allocator); AsyncComputePass->bAsyncComputeEnd = 1; EpilogueBarriersToBeginForGraphics.SetUseCrossPipelineFence(); GraphicsPass->bGraphicsJoin = 1; GraphicsPass->GetPrologueBarriersToEnd(Allocator).AddDependency(&EpilogueBarriersToBeginForGraphics); }; FRDGPass* PrevGraphicsForkPass = nullptr; FRDGPass* PrevGraphicsJoinPass = nullptr; FRDGPass* PrevAsyncComputePass = nullptr; for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (!PassesOnAsyncCompute[PassHandle] || PassesToCull[PassHandle]) { continue; } FRDGPass* AsyncComputePass = Passes[PassHandle]; const FRDGPassHandle GraphicsForkPassHandle = FindCrossPipelineProducer(PassHandle); const FRDGPassHandle GraphicsJoinPassHandle = FindCrossPipelineConsumer(PassHandle); AsyncComputePass->GraphicsForkPass = GraphicsForkPassHandle; AsyncComputePass->GraphicsJoinPass = GraphicsJoinPassHandle; FRDGPass* GraphicsForkPass = Passes[GraphicsForkPassHandle]; FRDGPass* GraphicsJoinPass = Passes[GraphicsJoinPassHandle]; // Extend the lifetime of resources used on async compute to the fork / join graphics passes. GraphicsForkPass->ResourcesToBegin.Add(AsyncComputePass); GraphicsJoinPass->ResourcesToEnd.Add(AsyncComputePass); if (PrevGraphicsForkPass != GraphicsForkPass) { InsertGraphicsToAsyncComputeFork(GraphicsForkPass, AsyncComputePass); } if (PrevGraphicsJoinPass != GraphicsJoinPass && PrevAsyncComputePass) { InsertAsyncToGraphicsComputeJoin(PrevAsyncComputePass, PrevGraphicsJoinPass); } PrevAsyncComputePass = AsyncComputePass; PrevGraphicsForkPass = GraphicsForkPass; PrevGraphicsJoinPass = GraphicsJoinPass; } // Last async compute pass in the graph needs to be manually joined back to the epilogue pass. if (PrevAsyncComputePass) { InsertAsyncToGraphicsComputeJoin(PrevAsyncComputePass, EpiloguePass); PrevAsyncComputePass->bAsyncComputeEndExecute = 1; } } // Traverses passes on the graphics pipe and merges raster passes with the same render targets into a single RHI render pass. if (GRDGMergeRenderPasses && RasterPassCount > 0) { SCOPED_NAMED_EVENT(FRDGBuilder_Compile_RenderPassMerge, FColor::Emerald); TArray PassesToMerge; FRDGPass* PrevPass = nullptr; const FRenderTargetBindingSlots* PrevRenderTargets = nullptr; const auto CommitMerge = [&] { if (PassesToMerge.Num()) { const FRDGPassHandle FirstPassHandle = PassesToMerge[0]; const FRDGPassHandle LastPassHandle = PassesToMerge.Last(); // Given an interval of passes to merge into a single render pass: [B, X, X, X, X, E] // // The begin pass (B) and end (E) passes will call {Begin, End}RenderPass, respectively. Also, // begin will handle all prologue barriers for the entire merged interval, and end will handle all // epilogue barriers. This avoids transitioning of resources within the render pass and batches the // transitions more efficiently. This assumes we have filtered out dependencies between passes from // the merge set, which is done during traversal. // (B) First pass in the merge sequence. { FRDGPass* Pass = Passes[FirstPassHandle]; Pass->bSkipRenderPassEnd = 1; Pass->EpilogueBarrierPass = LastPassHandle; } // (X) Intermediate passes. for (int32 PassIndex = 1, PassCount = PassesToMerge.Num() - 1; PassIndex < PassCount; ++PassIndex) { const FRDGPassHandle PassHandle = PassesToMerge[PassIndex]; FRDGPass* Pass = Passes[PassHandle]; Pass->bSkipRenderPassBegin = 1; Pass->bSkipRenderPassEnd = 1; Pass->PrologueBarrierPass = FirstPassHandle; Pass->EpilogueBarrierPass = LastPassHandle; } // (E) Last pass in the merge sequence. { FRDGPass* Pass = Passes[LastPassHandle]; Pass->bSkipRenderPassBegin = 1; Pass->PrologueBarrierPass = FirstPassHandle; } #if STATS GRDGStatRenderPassMergeCount += PassesToMerge.Num(); #endif } PassesToMerge.Reset(); PrevPass = nullptr; PrevRenderTargets = nullptr; }; for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (PassesToCull[PassHandle] || PassesWithEmptyParameters[PassHandle]) { continue; } if (PassesOnRaster[PassHandle]) { FRDGPass* NextPass = Passes[PassHandle]; // A pass where the user controls the render pass can't merge with other passes, and raster UAV passes can't merge due to potential interdependencies. if (EnumHasAnyFlags(NextPass->GetFlags(), ERDGPassFlags::SkipRenderPass) || NextPass->bUAVAccess) { CommitMerge(); continue; } // A graphics fork pass can't merge with a previous raster pass. if (NextPass->bGraphicsFork) { CommitMerge(); } const FRenderTargetBindingSlots& RenderTargets = NextPass->GetParameters().GetRenderTargets(); if (PrevPass) { check(PrevRenderTargets); if (PrevRenderTargets->CanMergeBefore(RenderTargets) #if WITH_MGPU && PrevPass->GPUMask == NextPass->GPUMask #endif ) { if (!PassesToMerge.Num()) { PassesToMerge.Add(PrevPass->GetHandle()); } PassesToMerge.Add(PassHandle); } else { CommitMerge(); } } PrevPass = NextPass; PrevRenderTargets = &RenderTargets; } else if (!PassesOnAsyncCompute[PassHandle]) { // A non-raster pass on the graphics pipe will invalidate the render target merge. CommitMerge(); } } CommitMerge(); } } void FRDGBuilder::Execute() { CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RDG); SCOPED_NAMED_EVENT(FRDGBuilder_Execute, FColor::Emerald); // Create the epilogue pass at the end of the graph just prior to compilation. EpiloguePass = Passes.Allocate(Allocator, RDG_EVENT_NAME("Graph Epilogue")); SetupEmptyPass(EpiloguePass); IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecuteBegin()); const FRDGPassHandle ProloguePassHandle = GetProloguePassHandle(); const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle(); FRDGPassHandle LastUntrackedPassHandle = ProloguePassHandle; if (!GRDGImmediateMode) { Compile(); IF_RDG_ENABLE_DEBUG(LogFile.Begin(BuilderName, &Passes, PassesToCull, GetProloguePassHandle(), GetEpiloguePassHandle())); { SCOPE_CYCLE_COUNTER(STAT_RDG_CollectResourcesTime); CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RDG_CollectResources); for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (!PassesToCull[PassHandle]) { CollectPassResources(PassHandle); } } for (const auto& Query : ExtractedTextures) { EndResourceRHI(EpiloguePassHandle, Query.Key, 1); } for (const auto& Query : ExtractedBuffers) { EndResourceRHI(EpiloguePassHandle, Query.Key, 1); } } { SCOPE_CYCLE_COUNTER(STAT_RDG_CollectBarriersTime); CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDG_CollectBarriers, GRDGVerboseCSVStats != 0); for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (!PassesToCull[PassHandle]) { CollectPassBarriers(PassHandle, LastUntrackedPassHandle); } } } } #if RDG_ENABLE_DEBUG const auto LogResource = [&](auto* Resource, auto& Registry) { if (!Resource->bCulled) { if (!Resource->bLastOwner) { auto* NextOwner = Registry[Resource->NextOwner]; LogFile.AddAliasEdge(Resource, Resource->LastPass, NextOwner, NextOwner->AcquirePass); } LogFile.AddFirstEdge(Resource, Resource->FirstPass); } }; #endif for (FRDGTextureHandle TextureHandle = Textures.Begin(); TextureHandle != Textures.End(); ++TextureHandle) { FRDGTextureRef Texture = Textures[TextureHandle]; if (Texture->GetRHIUnchecked()) { AddEpilogueTransition(Texture, LastUntrackedPassHandle); Texture->Finalize(); IF_RDG_ENABLE_DEBUG(LogResource(Texture, Textures)); } } for (FRDGBufferHandle BufferHandle = Buffers.Begin(); BufferHandle != Buffers.End(); ++BufferHandle) { FRDGBufferRef Buffer = Buffers[BufferHandle]; if (Buffer->GetRHIUnchecked()) { AddEpilogueTransition(Buffer, LastUntrackedPassHandle); Buffer->Finalize(); IF_RDG_ENABLE_DEBUG(LogResource(Buffer, Buffers)); } } IF_RDG_CPU_SCOPES(CPUScopeStacks.BeginExecute()); IF_RDG_GPU_SCOPES(GPUScopeStacks.BeginExecute()); IF_RDG_ENABLE_TRACE(Trace.OutputGraphBegin()); if (!GRDGImmediateMode) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_Execute_Passes); for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { if (!PassesToCull[PassHandle]) { ExecutePass(Passes[PassHandle]); } } IF_RDG_ENABLE_DEBUG(LogFile.End()); } else { ExecutePass(EpiloguePass); } RHICmdList.SetStaticUniformBuffers({}); #if WITH_MGPU if (NameForTemporalEffect != NAME_None) { TArray BroadcastTexturesForTemporalEffect; for (const auto& Query : ExtractedTextures) { if (EnumHasAnyFlags(Query.Key->Flags, ERDGTextureFlags::MultiFrame)) { BroadcastTexturesForTemporalEffect.Add(Query.Key->GetRHIUnchecked()); } } RHICmdList.BroadcastTemporalEffect(NameForTemporalEffect, BroadcastTexturesForTemporalEffect); } #endif for (const auto& Query : ExtractedTextures) { *Query.Value = Query.Key->PooledRenderTarget; } for (const auto& Query : ExtractedBuffers) { *Query.Value = Query.Key->PooledBuffer; } IF_RDG_ENABLE_TRACE(Trace.OutputGraphEnd(*this)); IF_RDG_GPU_SCOPES(GPUScopeStacks.Graphics.EndExecute()); IF_RDG_CPU_SCOPES(CPUScopeStacks.EndExecute()); IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecuteEnd()); #if STATS GRDGStatPassCount += Passes.Num(); GRDGStatPassWithParameterCount = GRDGStatPassCount; for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle) { GRDGStatPassWithParameterCount -= PassesWithEmptyParameters[PassHandle] ? 1 : 0; } GRDGStatBufferCount += Buffers.Num(); GRDGStatTextureCount += Textures.Num(); GRDGStatMemoryWatermark = FMath::Max(GRDGStatMemoryWatermark, Allocator.GetByteCount()); #endif Clear(); } void FRDGBuilder::Clear() { ExternalTextures.Empty(); ExternalBuffers.Empty(); ExtractedTextures.Empty(); ExtractedBuffers.Empty(); Passes.Clear(); Views.Clear(); Textures.Clear(); Buffers.Clear(); UniformBuffers.Clear(); Blackboard.Clear(); } void FRDGBuilder::SetupPass(FRDGPass* Pass) { IF_RDG_ENABLE_DEBUG(UserValidation.ValidateAddPass(Pass, bInDebugPassScope)); const FRDGParameterStruct PassParameters = Pass->GetParameters(); const FRDGPassHandle PassHandle = Pass->GetHandle(); const ERDGPassFlags PassFlags = Pass->GetFlags(); const ERHIPipeline PassPipeline = Pass->GetPipeline(); bool bPassUAVAccess = false; Pass->TextureStates.Reserve(PassParameters.GetTextureParameterCount() + (PassParameters.HasRenderTargets() ? (MaxSimultaneousRenderTargets + 1) : 0)); EnumerateTextureAccess(PassParameters, PassFlags, [&](FRDGViewRef TextureView, FRDGTextureRef Texture, ERHIAccess Access, FRDGTextureSubresourceRange Range) { check(Access != ERHIAccess::Unknown); const FRDGViewHandle NoUAVBarrierHandle = GetHandleIfNoUAVBarrier(TextureView); const EResourceTransitionFlags TransitionFlags = GetTextureViewTransitionFlags(TextureView, Texture); auto& PassState = Pass->TextureStates.FindOrAdd(Texture); PassState.ReferenceCount++; const bool bWholeTextureRange = Range.IsWholeResource(Texture->GetSubresourceLayout()); bool bWholePassState = IsWholeResource(PassState.State); // Convert the pass state to subresource dimensionality if we've found a subresource range. if (!bWholeTextureRange && bWholePassState) { InitAsSubresources(PassState.State, Texture->Layout, GetWholeResource(PassState.State)); bWholePassState = false; // Also convert the texture's internal arrays to subresources. We'll process everything as subresources from now on. if (IsWholeResource(Texture->MergeState)) { check(IsWholeResource(Texture->LastProducers)); InitAsSubresources(Texture->MergeState, Texture->Layout); InitAsSubresources(Texture->LastProducers, Texture->Layout); } } const auto AddSubresourceAccess = [&](FRDGSubresourceState& State) { State.Access = MakeValidAccess(State.Access | Access); State.Flags |= TransitionFlags; State.NoUAVBarrierFilter.AddHandle(NoUAVBarrierHandle); State.SetPass(PassPipeline, PassHandle); }; if (bWholePassState) { AddSubresourceAccess(GetWholeResource(PassState.State)); } else { EnumerateSubresourceRange(PassState.State, Texture->Layout, Range, AddSubresourceAccess); } bPassUAVAccess |= EnumHasAnyFlags(Access, ERHIAccess::UAVMask); Texture->bProduced |= IsWritableAccess(Access); }); Pass->BufferStates.Reserve(PassParameters.GetBufferParameterCount()); EnumerateBufferAccess(PassParameters, PassFlags, [&](FRDGViewRef BufferView, FRDGBufferRef Buffer, ERHIAccess Access) { check(Access != ERHIAccess::Unknown); const FRDGViewHandle NoUAVBarrierHandle = GetHandleIfNoUAVBarrier(BufferView); auto& PassState = Pass->BufferStates.FindOrAdd(Buffer); PassState.ReferenceCount++; PassState.State.Access = MakeValidAccess(PassState.State.Access | Access); PassState.State.NoUAVBarrierFilter.AddHandle(NoUAVBarrierHandle); PassState.State.SetPass(PassPipeline, PassHandle); bPassUAVAccess |= EnumHasAnyFlags(Access, ERHIAccess::UAVMask); Buffer->bProduced |= IsWritableAccess(Access); }); Pass->bUAVAccess = bPassUAVAccess; const bool bEmptyParameters = !Pass->TextureStates.Num() && !Pass->BufferStates.Num(); PassesWithEmptyParameters.Add(bEmptyParameters); // The pass can begin / end its own resources on the graphics pipe; async compute is scheduled during compilation. if (PassPipeline == ERHIPipeline::Graphics && !bEmptyParameters) { Pass->ResourcesToBegin.Add(Pass); Pass->ResourcesToEnd.Add(Pass); } SetupPassInternal(Pass, PassHandle, PassPipeline); } void FRDGBuilder::SetupEmptyPass(FRDGPass* Pass) { PassesWithEmptyParameters.Add(true); SetupPassInternal(Pass, Pass->GetHandle(), ERHIPipeline::Graphics); } void FRDGBuilder::SetupPassInternal(FRDGPass* Pass, FRDGPassHandle PassHandle, ERHIPipeline PassPipeline) { check(Pass->GetHandle() == PassHandle); check(Pass->GetPipeline() == PassPipeline); Pass->GraphicsJoinPass = PassHandle; Pass->GraphicsForkPass = PassHandle; Pass->PrologueBarrierPass = PassHandle; Pass->EpilogueBarrierPass = PassHandle; #if WITH_MGPU Pass->GPUMask = RHICmdList.GetGPUMask(); #endif #if STATS Pass->CommandListStat = CommandListStat; #endif IF_RDG_CPU_SCOPES(Pass->CPUScopes = CPUScopeStacks.GetCurrentScopes()); IF_RDG_GPU_SCOPES(Pass->GPUScopes = GPUScopeStacks.GetCurrentScopes(PassPipeline)); #if RDG_GPU_SCOPES && RDG_ENABLE_TRACE Pass->TraceEventScope = GPUScopeStacks.GetCurrentScopes(ERHIPipeline::Graphics).Event; #endif #if RDG_GPU_SCOPES && RDG_ENABLE_DEBUG if (const FRDGEventScope* Scope = Pass->GPUScopes.Event) { Pass->FullPathIfDebug = Scope->GetPath(Pass->Name); } #endif if (GRDGImmediateMode && Pass != EpiloguePass) { // Trivially redirect the merge states to the pass states, since we won't be compiling the graph. for (auto& TexturePair : Pass->TextureStates) { auto& PassState = TexturePair.Value; const uint32 SubresourceCount = PassState.State.Num(); PassState.MergeState.SetNum(SubresourceCount); for (uint32 Index = 0; Index < SubresourceCount; ++Index) { if (PassState.State[Index].Access != ERHIAccess::Unknown) { PassState.MergeState[Index] = &PassState.State[Index]; } } } for (auto& BufferPair : Pass->BufferStates) { auto& PassState = BufferPair.Value; PassState.MergeState = &PassState.State; } check(!EnumHasAnyFlags(PassPipeline, ERHIPipeline::AsyncCompute)); FRDGPassHandle LastUntrackedPassHandle = GetProloguePassHandle(); CollectPassResources(PassHandle); CollectPassBarriers(PassHandle, LastUntrackedPassHandle); ExecutePass(Pass); } IF_RDG_ENABLE_DEBUG(VisualizePassOutputs(Pass)); } void FRDGBuilder::ExecutePassPrologue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_ExecutePassPrologue); CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDGBuilder_ExecutePassPrologue, GRDGVerboseCSVStats != 0); IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecutePassBegin(Pass)); IF_RDG_CMDLIST_STATS(RHICmdList.SetCurrentStat(Pass->CommandListStat)); const ERDGPassFlags PassFlags = Pass->GetFlags(); const ERHIPipeline PassPipeline = Pass->GetPipeline(); if (Pass->PrologueBarriersToBegin) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->PrologueBarriersToBegin)); Pass->PrologueBarriersToBegin->Submit(RHICmdListPass, PassPipeline); } if (Pass->PrologueBarriersToEnd) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchEnd(Pass, *Pass->PrologueBarriersToEnd)); Pass->PrologueBarriersToEnd->Submit(RHICmdListPass, PassPipeline); } if (PassPipeline == ERHIPipeline::AsyncCompute) { RHICmdListPass.SetAsyncComputeBudget(Pass->AsyncComputeBudget); } if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster)) { if (!EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassBegin()) { static_cast(RHICmdListPass).BeginRenderPass(Pass->GetParameters().GetRenderPassInfo(), Pass->GetName()); } } BeginUAVOverlap(Pass, RHICmdListPass); } void FRDGBuilder::ExecutePassEpilogue(FRHIComputeCommandList& RHICmdListPass, FRDGPass* Pass) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_ExecutePassEpilogue); CSV_SCOPED_TIMING_STAT_EXCLUSIVE_CONDITIONAL(RDGBuilder_ExecutePassEpilogue, GRDGVerboseCSVStats != 0); EndUAVOverlap(Pass, RHICmdListPass); const ERDGPassFlags PassFlags = Pass->GetFlags(); const ERHIPipeline PassPipeline = Pass->GetPipeline(); const FRDGParameterStruct PassParameters = Pass->GetParameters(); if (EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster) && !EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass) && !Pass->SkipRenderPassEnd()) { static_cast(RHICmdListPass).EndRenderPass(); } for (FRHITexture* Texture : Pass->TexturesToDiscard) { RHIDiscardTransientResource(Texture); } for (FRHITexture* Texture : Pass->TexturesToAcquire) { RHIAcquireTransientResource(Texture); } FRDGTransitionQueue Transitions; if (Pass->EpilogueBarriersToBeginForGraphics) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->EpilogueBarriersToBeginForGraphics)); Pass->EpilogueBarriersToBeginForGraphics->Submit(RHICmdListPass, PassPipeline, Transitions); } if (Pass->EpilogueBarriersToBeginForAsyncCompute) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->EpilogueBarriersToBeginForAsyncCompute)); Pass->EpilogueBarriersToBeginForAsyncCompute->Submit(RHICmdListPass, PassPipeline, Transitions); } if (Pass->EpilogueBarriersToBeginForAll) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *Pass->EpilogueBarriersToBeginForAll)); Pass->EpilogueBarriersToBeginForAll->Submit(RHICmdListPass, PassPipeline, Transitions); } for (FRDGBarrierBatchBegin* BarriersToBegin : Pass->SharedEpilogueBarriersToBegin) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchBegin(Pass, *BarriersToBegin)); BarriersToBegin->Submit(RHICmdListPass, PassPipeline, Transitions); } Transitions.Begin(RHICmdListPass); if (Pass->EpilogueBarriersToEnd) { IF_RDG_ENABLE_DEBUG(BarrierValidation.ValidateBarrierBatchEnd(Pass, *Pass->EpilogueBarriersToEnd)); Pass->EpilogueBarriersToEnd->Submit(RHICmdListPass, PassPipeline); } IF_RDG_ENABLE_DEBUG(UserValidation.ValidateExecutePassEnd(Pass)); } void FRDGBuilder::ExecutePass(FRDGPass* Pass) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FRDGBuilder_ExecutePass); // Note that we must do this before doing anything with RHICmdList for the pass. // For example, if this pass only executes on GPU 1 we want to avoid adding a // 0-duration event for this pass on GPU 0's time line. SCOPED_GPU_MASK(RHICmdList, Pass->GPUMask); IF_RDG_CPU_SCOPES(CPUScopeStacks.BeginExecutePass(Pass)); IF_RDG_ENABLE_DEBUG(ConditionalDebugBreak(RDG_BREAKPOINT_PASS_EXECUTE, BuilderName.GetTCHAR(), Pass->GetName())); #if WITH_MGPU if (!bWaitedForTemporalEffect && NameForTemporalEffect != NAME_None) { RHICmdList.WaitForTemporalEffect(NameForTemporalEffect); bWaitedForTemporalEffect = true; } #endif // Execute the pass by invoking the prologue, then the pass body, then the epilogue. // The entire pass is executed using the command list on the specified pipeline. FRHIComputeCommandList& RHICmdListPass = (Pass->GetPipeline() == ERHIPipeline::AsyncCompute) ? static_cast(RHICmdListAsyncCompute) : RHICmdList; ExecutePassPrologue(RHICmdListPass, Pass); #if RDG_GPU_SCOPES const bool bUsePassEventScope = Pass != EpiloguePass && Pass != ProloguePass; if (bUsePassEventScope) { GPUScopeStacks.BeginExecutePass(Pass); } #endif Pass->Execute(RHICmdListPass); #if RDG_GPU_SCOPES if (bUsePassEventScope) { GPUScopeStacks.EndExecutePass(Pass); } #endif ExecutePassEpilogue(RHICmdListPass, Pass); if (Pass->bAsyncComputeEnd) { if (Pass->bAsyncComputeEndExecute) { IF_RDG_GPU_SCOPES(GPUScopeStacks.AsyncCompute.EndExecute()); } FRHIAsyncComputeCommandListImmediate::ImmediateDispatch(RHICmdListAsyncCompute); } if (GRDGDebugFlushGPU && !GRDGAsyncCompute) { RHICmdList.SubmitCommandsAndFlushGPU(); RHICmdList.BlockUntilGPUIdle(); } } void FRDGBuilder::CollectPassResources(FRDGPassHandle PassHandle) { FRDGPass* Pass = Passes[PassHandle]; const ERDGPassFlags PassFlags = Pass->GetFlags(); for (FRDGPass* PassToBegin : Pass->ResourcesToBegin) { const FRDGParameterStruct PassParameters = PassToBegin->GetParameters(); EnumerateTextureParameters(PassParameters, [&](auto Resource) { BeginResourceRHI(PassHandle, Resource); }); EnumerateBufferParameters(PassParameters, [&](auto Resource) { BeginResourceRHI(PassHandle, Resource); }); // Uniform buffer creation requires access to RHI resources through the validator. IF_RDG_ENABLE_DEBUG(FRDGUserValidation::SetAllowRHIAccess(PassToBegin, true)); { PassParameters.EnumerateUniformBuffers([&](FRDGUniformBufferBinding UniformBuffer) { BeginResourceRHI(UniformBuffer.GetUniformBuffer()); }); } IF_RDG_ENABLE_DEBUG(FRDGUserValidation::SetAllowRHIAccess(PassToBegin, false)); } for (FRDGPass* PassToEnd : Pass->ResourcesToEnd) { for (const auto& TexturePair : PassToEnd->TextureStates) { EndResourceRHI(PassHandle, TexturePair.Key, TexturePair.Value.ReferenceCount); } for (const auto& BufferPair : PassToEnd->BufferStates) { EndResourceRHI(PassHandle, BufferPair.Key, BufferPair.Value.ReferenceCount); } } } void FRDGBuilder::CollectPassBarriers(FRDGPassHandle PassHandle, FRDGPassHandle& LastUntrackedPassHandle) { FRDGPass* Pass = Passes[PassHandle]; IF_RDG_ENABLE_DEBUG(ConditionalDebugBreak(RDG_BREAKPOINT_PASS_COMPILE, BuilderName.GetTCHAR(), Pass->GetName())); if (EnumHasAnyFlags(Pass->GetFlags(), ERDGPassFlags::UntrackedAccess)) { LastUntrackedPassHandle = PassHandle; } if (PassesWithEmptyParameters[PassHandle]) { return; } for (const auto& TexturePair : Pass->TextureStates) { FRDGTextureRef Texture = TexturePair.Key; AddTransition(PassHandle, Texture, TexturePair.Value.MergeState, LastUntrackedPassHandle); Texture->bCulled = false; IF_RDG_ENABLE_TRACE(Trace.AddTexturePassDependency(Texture, Pass)); } for (const auto& BufferPair : Pass->BufferStates) { FRDGBufferRef Buffer = BufferPair.Key; AddTransition(PassHandle, Buffer, *BufferPair.Value.MergeState, LastUntrackedPassHandle); Buffer->bCulled = false; IF_RDG_ENABLE_TRACE(Trace.AddBufferPassDependency(Buffer, Pass)); } } void FRDGBuilder::AddEpilogueTransition(FRDGTextureRef Texture, FRDGPassHandle LastUntrackedPassHandle) { if (!Texture->bLastOwner || Texture->bCulled) { return; } const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle(); FRDGSubresourceState ScratchSubresourceState; // A known final state means extraction from the graph (or an external texture). if (Texture->AccessFinal != ERHIAccess::Unknown) { ScratchSubresourceState.SetPass(ERHIPipeline::Graphics, EpiloguePassHandle); ScratchSubresourceState.Access = Texture->AccessFinal; InitAsWholeResource(ScratchTextureState, &ScratchSubresourceState); } // Lifetime is within the graph, but a pass may have left the resource in an async compute state. We cannot // release the pooled texture back to the pool until we transition back to the graphics pipe. else if (Texture->bUsedByAsyncComputePass) { FRDGTextureSubresourceState& TextureState = Texture->GetState(); ScratchTextureState.SetNumUninitialized(TextureState.Num(), false); for (uint32 Index = 0, Count = ScratchTextureState.Num(); Index < Count; ++Index) { FRDGSubresourceState SubresourceState = TextureState[Index]; // Transition async compute back to the graphics pipe. if (SubresourceState.IsUsedBy(ERHIPipeline::AsyncCompute)) { SubresourceState.SetPass(ERHIPipeline::Graphics, EpiloguePassHandle); ScratchTextureState[Index] = AllocSubresource(SubresourceState); } else { ScratchTextureState[Index] = nullptr; } } } // No need to transition; texture stayed on the graphics pipe and its lifetime stayed within the graph. else { return; } AddTransition(EpiloguePassHandle, Texture, ScratchTextureState, LastUntrackedPassHandle); ScratchTextureState.Reset(); } void FRDGBuilder::AddEpilogueTransition(FRDGBufferRef Buffer, FRDGPassHandle LastUntrackedPassHandle) { if (!Buffer->bLastOwner || Buffer->bCulled) { return; } const FRDGPassHandle EpiloguePassHandle = GetEpiloguePassHandle(); ERHIAccess AccessFinal = Buffer->AccessFinal; // Transition async compute back to the graphics pipe. if (AccessFinal == ERHIAccess::Unknown) { const FRDGSubresourceState State = Buffer->GetState(); if (State.IsUsedBy(ERHIPipeline::AsyncCompute)) { AccessFinal = State.Access; } } if (AccessFinal != ERHIAccess::Unknown) { FRDGSubresourceState StateFinal; StateFinal.SetPass(ERHIPipeline::Graphics, EpiloguePassHandle); StateFinal.Access = AccessFinal; AddTransition(EpiloguePassHandle, Buffer, StateFinal, LastUntrackedPassHandle); } } void FRDGBuilder::AddTransition(FRDGPassHandle PassHandle, FRDGTextureRef Texture, const FRDGTextureTransientSubresourceStateIndirect& StateAfter, FRDGPassHandle LastUntrackedPassHandle) { const FRDGTextureSubresourceRange WholeRange = Texture->GetSubresourceRange(); const FRDGTextureSubresourceLayout Layout = Texture->Layout; FRDGTextureSubresourceState& StateBefore = Texture->GetState(); const auto AddSubresourceTransition = [&] ( const FRDGSubresourceState& SubresourceStateBefore, const FRDGSubresourceState& SubresourceStateAfter, FRDGTextureSubresource* Subresource) { check(SubresourceStateAfter.Access != ERHIAccess::Unknown); if (FRDGSubresourceState::IsTransitionRequired(SubresourceStateBefore, SubresourceStateAfter)) { FRHITransitionInfo Info; Info.Texture = Texture->GetRHIUnchecked(); Info.Type = FRHITransitionInfo::EType::Texture; Info.Flags = SubresourceStateAfter.Flags; Info.AccessBefore = SubresourceStateBefore.Access; Info.AccessAfter = SubresourceStateAfter.Access; if (Subresource) { Info.MipIndex = Subresource->MipIndex; Info.ArraySlice = Subresource->ArraySlice; Info.PlaneSlice = Subresource->PlaneSlice; } AddTransitionInternal(Texture, SubresourceStateBefore, SubresourceStateAfter, LastUntrackedPassHandle, Info); } if (Subresource) { IF_RDG_ENABLE_DEBUG(LogFile.AddTransitionEdge(PassHandle, SubresourceStateBefore, SubresourceStateAfter, Texture, *Subresource)); } else { IF_RDG_ENABLE_DEBUG(LogFile.AddTransitionEdge(PassHandle, SubresourceStateBefore, SubresourceStateAfter, Texture)); } }; const auto MergeSubresourceState = [&] (FRDGSubresourceState& SubresourceStateBefore, const FRDGSubresourceState& SubresourceStateAfter) { SubresourceStateBefore = SubresourceStateAfter; for (ERHIPipeline Pipeline : GetRHIPipelines()) { if (SubresourceStateBefore.IsUsedBy(Pipeline)) { SubresourceStateBefore.FirstPass[Pipeline] = PassHandle; } } }; if (IsWholeResource(StateBefore)) { // 1 -> 1 if (IsWholeResource(StateAfter)) { if (const FRDGSubresourceState* SubresourceStateAfter = GetWholeResource(StateAfter)) { FRDGSubresourceState& SubresourceStateBefore = GetWholeResource(StateBefore); AddSubresourceTransition(SubresourceStateBefore, *SubresourceStateAfter, nullptr); MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter); } } // 1 -> N else { const FRDGSubresourceState SubresourceStateBeforeWhole = GetWholeResource(StateBefore); InitAsSubresources(StateBefore, Layout, SubresourceStateBeforeWhole); WholeRange.EnumerateSubresources([&](FRDGTextureSubresource Subresource) { if (FRDGSubresourceState* SubresourceStateAfter = GetSubresource(StateAfter, Layout, Subresource)) { AddSubresourceTransition(SubresourceStateBeforeWhole, *SubresourceStateAfter, &Subresource); FRDGSubresourceState& SubresourceStateBefore = GetSubresource(StateBefore, Layout, Subresource); MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter); } }); } } else { // N -> 1 if (IsWholeResource(StateAfter)) { if (const FRDGSubresourceState* SubresourceStateAfter = GetWholeResource(StateAfter)) { WholeRange.EnumerateSubresources([&](FRDGTextureSubresource Subresource) { AddSubresourceTransition(GetSubresource(StateBefore, Layout, Subresource), *SubresourceStateAfter, &Subresource); }); InitAsWholeResource(StateBefore); FRDGSubresourceState& SubresourceStateBefore = GetWholeResource(StateBefore); MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter); } } // N -> N else { WholeRange.EnumerateSubresources([&](FRDGTextureSubresource Subresource) { if (FRDGSubresourceState* SubresourceStateAfter = GetSubresource(StateAfter, Layout, Subresource)) { FRDGSubresourceState& SubresourceStateBefore = GetSubresource(StateBefore, Layout, Subresource); AddSubresourceTransition(SubresourceStateBefore, *SubresourceStateAfter, &Subresource); MergeSubresourceState(SubresourceStateBefore, *SubresourceStateAfter); } }); } } } void FRDGBuilder::AddTransition(FRDGPassHandle PassHandle, FRDGBufferRef Buffer, FRDGSubresourceState StateAfter, FRDGPassHandle LastUntrackedPassHandle) { check(StateAfter.Access != ERHIAccess::Unknown); FRDGSubresourceState& StateBefore = Buffer->GetState(); if (FRDGSubresourceState::IsTransitionRequired(StateBefore, StateAfter)) { FRHITransitionInfo Info; Info.Resource = Buffer->GetRHIUnchecked(); Info.Type = FRHITransitionInfo::EType::Buffer; Info.Flags = StateAfter.Flags; Info.AccessBefore = StateBefore.Access; Info.AccessAfter = StateAfter.Access; AddTransitionInternal(Buffer, StateBefore, StateAfter, LastUntrackedPassHandle, Info); } IF_RDG_ENABLE_DEBUG(LogFile.AddTransitionEdge(PassHandle, StateBefore, StateAfter, Buffer)); StateBefore = StateAfter; } void FRDGBuilder::AddTransitionInternal( FRDGParentResource* Resource, FRDGSubresourceState StateBefore, FRDGSubresourceState StateAfter, FRDGPassHandle LastUntrackedPassHandle, const FRHITransitionInfo& TransitionInfo) { const ERHIPipeline Graphics = ERHIPipeline::Graphics; const ERHIPipeline AsyncCompute = ERHIPipeline::AsyncCompute; #if RDG_ENABLE_DEBUG StateBefore.Validate(); StateAfter.Validate(); #endif if (GRDGImmediateMode != 0) { FRDGPass* NextPass = Passes[StateAfter.FirstPass[Graphics]]; FRDGBarrierBatchBegin& BarriersToBegin = NextPass->GetPrologueBarriersToBegin(Allocator); BarriersToBegin.AddTransition(Resource, TransitionInfo); NextPass->GetPrologueBarriersToEnd(Allocator).AddDependency(&BarriersToBegin); return; } ERHIPipeline PipelinesBefore = StateBefore.GetPipelines(); ERHIPipeline PipelinesAfter = StateAfter.GetPipelines(); // This may be the first use of the resource in the graph, so we assign the prologue as the previous pass. if (PipelinesBefore == ERHIPipeline::None) { StateBefore.SetPass(Graphics, GetProloguePassHandle()); PipelinesBefore = Graphics; } check(PipelinesBefore != ERHIPipeline::None && PipelinesAfter != ERHIPipeline::None); checkf(StateBefore.GetLastPass() < StateAfter.GetFirstPass(), TEXT("Submitted a state for '%s' that begins before our previous state has ended."), Resource->Name); FRDGPassHandlesByPipeline& PassesBefore = StateBefore.LastPass; FRDGPassHandlesByPipeline& PassesAfter = StateAfter.FirstPass; // Before states may come from previous aliases of the texture. if (Resource->bTransient && StateBefore.GetLastPass() < Resource->AcquirePass) { checkf(PipelinesAfter == ERHIPipeline::Graphics, TEXT("The first use of transient resource %s is not exclusively on the graphics pipe."), Resource->Name); // If this got left in an async compute state, we need to end the transition in the graphics // fork pass, since the lifetime of the previous owner was extended to that point. The cost // should be better absorbed by the fence as well. if (EnumHasAnyFlags(PipelinesBefore, AsyncCompute)) { PassesAfter[Graphics] = Passes[PassesBefore[AsyncCompute]]->GraphicsJoinPass; } // Otherwise, can push the start of the transition forward until our alias is acquired. else { PassesBefore[Graphics] = Resource->AcquirePass; } } // Avoids splitting across a pass that is doing untracked RHI access. if (StateBefore.GetLastPass() < LastUntrackedPassHandle) { // Transitions exclusively on the graphics queue can be pushed forward. if (!EnumHasAnyFlags(PipelinesBefore | PipelinesAfter, AsyncCompute)) { PassesBefore[Graphics] = LastUntrackedPassHandle; } // Async compute transitions have to be split. Emit a warning to the user to avoid touching the resource. else { EmitRDGWarningf( TEXT("Resource '%s' is being split-transitioned across untracked pass '%s'. It was not possible to avoid the ") TEXT("split barrier due to async compute. Accessing this resource in the pass will cause an RHI validation failure. ") TEXT("Otherwise, it's safe to ignore this warning."), Resource->Name, Passes[LastUntrackedPassHandle]->GetName()); } } const auto GetEpilogueBarrierPassHandle = [&](FRDGPassHandle Handle) { return Passes[Handle]->EpilogueBarrierPass; }; const auto GetPrologueBarrierPassHandle = [&](FRDGPassHandle Handle) { return Passes[Handle]->PrologueBarrierPass; }; const auto GetEpilogueBarrierPass = [&](FRDGPassHandle Handle) { return Passes[GetEpilogueBarrierPassHandle(Handle)]; }; const auto GetPrologueBarrierPass = [&](FRDGPassHandle Handle) { return Passes[GetPrologueBarrierPassHandle(Handle)]; }; const uint32 PipelinesBeforeCount = FMath::CountBits(uint64(PipelinesBefore)); const uint32 PipelinesAfterCount = FMath::CountBits(uint64(PipelinesAfter)); // 1-to-1 or 1-to-N pipe transition. if (PipelinesBeforeCount == 1) { const FRDGPassHandle BeginPassHandle = StateBefore.GetLastPass(); const FRDGPassHandle FirstEndPassHandle = StateAfter.GetFirstPass(); FRDGPass* BeginPass = nullptr; FRDGBarrierBatchBegin* BarriersToBegin = nullptr; // We can only split the transition if the before / after passes are not the same. Aliasing or untracked access affect this. if (BeginPassHandle != FirstEndPassHandle) { BeginPass = GetEpilogueBarrierPass(BeginPassHandle); BarriersToBegin = &BeginPass->GetEpilogueBarriersToBeginFor(Allocator, PipelinesAfter); } else { checkf(PipelinesAfter == ERHIPipeline::Graphics, TEXT("Attempted to queue a non-graphics pipe transition for %s from the same begin / end pass. Pipelines: %s"), Resource->Name, *GetRHIPipelineName(PipelinesAfter)); BeginPass = GetPrologueBarrierPass(BeginPassHandle); BarriersToBegin = &BeginPass->GetPrologueBarriersToBegin(Allocator); } BarriersToBegin->AddTransition(Resource, TransitionInfo); for (ERHIPipeline Pipeline : GetRHIPipelines()) { // If doing a 1-to-N transition and this is the same pipe as the begin, we end it immediately afterwards. // This is because we can't guarantee that the other pipeline won't join back before the end. if (PipelinesBefore == Pipeline && PipelinesAfterCount > 1) { BeginPass->GetEpilogueBarriersToEnd(Allocator).AddDependency(BarriersToBegin); } else if (EnumHasAnyFlags(PipelinesAfter, Pipeline)) { FRDGPass* EndPass = GetPrologueBarrierPass(PassesAfter[Pipeline]); EndPass->GetPrologueBarriersToEnd(Allocator).AddDependency(BarriersToBegin); } } } // N-to-1 or N-to-N pipe transition. else { checkf(StateBefore.GetLastPass() != StateAfter.GetFirstPass(), TEXT("Attempted to queue a transition for resource '%s' from '%s' to '%s', but previous and next passes are the same on one pipe."), Resource->Name, *GetRHIPipelineName(PipelinesBefore), *GetRHIPipelineName(PipelinesAfter)); FRDGBarrierBatchBeginId Id; Id.PipelinesAfter = PipelinesAfter; for (ERHIPipeline Pipeline : GetRHIPipelines()) { Id.Passes[Pipeline] = GetEpilogueBarrierPassHandle(PassesBefore[Pipeline]); } FRDGBarrierBatchBegin*& BarriersToBegin = BarrierBatchMap.FindOrAdd(Id); if (!BarriersToBegin) { BarriersToBegin = Allocator.AllocNoDestruct(PipelinesBefore, PipelinesAfter, GetEpilogueBarriersToBeginDebugName(PipelinesAfter), Id.Passes); for (FRDGPassHandle PassHandle : Id.Passes) { Passes[PassHandle]->SharedEpilogueBarriersToBegin.Add(BarriersToBegin); } } BarriersToBegin->AddTransition(Resource, TransitionInfo); for (ERHIPipeline Pipeline : GetRHIPipelines()) { if (EnumHasAnyFlags(PipelinesAfter, Pipeline)) { FRDGPass* EndPass = GetPrologueBarrierPass(PassesAfter[Pipeline]); EndPass->GetPrologueBarriersToEnd(Allocator).AddDependency(BarriersToBegin); } } } } void FRDGBuilder::BeginResourceRHI(FRDGUniformBuffer* UniformBuffer) { check(UniformBuffer); if (UniformBuffer->UniformBufferRHI) { return; } const FRDGParameterStruct PassParameters = UniformBuffer->GetParameters(); const EUniformBufferValidation Validation = #if RDG_ENABLE_DEBUG EUniformBufferValidation::ValidateResources; #else EUniformBufferValidation::None; #endif UniformBuffer->UniformBufferRHI = RHICreateUniformBuffer(PassParameters.GetContents(), PassParameters.GetLayout(), UniformBuffer_SingleFrame, Validation); UniformBuffer->ResourceRHI = UniformBuffer->UniformBufferRHI; } void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGTextureRef Texture) { check(Texture); if (!Texture->FirstPass.IsValid()) { Texture->FirstPass = PassHandle; } if (Texture->PooledTexture) { return; } check(Texture->ReferenceCount > 0 || Texture->bExternal || IsResourceLifetimeExtended()); #if RDG_ENABLE_DEBUG { FRDGPass* Pass = Passes[PassHandle]; if (!Pass->bFirstTextureAllocated) { GRenderTargetPool.AddPhaseEvent(Pass->GetName()); Pass->bFirstTextureAllocated = 1; } } #endif TRefCountPtr PooledRenderTarget = GRenderTargetPool.FindFreeElementForRDG(RHICmdList, Texture->Desc, Texture->Name); FRDGTextureRef PreviousOwner = nullptr; Texture->SetRHI(PooledRenderTarget, PreviousOwner); Texture->bTransient = PooledRenderTarget->IsTransient(); check(!IsResourceLifetimeExtended() || !PreviousOwner); // Acquires are made in the last pass of the previous alias. FRDGPassHandle AcquirePassHandle = PreviousOwner ? PreviousOwner->LastPass : GetProloguePassHandle(); if (Texture->bTransient) { // We will handle the discard behavior ourselves. PooledRenderTarget->bAutoDiscard = false; FRDGPass* AcquirePass = Passes[AcquirePassHandle]; // Discards for the previous owner occur in the same pass as the acquire. if (PreviousOwner) { AcquirePass->TexturesToDiscard.Add(PreviousOwner->GetRHIUnchecked()); } AcquirePass->TexturesToAcquire.Add(Texture->GetRHIUnchecked()); } Texture->AcquirePass = AcquirePassHandle; } void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGTextureSRVRef SRV) { check(SRV); if (SRV->ResourceRHI) { return; } FRDGTextureRef Texture = SRV->Desc.Texture; FRDGPooledTexture* PooledTexture = Texture->PooledTexture; checkf(PooledTexture, TEXT("Pass parameters contained an SRV of RDG Texture %s, before that texture was referenced as a UAV or RTV, which isn't supported. Make sure to call ClearUnusedGraphResources to remove unbound parameters which can also cause this."), Texture->Name); if (!Texture->FirstPass.IsValid()) { Texture->FirstPass = PassHandle; } if (SRV->Desc.MetaData == ERDGTextureMetaDataAccess::HTile) { check(GRHISupportsExplicitHTile); if (!PooledTexture->HTileSRV) { PooledTexture->HTileSRV = RHICreateShaderResourceViewHTile((FRHITexture2D*)PooledTexture->Texture); } SRV->ResourceRHI = PooledTexture->HTileSRV; check(SRV->ResourceRHI); return; } if (SRV->Desc.MetaData == ERDGTextureMetaDataAccess::FMask) { if (!PooledTexture->FMaskSRV) { PooledTexture->FMaskSRV = RHICreateShaderResourceViewFMask((FRHITexture2D*)PooledTexture->Texture); } SRV->ResourceRHI = PooledTexture->FMaskSRV; check(SRV->ResourceRHI); return; } if (SRV->Desc.MetaData == ERDGTextureMetaDataAccess::CMask) { if (!PooledTexture->CMaskSRV) { PooledTexture->CMaskSRV = RHICreateShaderResourceViewWriteMask((FRHITexture2D*)PooledTexture->Texture); } SRV->ResourceRHI = PooledTexture->CMaskSRV; check(SRV->ResourceRHI); return; } for (const auto& SRVPair : PooledTexture->SRVs) { if (SRVPair.Key == SRV->Desc) { SRV->ResourceRHI = SRVPair.Value; return; } } FShaderResourceViewRHIRef RHIShaderResourceView = RHICreateShaderResourceView(PooledTexture->Texture, SRV->Desc); SRV->ResourceRHI = RHIShaderResourceView; PooledTexture->SRVs.Emplace(SRV->Desc, MoveTemp(RHIShaderResourceView)); } void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGTextureUAVRef UAV) { check(UAV); if (UAV->ResourceRHI) { return; } BeginResourceRHI(PassHandle, UAV->Desc.Texture); FRDGTextureRef Texture = UAV->Desc.Texture; FRDGPooledTexture* PooledTexture = Texture->PooledTexture; check(PooledTexture); if (UAV->Desc.MetaData == ERDGTextureMetaDataAccess::HTile) { check(GRHISupportsExplicitHTile); if (!PooledTexture->HTileUAV) { PooledTexture->HTileUAV = RHICreateUnorderedAccessViewHTile((FRHITexture2D*)PooledTexture->Texture); } UAV->ResourceRHI = PooledTexture->HTileUAV; check(UAV->ResourceRHI); return; } if (UAV->Desc.MetaData == ERDGTextureMetaDataAccess::Stencil) { if (!PooledTexture->StencilUAV) { PooledTexture->StencilUAV = RHICreateUnorderedAccessViewStencil((FRHITexture2D*)PooledTexture->Texture, 0); } UAV->ResourceRHI = PooledTexture->StencilUAV; check(UAV->ResourceRHI); return; } UAV->ResourceRHI = PooledTexture->MipUAVs[UAV->Desc.MipLevel]; } void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGBufferRef Buffer) { check(Buffer); if (!Buffer->FirstPass.IsValid()) { Buffer->FirstPass = PassHandle; } if (Buffer->PooledBuffer) { return; } check(Buffer->ReferenceCount > 0 || Buffer->bExternal || IsResourceLifetimeExtended()); TRefCountPtr PooledBuffer = GRenderGraphResourcePool.FindFreeBufferInternal(RHICmdList, Buffer->Desc, Buffer->Name); FRDGBufferRef PreviousOwner = nullptr; Buffer->SetRHI(PooledBuffer, PreviousOwner); Buffer->FirstPass = PassHandle; check(!IsResourceLifetimeExtended() || !PreviousOwner); // Acquires are made in the last pass of the previous alias. Buffer->AcquirePass = PreviousOwner ? PreviousOwner->LastPass : GetProloguePassHandle(); // Transient buffer are not yet implemented. } void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGBufferSRVRef SRV) { check(SRV); if (SRV->ResourceRHI) { return; } FRDGBufferRef Buffer = SRV->Desc.Buffer; checkf(Buffer->PooledBuffer, TEXT("Pass parameters contained an SRV of RDG buffer %s, before that buffer was referenced as a UAV, which isn't supported. Make sure to call ClearUnusedGraphResources to remove unbound parameters which can also cause this."), Buffer->Name); if (!Buffer->FirstPass.IsValid()) { Buffer->FirstPass = PassHandle; } SRV->ResourceRHI = Buffer->PooledBuffer->GetOrCreateSRV(SRV->Desc); } void FRDGBuilder::BeginResourceRHI(FRDGPassHandle PassHandle, FRDGBufferUAV* UAV) { check(UAV); if (UAV->ResourceRHI) { return; } FRDGBufferRef Buffer = UAV->Desc.Buffer; BeginResourceRHI(PassHandle, Buffer); UAV->ResourceRHI = Buffer->PooledBuffer->GetOrCreateUAV(UAV->Desc); } void FRDGBuilder::EndResourceRHI(FRDGPassHandle PassHandle, FRDGTextureRef Texture, uint32 ReferenceCount) { check(Texture); if (!IsResourceLifetimeExtended()) { check(Texture->ReferenceCount >= ReferenceCount); Texture->ReferenceCount -= ReferenceCount; if (Texture->ReferenceCount == 0) { // External textures should never release the reference. if (!Texture->bExternal) { Texture->Allocation = nullptr; } Texture->LastPass = PassHandle; } } } void FRDGBuilder::EndResourceRHI(FRDGPassHandle PassHandle, FRDGBufferRef Buffer, uint32 ReferenceCount) { check(Buffer); if (!IsResourceLifetimeExtended()) { check(Buffer->ReferenceCount >= ReferenceCount); Buffer->ReferenceCount -= ReferenceCount; if (Buffer->ReferenceCount == 0) { // External buffers should never release the reference. if (!Buffer->bExternal) { Buffer->Allocation = nullptr; } Buffer->LastPass = PassHandle; } } } #if RDG_ENABLE_DEBUG void FRDGBuilder::VisualizePassOutputs(const FRDGPass* Pass) { #if SUPPORTS_VISUALIZE_TEXTURE if (bInDebugPassScope) { return; } Pass->GetParameters().EnumerateTextures([&](FRDGParameter Parameter) { switch (Parameter.GetType()) { case UBMT_RDG_TEXTURE_ACCESS: { if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess()) { if (TextureAccess.GetAccess() == ERHIAccess::UAVCompute || TextureAccess.GetAccess() == ERHIAccess::UAVGraphics || TextureAccess.GetAccess() == ERHIAccess::RTV) { if (TOptional CaptureId = GVisualizeTexture.ShouldCapture(TextureAccess->Name, /* MipIndex = */ 0)) { GVisualizeTexture.CreateContentCapturePass(*this, TextureAccess.GetTexture(), *CaptureId); } } } } break; case UBMT_RDG_TEXTURE_UAV: { if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV()) { FRDGTextureRef Texture = UAV->Desc.Texture; if (TOptional CaptureId = GVisualizeTexture.ShouldCapture(Texture->Name, UAV->Desc.MipLevel)) { GVisualizeTexture.CreateContentCapturePass(*this, Texture, *CaptureId); } } } break; case UBMT_RENDER_TARGET_BINDING_SLOTS: { const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots(); RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget) { FRDGTextureRef Texture = RenderTarget.GetTexture(); if (TOptional CaptureId = GVisualizeTexture.ShouldCapture(Texture->Name, RenderTarget.GetMipIndex())) { GVisualizeTexture.CreateContentCapturePass(*this, Texture, *CaptureId); } }); const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil; if (FRDGTextureRef Texture = DepthStencil.GetTexture()) { const bool bHasStoreAction = DepthStencil.GetDepthStencilAccess().IsAnyWrite(); if (bHasStoreAction) { const uint32 MipIndex = 0; if (TOptional CaptureId = GVisualizeTexture.ShouldCapture(Texture->Name, MipIndex)) { GVisualizeTexture.CreateContentCapturePass(*this, Texture, *CaptureId); } } } } break; } }); #endif } void FRDGBuilder::ClobberPassOutputs(const FRDGPass* Pass) { if (!GRDGClobberResources) { return; } if (bInDebugPassScope) { return; } bInDebugPassScope = true; RDG_EVENT_SCOPE(*this, "RDG ClobberResources"); const FLinearColor ClobberColor = GetClobberColor(); Pass->GetParameters().Enumerate([&](FRDGParameter Parameter) { switch (Parameter.GetType()) { case UBMT_RDG_BUFFER_UAV: { if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV()) { FRDGBufferRef Buffer = UAV->GetParent(); if (UserValidation.TryMarkForClobber(Buffer)) { AddClearUAVPass(*this, UAV, GetClobberBufferValue()); } } } break; case UBMT_RDG_TEXTURE_ACCESS: { if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess()) { FRDGTextureRef Texture = TextureAccess.GetTexture(); if (UserValidation.TryMarkForClobber(Texture)) { if (EnumHasAnyFlags(TextureAccess.GetAccess(), ERHIAccess::UAVMask)) { for (int32 MipLevel = 0; MipLevel < Texture->Desc.NumMips; MipLevel++) { AddClearUAVPass(*this, this->CreateUAV(FRDGTextureUAVDesc(Texture, MipLevel)), ClobberColor); } } else if (EnumHasAnyFlags(TextureAccess.GetAccess(), ERHIAccess::RTV)) { AddClearRenderTargetPass(*this, Texture, ClobberColor); } } } } break; case UBMT_RDG_TEXTURE_UAV: { if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV()) { FRDGTextureRef Texture = UAV->GetParent(); if (UserValidation.TryMarkForClobber(Texture)) { if (Texture->Desc.NumMips == 1) { AddClearUAVPass(*this, UAV, ClobberColor); } else { for (int32 MipLevel = 0; MipLevel < Texture->Desc.NumMips; MipLevel++) { AddClearUAVPass(*this, this->CreateUAV(FRDGTextureUAVDesc(Texture, MipLevel)), ClobberColor); } } } } } break; case UBMT_RENDER_TARGET_BINDING_SLOTS: { const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots(); RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget) { FRDGTextureRef Texture = RenderTarget.GetTexture(); if (UserValidation.TryMarkForClobber(Texture)) { AddClearRenderTargetPass(*this, Texture, ClobberColor); } }); if (FRDGTextureRef Texture = RenderTargets.DepthStencil.GetTexture()) { if (UserValidation.TryMarkForClobber(Texture)) { AddClearDepthStencilPass(*this, Texture, true, GetClobberDepth(), true, GetClobberStencil()); } } } break; } }); bInDebugPassScope = false; } #endif //! RDG_ENABLE_DEBUG