2016-01-07 08:17:16 -05:00
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
/*=============================================================================
RenderingThread.cpp: Rendering thread implementation.
=============================================================================*/
2016-08-18 10:28:43 -04:00
# include "RenderCorePrivatePCH.h"
2014-03-14 14:13:41 -04:00
# include "RenderCore.h"
# include "RenderingThread.h"
# include "RHI.h"
# include "TickableObjectRenderThread.h"
# include "ExceptionHandling.h"
# include "TaskGraphInterfaces.h"
2014-05-05 11:15:08 -04:00
# include "StatsData.h"
2014-03-14 14:13:41 -04:00
//
// Globals
//
RENDERCORE_API bool GIsThreadedRendering = false ;
RENDERCORE_API bool GUseThreadedRendering = false ;
2014-09-03 10:52:00 -04:00
RENDERCORE_API bool GUseRHIThread = false ;
2014-03-14 14:13:41 -04:00
# if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
RENDERCORE_API bool GMainThreadBlockedOnRenderThread = false ;
# endif // #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
2014-05-12 08:40:54 -04:00
2014-03-14 14:13:41 -04:00
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
}
2015-05-27 11:02:10 -04:00
// set the thread back to real time mode
FPlatformProcess : : SetRealTimeMode ( ) ;
2014-03-14 14:13:41 -04:00
}
/**
* 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 )
{
2015-05-27 11:02:10 -04:00
// Suspend async loading thread so that it doesn't start queueing render commands
// while the render thread is suspended.
2016-03-14 21:21:09 -04:00
if ( IsAsyncLoadingMultithreaded ( ) )
{
SuspendAsyncLoading ( ) ;
}
2015-05-27 11:02:10 -04:00
2014-03-14 14:13:41 -04:00
bRecreateThread = bInRecreateThread ;
bUseRenderingThread = GUseThreadedRendering ;
bWasRenderingThreadRunning = GIsThreadedRendering ;
if ( bRecreateThread )
{
StopRenderingThread ( ) ;
2015-07-14 13:03:11 -04:00
// GUseThreadedRendering should be set to false after StopRenderingThread call since
// otherwise a wrong context could be used.
GUseThreadedRendering = false ;
2014-03-14 14:13:41 -04:00
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 )
{
2014-09-02 05:19:25 -04:00
DECLARE_CYCLE_STAT ( TEXT ( " FSimpleDelegateGraphTask.SuspendRendering " ) ,
STAT_FSimpleDelegateGraphTask_SuspendRendering ,
STATGROUP_TaskGraphTasks ) ;
2014-03-14 14:13:41 -04:00
FGraphEventRef CompleteHandle = FSimpleDelegateGraphTask : : CreateAndDispatchWhenReady (
FSimpleDelegateGraphTask : : FDelegate : : CreateStatic ( & SuspendRendering ) ,
2014-09-02 05:19:25 -04:00
GET_STATID ( STAT_FSimpleDelegateGraphTask_SuspendRendering ) , NULL , ENamedThreads : : RenderThread ) ;
2014-03-14 14:13:41 -04:00
// 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
{
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FSuspendRenderingThread ) ;
2014-03-14 14:13:41 -04:00
FTaskGraphInterface : : Get ( ) . WaitUntilTaskCompletes ( CompleteHandle , ENamedThreads : : GameThread ) ;
}
check ( GIsRenderingThreadSuspended ) ;
// Now tell the render thread to busy wait until it's resumed
2014-09-02 05:19:25 -04:00
DECLARE_CYCLE_STAT ( TEXT ( " FSimpleDelegateGraphTask.WaitAndResumeRendering " ) ,
STAT_FSimpleDelegateGraphTask_WaitAndResumeRendering ,
STATGROUP_TaskGraphTasks ) ;
2014-03-14 14:13:41 -04:00
FSimpleDelegateGraphTask : : CreateAndDispatchWhenReady (
FSimpleDelegateGraphTask : : FDelegate : : CreateStatic ( & WaitAndResumeRendering ) ,
2014-09-02 05:19:25 -04:00
GET_STATID ( STAT_FSimpleDelegateGraphTask_WaitAndResumeRendering ) , NULL , ENamedThreads : : RenderThread ) ;
2014-03-14 14:13:41 -04:00
}
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 ( )
{
2014-11-24 12:51:18 -05:00
# 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
2014-03-14 14:13:41 -04:00
if ( bRecreateThread )
{
GUseThreadedRendering = bUseRenderingThread ;
FPlatformAtomics : : InterlockedDecrement ( & GIsRenderingThreadSuspended ) ;
if ( bUseRenderingThread & & bWasRenderingThreadRunning )
{
StartRenderingThread ( ) ;
// Now tell the render thread to set it self to real time mode
2014-09-02 05:19:25 -04:00
DECLARE_CYCLE_STAT ( TEXT ( " FSimpleDelegateGraphTask.SetRealTimeMode " ) ,
STAT_FSimpleDelegateGraphTask_SetRealTimeMode ,
STATGROUP_TaskGraphTasks ) ;
2014-03-14 14:13:41 -04:00
FSimpleDelegateGraphTask : : CreateAndDispatchWhenReady (
FSimpleDelegateGraphTask : : FDelegate : : CreateStatic ( & FPlatformProcess : : SetRealTimeMode ) ,
2014-09-02 05:19:25 -04:00
GET_STATID ( STAT_FSimpleDelegateGraphTask_SetRealTimeMode ) , NULL , ENamedThreads : : RenderThread
) ;
2014-03-14 14:13:41 -04:00
}
}
else
{
// Resume the render thread again.
FPlatformAtomics : : InterlockedDecrement ( & GIsRenderingThreadSuspended ) ;
}
2016-03-14 21:21:09 -04:00
if ( IsAsyncLoadingMultithreaded ( ) )
{
ResumeAsyncLoading ( ) ;
}
2014-03-14 14:13:41 -04:00
}
/**
* Tick all rendering thread tickable objects
*/
/** Static array of tickable objects that are ticked from rendering thread*/
FTickableObjectRenderThread : : FRenderingThreadTickableObjectsArray FTickableObjectRenderThread : : RenderingThreadTickableObjects ;
2015-06-15 16:35:54 -04:00
FTickableObjectRenderThread : : FRenderingThreadTickableObjectsArray FTickableObjectRenderThread : : RenderingThreadHighFrequencyTickableObjects ;
void TickHighFrequencyTickables ( double CurTime )
{
static double LastHighFreqTime = FPlatformTime : : Seconds ( ) ;
float DeltaSecondsHighFreq = CurTime - LastHighFreqTime ;
// tick any high frequency rendering thread tickables.
for ( int32 ObjectIndex = 0 ; ObjectIndex < FTickableObjectRenderThread : : RenderingThreadHighFrequencyTickableObjects . Num ( ) ; ObjectIndex + + )
{
FTickableObjectRenderThread * TickableObject = FTickableObjectRenderThread : : RenderingThreadHighFrequencyTickableObjects [ ObjectIndex ] ;
// make sure it wants to be ticked and the rendering thread isn't suspended
if ( TickableObject - > IsTickable ( ) )
{
STAT ( FScopeCycleCounter ( TickableObject - > GetStatId ( ) ) ; )
TickableObject - > Tick ( DeltaSecondsHighFreq ) ;
}
}
LastHighFreqTime = CurTime ;
}
2014-03-14 14:13:41 -04:00
void TickRenderingTickables ( )
{
static double LastTickTime = FPlatformTime : : Seconds ( ) ;
2015-06-15 16:35:54 -04:00
2014-03-14 14:13:41 -04:00
// calc how long has passed since last tick
double CurTime = FPlatformTime : : Seconds ( ) ;
float DeltaSeconds = CurTime - LastTickTime ;
2015-06-15 16:35:54 -04:00
TickHighFrequencyTickables ( CurTime ) ;
2014-03-14 14:13:41 -04:00
if ( DeltaSeconds < ( 1.f / GRenderingThreadMaxIdleTickFrequency ) )
{
return ;
}
2014-05-12 08:40:54 -04:00
2014-03-14 14:13:41 -04:00
// 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 ;
2014-09-03 10:52:00 -04:00
/** The RHI thread runnable object. */
class FRHIThread : public FRunnable
{
public :
FRunnableThread * Thread ;
FRHIThread ( )
: Thread ( nullptr )
{
check ( IsInGameThread ( ) ) ;
}
2016-09-30 21:21:09 -04:00
virtual uint32 Run ( ) override
2014-09-03 10:52:00 -04:00
{
2016-03-17 20:30:20 -04:00
FMemory : : SetupTLSCachesOnCurrentThread ( ) ;
2014-09-03 10:52:00 -04:00
FTaskGraphInterface : : Get ( ) . AttachToThread ( ENamedThreads : : RHIThread ) ;
FTaskGraphInterface : : Get ( ) . ProcessThreadUntilRequestReturn ( ENamedThreads : : RHIThread ) ;
2016-03-17 20:30:20 -04:00
FMemory : : ClearAndDisableTLSCachesOnCurrentThread ( ) ;
2014-09-03 10:52:00 -04:00
return 0 ;
}
static FRHIThread & Get ( )
{
static FRHIThread Singleton ;
return Singleton ;
}
void Start ( )
{
2016-02-16 05:48:48 -05:00
Thread = FRunnableThread : : Create ( this , TEXT ( " RHIThread " ) , 512 * 1024 , TPri_SlightlyBelowNormal ,
2015-01-22 11:26:35 -05:00
FPlatformAffinity : : GetRHIThreadMask ( )
2014-09-03 10:52:00 -04:00
) ;
check ( Thread ) ;
}
} ;
2014-03-14 14:13:41 -04:00
/** 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 ( ) ;
}
2014-04-23 20:08:25 -04:00
// set the thread back to real time mode
FPlatformProcess : : SetRealTimeMode ( ) ;
2014-10-27 07:55:33 -04:00
# 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
2016-03-02 13:38:38 -05:00
FCoreDelegates : : PostRenderingThreadCreated . Broadcast ( ) ;
2014-03-14 14:13:41 -04:00
check ( GIsThreadedRendering ) ;
FTaskGraphInterface : : Get ( ) . ProcessThreadUntilRequestReturn ( ENamedThreads : : RenderThread ) ;
FPlatformMisc : : MemoryBarrier ( ) ;
check ( ! GIsThreadedRendering ) ;
2016-03-02 13:38:38 -05:00
FCoreDelegates : : PreRenderingThreadDestroyed . Broadcast ( ) ;
2015-08-12 18:33:47 -04:00
# if STATS
if ( FThreadStats : : WillEverCollectData ( ) )
{
FThreadStats : : ExplicitFlush ( ) ; // Another explicit flush to clean up the ScopeCount established above for any stats lingering since the last frame
}
# endif
2014-03-14 14:13:41 -04:00
ENamedThreads : : RenderThread = ENamedThreads : : GameThread ;
ENamedThreads : : RenderThread_Local = ENamedThreads : : GameThread_Local ;
FPlatformMisc : : MemoryBarrier ( ) ;
}
/**
* Advances stats for the rendering thread.
*/
2014-06-12 12:09:36 -04:00
static void AdvanceRenderingThreadStats ( int64 StatsFrame , int32 MasterDisableChangeTagStartFrame )
2014-03-14 14:13:41 -04:00
{
# if STATS
int64 Frame = StatsFrame ;
if ( ! FThreadStats : : IsCollectingData ( ) | | MasterDisableChangeTagStartFrame ! = FThreadStats : : MasterDisableChangeTag ( ) )
{
Frame = - StatsFrame ; // mark this as a bad frame
}
2015-11-18 16:20:49 -05:00
FThreadStats : : AddMessage ( FStatConstants : : AdvanceFrame . GetEncodedName ( ) , EStatOperation : : AdvanceFrameEventRenderThread , Frame ) ;
2014-03-14 14:13:41 -04:00
if ( IsInActualRenderingThread ( ) )
{
FThreadStats : : ExplicitFlush ( ) ;
}
# endif
}
2014-06-12 12:09:36 -04:00
/**
* 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 ( ) ;
}
}
2014-03-14 14:13:41 -04:00
/** The rendering thread runnable object. */
class FRenderingThread : public FRunnable
{
2016-09-30 21:21:09 -04:00
private :
/** Tracks if we have acquired ownership */
bool bAcquiredThreadOwnership ;
2014-03-14 14:13:41 -04:00
public :
/**
* Sync event to make sure that render thread is bound to the task graph before main thread queues work against it.
*/
FEvent * TaskGraphBoundSyncEvent ;
2015-03-24 07:29:32 -04:00
FRenderingThread ( )
2014-03-14 14:13:41 -04:00
{
2016-09-30 21:21:09 -04:00
bAcquiredThreadOwnership = false ;
2015-01-28 07:31:10 -05:00
TaskGraphBoundSyncEvent = FPlatformProcess : : GetSynchEventFromPool ( true ) ;
2014-03-14 14:13:41 -04:00
RHIFlushResources ( ) ;
}
2015-01-28 07:31:10 -05:00
virtual ~ FRenderingThread ( )
{
FPlatformProcess : : ReturnSynchEventToPool ( TaskGraphBoundSyncEvent ) ;
TaskGraphBoundSyncEvent = nullptr ;
}
2014-03-14 14:13:41 -04:00
// FRunnable interface.
2015-03-24 07:29:32 -04:00
virtual bool Init ( void ) override
2014-03-14 14:13:41 -04:00
{
2014-11-13 12:59:53 -05:00
GRenderThreadId = FPlatformTLS : : GetCurrentThreadId ( ) ;
2016-09-30 21:21:09 -04:00
// Acquire rendering context ownership on the current thread, unless using an RHI thread, which will be the real owner
if ( ! GUseRHIThread )
{
bAcquiredThreadOwnership = true ;
RHIAcquireThreadOwnership ( ) ;
}
2014-03-14 14:13:41 -04:00
return true ;
}
2015-03-24 07:29:32 -04:00
virtual void Exit ( void ) override
2014-03-14 14:13:41 -04:00
{
2016-09-30 21:21:09 -04:00
// Release rendering context ownership on the current thread if we had acquired it
if ( bAcquiredThreadOwnership )
{
bAcquiredThreadOwnership = false ;
RHIReleaseThreadOwnership ( ) ;
}
2014-11-13 12:59:53 -05:00
GRenderThreadId = 0 ;
2014-03-14 14:13:41 -04:00
}
2016-01-14 08:11:47 -05:00
# if PLATFORM_WINDOWS && !PLATFORM_SEH_EXCEPTIONS_DISABLED
static int32 FlushRHILogsAndReportCrash ( LPEXCEPTION_POINTERS ExceptionInfo )
{
if ( GDynamicRHI )
{
GDynamicRHI - > FlushPendingLogs ( ) ;
}
return ReportCrash ( ExceptionInfo ) ;
}
# endif
2015-03-24 07:29:32 -04:00
virtual uint32 Run ( void ) override
2014-03-14 14:13:41 -04:00
{
2016-03-17 20:30:20 -04:00
FMemory : : SetupTLSCachesOnCurrentThread ( ) ;
2016-07-05 14:25:57 -04:00
FPlatformProcess : : SetupRenderThread ( ) ;
2014-06-17 18:27:26 -04:00
2014-03-14 14:13:41 -04:00
# if PLATFORM_WINDOWS
if ( ! FPlatformMisc : : IsDebuggerPresent ( ) | | GAlwaysReportCrash )
{
# if !PLATFORM_SEH_EXCEPTIONS_DISABLED
__try
# endif
{
RenderingThreadMain ( TaskGraphBoundSyncEvent ) ;
}
# if !PLATFORM_SEH_EXCEPTIONS_DISABLED
2016-01-14 08:11:47 -05:00
__except ( FlushRHILogsAndReportCrash ( GetExceptionInformation ( ) ) )
2014-03-14 14:13:41 -04:00
{
GRenderingThreadError = GErrorHist ;
// 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 ) ;
}
2016-03-17 20:30:20 -04:00
FMemory : : ClearAndDisableTLSCachesOnCurrentThread ( ) ;
2014-03-14 14:13:41 -04:00
return 0 ;
}
} ;
/**
* If the rendering thread is in its idle loop (which ticks rendering tickables
*/
volatile bool GRunRenderingThreadHeartbeat = false ;
2016-06-08 16:02:23 -04:00
FThreadSafeCounter OutstandingHeartbeats ;
2014-03-14 14:13:41 -04:00
/** The rendering thread heartbeat runnable object. */
class FRenderingThreadTickHeartbeat : public FRunnable
{
public :
// FRunnable interface.
virtual bool Init ( void )
{
2016-06-08 16:02:23 -04:00
OutstandingHeartbeats . Reset ( ) ;
2014-03-14 14:13:41 -04:00
return true ;
}
virtual void Exit ( void )
{
}
virtual void Stop ( void )
{
}
virtual uint32 Run ( void )
{
while ( GRunRenderingThreadHeartbeat )
{
FPlatformProcess : : Sleep ( 1.f / ( 4.0f * GRenderingThreadMaxIdleTickFrequency ) ) ;
2016-06-08 16:02:23 -04:00
if ( ! GIsRenderingThreadSuspended & & OutstandingHeartbeats . GetValue ( ) < 4 )
2014-03-14 14:13:41 -04:00
{
2016-06-08 16:02:23 -04:00
OutstandingHeartbeats . Increment ( ) ;
2014-03-14 14:13:41 -04:00
ENQUEUE_UNIQUE_RENDER_COMMAND (
HeartbeatTickTickables ,
{
2016-06-08 16:02:23 -04:00
OutstandingHeartbeats . Decrement ( ) ;
2014-03-14 14:13:41 -04:00
// 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 ;
} ) ;
}
2016-03-13 18:53:13 -04:00
virtual void OnCVarChange ( bool & Dest , bool NewValue )
{
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER (
OnCVarChange2 ,
bool & , Dest , Dest ,
bool , NewValue , NewValue ,
{
Dest = NewValue ;
} ) ;
}
2014-03-14 14:13:41 -04:00
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 ;
}
} ;
2014-05-05 11:20:07 -04:00
static FString BuildRenderingThreadName ( uint32 ThreadIndex )
{
return FString : : Printf ( TEXT ( " %s %u " ) , * FName ( NAME_RenderThread ) . GetPlainNameString ( ) , ThreadIndex ) ;
}
2016-09-30 21:21:09 -04:00
class FOwnershipOfRHIThreadTask : public FCustomStatIDGraphTaskBase
{
public :
/**
* Constructor
* @param StatId The stat id for this task.
* @param InDesiredThread; Thread to run on, can be ENamedThreads::AnyThread
**/
FOwnershipOfRHIThreadTask ( bool bInAcquireOwnership , TStatId StatId )
: FCustomStatIDGraphTaskBase ( StatId )
, bAcquireOwnership ( bInAcquireOwnership )
{
}
/**
* Retrieve the thread that this task wants to run on.
* @return the thread that this task should run on.
**/
ENamedThreads : : Type GetDesiredThread ( )
{
return ENamedThreads : : RHIThread ;
}
static ESubsequentsMode : : Type GetSubsequentsMode ( ) { return ESubsequentsMode : : TrackSubsequents ; }
/**
* Actually execute the task.
* @param CurrentThread; the thread we are running on
* @param MyCompletionGraphEvent; my completion event. Not always useful since at the end of DoWork, you can assume you are done and hence further tasks do not need you as a prerequisite.
* However, MyCompletionGraphEvent can be useful for passing to other routines or when it is handy to set up subsequents before you actually do work.
**/
void DoTask ( ENamedThreads : : Type CurrentThread , const FGraphEventRef & MyCompletionGraphEvent )
{
// note that this task is the first task run on the thread, before GRHIThread is assigned, so we can't check IsInRHIThread()
if ( bAcquireOwnership )
{
GDynamicRHI - > RHIAcquireThreadOwnership ( ) ;
}
else
{
GDynamicRHI - > RHIReleaseThreadOwnership ( ) ;
}
}
private :
bool bAcquireOwnership ;
} ;
2014-03-14 14:13:41 -04:00
void StartRenderingThread ( )
{
static uint32 ThreadCount = 0 ;
check ( ! GIsThreadedRendering & & GUseThreadedRendering ) ;
2014-09-03 10:52:00 -04:00
check ( ! GRHIThread )
if ( GUseRHIThread )
{
2015-11-10 17:11:09 -05:00
FRHICommandListExecutor : : GetImmediateCommandList ( ) . ImmediateFlush ( EImmediateFlushType : : DispatchToRHIThread ) ;
2014-09-03 10:52:00 -04:00
if ( ! FTaskGraphInterface : : Get ( ) . IsThreadProcessingTasks ( ENamedThreads : : RHIThread ) )
{
FRHIThread : : Get ( ) . Start ( ) ;
}
DECLARE_CYCLE_STAT ( TEXT ( " Wait For RHIThread " ) , STAT_WaitForRHIThread , STATGROUP_TaskGraphTasks ) ;
2016-09-30 21:21:09 -04:00
FGraphEventRef CompletionEvent = TGraphTask < FOwnershipOfRHIThreadTask > : : CreateTask ( NULL , ENamedThreads : : GameThread ) . ConstructAndDispatchWhenReady ( true , GET_STATID ( STAT_WaitForRHIThread ) ) ;
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_StartRenderingThread ) ;
2014-09-03 10:52:00 -04:00
FTaskGraphInterface : : Get ( ) . WaitUntilTaskCompletes ( CompletionEvent , ENamedThreads : : GameThread_Local ) ;
GRHIThread = FRHIThread : : Get ( ) . Thread ;
check ( GRHIThread ) ;
2014-10-09 14:05:15 -04:00
GRHICommandList . LatchBypass ( ) ;
2014-09-03 10:52:00 -04:00
}
2014-03-14 14:13:41 -04:00
// Turn on the threaded rendering flag.
GIsThreadedRendering = true ;
// Create the rendering thread.
GRenderingThreadRunnable = new FRenderingThread ( ) ;
2016-09-30 21:21:09 -04:00
GRenderingThread = FRunnableThread : : Create ( GRenderingThreadRunnable , * BuildRenderingThreadName ( ThreadCount ) , 0 , FPlatformAffinity : : GetRenderingThreadPriority ( ) , FPlatformAffinity : : GetRenderingThreadMask ( ) ) ;
2014-03-14 14:13:41 -04:00
// 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 ( ) ;
2014-05-12 08:40:54 -04:00
GRenderingThreadHeartbeat = FRunnableThread : : Create ( GRenderingThreadRunnableHeartbeat , * FString : : Printf ( TEXT ( " RTHeartBeat %d " ) , ThreadCount ) , 16 * 1024 , TPri_AboveNormal , FPlatformAffinity : : GetRTHeartBeatMask ( ) ) ;
2014-03-14 14:13:41 -04:00
ThreadCount + + ;
}
2016-09-30 21:21:09 -04:00
2014-03-14 14:13:41 -04:00
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 ( ) ;
2016-08-31 21:22:32 -04:00
delete GRenderingThreadHeartbeat ;
2014-03-14 14:13:41 -04:00
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 )
{
2014-09-03 10:52:00 -04:00
if ( GRHIThread )
{
DECLARE_CYCLE_STAT ( TEXT ( " Wait For RHIThread Finish " ) , STAT_WaitForRHIThreadFinish , STATGROUP_TaskGraphTasks ) ;
2016-09-30 21:21:09 -04:00
FGraphEventRef ReleaseTask = TGraphTask < FOwnershipOfRHIThreadTask > : : CreateTask ( NULL , ENamedThreads : : GameThread ) . ConstructAndDispatchWhenReady ( false , GET_STATID ( STAT_WaitForRHIThreadFinish ) ) ;
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_StopRenderingThread_RHIThread ) ;
2016-09-30 21:21:09 -04:00
FTaskGraphInterface : : Get ( ) . WaitUntilTaskCompletes ( ReleaseTask , ENamedThreads : : GameThread_Local ) ;
2014-09-03 10:52:00 -04:00
GRHIThread = nullptr ;
}
2014-03-14 14:13:41 -04:00
check ( GRenderingThread ) ;
check ( ! GIsRenderingThreadSuspended ) ;
// Turn off the threaded rendering flag.
GIsThreadedRendering = false ;
2014-09-03 10:52:00 -04:00
{
FGraphEventRef QuitTask = TGraphTask < FReturnGraphTask > : : CreateTask ( NULL , ENamedThreads : : GameThread ) . ConstructAndDispatchWhenReady ( ENamedThreads : : RenderThread ) ;
2014-03-14 14:13:41 -04:00
2014-09-03 10:52:00 -04:00
// 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 ) )
2014-03-14 14:13:41 -04:00
{
2014-09-03 10:52:00 -04:00
while ( ( QuitTask . GetReference ( ) ! = nullptr ) & & ! QuitTask - > IsComplete ( ) )
{
FPlatformProcess : : Sleep ( 0.0f ) ;
}
}
else
{
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_StopRenderingThread ) ;
2014-09-03 10:52:00 -04:00
FTaskGraphInterface : : Get ( ) . WaitUntilTaskCompletes ( QuitTask , ENamedThreads : : GameThread_Local ) ;
2014-03-14 14:13:41 -04:00
}
}
// Wait for the rendering thread to return.
GRenderingThread - > WaitForCompletion ( ) ;
// Destroy the rendering thread objects.
delete GRenderingThread ;
GRenderingThread = NULL ;
2015-07-21 14:31:06 -04:00
GRHICommandList . LatchBypass ( ) ;
2014-03-14 14:13:41 -04:00
delete GRenderingThreadRunnable ;
GRenderingThreadRunnable = NULL ;
}
// Delete the pending cleanup objects which were in use by the rendering thread.
delete PendingCleanupObjects ;
}
2016-09-30 21:21:09 -04:00
2014-09-03 10:52:00 -04:00
check ( ! GRHIThread ) ;
2014-03-14 14:13:41 -04:00
}
void CheckRenderingThreadHealth ( )
{
if ( ! GIsRenderingThreadHealthy )
{
GErrorHist [ 0 ] = 0 ;
GIsCriticalError = false ;
UE_LOG ( LogRendererCore , Fatal , TEXT ( " Rendering thread exception: \r \n %s " ) , * GRenderingThreadError ) ;
}
if ( IsInGameThread ( ) )
{
2016-08-18 20:28:33 -04:00
if ( ! GIsCriticalError )
{
GLog - > FlushThreadedLogs ( ) ;
}
2014-03-14 14:13:41 -04:00
# 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
{
2014-09-02 05:19:25 -04:00
DECLARE_CYCLE_STAT ( TEXT ( " FNullGraphTask.FenceRenderCommand " ) ,
STAT_FNullGraphTask_FenceRenderCommand ,
STATGROUP_TaskGraphTasks ) ;
2016-05-06 08:00:16 -04:00
CompletionEvent = TGraphTask < FNullGraphTask > : : CreateTask ( NULL , ENamedThreads : : GameThread ) . ConstructAndDispatchWhenReady (
GET_STATID ( STAT_FNullGraphTask_FenceRenderCommand ) , ENamedThreads : : RenderThread ) ;
2014-03-14 14:13:41 -04:00
}
}
bool FRenderCommandFence : : IsFenceComplete ( ) const
{
if ( ! GIsThreadedRendering )
{
return true ;
}
2015-04-01 03:03:18 -04:00
check ( IsInGameThread ( ) | | IsInAsyncLoadingThread ( ) ) ;
2014-03-14 14:13:41 -04:00
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 )
{
2016-07-29 17:10:25 -04:00
SCOPE_TIME_GUARD ( TEXT ( " GameThreadWaitForTask " ) ) ;
2014-03-14 14:13:41 -04:00
check ( IsInGameThread ( ) ) ;
2016-07-29 17:10:25 -04:00
check ( IsValidRef ( Task ) ) ;
2014-03-14 14:13:41 -04:00
if ( ! Task - > IsComplete ( ) )
{
SCOPE_CYCLE_COUNTER ( STAT_GameIdleTime ) ;
{
static int32 NumRecursiveCalls = 0 ;
2015-01-28 07:31:10 -05:00
2014-03-14 14:13:41 -04:00
// 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.
2015-01-28 07:31:10 -05:00
FEvent * Event = FPlatformProcess : : GetSynchEventFromPool ( ) ;
2014-03-14 14:13:41 -04:00
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 )
{
2015-01-28 07:31:10 -05:00
// process gamethread tasks if there are any
2014-03-14 14:13:41 -04:00
FTaskGraphInterface : : Get ( ) . ProcessThreadUntilIdle ( ENamedThreads : : GameThread ) ;
}
bDone = Event - > Wait ( WaitTime ) ;
}
while ( ! bDone ) ;
// Return the event to the pool and decrement the recursion counter.
2015-01-28 07:31:10 -05:00
FPlatformProcess : : ReturnSynchEventToPool ( Event ) ;
Event = nullptr ;
2014-03-14 14:13:41 -04:00
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)
{
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER(STAT_FRenderCommandFence_Wait);
2014-03-14 14:13:41 -04:00
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 ( ) ) ;
2016-01-27 07:18:43 -05:00
FGraphEventRef PendingComplete = FrameRenderPrerequisites . CreatePrerequisiteCompletionHandle ( ENamedThreads : : GameThread ) ;
2014-03-14 14:13:41 -04:00
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 ( )
{
2016-06-27 13:42:20 -04:00
if ( ! GIsRHIInitialized )
{
return ;
}
2014-09-11 09:38:38 -04:00
ENQUEUE_UNIQUE_RENDER_COMMAND (
FlushPendingDeleteRHIResources ,
{
GRHICommandList . GetImmediateCommandList ( ) . ImmediateFlush ( EImmediateFlushType : : FlushRHIThreadFlushResources ) ;
}
) ;
2014-07-15 07:34:34 -04:00
2014-03-14 14:13:41 -04:00
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 ;
}
2014-07-15 07:34:34 -04:00
void FlushPendingDeleteRHIResources_GameThread ( )
{
2014-09-03 10:52:00 -04:00
if ( ! GRHIThread )
{
ENQUEUE_UNIQUE_RENDER_COMMAND (
FlushPendingDeleteRHIResources ,
2014-07-15 07:34:34 -04:00
{
FlushPendingDeleteRHIResources_RenderThread ( ) ;
}
2014-09-03 10:52:00 -04:00
) ;
}
2014-07-15 07:34:34 -04:00
}
void FlushPendingDeleteRHIResources_RenderThread ( )
{
2014-09-03 10:52:00 -04:00
if ( ! GRHIThread )
{
FRHIResource : : FlushPendingDeletes ( ) ;
}
2014-07-15 07:34:34 -04:00
}
2014-06-27 11:07:13 -04:00
FRHICommandListImmediate & GetImmediateCommandList_ForRenderCommand ( )
{
return FRHICommandListExecutor : : GetImmediateCommandList ( ) ;
}
2014-03-14 14:13:41 -04:00
/** The set of deferred cleanup objects which are pending cleanup. */
2016-01-27 07:18:43 -05:00
static TLockFreePointerListUnordered < FDeferredCleanupInterface , PLATFORM_CACHE_LINE_SIZE > PendingCleanupObjectsList ;
2014-03-14 14:13:41 -04:00
FPendingCleanupObjects : : FPendingCleanupObjects ( )
{
check ( IsInGameThread ( ) ) ;
PendingCleanupObjectsList . PopAll ( CleanupArray ) ;
}
FPendingCleanupObjects : : ~ FPendingCleanupObjects ( )
{
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FPendingCleanupObjects_Destruct ) ;
2014-03-14 14:13:41 -04:00
for ( int32 ObjectIndex = 0 ; ObjectIndex < CleanupArray . Num ( ) ; ObjectIndex + + )
{
CleanupArray [ ObjectIndex ] - > FinishCleanup ( ) ;
}
}
void BeginCleanup ( FDeferredCleanupInterface * CleanupObject )
{
PendingCleanupObjectsList . Push ( CleanupObject ) ;
}
FPendingCleanupObjects * GetPendingCleanupObjects ( )
{
return new FPendingCleanupObjects ;
}
2016-03-31 15:18:30 -04:00
void SetRHIThreadEnabled ( bool bEnable )
{
if ( bEnable ! = GUseRHIThread )
{
if ( GRHISupportsRHIThread )
{
if ( ! GIsThreadedRendering )
{
check ( ! GRHIThread ) ;
UE_LOG ( LogRendererCore , Display , TEXT ( " Can't switch to RHI thread mode when we are not running a multithreaded renderer. " ) ) ;
}
else
{
StopRenderingThread ( ) ;
GUseRHIThread = bEnable ;
StartRenderingThread ( ) ;
}
UE_LOG ( LogRendererCore , Display , TEXT ( " RHIThread is now %s. " ) , GRHIThread ? TEXT ( " active " ) : TEXT ( " inactive " ) ) ;
}
else
{
UE_LOG ( LogRendererCore , Display , TEXT ( " This RHI does not support the RHI thread. " ) ) ;
}
}
}
static void HandleRHIThreadEnableChanged ( const TArray < FString > & Args )
{
if ( Args . Num ( ) > 0 )
{
const bool bUseRHIThread = Args [ 0 ] . ToBool ( ) ;
SetRHIThreadEnabled ( bUseRHIThread ) ;
}
else
{
2016-06-27 13:42:20 -04:00
UE_LOG ( LogRendererCore , Display , TEXT ( " Usage: r.RHIThread.Enable 0/1; Currently %d " ) , ( int32 ) GUseRHIThread ) ;
2016-03-31 15:18:30 -04:00
}
}
static FAutoConsoleCommand CVarRHIThreadEnable (
TEXT ( " r.RHIThread.Enable " ) ,
TEXT ( " Enables/disabled the RHI Thread \n " ) ,
FConsoleCommandWithArgsDelegate : : CreateStatic ( & HandleRHIThreadEnableChanged )
2016-06-27 13:42:20 -04:00
) ;