2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-03-14 14:13:41 -04:00
/*=============================================================================
RenderingThread . cpp : Rendering thread implementation .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
# 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
}
// 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 )
{
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 ) ;
}
}
/**
* 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 ;
}
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 ( ) ) ;
}
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 ,
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
2014-03-14 14:13:41 -04:00
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 .
*/
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
}
2014-10-27 07:55:33 -04:00
// @TODO yrx 2014-10-17 Add AddAdvanceFrame message
Simple and Group Stat Exec commands can now be triggered from the level viewport Show menu directly.
#ttp 306334 - ROCKET: TASK: PUNTABLE: Stats: FN: Make diagnostic stats discoverable and available in the UI (don't require console to toggle)
#branch UE4
#change
DECLARE_STATS_GROUP û Added additional param GroupCategory, for subfolder use in the UI. Fixedup all Stats Group usage so the category is now propagated through where it needs to be.
Currently all Group stats have the Category æAdvancedÆ, and all engine stats have the Category æSimpleÆ û this is just to differentiate them for now, better categories will come along in future.
Modified FindOrAddMetaData as it now broadcasts a delegate (via a TaskGraph) whenever a new stat meta data is added û this was needed as not all the stat groups are æregisteredÆ when the level viewports are created (they are drip loaded), so the viewports need to listen for any additions thereafter...
GroupDescription is displayed as a tooltip in the UI for the stat entry and we may want to localize these.
RenderStats & RenderGroupedWithHierarchy: Modified so that it now takes the viewport that it should render to as a param (which is also used to determine if each stat should be visible).
Removed StatsEnabled delegate in favour of StatCheckEnabled, StatEnabled, StatDisabled, StatDisableAll for more finite usage and feedback when toggling them.
Modified FHUDGroupManager HandleCommand It now uses the new delegates to work out whether it needs to enable or disable for the current viewport, so itÆs more involved than a simple toggle, itÆs more ôis the stat enabled for the current viewport, and is it enabled for any viewportö delegate querying so it can react accordingly.
Added struct FSimpleStatFuncs: Which contains info on each æSimple StatÆ such as; name, category, description, renderfunc, togglefunc and the side of the viewport it should be rendered to
ExecSimpleStat û Calls Exec for a registered Simple Stat, ensuring the correct viewport is set
IsSimpleStat û Checks to see if a stat is a registered Simple Stat or not
SetSimpleStat û Sets the state of a specified Simple Stats
SetSimpleStats - Sets the state of the specified Simple Stats
RenderSimpleStats û Renders the Simple Stats if they are enabled, and have Render functions assigned.
Each Exec function had the code it executes which itÆs toggled and rendered into functions
Added FStatUnitData & FStatHitchesData: Moved all the globals/static variables used when enabling Stat Unit/Hitches into a struct as itÆs now used by multiple viewports and they needed their own copies. Also moved their draw functions here too.
FSceneViewport:
SwapStatCommands û Exchanges the enabled stats between two viewports, this is so when PIEing the stats which were enabled on the Level Viewport (if playing in active viewport only) get transposed to the Game Viewport, and then restored when PIE ends.
SEditorViewport:
ToggleStatCommand û Called when a stat is enabled/disabled from the UI
IsStatCommandVisible û Checks to see if a stat command should appear as visible in the UI
SEditorViewportViewMenu:
GenerateViewMenuContent û Made protected and virtual so it could be called externally.
FLevelViewportCommands: Added the code needed to generate commands for each of the Stat menu entries, however because not all stats are registered when this happens, it also creates some delegates to listen out for others that are registered later
Destructor û Needed to reset delegates
HandleNewGroupStat û Creates the new group stat commands
HandleNewStat û Creates the new stat command
FindStatIndex û Looks for where a stat should be inserted in the menu in order to maintain alphabetical order
SLevelViewport:
Modified the code so that the states of all the SimpleStats are saved so they can be restored next time the editor is ran (previously just handled FPS).
OnFloatingButtonClicked û Called whenever any of the level viewports floating buttons are clicked in order to correctly set the ælastÆ viewport global
OnToggleAllStatCommands û Called when the user selects æHide AllÆ from the viewport.
ToggleStatCommand û Called when the user selects any other stat option from the viewport.
BindStatCommand û Used to bind the menu action to the command name (used by delegate)
Added SLevelEditorViewportViewMenu (extends SEditorViewportViewMenu), and overrode GenerateViewMenuContent so that OnFloatingButtonClicked can be called whenever the menu is clicked on. This is also called during GenerateOptionsMenu, GenerateCameraMenu, GenerateShowMenu & OnToggleMaximize
Added global ptr GStatProcessingViewportClient (sim to Current, Last) used to keep track of which viewport the stat should be applied too (only valid within the scope of the Exec call).
FViewportClient:
Moved global ESoundShowFlags enum list into this class.
FCommonViewportClient:
Destructor û Needed to reset GStatProcessingViewportClient
FLevelEditorViewportClient
SetCurrentViewport û moved code responsible for setting the global æcurrentÆ viewport ptr into a func
SetLastKeyViewport û moved the code responsible for settings the global ælastÆ viewport ptr into a func
UGameViewportClient:
Destructor û Needed to cleanup delegate usage.
FViewportClient & FLevelEditorViewportClient & UGameViewportClient*
GetStatUnitData û The viewports copy of the variables needed when running the Stat Unit Exec
GetStatHitchesData û The viewports copy of the variables needed when running the Stat Hitches Exec
GetEnabledStats û Gets a list of all the stats which are enabled for the viewport
SetEnabledStats û Sets a list of all the stats which should be enabled for the viewport
IsStatEnabled û Checks to see if a specific stat is enabled for the viewport
SetStatEnabled û Sets a specifics stats state to enabled or disabled
GetSoundShowFlags û Gets which flags are enabled for the Stat Sounds Exec
SetSoundShowFlags û Sets which flags are enabled for the Stat Sounds Exec
HandleViewportStatCheckEnabled (delegate) û checks to see if a specific stat is enabled on this viewport
HandleViewportStatEnabled (delegate) û enables a specific stat for the viewport
HandleViewportStatDisabled (delegate) û disables a specific stat for the viewport
HandleViewportStatDisableAll (delegate) û disables all stats for the viewport
*FViewportClient has dummy virtual funcs and LevelEditor/Game both have the same implementations, the only differences is the GameViewports member variables are static so that the stat info persists between runs.
FLevelEditorViewportInstanceSettings deprecated bShowFPS in favour of an EnabledStats array (so we can track the state of all stats, not just FPS).
Added new config var bSaveSimpleStats: if enabled, restores previously enabled level viewport simple stats the next time the editor runs (defaults to false).
Modified FillShowFlagMenu so that thereÆs just one func and you specify where (if any) youÆd like a separator to occur.
Added FillShowStatsSubMenus so that menus can be generated which have submenus
Added the Stats sub menu to the View menu
Modified Execs so that the GStatProcessingViewportClient is set to the correct default viewport (if it wasnÆt specified), and clears again after itÆs been processed
HandleStatCommand now takes World and ViewportClient as params too û needed when Execs enabled other Execs so the world/viewport persists.
SetAverageUnitTimes û Added as a Setter func for GetAverageUnitTimes (moved code out of Stat Unit renderer and modified so that it only updates once per frame).
Stripped out all unneeded globals
[CL 2058522 by Andrew Brown in Main branch]
2014-04-29 04:04:27 -04:00
static FStatNameAndInfo Adv ( NAME_AdvanceFrame , " " , " " , TEXT ( " " ) , EStatDataType : : ST_int64 , true , false ) ;
2014-03-14 14:13:41 -04:00
FThreadStats : : AddMessage ( Adv . GetEncodedName ( ) , EStatOperation : : AdvanceFrameEventRenderThread , Frame ) ;
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
{
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
{
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 ( ) ;
2014-03-14 14:13:41 -04:00
// Acquire rendering context ownership on the current thread
RHIAcquireThreadOwnership ( ) ;
return true ;
}
2015-03-24 07:29:32 -04:00
virtual void Exit ( void ) override
2014-03-14 14:13:41 -04:00
{
// Release rendering context ownership on the current thread
RHIReleaseThreadOwnership ( ) ;
2014-11-13 12:59:53 -05:00
GRenderThreadId = 0 ;
2014-03-14 14:13:41 -04:00
}
2015-03-24 07:29:32 -04:00
virtual uint32 Run ( void ) override
2014-03-14 14:13:41 -04:00
{
2014-06-17 18:27:26 -04:00
FPlatformProcess : : SetupGameOrRenderThread ( true ) ;
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
__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 ;
}
} ;
2014-05-05 11:20:07 -04:00
static FString BuildRenderingThreadName ( uint32 ThreadIndex )
{
return FString : : Printf ( TEXT ( " %s %u " ) , * FName ( NAME_RenderThread ) . GetPlainNameString ( ) , ThreadIndex ) ;
}
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 )
{
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 ) ;
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 ( ) ;
2014-05-12 08:40:54 -04:00
GRenderingThread = FRunnableThread : : Create ( GRenderingThreadRunnable , * BuildRenderingThreadName ( ThreadCount ) , 0 , TPri_Normal , 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 + + ;
}
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 )
{
2014-09-03 10:52:00 -04:00
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 ) ;
2015-03-04 08:31:40 -05:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_StopRenderingThread_RHIThread ) ;
2014-09-03 10:52:00 -04:00
FTaskGraphInterface : : Get ( ) . WaitUntilTaskCompletes ( FlushTask , ENamedThreads : : GameThread_Local ) ;
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 ;
delete GRenderingThreadRunnable ;
GRenderingThreadRunnable = NULL ;
}
// Delete the pending cleanup objects which were in use by the rendering thread.
delete PendingCleanupObjects ;
}
2014-09-03 10:52:00 -04:00
check ( ! GRHIThread ) ;
2014-03-14 14:13:41 -04:00
}
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
{
2014-09-02 05:19:25 -04:00
DECLARE_CYCLE_STAT ( TEXT ( " FNullGraphTask.FenceRenderCommand " ) ,
STAT_FNullGraphTask_FenceRenderCommand ,
STATGROUP_TaskGraphTasks ) ;
2014-03-14 14:13:41 -04:00
if ( IsFenceComplete ( ) )
{
2014-09-02 05:19:25 -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
}
else
{
// we already had a fence, so we will chain this one to the old one as a prerequisite
FGraphEventArray Prerequistes ;
Prerequistes . Add ( CompletionEvent ) ;
2014-09-02 05:19:25 -04:00
CompletionEvent = TGraphTask < FNullGraphTask > : : CreateTask ( & Prerequistes , 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 )
{
check ( IsInGameThread ( ) ) ;
check ( IsValidRef ( Task ) ) ;
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 ( ) ) ;
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 ( )
{
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. */
static TLockFreePointerList < FDeferredCleanupInterface > PendingCleanupObjectsList ;
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 ;
}