// Copyright Epic Games, Inc. All Rights Reserved. #include "RenderGraphPass.h" #include "RenderGraphPrivate.h" #include "RenderGraphBuilder.h" FEmptyShaderParameters FRDGSentinelPass::EmptyShaderParameters; FUniformBufferStaticBindings FRDGParameterStruct::GetStaticUniformBuffers() const { FUniformBufferStaticBindings GlobalUniformBuffers; for (uint32 Index = 0, Count = Layout->UniformBuffers.Num(); Index < Count; ++Index) { const uint32 MemberOffset = Layout->UniformBuffers[Index].MemberOffset; const FUniformBufferBinding& UniformBuffer = *reinterpret_cast(const_cast(Contents + MemberOffset)); if (UniformBuffer && UniformBuffer.IsStatic()) { GlobalUniformBuffers.AddUniformBuffer(UniformBuffer.GetUniformBuffer()); } } EnumerateUniformBuffers([&](FRDGUniformBufferBinding UniformBuffer) { if (UniformBuffer.IsStatic()) { GlobalUniformBuffers.AddUniformBuffer(UniformBuffer->GetRHI()); } }); return GlobalUniformBuffers; } FRHIRenderPassInfo FRDGParameterStruct::GetRenderPassInfo() const { const FRenderTargetBindingSlots& RenderTargets = GetRenderTargets(); FRHIRenderPassInfo RenderPassInfo; uint32 SampleCount = 0; uint32 RenderTargetIndex = 0; RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget) { FRDGTextureRef Texture = RenderTarget.GetTexture(); FRDGTextureRef ResolveTexture = RenderTarget.GetResolveTexture(); ERenderTargetStoreAction StoreAction = EnumHasAnyFlags(Texture->Desc.Flags, TexCreate_Memoryless) ? ERenderTargetStoreAction::ENoAction : ERenderTargetStoreAction::EStore; if (ResolveTexture) { // Silently skip the resolve if the resolve texture is the same as the render target texture. if (ResolveTexture != Texture) { StoreAction = ERenderTargetStoreAction::EMultisampleResolve; } else { ResolveTexture = nullptr; } } auto& ColorRenderTarget = RenderPassInfo.ColorRenderTargets[RenderTargetIndex]; ColorRenderTarget.RenderTarget = Texture->GetRHI(); ColorRenderTarget.ResolveTarget = ResolveTexture ? ResolveTexture->GetRHI() : nullptr; ColorRenderTarget.ArraySlice = RenderTarget.GetArraySlice(); ColorRenderTarget.MipIndex = RenderTarget.GetMipIndex(); ColorRenderTarget.Action = MakeRenderTargetActions(RenderTarget.GetLoadAction(), StoreAction); SampleCount |= ColorRenderTarget.RenderTarget->GetNumSamples(); ++RenderTargetIndex; }); const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil; if (FRDGTextureRef Texture = DepthStencil.GetTexture()) { const FExclusiveDepthStencil ExclusiveDepthStencil = DepthStencil.GetDepthStencilAccess(); ERenderTargetStoreAction StoreAction = EnumHasAnyFlags(Texture->Desc.Flags, TexCreate_Memoryless) ? ERenderTargetStoreAction::ENoAction : ERenderTargetStoreAction::EStore; FRDGTextureRef ResolveTexture = DepthStencil.GetResolveTexture(); if (ResolveTexture) { // Silently skip the resolve if the resolve texture is the same as the render target texture. if (ResolveTexture != Texture) { StoreAction = ERenderTargetStoreAction::EMultisampleResolve; } else { ResolveTexture = nullptr; } } const ERenderTargetStoreAction DepthStoreAction = ExclusiveDepthStencil.IsUsingDepth() ? StoreAction : ERenderTargetStoreAction::ENoAction; const ERenderTargetStoreAction StencilStoreAction = ExclusiveDepthStencil.IsUsingStencil() ? StoreAction : ERenderTargetStoreAction::ENoAction; auto& DepthStencilTarget = RenderPassInfo.DepthStencilRenderTarget; DepthStencilTarget.DepthStencilTarget = Texture->GetRHI(); DepthStencilTarget.ResolveTarget = ResolveTexture ? ResolveTexture->GetRHI() : nullptr; DepthStencilTarget.Action = MakeDepthStencilTargetActions( MakeRenderTargetActions(DepthStencil.GetDepthLoadAction(), DepthStoreAction), MakeRenderTargetActions(DepthStencil.GetStencilLoadAction(), StencilStoreAction)); DepthStencilTarget.ExclusiveDepthStencil = ExclusiveDepthStencil; SampleCount |= DepthStencilTarget.DepthStencilTarget->GetNumSamples(); } RenderPassInfo.ResolveRect = RenderTargets.ResolveRect; RenderPassInfo.NumOcclusionQueries = RenderTargets.NumOcclusionQueries; RenderPassInfo.SubpassHint = RenderTargets.SubpassHint; RenderPassInfo.MultiViewCount = RenderTargets.MultiViewCount; RenderPassInfo.ShadingRateTexture = RenderTargets.ShadingRateTexture ? RenderTargets.ShadingRateTexture->GetRHI() : nullptr; // @todo: should define this as a state that gets passed through? Max seems appropriate for now. RenderPassInfo.ShadingRateTextureCombiner = RenderPassInfo.ShadingRateTexture.IsValid() ? VRSRB_Max : VRSRB_Passthrough; return RenderPassInfo; } FRHICommandList* FRDGDispatchPassBuilder::CreateCommandList() { FRHICommandList* RHICmdList = nullptr; if (RenderPassInfo && Pass->bParallelExecute && GRHISupportsParallelRenderPasses) { FRHISubCommandList* SubCmdList = new FRHISubCommandList(Pass->GetGPUMask(), RenderPassInfo); SubCmdList->SwitchPipeline(Pass->GetPipeline()); SubCommandLists.Emplace(SubCmdList); RHICmdList = SubCmdList; } else { RHICmdList = new FRHICommandList(Pass->GetGPUMask()); RHICmdList->SwitchPipeline(Pass->GetPipeline()); // When parallel executing, the pass commands are embedded directly into the first command list. if (Pass->bParallelExecute && Pass->CommandLists.IsEmpty()) { FRDGBuilder::PushPreScopes(*RHICmdList, Pass); FRDGBuilder::ExecutePassPrologue(*RHICmdList, Pass); } if (RenderPassInfo) { RHICmdList->BeginRenderPass(*RenderPassInfo, TEXT("DispatchPass")); } Pass->CommandLists.Emplace(RHICmdList); } RHICmdList->SetStaticUniformBuffers(StaticUniformBuffers); return RHICmdList; } void FRDGDispatchPassBuilder::Finish() { // With serial execution the pass commands are embedded in the immediate command list instead. if (!Pass->bParallelExecute) { Pass->CommandListsEvent.Trigger(); return; } FRHICommandList* RHICmdList = nullptr; if(SubCommandLists.Num()) { RHICmdList = new FRHICommandList(Pass->GetGPUMask()); RHICmdList->SwitchPipeline(Pass->GetPipeline()); FRDGBuilder::PushPreScopes(*RHICmdList, Pass); FRDGBuilder::ExecutePassPrologue(*RHICmdList, Pass); RHICmdList->InsertParallelRenderPass(RenderPassInfo, MoveTemp(SubCommandLists)); Pass->CommandLists.Emplace(RHICmdList); Pass->CommandListsEvent.Trigger(); } else { const bool bEmptyCommandLists = Pass->CommandLists.IsEmpty(); // Create a command list to embed the epilogue (and prologue as well if no user command lists were requested). RHICmdList = new FRHICommandList(Pass->GetGPUMask()); Pass->CommandLists.Emplace(RHICmdList); Pass->CommandListsEvent.Trigger(); RHICmdList->SwitchPipeline(Pass->GetPipeline()); if (bEmptyCommandLists) { FRDGBuilder::PushPreScopes(*RHICmdList, Pass); FRDGBuilder::ExecutePassPrologue(*RHICmdList, Pass); } } FRDGBuilder::ExecutePassEpilogue(*RHICmdList, Pass); FRDGBuilder::PopPreScopes(*RHICmdList, Pass); RHICmdList->FinishRecording(); } FRDGBarrierBatchBegin::FRDGBarrierBatchBegin(ERHIPipeline InPipelineToBegin, ERHIPipeline InPipelinesToEnd, const TCHAR* InName, FRDGPass* InPass) : PipelinesToBegin(InPipelineToBegin) , PipelinesToEnd(InPipelinesToEnd) #if RDG_ENABLE_DEBUG , DebugPasses(InPlace, nullptr) , DebugName(InName) #endif { #if RDG_ENABLE_DEBUG DebugPasses[InPipelineToBegin] = InPass; #endif } FRDGBarrierBatchBegin::FRDGBarrierBatchBegin(ERHIPipeline InPipelinesToBegin, ERHIPipeline InPipelinesToEnd, const TCHAR* InName, FRDGPassesByPipeline InPasses) : PipelinesToBegin(InPipelinesToBegin) , PipelinesToEnd(InPipelinesToEnd) #if RDG_ENABLE_DEBUG , DebugPasses(InPasses) , DebugName(InName) #endif {} void FRDGBarrierBatchBegin::AddTransition(FRDGViewableResource* Resource, FRDGTransitionInfo Info) { Transitions.Add(Info); bTransitionNeeded = true; #if RDG_STATS GRDGStatTransitionCount++; #endif #if RDG_ENABLE_DEBUG DebugTransitionResources.Add(Resource); #endif } void FRDGBarrierBatchBegin::AddAlias(FRDGViewableResource* Resource, const FRHITransientAliasingInfo& Info) { Aliases.Add(Info); bTransitionNeeded = true; #if RDG_STATS GRDGStatAliasingCount++; #endif #if RDG_ENABLE_DEBUG DebugAliasingResources.Add(Resource); #endif } void FRDGBarrierBatchBegin::CreateTransition(TConstArrayView TransitionsRHI) { check(bTransitionNeeded && !Transition); Transition = RHICreateTransition(FRHITransitionCreateInfo(PipelinesToBegin, PipelinesToEnd, TransitionFlags, TransitionsRHI, Aliases)); if (bSeparateFenceTransitionNeeded) { SeparateFenceTransition = RHICreateTransition(FRHITransitionCreateInfo(PipelinesToBegin, PipelinesToEnd)); } } void FRDGBarrierBatchBegin::Submit(FRHIComputeCommandList& RHICmdList, ERHIPipeline Pipeline, FRDGTransitionQueue& TransitionsToBegin) { if (SeparateFenceTransition) { TransitionsToBegin.Emplace(SeparateFenceTransition); } if (Transition) { TransitionsToBegin.Emplace(Transition); } #if RDG_STATS GRDGStatTransitionBatchCount++; #endif } FRDGBarrierBatchEndId FRDGBarrierBatchEnd::GetId() const { return FRDGBarrierBatchEndId(Pass->GetHandle(), BarrierLocation); } bool FRDGBarrierBatchEnd::IsPairedWith(const FRDGBarrierBatchBegin& BeginBatch) const { return GetId() == BeginBatch.BarriersToEnd[Pass->GetPipeline()]; } void FRDGBarrierBatchBegin::Submit(FRHIComputeCommandList& RHICmdList, ERHIPipeline Pipeline) { FRDGTransitionQueue TransitionsToBegin; Submit(RHICmdList, Pipeline, TransitionsToBegin); if (!TransitionsToBegin.IsEmpty()) { RHICmdList.BeginTransitions(TransitionsToBegin); } } void FRDGBarrierBatchEnd::AddDependency(FRDGBarrierBatchBegin* BeginBatch) { #if RDG_ENABLE_DEBUG check(BeginBatch); for (ERHIPipeline Pipeline : MakeFlagsRange(ERHIPipeline::All)) { const FRDGPass* BeginPass = BeginBatch->DebugPasses[Pipeline]; if (BeginPass) { checkf(BeginPass->GetHandle() <= Pass->GetHandle(), TEXT("A transition end batch for pass %s is dependent on begin batch for pass %s."), Pass->GetName(), BeginPass->GetName()); } } #endif { const FRDGBarrierBatchEndId Id = GetId(); FRDGBarrierBatchEndId& EarliestEndId = BeginBatch->BarriersToEnd[Pass->GetPipeline()]; if (EarliestEndId == Id) { return; } const FRDGBarrierBatchEndId MinId( FRDGPassHandle::Min(EarliestEndId.PassHandle, Id.PassHandle), (ERDGBarrierLocation)FMath::Min((int32)EarliestEndId.BarrierLocation, (int32)Id.BarrierLocation)); if (MinId == Id) { Dependencies.Add(BeginBatch); EarliestEndId = MinId; } } } void FRDGBarrierBatchEnd::Submit(FRHIComputeCommandList& RHICmdList, ERHIPipeline Pipeline) { const FRDGBarrierBatchEndId Id(Pass->GetHandle(), BarrierLocation); FRDGTransitionQueue Transitions; Transitions.Reserve(Dependencies.Num()); for (FRDGBarrierBatchBegin* Dependent : Dependencies) { if (Dependent->BarriersToEnd[Pipeline] == Id) { if (Dependent->SeparateFenceTransition) { Transitions.Emplace(Dependent->SeparateFenceTransition); } Transitions.Emplace(Dependent->Transition); } } if (!Transitions.IsEmpty()) { RHICmdList.EndTransitions(Transitions); } } FRDGBarrierBatchBegin& FRDGPass::GetPrologueBarriersToBegin(FRDGAllocator& Allocator, FRDGTransitionCreateQueue& CreateQueue) { if (!PrologueBarriersToBegin) { PrologueBarriersToBegin = Allocator.AllocNoDestruct(Pipeline, Pipeline, TEXT("Prologue"), this); CreateQueue.Emplace(PrologueBarriersToBegin); } return *PrologueBarriersToBegin; } FRDGBarrierBatchBegin& FRDGPass::GetEpilogueBarriersToBeginForGraphics(FRDGAllocator& Allocator, FRDGTransitionCreateQueue& CreateQueue) { if (!EpilogueBarriersToBeginForGraphics) { EpilogueBarriersToBeginForGraphics = Allocator.AllocNoDestruct(Pipeline, ERHIPipeline::Graphics, GetEpilogueBarriersToBeginDebugName(ERHIPipeline::Graphics), this); if (!EpilogueBarriersToBeginForGraphics->IsTransitionNeeded()) { EpilogueBarriersToBeginForGraphics->Reserve(NumTransitionsToReserve); CreateQueue.Emplace(EpilogueBarriersToBeginForGraphics); } } return *EpilogueBarriersToBeginForGraphics; } FRDGBarrierBatchBegin& FRDGPass::GetEpilogueBarriersToBeginForAsyncCompute(FRDGAllocator& Allocator, FRDGTransitionCreateQueue& CreateQueue) { if (!EpilogueBarriersToBeginForAsyncCompute) { EpilogueBarriersToBeginForAsyncCompute = Allocator.AllocNoDestruct(Pipeline, ERHIPipeline::AsyncCompute, GetEpilogueBarriersToBeginDebugName(ERHIPipeline::AsyncCompute), this); CreateQueue.Emplace(EpilogueBarriersToBeginForAsyncCompute); } return *EpilogueBarriersToBeginForAsyncCompute; } FRDGBarrierBatchBegin& FRDGPass::GetEpilogueBarriersToBeginForAll(FRDGAllocator& Allocator, FRDGTransitionCreateQueue& CreateQueue) { if (!EpilogueBarriersToBeginForAll) { EpilogueBarriersToBeginForAll = Allocator.AllocNoDestruct(Pipeline, ERHIPipeline::All, GetEpilogueBarriersToBeginDebugName(ERHIPipeline::All), this); CreateQueue.Emplace(EpilogueBarriersToBeginForAll); } return *EpilogueBarriersToBeginForAll; } FRDGBarrierBatchEnd& FRDGPass::GetPrologueBarriersToEnd(FRDGAllocator& Allocator) { if (!PrologueBarriersToEnd) { PrologueBarriersToEnd = Allocator.AllocNoDestruct(this, ERDGBarrierLocation::Prologue); } return *PrologueBarriersToEnd; } FRDGBarrierBatchEnd& FRDGPass::GetEpilogueBarriersToEnd(FRDGAllocator& Allocator) { if (!EpilogueBarriersToEnd) { EpilogueBarriersToEnd = Allocator.AllocNoDestruct(this, ERDGBarrierLocation::Epilogue); } return *EpilogueBarriersToEnd; } FRDGPass::FRDGPass( FRDGEventName&& InName, FRDGParameterStruct InParameterStruct, ERDGPassFlags InFlags, ERDGPassTaskMode InTaskMode) : Name(Forward(InName)) , ParameterStruct(InParameterStruct) , Flags(InFlags) , TaskMode(InTaskMode) , Pipeline(EnumHasAnyFlags(Flags, ERDGPassFlags::AsyncCompute) ? ERHIPipeline::AsyncCompute : ERHIPipeline::Graphics) {} #if RDG_ENABLE_DEBUG const TCHAR* FRDGPass::GetName() const { // When in debug runtime mode, use the full path name. if (!FullPathIfDebug.IsEmpty()) { return *FullPathIfDebug; } else { return Name.GetTCHAR(); } } #endif