Files
UnrealEngine/Engine/Source/Runtime/RenderCore/Public/RenderingThread.h
2025-05-18 13:04:45 +08:00

1385 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
RenderingThread.h: Rendering thread definitions.
=============================================================================*/
#pragma once
#include "Async/TaskGraphInterfaces.h"
#include "Containers/Array.h"
#include "Containers/List.h"
#include "CoreGlobals.h"
#include "CoreMinimal.h"
#include "CoreTypes.h"
#include "Delegates/Delegate.h"
#include "HAL/PlatformMemory.h"
#include "Misc/AssertionMacros.h"
#include "Misc/TVariant.h"
#include "MultiGPU.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "RHI.h"
#include "RHICommandList.h"
#include "Serialization/MemoryLayout.h"
#include "Stats/Stats.h"
#include "Templates/Atomic.h"
#include "Templates/Function.h"
#include "Templates/UnrealTemplate.h"
#include "Templates/UnrealTypeTraits.h"
#include "Trace/Trace.h"
#include "Async/Mutex.h"
#include "Tasks/Pipe.h"
#define UE_API RENDERCORE_API
namespace UE { namespace Trace { class FChannel; } }
enum class EParallelForFlags;
////////////////////////////////////
// Render thread API
////////////////////////////////////
namespace FFrameEndSync
{
enum class EFlushMode
{
// Blocks the caller until the N - m frame has completed, where m is driven by various config.
EndFrame,
// Blocks the caller until all rendering work is completed on the CPU. Does not sync with the GPU.
Threads
};
// Syncs the game thread based on progress throughout the rendering pipeline.
RENDERCORE_API void Sync(EFlushMode FlushMode);
};
/**
* Whether the renderer is currently running in a separate thread.
* If this is false, then all rendering commands will be executed immediately instead of being enqueued in the rendering command buffer.
*/
extern RENDERCORE_API bool GIsThreadedRendering;
/**
* Whether the rendering thread should be created or not.
* Currently set by command line parameter and by the ToggleRenderingThread console command.
*/
extern RENDERCORE_API bool GUseThreadedRendering;
// Global for handling the "togglerenderthread" command.
extern RENDERCORE_API TOptional<bool> GPendingUseThreadedRendering;
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
FORCEINLINE void CheckNotBlockedOnRenderThread() {}
#else // #if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
/** Whether the main thread is currently blocked on the rendering thread, e.g. a call to FlushRenderingCommands. */
extern RENDERCORE_API TAtomic<bool> GMainThreadBlockedOnRenderThread;
/** Asserts if called from the main thread when the main thread is blocked on the rendering thread. */
FORCEINLINE void CheckNotBlockedOnRenderThread() { ensure(!GMainThreadBlockedOnRenderThread.Load(EMemoryOrder::Relaxed) || !IsInGameThread()); }
#endif // #if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Called during engine init to setup the rendering thread.
extern RENDERCORE_API void InitRenderingThread();
// Called during engine shutdown to stop the rendering thread.
extern RENDERCORE_API void ShutdownRenderingThread();
// Called once per frame by the game thread to latch the latest render thread config.
extern RENDERCORE_API void LatchRenderThreadConfiguration();
/**
* Checks if the rendering thread is healthy and running.
* If it has crashed, UE_LOG is called with the exception information.
*/
extern RENDERCORE_API void CheckRenderingThreadHealth();
/** Checks if the rendering thread is healthy and running, without crashing */
extern RENDERCORE_API bool IsRenderingThreadHealthy();
/**
* Advances stats for the rendering thread. Called from the game thread.
*/
extern RENDERCORE_API void AdvanceRenderingThreadStatsGT( bool bDiscardCallstack, int64 StatsFrame, int32 DisableChangeTagStartFrame );
/**
* Waits for the rendering thread to finish executing all pending rendering commands. Should only be used from the game thread.
*/
extern RENDERCORE_API void FlushRenderingCommands();
UE_DEPRECATED(5.6, "FlushPendingDeleteRHIResources_GameThread is deprecated. Enqueue a render command that calls ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources) on the immediate RHI command list instead.")
extern RENDERCORE_API void FlushPendingDeleteRHIResources_GameThread();
UE_DEPRECATED(5.6, "FlushPendingDeleteRHIResources_RenderThread is deprecated. Call ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources) on the immediate RHI command list instead.")
extern RENDERCORE_API void FlushPendingDeleteRHIResources_RenderThread();
extern RENDERCORE_API void TickRenderingTickables();
extern RENDERCORE_API void StartRenderCommandFenceBundler();
extern RENDERCORE_API void StopRenderCommandFenceBundler();
class FCoreRenderDelegates
{
public:
DECLARE_MULTICAST_DELEGATE(FOnFlushRenderingCommandsStart);
static RENDERCORE_API FOnFlushRenderingCommandsStart OnFlushRenderingCommandsStart;
DECLARE_MULTICAST_DELEGATE(FOnFlushRenderingCommandsEnd);
static RENDERCORE_API FOnFlushRenderingCommandsEnd OnFlushRenderingCommandsEnd;
};
////////////////////////////////////
// Render commands
////////////////////////////////////
UE_TRACE_CHANNEL_EXTERN(RenderCommandsChannel, RENDERCORE_API);
//
// Macros for using render commands.
//
// ideally this would be inline, however that changes the module dependency situation
extern RENDERCORE_API class FRHICommandListImmediate& GetImmediateCommandList_ForRenderCommand();
DECLARE_STATS_GROUP(TEXT("Render Thread Commands"), STATGROUP_RenderThreadCommands, STATCAT_Advanced);
// Log render commands on server for debugging
#if 0 // UE_SERVER && UE_BUILD_DEBUG
#define LogRenderCommand(TypeName) UE_LOG(LogRHI, Warning, TEXT("Render command '%s' is being executed on a dedicated server."), TEXT(#TypeName))
#else
#define LogRenderCommand(TypeName)
#endif
// conditions when rendering commands are executed in the thread
#if UE_SERVER
#define ShouldExecuteOnRenderThread() false
#else
#define ShouldExecuteOnRenderThread() (LIKELY(GIsThreadedRendering || !IsInGameThread()))
#endif // UE_SERVER
class FRenderCommandTag
{
public:
const TCHAR* GetName() const
{
return Name;
}
TStatId GetStatId() const
{
return StatId;
}
uint32& GetSpecId() const
{
return SpecId;
}
protected:
FRenderCommandTag(const TCHAR* InName, TStatId InStatId)
: Name(InName)
, StatId(InStatId)
{}
private:
const TCHAR* Name;
TStatId StatId;
mutable uint32 SpecId;
};
/** Type that contains profiler data necessary to mark up render commands for various profilers. */
template <typename TSTR>
class TRenderCommandTag : public FRenderCommandTag
{
public:
static const TRenderCommandTag& Get()
{
#if STATS
struct FStatData
{
typedef FStatGroup_STATGROUP_RenderThreadCommands TGroup;
static FORCEINLINE const char* GetStatName()
{
return TSTR::CStr();
}
static FORCEINLINE const TCHAR* GetDescription()
{
return TSTR::TStr();
}
static FORCEINLINE EStatDataType::Type GetStatType()
{
return EStatDataType::ST_int64;
}
static FORCEINLINE bool IsClearEveryFrame()
{
return true;
}
static FORCEINLINE bool IsCycleStat()
{
return true;
}
static FORCEINLINE FPlatformMemory::EMemoryCounterRegion GetMemoryRegion()
{
return FPlatformMemory::MCR_Invalid;
}
};
static FThreadSafeStaticStat<FStatData> Stat;
static TRenderCommandTag Tag(TSTR::TStr(), Stat.GetStatId());
#else
static TRenderCommandTag Tag(TSTR::TStr(), TStatId());
#endif
return Tag;
}
private:
TRenderCommandTag(const TCHAR* InName, TStatId InStatId)
: FRenderCommandTag(InName, InStatId)
{}
};
/** Declares a new render command tag type from a name. */
#define DECLARE_RENDER_COMMAND_TAG(Type, Name) \
struct PREPROCESSOR_JOIN(TSTR_, PREPROCESSOR_JOIN(Name, __LINE__)) \
{ \
static const char* CStr() { return #Name; } \
static const TCHAR* TStr() { return TEXT(#Name); } \
}; \
using Type = TRenderCommandTag<PREPROCESSOR_JOIN(TSTR_, PREPROCESSOR_JOIN(Name, __LINE__))>;
/** Describes which pipes are configured to use the render command pipe system. */
enum class ERenderCommandPipeMode : uint8
{
/** Bypasses the render command pipe system altogether. Render commands are issued using tasks. */
None,
/** The render command pipe on the render thread pipe is active, and all other pipes forward to the render thread pipe. */
RenderThread,
/** All render command pipes are active. */
All
};
enum class ERenderCommandPipeFlags : uint8
{
None = 0,
/** Initializes the render command pipe in a disabled state. */
Disabled = 1 << 0
};
ENUM_CLASS_FLAGS(ERenderCommandPipeFlags);
class FRenderCommandPipe;
using FRenderCommandPipeBitArrayAllocator = TInlineAllocator<1, FConcurrentLinearArrayAllocator>;
using FRenderCommandPipeBitArray = TBitArray<FRenderCommandPipeBitArrayAllocator>;
using FRenderCommandPipeSetBitIterator = TConstSetBitIterator<FRenderCommandPipeBitArrayAllocator>;
using FRenderCommandFunctionVariant = TVariant<
TUniqueFunction<void()>
, TUniqueFunction<void(FRHICommandList&)>
, TUniqueFunction<void(FRHICommandListImmediate&)>
>;
namespace UE::RenderCommandPipe
{
// [Game Thread] Initializes all statically initialized render command pipes.
extern RENDERCORE_API void Initialize();
// [Game Thread (Parallel)] Returns whether any render command pipes are currently recording on the game thread timeline.
extern RENDERCORE_API bool IsRecording();
// [Render Thread (Parallel)] Returns whether any render command pipes are currently replaying commands on the render thread timeline.
extern RENDERCORE_API bool IsReplaying();
// [Render Thread (Parallel)] Returns whether the specific render command pipe is replaying.
extern RENDERCORE_API bool IsReplaying(const FRenderCommandPipe& Pipe);
// [Game Thread] Starts recording render commands into pipes. Returns whether the operation succeeded.
extern RENDERCORE_API void StartRecording();
extern RENDERCORE_API void StartRecording(const FRenderCommandPipeBitArray& PipeBits);
// [Game Thread] Stops recording commands into pipes and syncs all remaining pipe work to the render thread. Returns whether the operation succeeded.
extern RENDERCORE_API FRenderCommandPipeBitArray StopRecording();
extern RENDERCORE_API FRenderCommandPipeBitArray StopRecording(TConstArrayView<FRenderCommandPipe*> Pipes);
// Returns the list of all registered pipes.
extern RENDERCORE_API TConstArrayView<FRenderCommandPipe*> GetPipes();
// A delegate to receive events at sync points when recording is stopped. Input is a bit array of pipes that were stopped.
DECLARE_MULTICAST_DELEGATE_OneParam(FStopRecordingDelegate, const FRenderCommandPipeBitArray&);
extern RENDERCORE_API FStopRecordingDelegate& GetStopRecordingDelegate();
// [Game Thread] Stops render command pipe recording during the duration of the scope and restarts recording once the scope is complete.
class FSyncScope
{
public:
UE_API FSyncScope();
UE_API FSyncScope(TConstArrayView<FRenderCommandPipe*> Pipes);
UE_API ~FSyncScope();
private:
FRenderCommandPipeBitArray PipeBits;
};
// Utility class containing a simple linked list of render commands.
class FCommandList
{
enum class ECommandType : uint8
{
ExecuteFunction,
ExecuteCommandList
};
struct FCommand
{
virtual ~FCommand() = default;
FCommand(ECommandType InType)
: Type(InType)
{}
FCommand* Next = nullptr;
ECommandType Type;
};
struct FExecuteFunctionCommand
: public FCommand
, public UE::FInheritedContextBase
{
FExecuteFunctionCommand(FRenderCommandFunctionVariant&& InFunction, const FRenderCommandTag& InTag)
: FCommand(ECommandType::ExecuteFunction)
, Tag(InTag)
, Function(MoveTemp(InFunction))
{
CaptureInheritedContext();
}
const FRenderCommandTag& Tag;
FRenderCommandFunctionVariant Function;
};
struct FExecuteCommandListCommand : public FCommand
{
FExecuteCommandListCommand(FCommandList* InCommandList)
: FCommand(ECommandType::ExecuteCommandList)
, CommandList(InCommandList)
{}
FCommandList* CommandList;
};
public:
FCommandList(FMemStackBase& InAllocator)
: Allocator(InAllocator)
{}
// Assigns the allocator reference and then moves the command list and its allocator contents into this one.
FCommandList(FMemStackBase& InAllocator, FCommandList& CommandListToConsume)
: Allocator(InAllocator)
{
Allocator = MoveTemp(CommandListToConsume.Allocator);
Commands = CommandListToConsume.Commands;
CommandListToConsume.Commands = {};
#if DO_CHECK
bClosed = CommandListToConsume.bClosed;
CommandListToConsume.bClosed = false;
#endif
}
~FCommandList()
{
Release();
}
template <typename RenderCommandTag, typename FunctionType>
FORCEINLINE bool Enqueue(FunctionType&& Function)
{
return Enqueue<RenderCommandTag>(FRenderCommandFunctionVariant(TInPlaceType<FunctionType>(), MoveTemp(Function)));
}
template <typename RenderCommandTag>
FORCEINLINE bool Enqueue(FRenderCommandFunctionVariant&& Function)
{
return Enqueue(MoveTemp(Function), RenderCommandTag::Get());
}
RENDERCORE_API bool Enqueue(FRenderCommandFunctionVariant&& Function, const FRenderCommandTag& Tag);
RENDERCORE_API bool Enqueue(FCommandList* CommandList);
void Close()
{
#if DO_CHECK
bClosed = true;
#endif
}
template <typename LambdaType>
void ConsumeCommands(const LambdaType& Lambda)
{
#if DO_CHECK
check(bClosed);
#endif
for (FCommand* Command = Commands.Head; Command; Command = Command->Next)
{
if (Command->Type == ECommandType::ExecuteFunction)
{
FExecuteFunctionCommand* FunctionCommand = static_cast<FExecuteFunctionCommand*>(Command);
UE::FInheritedContextScope InheritedContextScope = FunctionCommand->RestoreInheritedContext();
Lambda(MoveTemp(FunctionCommand->Function), FunctionCommand->Tag);
FunctionCommand->~FExecuteFunctionCommand();
}
else
{
check(Command->Type == ECommandType::ExecuteCommandList);
FExecuteCommandListCommand* CommandListCommand = static_cast<FExecuteCommandListCommand*>(Command);
CommandListCommand->CommandList->ConsumeCommands(Lambda);
CommandListCommand->~FExecuteCommandListCommand();
}
}
Commands = {};
#if DO_CHECK
bClosed = false;
#endif
}
FORCEINLINE int32 NumCommands() const
{
return Commands.Num;
}
FORCEINLINE bool IsEmpty() const
{
return !Commands.Head;
}
private:
RENDERCORE_API bool Enqueue(FCommand* Command);
template <typename T, typename... TArgs>
FORCEINLINE T* AllocNoDestruct(TArgs&&... Args)
{
return new (Allocator) T(Forward<TArgs&&>(Args)...);
}
RENDERCORE_API void Release();
FMemStackBase& Allocator;
struct
{
FCommand* Head = nullptr;
FCommand* Tail = nullptr;
int32 Num = 0;
} Commands;
#if DO_CHECK
bool bClosed = false;
#endif
};
}
extern RENDERCORE_API ERenderCommandPipeMode GRenderCommandPipeMode;
class FRenderCommandList;
class FRenderCommandPipeBase
{
public:
~FRenderCommandPipeBase()
{
delete Context;
}
protected:
void ResetContext()
{
// If the context's command list is not empty then a task must have been launched that will consume its contents.
// Replace the context with a new one and mark the old one for deletion. Any new commands issued into the new context
// will issue a new task scheduled after this command list executes.
if (!Context->CommandList.IsEmpty())
{
Context->bDeleteAfterExecute = true;
Context = new FContext();
}
}
struct FContext
{
FContext()
: Allocator(FMemStackBase::EPageSize::Large)
, CommandList(Allocator)
{}
FContext(FContext&& Other)
: CommandList(Allocator, Other.CommandList)
, bDeleteAfterExecute(Other.bDeleteAfterExecute)
{}
FMemStackBase Allocator;
UE::RenderCommandPipe::FCommandList CommandList;
bool bDeleteAfterExecute = false;
};
FContext* Context = new FContext;
UE::FMutex Mutex;
};
class FRenderThreadCommandPipe : public FRenderCommandPipeBase
{
RENDERCORE_API static void ExecuteCommands(FRenderCommandList* CommandList);
RENDERCORE_API static void ExecuteCommands(UE::RenderCommandPipe::FCommandList& CommandList);
class FRenderCommandTaskBase
{
public:
static ENamedThreads::Type GetDesiredThread()
{
check(!GIsThreadedRendering || ENamedThreads::GetRenderThread() != ENamedThreads::GameThread);
return ENamedThreads::GetRenderThread();
}
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::FireAndForget;
}
};
template <typename LambdaType>
class TRenderCommandTask : public FRenderCommandTaskBase
{
public:
TRenderCommandTask(LambdaType&& InLambda, const FRenderCommandTag& InTag)
: Tag(InTag)
, Lambda(Forward<LambdaType>(InLambda))
{}
void DoTask(ENamedThreads::Type, const FGraphEventRef&)
{
TRACE_CPUPROFILER_EVENT_SCOPE_ON_CHANNEL_STR(Tag.GetName(), RenderCommandsChannel);
Lambda(GetImmediateCommandList_ForRenderCommand());
}
FORCEINLINE_DEBUGGABLE TStatId GetStatId() const
{
return Tag.GetStatId();
}
private:
const FRenderCommandTag& Tag;
LambdaType Lambda;
};
class FRenderCommandListTask : public FRenderCommandTaskBase
{
public:
FRenderCommandListTask(FRenderCommandList* InCommandList)
: CommandList(InCommandList)
{}
void DoTask(ENamedThreads::Type, const FGraphEventRef&)
{
FRenderThreadCommandPipe::ExecuteCommands(CommandList);
}
private:
FRenderCommandList* CommandList;
};
public:
template <typename RenderCommandTag, typename LambdaType>
static void Enqueue(LambdaType&& Lambda)
{
const FRenderCommandTag& Tag = RenderCommandTag::Get();
TRACE_CPUPROFILER_EVENT_SCOPE_USE_ON_CHANNEL(Tag.GetSpecId(), Tag.GetName(), EventScope, RenderCommandsChannel, true);
if (!IsInRenderingThread() && ShouldExecuteOnRenderThread())
{
CheckNotBlockedOnRenderThread();
if (GRenderCommandPipeMode != ERenderCommandPipeMode::None)
{
Instance.EnqueueAndLaunch(MoveTemp(Lambda), Tag);
}
else
{
TGraphTask<TRenderCommandTask<LambdaType>>::CreateTask().ConstructAndDispatchWhenReady(MoveTemp(Lambda), Tag);
}
}
else
{
FScopeCycleCounter CycleScope(Tag.GetStatId());
Lambda(GetImmediateCommandList_ForRenderCommand());
}
}
static void Enqueue(FRenderCommandList* CommandList)
{
if (CommandList)
{
if (!IsInRenderingThread() && ShouldExecuteOnRenderThread())
{
CheckNotBlockedOnRenderThread();
if (GRenderCommandPipeMode != ERenderCommandPipeMode::None)
{
Instance.EnqueueAndLaunch(CommandList);
}
else
{
TGraphTask<FRenderCommandListTask>::CreateTask().ConstructAndDispatchWhenReady(CommandList);
}
}
else
{
ExecuteCommands(CommandList);
}
}
}
private:
static RENDERCORE_API FRenderThreadCommandPipe Instance;
RENDERCORE_API void EnqueueAndLaunch(FRenderCommandList* CommandList);
RENDERCORE_API void EnqueueAndLaunch(TUniqueFunction<void(FRHICommandListImmediate&)>&& Function, const FRenderCommandTag& Tag);
RENDERCORE_API void Enqueue(TUniqueFunction<void(FRHICommandListImmediate&)>&& Function, const FRenderCommandTag& Tag);
};
class FRenderCommandPipe : public FRenderCommandPipeBase
{
public:
using FCommandListFunction = TUniqueFunction<void(FRHICommandList&)>;
using FEmptyFunction = TUniqueFunction<void()>;
RENDERCORE_API FRenderCommandPipe(const TCHAR* Name, ERenderCommandPipeFlags Flags, const TCHAR* CVarName, const TCHAR* CVarDescription);
FORCEINLINE const TCHAR* GetName() const
{
return Name;
}
FORCEINLINE bool IsValid() const
{
return Index != INDEX_NONE;
}
FORCEINLINE int32 GetIndex() const
{
check(IsValid());
return Index;
}
FORCEINLINE bool IsReplaying() const
{
ensure(IsInParallelRenderingThread());
return bReplaying;
}
FORCEINLINE bool IsRecording() const
{
return bRecording;
}
FORCEINLINE bool IsEmpty() const
{
return NumInFlightCommands.load(std::memory_order_relaxed) == 0 && NumInFlightCommandLists.load(std::memory_order_relaxed) == 0;
}
void SetEnabled(bool bInIsEnabled)
{
check(IsInGameThread());
bEnabled = bInIsEnabled;
}
//////////////////////////////////////////////////////////////////////////
bool Enqueue(FRenderCommandList* RenderCommandList)
{
if (RenderCommandList)
{
UE::TScopeLock Lock(Mutex);
checkf(!UE::RenderCommandPipe::IsReplaying(*this), TEXT("Enqueuing command queues from the render command pipe replay task is not allowed."));
if (RecordTask.IsValid())
{
EnqueueAndLaunch(RenderCommandList);
return true;
}
}
return false;
}
template <typename RenderCommandTag>
bool Enqueue(FCommandListFunction& Function)
{
// Execute the function directly if this is being called recursively from within another pipe command.
if (UE::RenderCommandPipe::IsReplaying(*this))
{
ExecuteCommand(MoveTemp(Function), RenderCommandTag::Get());
return true;
}
UE::TScopeLock Lock(Mutex);
if (RecordTask.IsValid())
{
EnqueueAndLaunch(MoveTemp(Function), RenderCommandTag::Get());
return true;
}
return false;
}
template <typename RenderCommandTag>
bool Enqueue(FEmptyFunction& Function)
{
// Execute the function directly if this is being called recursively from within another pipe command.
if (UE::RenderCommandPipe::IsReplaying(*this))
{
ExecuteCommand(MoveTemp(Function), RenderCommandTag::Get());
return true;
}
UE::TScopeLock Lock(Mutex);
if (RecordTask.IsValid())
{
EnqueueAndLaunch(MoveTemp(Function), RenderCommandTag::Get());
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
private:
friend class FRenderCommandPipeRegistry;
friend class FRenderCommandDispatcher;
RENDERCORE_API void EnqueueAndLaunch(FRenderCommandList* CommandList);
RENDERCORE_API void EnqueueAndLaunch(FRenderCommandFunctionVariant&& FunctionVariant, const FRenderCommandTag& Tag);
void EnqueueAndLaunch(FCommandListFunction&& Function, const FRenderCommandTag& Tag)
{
EnqueueAndLaunch(FRenderCommandFunctionVariant(TInPlaceType<FCommandListFunction>(), MoveTemp(Function)), Tag);
}
void EnqueueAndLaunch(FEmptyFunction&& Function, const FRenderCommandTag& Tag)
{
EnqueueAndLaunch(FRenderCommandFunctionVariant(TInPlaceType<FEmptyFunction>(), MoveTemp(Function)), Tag);
}
RENDERCORE_API void ExecuteCommand(FRenderCommandFunctionVariant&& FunctionVariant, const FRenderCommandTag& Tag);
void ExecuteCommand(FCommandListFunction&& Function, const FRenderCommandTag& Tag)
{
ExecuteCommand(FRenderCommandFunctionVariant(TInPlaceType<FCommandListFunction>(), MoveTemp(Function)), Tag);
}
void ExecuteCommand(FEmptyFunction&& Function, const FRenderCommandTag& Tag)
{
ExecuteCommand(FRenderCommandFunctionVariant(TInPlaceType<FEmptyFunction>(), MoveTemp(Function)), Tag);
}
void ExecuteCommands(UE::RenderCommandPipe::FCommandList& CommandList);
void ExecuteCommands(FRenderCommandList* CommandList);
const TCHAR* Name;
UE::Tasks::FTask RecordTask;
FRHICommandList* RHICmdList = nullptr;
TLinkedList<FRenderCommandPipe*> GlobalListLink;
FAutoConsoleVariable ConsoleVariable;
std::atomic_int32_t NumInFlightCommands{ 0 };
std::atomic_int32_t NumInFlightCommandLists{ 0 };
int32 Index = INDEX_NONE;
bool bRecording = false;
bool bReplaying = false;
bool bEnabled = true;
};
enum class ERenderCommandListFlags : uint8
{
None,
// Closes the command list on a call to Submit. Enables an optimization to skip submitting empty lists.
CloseOnSubmit = 1 << 0
};
ENUM_CLASS_FLAGS(ERenderCommandListFlags);
/*
Represents a command list of render commands that can be recorded on a thread and submitted. Recording is done using the FRecordScope which
sets the command list in TLS, diverting any render commands enqueued via ENQUEUE_RENDER_COMMAND into the command list. Command lists can
submit into other command lists as well as the main render command pipes. Command lists are useful for a couple reasons. First, the cost
of queuing commands into command lists is very light when recording into command lists as there are no locks, at the cost of deferring
submission of the work. Second, command lists can be submitted and recorded asynchronously from each other.
Command lists actually contain several sub-command lists, one for each render command pipe. At submission time the sub-command lists are
submitted separately. It doesn't matter if commands are enqueued to a pipe, they all go into the same command list.
Command lists have two operations, Close and Submit. Call Close when recording is complete. Call Submit to patch the command list into a parent
command list or the global render command pipes. Use FRenderCommandDispatcher::Submit to submit command lists.
Command lists support a fast-path with ERenderCommandListFlags::CloseOnSubmit. This fuses the Close / Submit operations but enables an optimization
to skip empty lists at the end, which is helpful when managing a large number of command lists (see FParallelForContext for a concrete use case).
*/
class FRenderCommandList final : public TConcurrentLinearObject<FRenderCommandList>
{
friend class FRenderCommandPipe;
friend class FRenderThreadCommandPipe;
friend class FRenderCommandDispatcher;
static thread_local FRenderCommandList* InstanceTLS;
static RENDERCORE_API FRenderCommandList* GetInstanceTLS();
static RENDERCORE_API FRenderCommandList* SetInstanceTLS(FRenderCommandList* CommandList);
public:
using EPageSize = FMemStackBase::EPageSize;
static FRenderCommandList* Create(ERenderCommandListFlags InFlags = ERenderCommandListFlags::None, EPageSize InPageSize = EPageSize::Small)
{
return new FRenderCommandList(InFlags, InPageSize);
}
RENDERCORE_API ~FRenderCommandList();
enum class EStopRecordingAction
{
None,
// Calls Close on the command list when the scope is complete.
Close,
// Calls Close and Submit on the command list when the scope is complete.
Submit
};
// A scope to bind a command list for recording on the current thread.
class FRecordScope
{
public:
RENDERCORE_API ~FRecordScope();
RENDERCORE_API FRecordScope(FRenderCommandList* InCommandList, EStopRecordingAction StopAction = EStopRecordingAction::None);
FRecordScope(const FRecordScope&) = delete;
private:
FRenderCommandList* CommandList;
FRenderCommandList* PreviousCommandList;
EStopRecordingAction StopAction;
};
// A scope to unbind and flush the contents of the currently recording command list if there is one.
class FFlushScope
{
public:
RENDERCORE_API FFlushScope();
RENDERCORE_API ~FFlushScope();
FFlushScope(const FFlushScope&) = delete;
private:
FRenderCommandList* CommandList;
};
// Call when the command list recording is finished.
RENDERCORE_API void Close();
/*
A task context for use with parallel for that allocates a command list for each task thread.
Example Usage:
FRenderCommandList* ParentCommandList = FRenderCommandList::Create();
UE::Tasks::Launch(UE_SOURCE_LOCATION, [ParentCommandList]
{
// Closes recording of the command list on completion of the scope.
FRenderCommandList::FRecordScope RecordScope(ParentCommandList, FRenderCommandList::EStopRecordingAction::Close);
// Constructs a parallel for context with the command list as the root.
FRenderCommandList::FParallelForContext ParallelForContext(ParentCommandList, NumContexts);
// Issue a parallel with a command list per thread.
ParallelForWithExistingTaskContext(TEXT("ParallelFor"), ParallelForContext.GetCommandLists(), ..., [] (FRenderCommandList* InRenderCommandList)
{
FRenderCommandList::FRecordScope RecordScope(InRenderCommandList);
// Commands are recorded into InRenderCommandList.
});
});
ENQUEUE_RENDER_COMMAND(... [] { ... }); // Command A
// Submit the command list at any time. All included render commands are patched between commands A and C.
ParentCommandList->Submit();
ENQUEUE_RENDER_COMMAND(... [] { ... }); // Command C
*/
class FParallelForContext
{
public:
~FParallelForContext()
{
Submit();
}
RENDERCORE_API FParallelForContext(FRenderCommandList* InRootCommandList, int32 NumContexts);
RENDERCORE_API FParallelForContext(FRenderCommandList* InRootCommandList, int32 NumTasks, int32 BatchSize, EParallelForFlags Flags);
FParallelForContext(const FParallelForContext&) = delete;
FRenderCommandList* GetRootCommandList()
{
return RootCommandList;
}
TArrayView<FRenderCommandList*> GetCommandLists()
{
return TaskCommandLists;
}
RENDERCORE_API void Submit();
private:
FRenderCommandList* RootCommandList;
TArray<FRenderCommandList*, FConcurrentLinearArrayAllocator> TaskCommandLists;
bool bSubmitRootCommandList = false;
};
UE::Tasks::FTask GetDispatchTask() const
{
if (DispatchTaskEvent)
{
return *DispatchTaskEvent;
}
return {};
}
private:
RENDERCORE_API FRenderCommandList(ERenderCommandListFlags InFlags, EPageSize InPageSize);
/** Use FRenderCommandDispatcher::Submit to submit command lists. */
RENDERCORE_API void Submit(FRenderCommandList* InParentCommandList = nullptr);
const UE::Tasks::FTask* TryGetDispatchTask() const
{
return DispatchTaskEvent.GetPtrOrNull();
}
int32 ReleasePipeRefs(int32 InNumRefs)
{
int32 NumRefs = NumPipeRefs.fetch_sub(InNumRefs, std::memory_order_acq_rel) - InNumRefs;
check(NumRefs >= 0);
if (!NumRefs)
{
delete this;
}
return NumRefs;
}
int32 ReleasePipeRef()
{
return ReleasePipeRefs(1);
}
bool HasDispatchTask()
{
return DispatchTaskEvent.IsSet();
}
void LazyInit()
{
if (!bInitialized)
{
Init();
}
}
RENDERCORE_API void Init();
RENDERCORE_API void Flush();
template <typename RenderCommandTag, typename FunctionType>
FORCEINLINE bool Enqueue(FunctionType&& Function)
{
return GetRenderThread().Enqueue<RenderCommandTag>(MoveTemp(Function));
}
template <typename RenderCommandTag, typename FunctionType>
FORCEINLINE bool Enqueue(FRenderCommandPipe* Pipe, FunctionType&& Function)
{
return Get(Pipe).Enqueue<RenderCommandTag>(MoveTemp(Function));
}
FORCEINLINE UE::RenderCommandPipe::FCommandList& GetRenderThread()
{
LazyInit();
return CommandLists.Last();
}
FORCEINLINE UE::RenderCommandPipe::FCommandList& Get(int32 PipeIndex)
{
LazyInit();
return CommandLists[PipeIndex];
}
FORCEINLINE UE::RenderCommandPipe::FCommandList& Get(FRenderCommandPipe* Pipe)
{
LazyInit();
return Pipe && Pipe->IsValid() ? CommandLists[Pipe->GetIndex()] : GetRenderThread();
}
FMemStackBase Allocator;
TArray<UE::RenderCommandPipe::FCommandList, FConcurrentLinearArrayAllocator> CommandLists;
TOptional<UE::Tasks::FTaskEvent> DispatchTaskEvent;
std::atomic_int32_t NumPipeRefs = 0;
const ERenderCommandListFlags Flags;
uint8 bInitialized : 1 = false;
uint8 bRecording : 1 = true;
uint8 bSubmitted : 1 = false;
#if DO_CHECK
uint8 NumRecordScopeRefs = 0;
#endif
struct
{
FRenderCommandList* Head = nullptr;
FRenderCommandList* Tail = nullptr;
} Children;
FRenderCommandList* Parent = nullptr;
FRenderCommandList* NextSibling = nullptr;
FRenderCommandPipeBitArray PipeEnqueueFailedBits;
friend class FRenderCommandDispatcher;
};
class FRenderCommandDispatcher
{
public:
/**
* Call to submit a command list into a parent command list or render command pipes. If the parent command list is null the recording instance
* is pulled from the currently bound render command list (set via FRecordScope). If both are null the commands are submitted to the global render
* command pipes.
*/
static void Submit(FRenderCommandList* RenderCommandList, FRenderCommandList* ParentCommandList = nullptr)
{
RenderCommandList->Submit(ParentCommandList);
}
template <typename RenderCommandTag>
static void Enqueue(TUniqueFunction<void(FRHICommandListImmediate&)>&& Function)
{
#if !WITH_STATE_STREAM
if (FRenderCommandList* CommandList = FRenderCommandList::GetInstanceTLS())
{
CommandList->Enqueue<RenderCommandTag>(MoveTemp(Function));
return;
}
#endif
FRenderThreadCommandPipe::Enqueue<RenderCommandTag>(MoveTemp(Function));
}
template <typename RenderCommandTag>
static void Enqueue(FRenderCommandPipe* Pipe, FRenderCommandPipe::FCommandListFunction&& Function)
{
#if !WITH_STATE_STREAM
if (FRenderCommandList* CommandList = FRenderCommandList::GetInstanceTLS())
{
CommandList->Enqueue<RenderCommandTag>(Pipe, MoveTemp(Function));
return;
}
if (GRenderCommandPipeMode == ERenderCommandPipeMode::All && Pipe && Pipe->Enqueue<RenderCommandTag>(Function))
{
return;
}
#endif
FRenderThreadCommandPipe::Enqueue<RenderCommandTag>([Function = MoveTemp(Function)](FRHICommandListImmediate& RHICmdList) { Function(RHICmdList); });
}
template <typename RenderCommandTag>
FORCEINLINE static void Enqueue(FRenderCommandPipe& Pipe, FRenderCommandPipe::FCommandListFunction&& Function)
{
Enqueue<RenderCommandTag>(&Pipe, MoveTemp(Function));
}
template <typename RenderCommandTag>
static void Enqueue(FRenderCommandPipe* Pipe, FRenderCommandPipe::FEmptyFunction&& Function)
{
#if !WITH_STATE_STREAM
if (FRenderCommandList* CommandListSet = FRenderCommandList::GetInstanceTLS())
{
CommandListSet->Enqueue<RenderCommandTag>(Pipe, MoveTemp(Function));
return;
}
if (GRenderCommandPipeMode == ERenderCommandPipeMode::All && Pipe && Pipe->Enqueue<RenderCommandTag>(Function))
{
return;
}
#endif
FRenderThreadCommandPipe::Enqueue<RenderCommandTag>([Function = MoveTemp(Function)](FRHICommandListImmediate&) { Function(); });
}
template <typename RenderCommandTag>
FORCEINLINE static void Enqueue(FRenderCommandPipe& Pipe, FRenderCommandPipe::FEmptyFunction&& Function)
{
Enqueue<RenderCommandTag>(&Pipe, MoveTemp(Function));
}
};
/** Declares an extern reference to a render command pipe. */
#define DECLARE_RENDER_COMMAND_PIPE(Name, PrefixKeywords) \
namespace UE::RenderCommandPipe { extern PrefixKeywords FRenderCommandPipe Name; }
/** Defines a render command pipe. */
#define DEFINE_RENDER_COMMAND_PIPE(Name, Flags) \
namespace UE::RenderCommandPipe \
{ \
FRenderCommandPipe Name( \
TEXT(#Name), \
Flags, \
TEXT("r.RenderCommandPipe." #Name), \
TEXT("Whether to enable the " #Name " Render Command Pipe") \
TEXT(" 0: off;") \
TEXT(" 1: on (default)") \
); \
}
/** Enqueues a render command to a render pipe. The default implementation takes a lambda and schedules on the render thread.
* Alternative implementations accept either a reference or pointer to an FRenderCommandPipe instance to schedule on an async
* pipe, if enabled.
*/
#define ENQUEUE_RENDER_COMMAND(Type) \
DECLARE_RENDER_COMMAND_TAG(PREPROCESSOR_JOIN(FRenderCommandTag_, PREPROCESSOR_JOIN(Type, __LINE__)), Type) \
FRenderCommandDispatcher::Enqueue<PREPROCESSOR_JOIN(FRenderCommandTag_, PREPROCESSOR_JOIN(Type, __LINE__))>
////////////////////////////////////
// RenderThread scoped work
////////////////////////////////////
struct FRenderThreadStructBase
{
FRenderThreadStructBase() = default;
// Copy construction is not allowed. Used to avoid accidental copying in the command lambda.
FRenderThreadStructBase(const FRenderThreadStructBase&) = delete;
void InitRHI(FRHICommandListImmediate&) {}
void ReleaseRHI(FRHICommandListImmediate&) {}
};
/** Represents a struct with a lifetime that spans multiple render commands with scoped initialization
* and release on the render thread.
*
* Example:
*
* struct FMyStruct : public FRenderThreadStructBase
* {
* FInitializer { int32 Foo; int32 Bar; };
*
* FMyStruct(const FInitializer& InInitializer)
* : Initializer(InInitializer)
* {
* // Called immediately by TRenderThreadStruct when created.
* }
*
* ~FMyStruct()
* {
* // Called on the render thread when TRenderThreadStruct goes out of scope.
* }
*
* void InitRHI(FRHICommandListImmediate& RHICmdList)
* {
* // Called on the render thread by TRenderThreadStruct when created.
* }
*
* void ReleaseRHI(FRHICommandListImmediate& RHICmdList)
* {
* // Called on the render thread when TRenderThreadStruct goes out of scope.
* }
*
* FInitializer Initializer;
* };
*
* // On Main Thread
*
* {
* TRenderThreadStruct<FMyStruct> MyStruct(FMyStruct::FInitializer{1, 2});
*
* ENQUEUE_RENDER_COMMAND(CommandA)[MyStruct = MyStruct.Get()](FRHICommandListImmediate& RHICmdList)
* {
* // Do something with MyStruct.
* };
*
* ENQUEUE_RENDER_COMMAND(CommandB)[MyStruct = MyStrucft.Get()](FRHICommandListImmediate& RHICmdList)
* {
* // Do something else with MyStruct.
* };
*
* // MyStruct instance is automatically released and deleted on the render thread.
* }
*/
template <typename StructType>
class TRenderThreadStruct
{
public:
static_assert(TIsDerivedFrom<StructType, FRenderThreadStructBase>::IsDerived, "StructType must be derived from FRenderThreadStructBase.");
template <typename... TArgs>
TRenderThreadStruct(TArgs&&... Args)
: Struct(new StructType(Forward<TArgs&&>(Args)...))
{
ENQUEUE_RENDER_COMMAND(InitStruct)([Struct = Struct](FRHICommandListImmediate& RHICmdList)
{
Struct->InitRHI(RHICmdList);
});
}
~TRenderThreadStruct()
{
ENQUEUE_RENDER_COMMAND(DeleteStruct)([Struct = Struct](FRHICommandListImmediate& RHICmdList)
{
Struct->ReleaseRHI(RHICmdList);
delete Struct;
});
Struct = nullptr;
}
TRenderThreadStruct(const TRenderThreadStruct&) = delete;
const StructType* operator->() const
{
return Struct;
}
StructType* operator->()
{
return Struct;
}
const StructType& operator*() const
{
return *Struct;
}
StructType& operator*()
{
return *Struct;
}
const StructType* Get() const
{
return Struct;
}
StructType* Get()
{
return Struct;
}
private:
StructType* Struct;
};
DECLARE_MULTICAST_DELEGATE(FStopRenderingThread);
using FStopRenderingThreadDelegate = FStopRenderingThread::FDelegate;
extern RENDERCORE_API FDelegateHandle RegisterStopRenderingThreadDelegate(const FStopRenderingThreadDelegate& InDelegate);
extern RENDERCORE_API void UnregisterStopRenderingThreadDelegate(FDelegateHandle InDelegateHandle);
///////////////////////////////////////////////////////////////////////////////
// Deprecated Types
class UE_DEPRECATED(5.6, "FRenderThreadScope is no longer used.") FRenderThreadScope
{
typedef TFunction<void(FRHICommandListImmediate&)> RenderCommandFunction;
typedef TArray<RenderCommandFunction> RenderCommandFunctionArray;
public:
FRenderThreadScope()
{
RenderCommands = new RenderCommandFunctionArray;
}
~FRenderThreadScope()
{
RenderCommandFunctionArray* RenderCommandArray = RenderCommands;
ENQUEUE_RENDER_COMMAND(DispatchScopeCommands)(
[RenderCommandArray](FRHICommandListImmediate& RHICmdList)
{
for(int32 Index = 0; Index < RenderCommandArray->Num(); Index++)
{
(*RenderCommandArray)[Index](RHICmdList);
}
delete RenderCommandArray;
});
}
void EnqueueRenderCommand(RenderCommandFunction&& Lambda)
{
RenderCommands->Add(MoveTemp(Lambda));
}
private:
RenderCommandFunctionArray* RenderCommands;
};
class UE_DEPRECATED(5.6, "FRenderCommand is no longer used") FRenderCommand
{
public:
static ENamedThreads::Type GetDesiredThread()
{
check(!GIsThreadedRendering || ENamedThreads::GetRenderThread() != ENamedThreads::GameThread);
return ENamedThreads::GetRenderThread();
}
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::FireAndForget;
}
};
template <typename TagType, typename LambdaType>
class UE_DEPRECATED(5.6, "TEnqueueUniqueRenderCommandType is no longer used.") TEnqueueUniqueRenderCommandType : public FRenderCommand
{
public:
TEnqueueUniqueRenderCommandType(LambdaType&& InLambda) : Lambda(Forward<LambdaType>(InLambda)) {}
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
TRACE_CPUPROFILER_EVENT_SCOPE_ON_CHANNEL_STR(TagType::GetName(), RenderCommandsChannel);
FRHICommandListImmediate& RHICmdList = GetImmediateCommandList_ForRenderCommand();
Lambda(RHICmdList);
}
FORCEINLINE_DEBUGGABLE TStatId GetStatId() const
{
return TagType::GetStatId();
}
private:
LambdaType Lambda;
};
///////////////////////////////////////////////////////////////////////////////
#undef UE_API