Files
UnrealEngineUWP/Engine/Source/Runtime/RenderCore/Public/RenderingThread.h
luke thatcher bdf2d7d6c7 Improved game thread frame sync to resolve crashes introduced in 36468180 and worked around in 36479070
- Various legacy game thread code assumes the render thread will never be more than one frame behind. The original change in 36468180 switched from syncing the GT with the RT, to syncing the GT with the RHIT. That left the render thread "floating" in the center of the pipeline, and led to cases where resources are deleted too soon.
 - New approach is to always sync with the GT with the N-1 RT frame, so the GT is never too far ahead of the RT. This maintains compatibility with the legacy GT code paths. In addition to the GT->RT sync, we also sync with the RHIT to prevent the engine running ahead, which was the original bug that 36468180 was fixing.
 - "r.GTSyncType" mode 0 now allows for 1 frame of GT->RT overlap, and 2 frames of GT->RHIT overlap.
 - For debugging purposes, "r.GTSyncType" can also be made negative, which increases the number of GT->RHIT overlap frames, e.g. "r.GTSyncType -3" gives 5 frames of GT->RHIT overlap. While this is not overly useful in a shipped title, it can be used to prove the correctness of the rendering pipeline.
 - Merged the FDeferredCleanupInterface / FPendingCleanupObjects processing into the FFrameEndSync code path, plus made FFrameEndSync a static singleton. This allows us to manage the N frames of overlap between the GT and RHIT in a central place, and correctly cleanup deferred resources when the RT fences have passed.

In future, we will need to revisit this as part of the frame pacing initiative. Game thread code should be written to not require render thread fences for correctness / threadsafety.

#jira UE-223692
#rb zach.bethel

[CL 36758377 by luke thatcher in 5.5 branch]
2024-10-01 19:38:37 -04:00

