// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/StaticArray.h" #include "Containers/UnrealString.h" #include "DynamicRenderScaling.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Misc/AssertionMacros.h" #include "MultiGPU.h" #include "ProfilingDebugging/CsvProfiler.h" #include "ProfilingDebugging/CsvProfilerConfig.h" #include "ProfilingDebugging/CpuProfilerTrace.h" #include "ProfilingDebugging/RealtimeGPUProfiler.h" #include "RHI.h" #include "RHICommandList.h" #include "RenderGraphAllocator.h" #include "RenderGraphDefinitions.h" #include "RendererInterface.h" #include "Stats/Stats.h" #include "Templates/UnrealTemplate.h" #include "UObject/NameTypes.h" ////////////////////////////////////////////////////////////////////////// // // GPU Events - Named hierarchical events emitted to external profiling tools. // ////////////////////////////////////////////////////////////////////////// class FRDGScopeState; /** Stores a GPU event name for the render graph. Draw events can be compiled out entirely from * a release build for performance. */ class FRDGEventName final { public: FRDGEventName() = default; // Constructors require a string that matches the RDG builder lifetime, as copies are not made in all configurations. RENDERCORE_API explicit FRDGEventName(const TCHAR* EventFormat, ...); RENDERCORE_API FRDGEventName(int32 NonVariadic, const TCHAR* EventName); FRDGEventName(const FRDGEventName& Other) = default; FRDGEventName& operator=(const FRDGEventName& Other) = default; RENDERCORE_API const TCHAR* GetTCHAR() const; bool HasFormattedString() const { #if RDG_EVENTS == RDG_EVENTS_STRING_COPY return !FormattedEventName.IsEmpty(); #else return false; #endif } private: #if RDG_EVENTS >= RDG_EVENTS_STRING_REF // Event format kept around to still have a clue what error might be causing the problem in error messages. const TCHAR* EventFormat = TEXT(""); #endif #if RDG_EVENTS == RDG_EVENTS_STRING_COPY FString FormattedEventName; #endif }; enum class ERDGScopeFlags : uint8 { None = 0, // Disables any nested scopes of the same type. Final = 1 << 0, // Ensures the scope is always emitted (ignores cvars that disable scopes) AlwaysEnable = 1 << 1, // The scope includes a GPU stat, so may need to be enabled even when cvars are disabling scopes. Stat = 1 << 2, }; ENUM_CLASS_FLAGS(ERDGScopeFlags); #if HAS_GPU_STATS && (RHI_NEW_GPU_PROFILER == 0) // Scope type for the legacy "realtime" GPU profiler and draw call counter stats struct FRDGScope_GPU { FRealtimeGPUProfilerQuery StartQuery; FRealtimeGPUProfilerQuery StopQuery; FName StatName; TStatId StatId; FString StatDescription; TOptional PreviousCategory {}; FRHIDrawStatsCategory const* CurrentCategory = nullptr; bool bEmitDuringExecute; inline FRDGScope_GPU(FRDGScopeState& State, FRHIGPUMask GPUMask, const FName& CsvStatName, const TStatId& Stat, const TCHAR* Description, FRHIDrawStatsCategory const& Category); inline ~FRDGScope_GPU(); inline void ImmediateEnd(FRDGScopeState& State); inline void BeginCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope); inline void EndCPU (FRHIComputeCommandList& RHICmdList, bool bPreScope); inline void BeginGPU(FRHIComputeCommandList& RHICmdList); inline void EndGPU (FRHIComputeCommandList& RHICmdList); }; #endif // HAS_GPU_STATS && (RHI_NEW_GPU_PROFILER == 0) #if CSV_PROFILER_STATS struct FRDGScope_CSVExclusive { const char* const StatName; FRDGScope_CSVExclusive(FRDGScopeState&, const char* StatName) : StatName(StatName) { FCsvProfiler::BeginExclusiveStat(StatName); } void ImmediateEnd(FRDGScopeState&) { FCsvProfiler::EndExclusiveStat(StatName); } void BeginCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope) { FCsvProfiler::BeginExclusiveStat(StatName); } void EndCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope) { FCsvProfiler::EndExclusiveStat(StatName); } void BeginGPU(FRHIComputeCommandList& RHICmdList) { } void EndGPU(FRHIComputeCommandList& RHICmdList) { } }; #endif // CSV_PROFILER_STATS struct FRDGScope_Budget { class FRDGTimingFrame* Frame = nullptr; int32 ScopeId; bool bPop; RENDERCORE_API FRDGScope_Budget(FRDGScopeState& State, DynamicRenderScaling::FBudget const& Budget); RENDERCORE_API void ImmediateEnd(FRDGScopeState& State); void BeginCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope) { } void EndCPU (FRHIComputeCommandList& RHICmdList, bool bPreScope) { } RENDERCORE_API void BeginGPU(FRHIComputeCommandList& RHICmdList); RENDERCORE_API void EndGPU (FRHIComputeCommandList& RHICmdList); }; #if RDG_EVENTS // Scope type for inserting named events on the CPU and GPU timelines. class FRDGScope_RHI { FRHIBreadcrumbNode* Node = nullptr; inline FRDGScope_RHI(FRDGScopeState& State, FRHIBreadcrumbNode* Node); public: template inline FRDGScope_RHI(FRDGScopeState& State, TRHIBreadcrumbInitializer&& Args); inline void ImmediateEnd(FRDGScopeState& State); void BeginCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope) { if (Node) { RHICmdList.BeginBreadcrumbCPU(Node, !bPreScope); if (!bPreScope) { RHICmdList.BeginBreadcrumbGPU(Node, RHICmdList.GetPipeline()); } } } void EndCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope) { if (Node) { if (!bPreScope) { RHICmdList.EndBreadcrumbGPU(Node, RHICmdList.GetPipeline()); } RHICmdList.EndBreadcrumbCPU(Node, !bPreScope); } } // Nothing to do for Begin/EndGPU. The RHI API only requires breadcrumbs to be // begun/ended once, and will automatically fixup other pipelines whenever we switch. void BeginGPU(FRHIComputeCommandList& RHICmdList) {} void EndGPU (FRHIComputeCommandList& RHICmdList) {} TCHAR const* GetTCHAR(FRHIBreadcrumb::FBuffer& Buffer) const { return Node->GetTCHAR(Buffer); } }; #endif // RDG_EVENTS // // Main RDG scope class. // // A tree of these scopes is created by the render thread as the RenderGraph is built. // Each scope type implementation uses the following functions, which are called during different RDG phases: // // Constructor / ImmediateEnd() - Render thread timeline. Called once, either side of scoped graph building work. // // BeginCPU / EndCPU - Parallel threads. Called during RDG pass lambdas execution. Scopes may be // entered / exited multiple times depending on parallel pass set bucketing. // // BeginGPU / EndGPU - Parallel threads. Called once for each GPU pipeline the scope covers. // Used for inserting commands on the RHICmdList. The command list passed to // Begin / End may be different in each, depending on parallel pass set bucketing. // struct FRDGScope { FRDGScope* const Parent; FRDGPass* CPUFirstPass = nullptr; FRDGPass* CPULastPass = nullptr; TRHIPipelineArray GPUFirstPass { InPlace, nullptr }; TRHIPipelineArray GPULastPass { InPlace, nullptr }; template class TStorage { typedef TVariant TImpl; TImpl Impl; public: template void Dispatch(TCallback&& Callback) { size_t Index = Impl.GetIndex(); check(Index > 0); ((Index == Impl.template IndexOfType() ? Callback(Impl.template Get()),0 : 0), ...); } template void Emplace(TArgs&&... Args) { Impl.template Emplace(Forward(Args)...); } template static constexpr SIZE_T GetTypeIndex() { return TImpl::template IndexOfType(); } template TScopeType* Get() { return Impl.GetIndex() == Impl.template IndexOfType() ? &Impl.template Get() : nullptr; } template TScopeType const* Get() const { return const_cast(*this).Get(); } }; typedef TStorage< FRDGScope_Budget #if RDG_EVENTS , FRDGScope_RHI #endif #if HAS_GPU_STATS && (RHI_NEW_GPU_PROFILER == 0) , FRDGScope_GPU #endif #if CSV_PROFILER_STATS , FRDGScope_CSVExclusive #endif > FStorage; FStorage Impl; template static constexpr uint32 GetTypeMask() { return 1u << FStorage::GetTypeIndex(); } #if RDG_ENABLE_TRACE bool bVisited = false; #endif FRDGScope(FRDGScope* Parent) : Parent(Parent) {} void ImmediateEnd(FRDGScopeState& State) { Impl.Dispatch([&](auto& Scope) { Scope.ImmediateEnd(State); }); } void BeginCPU(FRHIComputeCommandList& RHICmdList, bool bPreScope) { Impl.Dispatch([&](auto& Scope) { Scope.BeginCPU(RHICmdList, bPreScope); }); } void BeginGPU(FRHIComputeCommandList& RHICmdList ) { Impl.Dispatch([&](auto& Scope) { Scope.BeginGPU(RHICmdList ); }); } void EndCPU (FRHIComputeCommandList& RHICmdList, bool bPreScope) { Impl.Dispatch([&](auto& Scope) { Scope.EndCPU (RHICmdList, bPreScope); }); } void EndGPU (FRHIComputeCommandList& RHICmdList ) { Impl.Dispatch([&](auto& Scope) { Scope.EndGPU (RHICmdList ); }); } template TScopeType * Get() { return Impl.Get(); } template TScopeType const* Get() const { return Impl.Get(); } FString GetFullPath(FRDGEventName const& PassName); }; template class TRDGEventScopeGuard { FRDGScopeState& State; FRDGScope* const Scope; TRDGEventScopeGuard(TRDGEventScopeGuard const&) = delete; TRDGEventScopeGuard(TRDGEventScopeGuard&&) = delete; public: template inline TRDGEventScopeGuard(FRDGScopeState& State, ERDGScopeFlags Flags, TArgs&&... Args); inline ~TRDGEventScopeGuard(); private: static constexpr uint32 TypeMask = 1u << FRDGScope::FStorage::GetTypeIndex(); }; /** Macros for create render graph event names and scopes. * * FRDGEventName Name = RDG_EVENT_NAME("MyPass %sx%s", ViewRect.Width(), ViewRect.Height()); * * RDG_EVENT_SCOPE(GraphBuilder, "MyProcessing %sx%s", ViewRect.Width(), ViewRect.Height()); */ #if RDG_EVENTS namespace UE::RHI::Breadcrumbs::Private { template <> struct TValue { static constexpr bool bValidType = true; TCHAR const* StringPointer; TCHAR StringStorage[FRHIBreadcrumb::MaxLength]; TValue(FRDGEventName const& Value) { if (Value.HasFormattedString()) { // We must take a string copy as RHI breadcrumb TValues must be trivially destructable. FCString::Strncpy(StringStorage, Value.GetTCHAR(), UE_ARRAY_COUNT(StringStorage)); StringPointer = StringStorage; } else { StringPointer = Value.GetTCHAR(); } } struct FConvert { TCHAR const* Inner; FConvert(TValue const& Value) : Inner(Value.StringPointer) {} }; void Serialize(UE::RHI::GPUProfiler::FMetadataSerializer& Serializer) const { Serializer.AppendValue(StringPointer); } }; // Type to force the selection of the below TValue specialization. struct FRDGFormatTag {}; template struct TValue> { static constexpr bool bValidType = true; TCHAR Value[FRHIBreadcrumb::MaxLength]; TValue(std::tuple const& Wrapper) { std::apply([this](FRDGFormatTag&, auto&& FormatString, auto&&... Values) { FCString::Snprintf(Value, UE_ARRAY_COUNT(Value), FormatString, std::forward(Values)...); }, Wrapper); } struct FConvert { TCHAR const* Inner; FConvert(TValue const& Value) : Inner(Value.Value) {} }; void Serialize(UE::RHI::GPUProfiler::FMetadataSerializer& Serializer) const { Serializer.AppendValue(Value); } }; } // Returns an RHI breadcrumb initializer appropriate for the given varargs. #define RDG_BREADCRUMB_DESC_FORWARD_VALUES(StaticName, FormatString, GPUStatArgs) \ [&](UE::RHI::Breadcrumbs::Private::FRDGFormatTag&& Tag, auto&&... Values) -> auto \ { \ using namespace UE::RHI::Breadcrumbs::Private; \ if constexpr (std::is_same_v, FRDGEventName>) \ { \ /* Single FRDGEventName */ \ static_assert(sizeof...(Values) == 0, "Unexpected additional arguments"); \ return RHI_BREADCRUMB_DESC_FORWARD_VALUES(StaticName, FForceNoSprintf(), GPUStatArgs)( \ std::forward(FormatString) \ ); \ } \ else if constexpr (TIsValidArgs::Value) \ { \ /* Varargs are compatible with RHI breadcrumbs. Forward the args directly. */ \ return RHI_BREADCRUMB_DESC_FORWARD_VALUES(StaticName, FormatString, GPUStatArgs)( \ std::forward(Values)... \ ); \ } \ else \ { \ /* Args are not compatible with RHI breadcrumbs. Do the snprintf now */ \ return RHI_BREADCRUMB_DESC_COPY_VALUES(StaticName, FForceNoSprintf(), GPUStatArgs)( \ std::forward_as_tuple( \ Tag \ , FormatString \ , std::forward(Values)... \ ) \ ); \ } \ } #define RDG_EVENT_SCOPE_CONSTRUCT(ObjectName, GraphBuilder, Condition, ScopeFlags, GPUStatArgs, StaticName, FormatString, ...) \ do \ { \ if ((Condition) && ((GraphBuilder).ShouldAllocScope((ObjectName), ScopeFlags))) \ { \ (ObjectName).Emplace( \ (GraphBuilder) \ , ScopeFlags \ , RDG_BREADCRUMB_DESC_FORWARD_VALUES( \ StaticName \ , FormatString \ , GPUStatArgs \ )(UE::RHI::Breadcrumbs::Private::FRDGFormatTag(), ##__VA_ARGS__) \ ); \ } \ } while (false) #define RDG_EVENT_SCOPE_IMPL(GraphBuilder, Condition, ScopeFlags, GPUStatArgs, StaticName, FormatString, ...) \ TOptional> PREPROCESSOR_JOIN(__RDG_ScopeRef_, __LINE__); \ RDG_EVENT_SCOPE_CONSTRUCT( \ PREPROCESSOR_JOIN(__RDG_ScopeRef_, __LINE__) \ , GraphBuilder \ , Condition \ , ScopeFlags \ , GPUStatArgs \ , StaticName \ , FormatString \ , ##__VA_ARGS__ \ ) #if WITH_RHI_BREADCRUMBS_FULL #if RDG_EVENTS >= RDG_EVENTS_STRING_COPY #define RDG_SCOPE_ARGS(Format, ...) TEXT(Format), ##__VA_ARGS__ #else #define RDG_SCOPE_ARGS(Format, ...) nullptr #endif // Skip expensive string formatting for the relatively common case of no varargs. We detect this by stringizing the varargs and checking if the string is non-empty (more than just a null terminator). #define RDG_EVENT_NAME(Format, ...) (sizeof(#__VA_ARGS__ "") > 1 ? FRDGEventName(TEXT(Format), ##__VA_ARGS__) : FRDGEventName(1, TEXT(Format))) #define RDG_EVENT_SCOPE( GraphBuilder, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, true, ERDGScopeFlags::None , RHI_GPU_STAT_ARGS_NONE , TEXT(Format) , RDG_SCOPE_ARGS(Format, ##__VA_ARGS__)) #define RDG_EVENT_SCOPE_STAT( GraphBuilder, StatName, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, true, ERDGScopeFlags::Stat , RHI_GPU_STAT_ARGS(StatName), TEXT(Format) , RDG_SCOPE_ARGS(Format, ##__VA_ARGS__)) #define RDG_EVENT_SCOPE_CONDITIONAL( GraphBuilder, Condition, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, Condition, ERDGScopeFlags::None , RHI_GPU_STAT_ARGS_NONE , TEXT(Format) , RDG_SCOPE_ARGS(Format, ##__VA_ARGS__)) #define RDG_EVENT_SCOPE_CONDITIONAL_STAT(GraphBuilder, Condition, StatName, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, Condition, ERDGScopeFlags::Stat , RHI_GPU_STAT_ARGS(StatName), TEXT(Format) , RDG_SCOPE_ARGS(Format, ##__VA_ARGS__)) // The 'Final' version disables any further child scopes or pass events. It is intended to group overlapping passes as events can disable overlap on certain GPUs. #define RDG_EVENT_SCOPE_FINAL( GraphBuilder, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, true, ERDGScopeFlags::Final, RHI_GPU_STAT_ARGS_NONE , TEXT(Format) , RDG_SCOPE_ARGS(Format, ##__VA_ARGS__)) // Used in places which have an existing FRDGEventName, e.g. RDG pass name scopes. Prefer to use the other RDG scope macros instead. #define RDG_EVENT_SCOPE_CONDITIONAL_NAME(GraphBuilder, Condition, EventName ) RDG_EVENT_SCOPE_IMPL(GraphBuilder, Condition, ERDGScopeFlags::None , RHI_GPU_STAT_ARGS_NONE , TEXT("RDGEvent"), EventName) #else // WITH_RHI_BREADCRUMBS_MINIMAL // // Keep only the STAT RDG scopes enabled in MINIMAL mode. // Also disable the varargs. We don't capture the format strings and varargs in MINIMAL mode // #define RDG_EVENT_NAME(Format, ...) FRDGEventName() #define RDG_EVENT_SCOPE_STAT( GraphBuilder, StatName, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, true, ERDGScopeFlags::Stat , RHI_GPU_STAT_ARGS(StatName), TEXT(Format), nullptr) #define RDG_EVENT_SCOPE_CONDITIONAL_STAT(GraphBuilder, Condition, StatName, Format, ...) RDG_EVENT_SCOPE_IMPL(GraphBuilder, Condition, ERDGScopeFlags::Stat , RHI_GPU_STAT_ARGS(StatName), TEXT(Format), nullptr) #define RDG_EVENT_SCOPE( GraphBuilder, Format, ...) do { } while (false) #define RDG_EVENT_SCOPE_CONDITIONAL( GraphBuilder, Condition, Format, ...) do { } while (false) #define RDG_EVENT_SCOPE_FINAL( GraphBuilder, Format, ...) do { } while (false) #define RDG_EVENT_SCOPE_CONDITIONAL_NAME(GraphBuilder, Condition, EventName ) do { } while (false) #endif #else #define RDG_EVENT_NAME(...) FRDGEventName() #define RDG_EVENT_SCOPE(...) do { } while (false) #define RDG_EVENT_SCOPE_STAT(...) do { } while (false) #define RDG_EVENT_SCOPE_CONDITIONAL(...) do { } while (false) #define RDG_EVENT_SCOPE_CONDITIONAL_STAT(...) do { } while (false) #define RDG_EVENT_SCOPE_FINAL(...) do { } while (false) #define RDG_EVENT_SCOPE_CONDITIONAL_NAME(...) do { } while (false) #endif #if HAS_GPU_STATS && (RHI_NEW_GPU_PROFILER == 0) #define RDG_GPU_STAT_SCOPE(GraphBuilder, StatName) TRDGEventScopeGuard PREPROCESSOR_JOIN(__RDG_GPUStatEvent_##StatName,__LINE__) ((GraphBuilder), ERDGScopeFlags::AlwaysEnable, (GraphBuilder).RHICmdList.GetGPUMask(), CSV_STAT_FNAME(StatName), GET_STATID(Stat_GPU_##StatName), nullptr , DrawcallCountCategory_##StatName); #define RDG_GPU_STAT_SCOPE_VERBOSE(GraphBuilder, StatName, Description) TRDGEventScopeGuard PREPROCESSOR_JOIN(__RDG_GPUStatEvent_##StatName,__LINE__) ((GraphBuilder), ERDGScopeFlags::AlwaysEnable, (GraphBuilder).RHICmdList.GetGPUMask(), CSV_STAT_FNAME(StatName), GET_STATID(Stat_GPU_##StatName), Description, DrawcallCountCategory_##StatName); #else #define RDG_GPU_STAT_SCOPE(GraphBuilder, StatName) #define RDG_GPU_STAT_SCOPE_VERBOSE(GraphBuilder, StatName, Description) #endif #if CSV_PROFILER_STATS #define RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, StatName) TRDGEventScopeGuard PREPROCESSOR_JOIN(__RDG_CSVStat_##StatName,__LINE__) ((GraphBuilder), ERDGScopeFlags::AlwaysEnable, #StatName); #else #define RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, StatName) #endif /** Injects a scope onto both the RDG and RHI timeline. */ #define RDG_RHI_EVENT_SCOPE( GraphBuilder, Name) RDG_EVENT_SCOPE(GraphBuilder, #Name); RHI_BREADCRUMB_EVENT(GraphBuilder.RHICmdList, #Name) #define RDG_RHI_EVENT_SCOPE_STAT(GraphBuilder, Stat, Name) RDG_EVENT_SCOPE_STAT(GraphBuilder, Stat, #Name); RHI_BREADCRUMB_EVENT_STAT(GraphBuilder.RHICmdList, Stat, #Name) #define RDG_RHI_GPU_STAT_SCOPE( GraphBuilder, StatName) RDG_GPU_STAT_SCOPE(GraphBuilder, StatName); SCOPED_GPU_STAT(GraphBuilder.RHICmdList, StatName); namespace DynamicRenderScaling { class FRDGScope final : public TRDGEventScopeGuard { public: FRDGScope(FRDGScopeState& State, FBudget const& Budget) : TRDGEventScopeGuard(State, ERDGScopeFlags::AlwaysEnable, Budget) {} }; } // namespace DynamicRenderScaling enum class ERDGScopeMode : uint8 { Disabled = 0, TopLevelOnly = 1, AllEvents = 2, AllEventsAndPassNames = 3 }; class FRDGScopeState { protected: struct FState { struct FRDGScope* Current = nullptr; DynamicRenderScaling::FBudget const* ActiveBudget = nullptr; uint32 Mask = 0; bool const bImmediate; bool const bParallelExecute; #if RDG_EVENTS == RDG_EVENTS_NONE static constexpr ERDGScopeMode const ScopeMode = ERDGScopeMode::Disabled; #else ERDGScopeMode const ScopeMode; #endif FState(bool bInImmediate, bool bInParallelExecute); } ScopeState; struct { // Allocator for all root graph allocations on the graph builder thread. FRDGAllocator Root; // Allocator for async pass and parallel execute setup. FRDGAllocator Task; // Allocator for all allocations related to states / transitions. FRDGAllocator Transition; int32 GetByteCount() const { return Root.GetByteCount() + Task.GetByteCount() + Transition.GetByteCount(); } } Allocators; public: /** The RHI command list used for the render graph. */ FRHICommandListImmediate& RHICmdList; #if WITH_RHI_BREADCRUMBS protected: FRHIBreadcrumbNode* LocalCurrentBreadcrumb = FRHIBreadcrumbNode::Sentinel; FRHIBreadcrumbList LocalBreadcrumbList {}; TSharedPtr LocalBreadcrumbAllocator; public: FRHIBreadcrumbNode*& CurrentBreadcrumbRef; FRHIBreadcrumbAllocator& GetBreadcrumbAllocator() { if (ScopeState.bImmediate) { return RHICmdList.GetBreadcrumbAllocator(); } else { if (!LocalBreadcrumbAllocator.IsValid()) { LocalBreadcrumbAllocator = MakeShared(); } return *LocalBreadcrumbAllocator; } } #endif // WITH_RHI_BREADCRUMBS public: FRDGScopeState(FRHICommandListImmediate& InRHICmdList, bool bImmediate, bool bParallelExecute) : ScopeState(bImmediate, bParallelExecute) , RHICmdList(InRHICmdList) #if WITH_RHI_BREADCRUMBS , CurrentBreadcrumbRef(bImmediate ? InRHICmdList.GetCurrentBreadcrumbRef() : LocalCurrentBreadcrumb) #endif {} bool ShouldEmitEvents() const { return ScopeState.ScopeMode != ERDGScopeMode::Disabled; } template inline bool ShouldAllocScope(TOptional> const&, ERDGScopeFlags Flags) const; template friend class TRDGEventScopeGuard; friend FRDGScope_Budget; #if HAS_GPU_STATS && (RHI_NEW_GPU_PROFILER == 0) friend FRDGScope_GPU; #endif #if RDG_EVENTS friend FRDGScope_RHI; #endif friend DynamicRenderScaling::FRDGScope; }; #include "RenderGraphEvent.inl" // IWYU pragma: export