You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Linkers are no longer UObjects. Renamed ULinker, ULinkerLoad and ULinkerSave to FLinker, FLinkerLoad, FLinkerSave respectively - Linkers are now associated with their UPackages - Linker version is now stored in UPackages - Async loading is now performed on a separate thread (if platform supports it and only in cooked builds), with the exception of PostLoad which is still done on the game thread - Added UObject::IsPostLoadThreadSafe() function to determine if PostLoad is thread safe and can be executed on the async loading thread (defaults to false) - UObject creation is now thread safe and can be performed on any thread - Move many of the linker/UObject globals into FUObjectThreadContext (TLS) - GetAsyncLoadPercentage() now takes PostLoad into account - More async loading stats - Added AtomicallySetFlags/ClearFlags to UObject - Made FModuleManager thread safe. - Added FGCScopeGuard as means of preventing GC from executing from non-game thread - It's possible to disable async loading thread through ini settings. - Cancelling async loading will now also trigger GC - Implemented a basic version of async streaming priorities. Change 2410813 by Mikolaj Sieluzycki: Change Sleep in while loop to ConditionalSleep in FMultiReaderSingleWriterGT Change 2410734 by Mikolaj Sieluzycki: Make FModuleManager thread safe. Change 2399879 by Mikolaj Sieluzycki: Basic version of async streaming priorities. Change 2410707 by Mikolaj Sieluzycki: Implement conditional and no stat versions of sleep. Change 2371939 by Robert Manuszewski: Async Loading Improvements: adding more stats (accumulators) Change 2372403 by Robert Manuszewski: Fixing compile errors when STATs are not enabled Change 2371526 by Robert Manuszewski: AsyncLoading Improvements (WIP) Change 2407198 by Robert Manuszewski: Re-implementing delegate fixes for Async Loading Change 2407425 by Robert Manuszewski: Re-implementing cancelling async loading in the async loading branch. Change 2484362 by Robert Manuszewski: Making it possible to disable async loading thread through ini settings. Change 2484744 by Robert Manuszewski: Minimizing locks in GC and other threads when handling UObjects Change 2480190 by Robert Manuszewski: Fixing infinite stall after canceling async loading in non-cooked builds Change 2484268 by Robert Manuszewski: Fixing crash when allocating permanent object pool. Change 2489761 by Robert Manuszewski: Fixing BulkData using linker archive on the main thread even if the linker was created on the async loading thread. Change 2493624 by Robert Manuszewski: Cancelling async loading will now also trigger GC Change 2487881 by Robert Manuszewski: Making ShaderIdMap operations thread safe. Change 2488067 by Robert Manuszewski: Fixing GetAsyncLoadPercentage. It will now also respect PostLoad. Change 2458640 by Robert Manuszewski: Fixing crash in PIE Change 2458825 by Robert Manuszewski: Fixing a few crashes when streaming and the package is missing. Change 2476935 by Robert Manuszewski: Fixing crash while async loading ANavigationData Change 2477361 by Robert Manuszewski: Fixing crashes in cooked game Change 2480095 by Robert Manuszewski: Making FUObjectArray more thread safe Change 2475443 by Robert Manuszewski: Re-enabling single-threaded async loading path for the editor and platforms that don't support multithreading. Change 2475458 by Robert Manuszewski: Making sure bulk data is only loaded on a separate thread if it's not being loaded on the async loading thread. Change 2476661 by Robert Manuszewski: Fixing FlushAsyncLoading not flushing everything Change 2401089 by Jaroslaw Surowiec: Core - Added AtomicallySetFlags/ClearFlags to UObject, added a comment to ThisThreadAtomicallyClearedRFUnreachable [CL 2498249 by Robert Manuszewski in Main branch]
909 lines
27 KiB
C++
909 lines
27 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
RenderingThread.cpp: Rendering thread implementation.
|
|
=============================================================================*/
|
|
|
|
#include "RenderCore.h"
|
|
#include "RenderingThread.h"
|
|
#include "RHI.h"
|
|
#include "TickableObjectRenderThread.h"
|
|
#include "ExceptionHandling.h"
|
|
#include "TaskGraphInterfaces.h"
|
|
#include "StatsData.h"
|
|
|
|
//
|
|
// Globals
|
|
//
|
|
|
|
|
|
|
|
RENDERCORE_API bool GIsThreadedRendering = false;
|
|
RENDERCORE_API bool GUseThreadedRendering = false;
|
|
RENDERCORE_API bool GUseRHIThread = false;
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
RENDERCORE_API bool GMainThreadBlockedOnRenderThread = false;
|
|
#endif // #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
|
|
static FRunnable* GRenderingThreadRunnable = NULL;
|
|
|
|
/** If the rendering thread has been terminated by an unhandled exception, this contains the error message. */
|
|
FString GRenderingThreadError;
|
|
|
|
/**
|
|
* Polled by the game thread to detect crashes in the rendering thread.
|
|
* If the rendering thread crashes, it sets this variable to false.
|
|
*/
|
|
volatile bool GIsRenderingThreadHealthy = true;
|
|
|
|
|
|
/**
|
|
* Maximum rate the rendering thread will tick tickables when idle (in Hz)
|
|
*/
|
|
float GRenderingThreadMaxIdleTickFrequency = 40.f;
|
|
|
|
/** Function to stall the rendering thread **/
|
|
static void SuspendRendering()
|
|
{
|
|
FPlatformAtomics::InterlockedIncrement(&GIsRenderingThreadSuspended);
|
|
FPlatformMisc::MemoryBarrier();
|
|
}
|
|
|
|
/** Function to wait and resume rendering thread **/
|
|
static void WaitAndResumeRendering()
|
|
{
|
|
while ( GIsRenderingThreadSuspended )
|
|
{
|
|
// Just sleep a little bit.
|
|
FPlatformProcess::Sleep( 0.001f ); //@todo this should be a more principled wait
|
|
}
|
|
|
|
// set the thread back to real time mode
|
|
FPlatformProcess::SetRealTimeMode();
|
|
}
|
|
|
|
/**
|
|
* Constructor that flushes and suspends the renderthread
|
|
* @param bRecreateThread - Whether the rendering thread should be completely destroyed and recreated, or just suspended.
|
|
*/
|
|
FSuspendRenderingThread::FSuspendRenderingThread( bool bInRecreateThread )
|
|
{
|
|
bRecreateThread = bInRecreateThread;
|
|
bUseRenderingThread = GUseThreadedRendering;
|
|
bWasRenderingThreadRunning = GIsThreadedRendering;
|
|
if ( bRecreateThread )
|
|
{
|
|
GUseThreadedRendering = false;
|
|
StopRenderingThread();
|
|
FPlatformAtomics::InterlockedIncrement( &GIsRenderingThreadSuspended );
|
|
}
|
|
else
|
|
{
|
|
if ( GIsRenderingThreadSuspended == 0 )
|
|
{
|
|
// First tell the render thread to finish up all pending commands and then suspend its activities.
|
|
|
|
// this ensures that async stuff will be completed too
|
|
FlushRenderingCommands();
|
|
|
|
if (GIsThreadedRendering)
|
|
{
|
|
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.SuspendRendering"),
|
|
STAT_FSimpleDelegateGraphTask_SuspendRendering,
|
|
STATGROUP_TaskGraphTasks);
|
|
|
|
FGraphEventRef CompleteHandle = FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
|
|
FSimpleDelegateGraphTask::FDelegate::CreateStatic(&SuspendRendering),
|
|
GET_STATID(STAT_FSimpleDelegateGraphTask_SuspendRendering), NULL, ENamedThreads::RenderThread);
|
|
|
|
// Busy wait while Kismet debugging, to avoid opportunistic execution of game thread tasks
|
|
// If the game thread is already executing tasks, then we have no choice but to spin
|
|
if (GIntraFrameDebuggingGameThread || FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::GameThread) )
|
|
{
|
|
while (!GIsRenderingThreadSuspended)
|
|
{
|
|
FPlatformProcess::Sleep(0.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FSuspendRenderingThread);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(CompleteHandle, ENamedThreads::GameThread);
|
|
}
|
|
check(GIsRenderingThreadSuspended);
|
|
|
|
// Now tell the render thread to busy wait until it's resumed
|
|
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.WaitAndResumeRendering"),
|
|
STAT_FSimpleDelegateGraphTask_WaitAndResumeRendering,
|
|
STATGROUP_TaskGraphTasks);
|
|
|
|
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
|
|
FSimpleDelegateGraphTask::FDelegate::CreateStatic(&WaitAndResumeRendering),
|
|
GET_STATID(STAT_FSimpleDelegateGraphTask_WaitAndResumeRendering), NULL, ENamedThreads::RenderThread);
|
|
}
|
|
else
|
|
{
|
|
SuspendRendering();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The render-thread is already suspended. Just bump the ref-count.
|
|
FPlatformAtomics::InterlockedIncrement( &GIsRenderingThreadSuspended );
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Destructor that starts the renderthread again */
|
|
FSuspendRenderingThread::~FSuspendRenderingThread()
|
|
{
|
|
#if PLATFORM_MAC // On OS X Apple's context sharing is a strict interpretation of the spec. so a resource is only properly visible to other contexts
|
|
// in the share group after a flush. Thus we call RHIFlushResources which will flush the current context's commands to GL (but not wait for them).
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(FlushCommand,
|
|
RHIFlushResources();
|
|
);
|
|
#endif
|
|
|
|
if ( bRecreateThread )
|
|
{
|
|
GUseThreadedRendering = bUseRenderingThread;
|
|
FPlatformAtomics::InterlockedDecrement( &GIsRenderingThreadSuspended );
|
|
if ( bUseRenderingThread && bWasRenderingThreadRunning )
|
|
{
|
|
StartRenderingThread();
|
|
|
|
// Now tell the render thread to set it self to real time mode
|
|
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.SetRealTimeMode"),
|
|
STAT_FSimpleDelegateGraphTask_SetRealTimeMode,
|
|
STATGROUP_TaskGraphTasks);
|
|
|
|
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
|
|
FSimpleDelegateGraphTask::FDelegate::CreateStatic(&FPlatformProcess::SetRealTimeMode),
|
|
GET_STATID(STAT_FSimpleDelegateGraphTask_SetRealTimeMode), NULL, ENamedThreads::RenderThread
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Resume the render thread again.
|
|
FPlatformAtomics::InterlockedDecrement( &GIsRenderingThreadSuspended );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Tick all rendering thread tickable objects
|
|
*/
|
|
|
|
/** Static array of tickable objects that are ticked from rendering thread*/
|
|
FTickableObjectRenderThread::FRenderingThreadTickableObjectsArray FTickableObjectRenderThread::RenderingThreadTickableObjects;
|
|
|
|
void TickRenderingTickables()
|
|
{
|
|
static double LastTickTime = FPlatformTime::Seconds();
|
|
|
|
// calc how long has passed since last tick
|
|
double CurTime = FPlatformTime::Seconds();
|
|
float DeltaSeconds = CurTime - LastTickTime;
|
|
|
|
if (DeltaSeconds < (1.f/GRenderingThreadMaxIdleTickFrequency))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// tick any rendering thread tickables
|
|
for (int32 ObjectIndex = 0; ObjectIndex < FTickableObjectRenderThread::RenderingThreadTickableObjects.Num(); ObjectIndex++)
|
|
{
|
|
FTickableObjectRenderThread* TickableObject = FTickableObjectRenderThread::RenderingThreadTickableObjects[ObjectIndex];
|
|
// make sure it wants to be ticked and the rendering thread isn't suspended
|
|
if (TickableObject->IsTickable())
|
|
{
|
|
STAT(FScopeCycleCounter(TickableObject->GetStatId());)
|
|
TickableObject->Tick(DeltaSeconds);
|
|
}
|
|
}
|
|
// update the last time we ticked
|
|
LastTickTime = CurTime;
|
|
}
|
|
|
|
/** Accumulates how many cycles the renderthread has been idle. It's defined in RenderingThread.cpp. */
|
|
uint32 GRenderThreadIdle[ERenderThreadIdleTypes::Num] = {0};
|
|
/** Accumulates how times renderthread was idle. It's defined in RenderingThread.cpp. */
|
|
uint32 GRenderThreadNumIdle[ERenderThreadIdleTypes::Num] = {0};
|
|
/** How many cycles the renderthread used (excluding idle time). It's set once per frame in FViewport::Draw. */
|
|
uint32 GRenderThreadTime = 0;
|
|
|
|
|
|
|
|
/** The RHI thread runnable object. */
|
|
class FRHIThread : public FRunnable
|
|
{
|
|
public:
|
|
FRunnableThread* Thread;
|
|
|
|
FRHIThread()
|
|
: Thread(nullptr)
|
|
{
|
|
check(IsInGameThread());
|
|
}
|
|
|
|
virtual uint32 Run()
|
|
{
|
|
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::RHIThread);
|
|
FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RHIThread);
|
|
return 0;
|
|
}
|
|
|
|
static FRHIThread& Get()
|
|
{
|
|
static FRHIThread Singleton;
|
|
return Singleton;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
Thread = FRunnableThread::Create(this, TEXT("RHIThread"), 512 * 1024, TPri_Normal,
|
|
FPlatformAffinity::GetRHIThreadMask()
|
|
);
|
|
check(Thread);
|
|
}
|
|
};
|
|
|
|
/** The rendering thread main loop */
|
|
void RenderingThreadMain( FEvent* TaskGraphBoundSyncEvent )
|
|
{
|
|
ENamedThreads::RenderThread = ENamedThreads::Type(ENamedThreads::ActualRenderingThread);
|
|
ENamedThreads::RenderThread_Local = ENamedThreads::Type(ENamedThreads::ActualRenderingThread_Local);
|
|
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::RenderThread);
|
|
FPlatformMisc::MemoryBarrier();
|
|
|
|
// Inform main thread that the render thread has been attached to the taskgraph and is ready to receive tasks
|
|
if( TaskGraphBoundSyncEvent != NULL )
|
|
{
|
|
TaskGraphBoundSyncEvent->Trigger();
|
|
}
|
|
|
|
// set the thread back to real time mode
|
|
FPlatformProcess::SetRealTimeMode();
|
|
|
|
#if STATS
|
|
if (FThreadStats::WillEverCollectData())
|
|
{
|
|
FThreadStats::ExplicitFlush(); // flush the stats and set update the scope so we don't flush again until a frame update, this helps prevent fragmentation
|
|
}
|
|
#endif
|
|
|
|
check(GIsThreadedRendering);
|
|
FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RenderThread);
|
|
FPlatformMisc::MemoryBarrier();
|
|
check(!GIsThreadedRendering);
|
|
ENamedThreads::RenderThread = ENamedThreads::GameThread;
|
|
ENamedThreads::RenderThread_Local = ENamedThreads::GameThread_Local;
|
|
FPlatformMisc::MemoryBarrier();
|
|
}
|
|
|
|
/**
|
|
* Advances stats for the rendering thread.
|
|
*/
|
|
static void AdvanceRenderingThreadStats(int64 StatsFrame, int32 MasterDisableChangeTagStartFrame)
|
|
{
|
|
#if STATS
|
|
int64 Frame = StatsFrame;
|
|
if (!FThreadStats::IsCollectingData() || MasterDisableChangeTagStartFrame != FThreadStats::MasterDisableChangeTag())
|
|
{
|
|
Frame = -StatsFrame; // mark this as a bad frame
|
|
}
|
|
// @TODO yrx 2014-10-17 Add AddAdvanceFrame message
|
|
static FStatNameAndInfo Adv(NAME_AdvanceFrame, "", "", TEXT(""), EStatDataType::ST_int64, true, false);
|
|
FThreadStats::AddMessage(Adv.GetEncodedName(), EStatOperation::AdvanceFrameEventRenderThread, Frame);
|
|
if( IsInActualRenderingThread() )
|
|
{
|
|
FThreadStats::ExplicitFlush();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Advances stats for the rendering thread. Called from the game thread.
|
|
*/
|
|
void AdvanceRenderingThreadStatsGT( bool bDiscardCallstack, int64 StatsFrame, int32 MasterDisableChangeTagStartFrame )
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER
|
|
(
|
|
RenderingThreadTickCommand,
|
|
int64, SentStatsFrame, StatsFrame,
|
|
int32, SentMasterDisableChangeTagStartFrame, MasterDisableChangeTagStartFrame,
|
|
{
|
|
AdvanceRenderingThreadStats( SentStatsFrame, SentMasterDisableChangeTagStartFrame );
|
|
}
|
|
);
|
|
if( bDiscardCallstack )
|
|
{
|
|
// we need to flush the rendering thread here, otherwise it can get behind and then the stats will get behind.
|
|
FlushRenderingCommands();
|
|
}
|
|
}
|
|
|
|
/** The rendering thread runnable object. */
|
|
class FRenderingThread : public FRunnable
|
|
{
|
|
public:
|
|
/**
|
|
* Sync event to make sure that render thread is bound to the task graph before main thread queues work against it.
|
|
*/
|
|
FEvent* TaskGraphBoundSyncEvent;
|
|
|
|
FRenderingThread()
|
|
{
|
|
TaskGraphBoundSyncEvent = FPlatformProcess::GetSynchEventFromPool(true);
|
|
RHIFlushResources();
|
|
}
|
|
|
|
virtual ~FRenderingThread()
|
|
{
|
|
FPlatformProcess::ReturnSynchEventToPool(TaskGraphBoundSyncEvent);
|
|
TaskGraphBoundSyncEvent = nullptr;
|
|
}
|
|
|
|
// FRunnable interface.
|
|
virtual bool Init(void) override
|
|
{
|
|
GRenderThreadId = FPlatformTLS::GetCurrentThreadId();
|
|
|
|
// Acquire rendering context ownership on the current thread
|
|
RHIAcquireThreadOwnership();
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void Exit(void) override
|
|
{
|
|
// Release rendering context ownership on the current thread
|
|
RHIReleaseThreadOwnership();
|
|
|
|
GRenderThreadId = 0;
|
|
}
|
|
|
|
virtual uint32 Run(void) override
|
|
{
|
|
FPlatformProcess::SetupGameOrRenderThread(true);
|
|
|
|
#if PLATFORM_WINDOWS
|
|
if ( !FPlatformMisc::IsDebuggerPresent() || GAlwaysReportCrash )
|
|
{
|
|
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
|
|
__try
|
|
#endif
|
|
{
|
|
RenderingThreadMain( TaskGraphBoundSyncEvent );
|
|
}
|
|
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
|
|
__except( ReportCrash( GetExceptionInformation() ) )
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
GRenderingThreadError = GErrorHist;
|
|
#endif
|
|
|
|
// Use a memory barrier to ensure that the game thread sees the write to GRenderingThreadError before
|
|
// the write to GIsRenderingThreadHealthy.
|
|
FPlatformMisc::MemoryBarrier();
|
|
|
|
GIsRenderingThreadHealthy = false;
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
#endif // PLATFORM_WINDOWS
|
|
{
|
|
RenderingThreadMain( TaskGraphBoundSyncEvent );
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* If the rendering thread is in its idle loop (which ticks rendering tickables
|
|
*/
|
|
volatile bool GRunRenderingThreadHeartbeat = false;
|
|
|
|
/** The rendering thread heartbeat runnable object. */
|
|
class FRenderingThreadTickHeartbeat : public FRunnable
|
|
{
|
|
public:
|
|
|
|
// FRunnable interface.
|
|
virtual bool Init(void)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual void Exit(void)
|
|
{
|
|
}
|
|
|
|
virtual void Stop(void)
|
|
{
|
|
}
|
|
|
|
virtual uint32 Run(void)
|
|
{
|
|
while(GRunRenderingThreadHeartbeat)
|
|
{
|
|
FPlatformProcess::Sleep(1.f/(4.0f * GRenderingThreadMaxIdleTickFrequency));
|
|
if (!GIsRenderingThreadSuspended)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
HeartbeatTickTickables,
|
|
{
|
|
// make sure that rendering thread tickables get a chance to tick, even if the render thread is starving
|
|
if (!GIsRenderingThreadSuspended)
|
|
{
|
|
TickRenderingTickables();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
FRunnableThread* GRenderingThreadHeartbeat = NULL;
|
|
FRunnable* GRenderingThreadRunnableHeartbeat = NULL;
|
|
|
|
// not done in the CVar system as we don't access to render thread specifics there
|
|
struct FConsoleRenderThreadPropagation : public IConsoleThreadPropagation
|
|
{
|
|
virtual void OnCVarChange(int32& Dest, int32 NewValue)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
OnCVarChange1,
|
|
int32&, Dest, Dest,
|
|
int32, NewValue, NewValue,
|
|
{
|
|
Dest = NewValue;
|
|
});
|
|
}
|
|
|
|
virtual void OnCVarChange(float& Dest, float NewValue)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
OnCVarChange2,
|
|
float&, Dest, Dest,
|
|
float, NewValue, NewValue,
|
|
{
|
|
Dest = NewValue;
|
|
});
|
|
}
|
|
|
|
virtual void OnCVarChange(FString& Dest, const FString& NewValue)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
OnCVarChange3,
|
|
FString&, Dest, Dest,
|
|
const FString&, NewValue, NewValue,
|
|
{
|
|
Dest = NewValue;
|
|
});
|
|
}
|
|
|
|
static FConsoleRenderThreadPropagation& GetSingleton()
|
|
{
|
|
static FConsoleRenderThreadPropagation This;
|
|
|
|
return This;
|
|
}
|
|
|
|
};
|
|
|
|
static FString BuildRenderingThreadName( uint32 ThreadIndex )
|
|
{
|
|
return FString::Printf( TEXT( "%s %u" ), *FName( NAME_RenderThread ).GetPlainNameString(), ThreadIndex );
|
|
}
|
|
|
|
void StartRenderingThread()
|
|
{
|
|
static uint32 ThreadCount = 0;
|
|
check(!GIsThreadedRendering && GUseThreadedRendering);
|
|
|
|
check(!GRHIThread)
|
|
if (GUseRHIThread)
|
|
{
|
|
if (!FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::RHIThread))
|
|
{
|
|
FRHIThread::Get().Start();
|
|
}
|
|
DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread"), STAT_WaitForRHIThread, STATGROUP_TaskGraphTasks);
|
|
|
|
FGraphEventRef CompletionEvent = TGraphTask<FNullGraphTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(GET_STATID(STAT_WaitForRHIThread), ENamedThreads::RHIThread);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_StartRenderingThread);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(CompletionEvent, ENamedThreads::GameThread_Local);
|
|
GRHIThread = FRHIThread::Get().Thread;
|
|
check(GRHIThread);
|
|
GRHICommandList.LatchBypass();
|
|
}
|
|
|
|
// Turn on the threaded rendering flag.
|
|
GIsThreadedRendering = true;
|
|
|
|
// Create the rendering thread.
|
|
GRenderingThreadRunnable = new FRenderingThread();
|
|
|
|
GRenderingThread = FRunnableThread::Create( GRenderingThreadRunnable, *BuildRenderingThreadName( ThreadCount ), 0, TPri_Normal, FPlatformAffinity::GetRenderingThreadMask());
|
|
|
|
// Wait for render thread to have taskgraph bound before we dispatch any tasks for it.
|
|
((FRenderingThread*)GRenderingThreadRunnable)->TaskGraphBoundSyncEvent->Wait();
|
|
|
|
// register
|
|
IConsoleManager::Get().RegisterThreadPropagation(GRenderingThread->GetThreadID(), &FConsoleRenderThreadPropagation::GetSingleton());
|
|
|
|
// ensure the thread has actually started and is idling
|
|
FRenderCommandFence Fence;
|
|
Fence.BeginFence();
|
|
Fence.Wait();
|
|
|
|
GRunRenderingThreadHeartbeat = true;
|
|
// Create the rendering thread heartbeat
|
|
GRenderingThreadRunnableHeartbeat = new FRenderingThreadTickHeartbeat();
|
|
|
|
GRenderingThreadHeartbeat = FRunnableThread::Create(GRenderingThreadRunnableHeartbeat, *FString::Printf(TEXT("RTHeartBeat %d"), ThreadCount), 16 * 1024, TPri_AboveNormal, FPlatformAffinity::GetRTHeartBeatMask());
|
|
|
|
ThreadCount++;
|
|
}
|
|
|
|
void StopRenderingThread()
|
|
{
|
|
// This function is not thread-safe. Ensure it is only called by the main game thread.
|
|
check( IsInGameThread() );
|
|
|
|
// unregister
|
|
IConsoleManager::Get().RegisterThreadPropagation();
|
|
|
|
// stop the render thread heartbeat first
|
|
if (GRunRenderingThreadHeartbeat)
|
|
{
|
|
GRunRenderingThreadHeartbeat = false;
|
|
// Wait for the rendering thread heartbeat to return.
|
|
GRenderingThreadHeartbeat->WaitForCompletion();
|
|
GRenderingThreadHeartbeat = NULL;
|
|
delete GRenderingThreadRunnableHeartbeat;
|
|
GRenderingThreadRunnableHeartbeat = NULL;
|
|
}
|
|
|
|
if( GIsThreadedRendering )
|
|
{
|
|
// Get the list of objects which need to be cleaned up when the rendering thread is done with them.
|
|
FPendingCleanupObjects* PendingCleanupObjects = GetPendingCleanupObjects();
|
|
|
|
// Make sure we're not in the middle of streaming textures.
|
|
(*GFlushStreamingFunc)();
|
|
|
|
// Wait for the rendering thread to finish executing all enqueued commands.
|
|
FlushRenderingCommands();
|
|
|
|
// The rendering thread may have already been stopped during the call to GFlushStreamingFunc or FlushRenderingCommands.
|
|
if ( GIsThreadedRendering )
|
|
{
|
|
if (GRHIThread)
|
|
{
|
|
DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread Finish"), STAT_WaitForRHIThreadFinish, STATGROUP_TaskGraphTasks);
|
|
FGraphEventRef FlushTask = TGraphTask<FNullGraphTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(GET_STATID(STAT_WaitForRHIThreadFinish), ENamedThreads::RHIThread);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_StopRenderingThread_RHIThread);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(FlushTask, ENamedThreads::GameThread_Local);
|
|
GRHIThread = nullptr;
|
|
}
|
|
|
|
check( GRenderingThread );
|
|
check(!GIsRenderingThreadSuspended);
|
|
|
|
// Turn off the threaded rendering flag.
|
|
GIsThreadedRendering = false;
|
|
|
|
{
|
|
FGraphEventRef QuitTask = TGraphTask<FReturnGraphTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(ENamedThreads::RenderThread);
|
|
|
|
// Busy wait while BP debugging, to avoid opportunistic execution of game thread tasks
|
|
// If the game thread is already executing tasks, then we have no choice but to spin
|
|
if (GIntraFrameDebuggingGameThread || FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::GameThread) )
|
|
{
|
|
while ((QuitTask.GetReference() != nullptr) && !QuitTask->IsComplete())
|
|
{
|
|
FPlatformProcess::Sleep(0.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_StopRenderingThread);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(QuitTask, ENamedThreads::GameThread_Local);
|
|
}
|
|
}
|
|
|
|
// Wait for the rendering thread to return.
|
|
GRenderingThread->WaitForCompletion();
|
|
|
|
// Destroy the rendering thread objects.
|
|
delete GRenderingThread;
|
|
GRenderingThread = NULL;
|
|
|
|
delete GRenderingThreadRunnable;
|
|
GRenderingThreadRunnable = NULL;
|
|
}
|
|
|
|
// Delete the pending cleanup objects which were in use by the rendering thread.
|
|
delete PendingCleanupObjects;
|
|
}
|
|
check(!GRHIThread);
|
|
}
|
|
|
|
void CheckRenderingThreadHealth()
|
|
{
|
|
if(!GIsRenderingThreadHealthy)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
GErrorHist[0] = 0;
|
|
#endif
|
|
GIsCriticalError = false;
|
|
UE_LOG(LogRendererCore, Fatal,TEXT("Rendering thread exception:\r\n%s"),*GRenderingThreadError);
|
|
}
|
|
|
|
|
|
if (IsInGameThread())
|
|
{
|
|
GLog->FlushThreadedLogs();
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
TGuardValue<bool> GuardMainThreadBlockedOnRenderThread(GMainThreadBlockedOnRenderThread,true);
|
|
#endif
|
|
SCOPE_CYCLE_COUNTER(STAT_PumpMessages);
|
|
FPlatformMisc::PumpMessages(false);
|
|
}
|
|
}
|
|
|
|
bool IsRenderingThreadHealthy()
|
|
{
|
|
return GIsRenderingThreadHealthy;
|
|
}
|
|
|
|
|
|
void FRenderCommandFence::BeginFence()
|
|
{
|
|
if (!GIsThreadedRendering)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DECLARE_CYCLE_STAT(TEXT("FNullGraphTask.FenceRenderCommand"),
|
|
STAT_FNullGraphTask_FenceRenderCommand,
|
|
STATGROUP_TaskGraphTasks);
|
|
|
|
if (IsFenceComplete())
|
|
{
|
|
CompletionEvent = TGraphTask<FNullGraphTask>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(
|
|
GET_STATID(STAT_FNullGraphTask_FenceRenderCommand), ENamedThreads::RenderThread);
|
|
}
|
|
else
|
|
{
|
|
// we already had a fence, so we will chain this one to the old one as a prerequisite
|
|
FGraphEventArray Prerequistes;
|
|
Prerequistes.Add(CompletionEvent);
|
|
CompletionEvent = TGraphTask<FNullGraphTask>::CreateTask(&Prerequistes, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(
|
|
GET_STATID(STAT_FNullGraphTask_FenceRenderCommand), ENamedThreads::RenderThread);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FRenderCommandFence::IsFenceComplete() const
|
|
{
|
|
if (!GIsThreadedRendering)
|
|
{
|
|
return true;
|
|
}
|
|
check(IsInGameThread() || IsInAsyncLoadingThread());
|
|
CheckRenderingThreadHealth();
|
|
if (!CompletionEvent.GetReference() || CompletionEvent->IsComplete())
|
|
{
|
|
CompletionEvent = NULL; // this frees the handle for other uses, the NULL state is considered completed
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** How many cycles the gamethread used (excluding idle time). It's set once per frame in FViewport::Draw. */
|
|
uint32 GGameThreadTime = 0;
|
|
/** How many cycles it took to swap buffers to present the frame. */
|
|
uint32 GSwapBufferTime = 0;
|
|
|
|
static int32 GTimeToBlockOnRenderFence = 1;
|
|
static FAutoConsoleVariableRef CVarTimeToBlockOnRenderFence(
|
|
TEXT("g.TimeToBlockOnRenderFence"),
|
|
GTimeToBlockOnRenderFence,
|
|
TEXT("Number of milliseconds the game thread should block when waiting on a render thread fence.")
|
|
);
|
|
|
|
/**
|
|
* Block the game thread waiting for a task to finish on the rendering thread.
|
|
*/
|
|
static void GameThreadWaitForTask(const FGraphEventRef& Task, bool bEmptyGameThreadTasks = false)
|
|
{
|
|
check(IsInGameThread());
|
|
check(IsValidRef(Task));
|
|
|
|
if (!Task->IsComplete())
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GameIdleTime);
|
|
{
|
|
static int32 NumRecursiveCalls = 0;
|
|
|
|
// Check for recursion. It's not completely safe but because we pump messages while
|
|
// blocked it is expected.
|
|
NumRecursiveCalls++;
|
|
if (NumRecursiveCalls > 1)
|
|
{
|
|
UE_LOG(LogRendererCore,Warning,TEXT("FlushRenderingCommands called recursively! %d calls on the stack."), NumRecursiveCalls);
|
|
}
|
|
if (NumRecursiveCalls > 1 || FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::GameThread))
|
|
{
|
|
bEmptyGameThreadTasks = false; // we don't do this on recursive calls or if we are at a blueprint breakpoint
|
|
}
|
|
|
|
// Grab an event from the pool and fire off a task to trigger it.
|
|
FEvent* Event = FPlatformProcess::GetSynchEventFromPool();
|
|
FTaskGraphInterface::Get().TriggerEventWhenTaskCompletes(Event, Task, ENamedThreads::GameThread);
|
|
|
|
// Check rendering thread health needs to be called from time to
|
|
// time in order to pump messages, otherwise the RHI may block
|
|
// on vsync causing a deadlock. Also we should make sure the
|
|
// rendering thread hasn't crashed :)
|
|
bool bDone;
|
|
uint32 WaitTime = FMath::Clamp<uint32>(GTimeToBlockOnRenderFence, 0, 33);
|
|
do
|
|
{
|
|
CheckRenderingThreadHealth();
|
|
if (bEmptyGameThreadTasks)
|
|
{
|
|
// process gamethread tasks if there are any
|
|
FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
|
|
}
|
|
bDone = Event->Wait(WaitTime);
|
|
}
|
|
while (!bDone);
|
|
|
|
// Return the event to the pool and decrement the recursion counter.
|
|
FPlatformProcess::ReturnSynchEventToPool(Event);
|
|
Event = nullptr;
|
|
NumRecursiveCalls--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Waits for pending fence commands to retire.
|
|
*/
|
|
void FRenderCommandFence::Wait(bool bProcessGameThreadTasks) const
|
|
{
|
|
if (!IsFenceComplete())
|
|
{
|
|
#if 0
|
|
// on most platforms this is a better solution because it doesn't spin
|
|
// windows needs to pump messages
|
|
if (bProcessGameThreadTasks)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FRenderCommandFence_Wait);
|
|
FTaskGraphInterface::Get().WaitUntilTaskCompletes(CompletionEvent, ENamedThreads::GameThread);
|
|
}
|
|
#endif
|
|
GameThreadWaitForTask(CompletionEvent, bProcessGameThreadTasks);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List of tasks that must be completed before we start a render frame
|
|
* Note, normally, you don't need the render command themselves in this list workers that queue render commands are usually sufficient
|
|
*/
|
|
static FCompletionList FrameRenderPrerequisites;
|
|
|
|
/**
|
|
* Adds a task that must be completed either before the next scene draw or a flush rendering commands
|
|
* Note, normally, you don't need the render command themselves in this list workers that queue render commands are usually sufficient
|
|
* @param TaskToAdd, task to add as a pending render thread task
|
|
*/
|
|
void AddFrameRenderPrerequisite(const FGraphEventRef& TaskToAdd)
|
|
{
|
|
FrameRenderPrerequisites.Add(TaskToAdd);
|
|
}
|
|
|
|
/**
|
|
* Gather the frame render prerequisites and make sure all render commands are at least queued
|
|
*/
|
|
void AdvanceFrameRenderPrerequisite()
|
|
{
|
|
checkSlow(IsInGameThread());
|
|
FGraphEventRef PendingComplete = FrameRenderPrerequisites.CreatePrerequisiteCompletionHandle();
|
|
if (PendingComplete.GetReference())
|
|
{
|
|
GameThreadWaitForTask(PendingComplete);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Waits for the rendering thread to finish executing all pending rendering commands. Should only be used from the game thread.
|
|
*/
|
|
void FlushRenderingCommands()
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
FlushPendingDeleteRHIResources,
|
|
{
|
|
GRHICommandList.GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
|
|
}
|
|
);
|
|
|
|
AdvanceFrameRenderPrerequisite();
|
|
|
|
// Find the objects which may be cleaned up once the rendering thread command queue has been flushed.
|
|
FPendingCleanupObjects* PendingCleanupObjects = GetPendingCleanupObjects();
|
|
|
|
// Issue a fence command to the rendering thread and wait for it to complete.
|
|
FRenderCommandFence Fence;
|
|
Fence.BeginFence();
|
|
Fence.Wait();
|
|
|
|
// Delete the objects which were enqueued for deferred cleanup before the command queue flush.
|
|
delete PendingCleanupObjects;
|
|
}
|
|
|
|
void FlushPendingDeleteRHIResources_GameThread()
|
|
{
|
|
if (!GRHIThread)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
FlushPendingDeleteRHIResources,
|
|
{
|
|
FlushPendingDeleteRHIResources_RenderThread();
|
|
}
|
|
);
|
|
}
|
|
}
|
|
void FlushPendingDeleteRHIResources_RenderThread()
|
|
{
|
|
if (!GRHIThread)
|
|
{
|
|
FRHIResource::FlushPendingDeletes();
|
|
}
|
|
}
|
|
|
|
|
|
FRHICommandListImmediate& GetImmediateCommandList_ForRenderCommand()
|
|
{
|
|
return FRHICommandListExecutor::GetImmediateCommandList();
|
|
}
|
|
|
|
/** The set of deferred cleanup objects which are pending cleanup. */
|
|
static TLockFreePointerList<FDeferredCleanupInterface> PendingCleanupObjectsList;
|
|
|
|
FPendingCleanupObjects::FPendingCleanupObjects()
|
|
{
|
|
check(IsInGameThread());
|
|
PendingCleanupObjectsList.PopAll(CleanupArray);
|
|
}
|
|
|
|
FPendingCleanupObjects::~FPendingCleanupObjects()
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FPendingCleanupObjects_Destruct);
|
|
|
|
for(int32 ObjectIndex = 0;ObjectIndex < CleanupArray.Num();ObjectIndex++)
|
|
{
|
|
CleanupArray[ObjectIndex]->FinishCleanup();
|
|
}
|
|
}
|
|
|
|
void BeginCleanup(FDeferredCleanupInterface* CleanupObject)
|
|
{
|
|
PendingCleanupObjectsList.Push(CleanupObject);
|
|
}
|
|
|
|
FPendingCleanupObjects* GetPendingCleanupObjects()
|
|
{
|
|
return new FPendingCleanupObjects;
|
|
}
|