755 lines
23 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 "Stats/Stats2.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"
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_2
#include "RenderDeferredCleanup.h"
#endif
namespace UE { namespace Trace { class FChannel; } }
////////////////////////////////////
// Render thread API
////////////////////////////////////
/**
* 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)
static 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. */
static 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();
extern RENDERCORE_API void FlushPendingDeleteRHIResources_GameThread();
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
/** Type that contains profiler data necessary to mark up render commands for various profilers. */
template <typename TSTR>
struct TRenderCommandTag
{
static const TCHAR* GetName()
{
return TSTR::TStr();
}
static uint32& GetSpecId()
{
static uint32 SpecId;
return SpecId;
}
static TStatId GetStatId()
{
#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 struct FThreadSafeStaticStat<FStatData> Stat;
return Stat.GetStatId();
#else
return TStatId();
#endif
}
};
/** 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__))>;
/** The parent class of commands stored in the rendering command queue. */
class FRenderCommand
{
public:
// All render commands run on the render thread
static ENamedThreads::Type GetDesiredThread()
{
check(!GIsThreadedRendering || ENamedThreads::GetRenderThread() != ENamedThreads::GameThread);
return ENamedThreads::GetRenderThread();
}
static ESubsequentsMode::Type GetSubsequentsMode()
{
// Don't support tasks having dependencies on us, reduces task graph overhead tracking and dealing with subsequents
return ESubsequentsMode::FireAndForget;
}
};
template <typename TagType, typename LambdaType>
class 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;
};
/** Describes which pipes are configured to use the render command pipe system. */
enum class ERenderCommandPipeMode
{
/** 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, FConcurrentLinearBitArrayAllocator>;
using FRenderCommandPipeBitArray = TBitArray<FRenderCommandPipeBitArrayAllocator>;
using FRenderCommandPipeSetBitIterator = TConstSetBitIterator<FRenderCommandPipeBitArrayAllocator>;
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 TConstArrayView<FRenderCommandPipe*> GetPipes();
// [Game Thread] Stops render command pipe recording during the duration of the scope and restarts recording once the scope is complete.
class RENDERCORE_API FSyncScope
{
public:
FSyncScope();
FSyncScope(TConstArrayView<FRenderCommandPipe*> Pipes);
~FSyncScope();
private:
FRenderCommandPipeBitArray PipeBits;
};
}
extern RENDERCORE_API ERenderCommandPipeMode GRenderCommandPipeMode;
class FRenderThreadCommandPipe
{
public:
template <typename RenderCommandTag, typename LambdaType>
FORCEINLINE_DEBUGGABLE static void Enqueue(LambdaType&& Lambda)
{
if (GRenderCommandPipeMode != ERenderCommandPipeMode::None)
{
Instance.EnqueueAndLaunch(RenderCommandTag::GetName(), RenderCommandTag::GetSpecId(), RenderCommandTag::GetStatId(), MoveTemp(Lambda));
}
else
{
TGraphTask<TEnqueueUniqueRenderCommandType<RenderCommandTag, LambdaType>>::CreateTask().ConstructAndDispatchWhenReady(MoveTemp(Lambda));
}
}
private:
static RENDERCORE_API FRenderThreadCommandPipe Instance;
RENDERCORE_API void EnqueueAndLaunch(const TCHAR* Name, uint32& SpecId, TStatId StatId, TUniqueFunction<void(FRHICommandListImmediate&)>&& Function);
struct FCommand
{
FCommand(const TCHAR* InName, uint32& OutSpecId, TStatId InStatId, TUniqueFunction<void(FRHICommandListImmediate&)>&& InFunction)
: Name(InName)
, SpecId(&OutSpecId)
, StatId(InStatId)
, Function(MoveTemp(InFunction))
{}
const TCHAR* Name;
uint32* SpecId;
TStatId StatId;
TUniqueFunction<void(FRHICommandListImmediate&)> Function;
};
int32 ProduceIndex = 0;
TStaticArray<TArray<FCommand>, 2> Queues;
UE::FMutex Mutex;
};
template<typename RenderCommandTag, typename LambdaType>
FORCEINLINE_DEBUGGABLE void EnqueueUniqueRenderCommand(LambdaType&& Lambda)
{
TRACE_CPUPROFILER_EVENT_SCOPE_USE_ON_CHANNEL(RenderCommandTag::GetSpecId(), RenderCommandTag::GetName(), EventScope, RenderCommandsChannel, true);
if (IsInRenderingThread())
{
Lambda(GetImmediateCommandList_ForRenderCommand());
}
else if (ShouldExecuteOnRenderThread())
{
CheckNotBlockedOnRenderThread();
FRenderThreadCommandPipe::Enqueue<RenderCommandTag, LambdaType>(MoveTemp(Lambda));
}
else
{
FScopeCycleCounter CycleScope(RenderCommandTag::GetStatId());
Lambda(GetImmediateCommandList_ForRenderCommand());
}
}
template<typename RenderCommandTag, typename LambdaType>
FORCEINLINE_DEBUGGABLE void EnqueueUniqueRenderCommand(LambdaType& Lambda)
{
static_assert(sizeof(LambdaType) == 0, "EnqueueUniqueRenderCommand enforces use of rvalue and therefore move to avoid an extra copy of the Lambda");
}
class FRenderCommandPipe
{
public:
using FCommandListFunction = TUniqueFunction<void(FRHICommandList&)>;
using FEmptyFunction = TUniqueFunction<void()>;
RENDERCORE_API FRenderCommandPipe(const TCHAR* Name, ERenderCommandPipeFlags Flags, const TCHAR* CVarName, const TCHAR* CVarDescription);
RENDERCORE_API ~FRenderCommandPipe();
FORCEINLINE const TCHAR* GetName() const
{
return Name;
}
FORCEINLINE bool IsReplaying() const
{
ensure(IsInParallelRenderingThread());
return Frame_RenderThread != nullptr;
}
FORCEINLINE bool IsRecording() const
{
return bRecording;
}
FORCEINLINE bool IsEmpty() const
{
return NumInFlightCommands.load(std::memory_order_relaxed) == 0;
}
void SetEnabled(bool bInIsEnabled)
{
check(IsInGameThread());
bEnabled = bInIsEnabled;
}
template <typename RenderCommandTag>
static void Enqueue(FRenderCommandPipe* Pipe, FCommandListFunction&& Function)
{
if (GRenderCommandPipeMode == ERenderCommandPipeMode::All && Pipe)
{
UE::TScopeLock Lock(Pipe->Mutex);
// Execute the function directly if this is being called recursively from within another pipe command.
if (UE::RenderCommandPipe::IsReplaying(*Pipe))
{
Pipe->ExecuteCommand(MoveTemp(Function), RenderCommandTag::GetName(), RenderCommandTag::GetSpecId());
return;
}
if (Pipe->Frame_GameThread)
{
Pipe->EnqueueAndLaunch(MoveTemp(Function), RenderCommandTag::GetName(), RenderCommandTag::GetSpecId());
return;
}
}
EnqueueUniqueRenderCommand<RenderCommandTag>([Function = MoveTemp(Function)](FRHICommandListImmediate& RHICmdList) { Function(RHICmdList); });
}
template <typename RenderCommandTag>
static void Enqueue(FRenderCommandPipe& Pipe, FCommandListFunction&& Function)
{
Enqueue<RenderCommandTag>(&Pipe, MoveTemp(Function));
}
template <typename RenderCommandTag>
static void Enqueue(FRenderCommandPipe* Pipe, FEmptyFunction&& Function)
{
if (GRenderCommandPipeMode == ERenderCommandPipeMode::All && Pipe)
{
UE::TScopeLock Lock(Pipe->Mutex);
// Execute the function directly if this is being called recursively from within another pipe command.
if (UE::RenderCommandPipe::IsReplaying(*Pipe))
{
Pipe->ExecuteCommand(MoveTemp(Function), RenderCommandTag::GetName(), RenderCommandTag::GetSpecId());
return;
}
if (Pipe->Frame_GameThread)
{
Pipe->EnqueueAndLaunch(MoveTemp(Function), RenderCommandTag::GetName(), RenderCommandTag::GetSpecId());
return;
}
}
EnqueueUniqueRenderCommand<RenderCommandTag>([Function = MoveTemp(Function)](FRHICommandListImmediate&) { Function(); });
}
template <typename RenderCommandTag>
static void Enqueue(FRenderCommandPipe& Pipe, FEmptyFunction&& Function)
{
Enqueue<RenderCommandTag>(&Pipe, MoveTemp(Function));
}
template <typename RenderCommandTag, typename LambdaType>
FORCEINLINE static void Enqueue(LambdaType&& Lambda)
{
EnqueueUniqueRenderCommand<RenderCommandTag>(MoveTemp(Lambda));
}
private:
friend class FRenderCommandPipeRegistry;
using FFunctionVariant = TVariant<FEmptyFunction, FCommandListFunction>;
RENDERCORE_API void EnqueueAndLaunch(FFunctionVariant&& FunctionVariant, const TCHAR* Name, uint32& SpecId);
void EnqueueAndLaunch(FCommandListFunction&& Function, const TCHAR* CommandName, uint32& CommandSpecId)
{
EnqueueAndLaunch(FFunctionVariant(TInPlaceType<FCommandListFunction>(), MoveTemp(Function)), CommandName, CommandSpecId);
}
void EnqueueAndLaunch(FEmptyFunction&& Function, const TCHAR* CommandName, uint32& CommandSpecId)
{
EnqueueAndLaunch(FFunctionVariant(TInPlaceType<FEmptyFunction>(), MoveTemp(Function)), CommandName, CommandSpecId);
}
RENDERCORE_API void ExecuteCommand(FFunctionVariant&& FunctionVariant, const TCHAR* CommandName, uint32& CommandSpecId);
void ExecuteCommand(FCommandListFunction&& Function, const TCHAR* CommandName, uint32& CommandSpecId)
{
ExecuteCommand(FFunctionVariant(TInPlaceType<FCommandListFunction>(), MoveTemp(Function)), CommandName, CommandSpecId);
}
void ExecuteCommand(FEmptyFunction&& Function, const TCHAR* CommandName, uint32& CommandSpecId)
{
ExecuteCommand(FFunctionVariant(TInPlaceType<FEmptyFunction>(), MoveTemp(Function)), CommandName, CommandSpecId);
}
struct FCommand
{
FCommand(FFunctionVariant&& InFunction, const TCHAR* InName, uint32& InOutSpecId)
: Function(MoveTemp(InFunction))
, Name(InName)
, SpecId(&InOutSpecId)
{}
FFunctionVariant Function;
const TCHAR* Name;
uint32* SpecId;
};
struct FFrame : public TConcurrentLinearObject<FFrame>
{
FFrame(const UE::Tasks::FTaskEvent& InTaskEvent)
: LastTask(InTaskEvent)
{}
UE::Tasks::FTask LastTask;
TArray<FCommand> Queue;
FRHICommandList* RHICmdList = nullptr;
};
const TCHAR* Name;
UE::FMutex Mutex;
FFrame* Frame_GameThread = nullptr;
FFrame* Frame_RenderThread = nullptr;
TLinkedList<FRenderCommandPipe*> GlobalListLink;
FAutoConsoleVariable ConsoleVariable;
std::atomic_int32_t NumInFlightCommands{ 0 };
uint16 Index = uint16(-1);
bool bRecording = false;
bool bEnabled = true;
};
/** 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) \
FRenderCommandPipe::Enqueue<PREPROCESSOR_JOIN(FRenderCommandTag_, PREPROCESSOR_JOIN(Type, __LINE__))>
////////////////////////////////////
// RenderThread scoped work
////////////////////////////////////
class 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;
};
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);
namespace FFrameEndSync
{
/**
* Syncs the game thread based on progress throughout the rendering pipeline
* @param bFullSync - when true, blocks the caller until all rendering work is completed,
* otherwise only blocks until the N - m frame has completed, where m is driven by various config.
*/
RENDERCORE_API void Sync(bool bFullSync = false);
};