You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
==========================
MAJOR FEATURES + CHANGES
==========================
#lockdown Nick.Penwarden
Change 2903938 on 2016/03/10 by Frank.Gigliotti
Added an instance ID to FAnimMontageInstance
#CodeReview Laurent.Delayen
#RB Laurent.Delayen
#Tests PIE
Change 2903745 on 2016/03/10 by Wes.Hunt
Update Oodle TPS
#rb none
#tests none
#codereview:john.pollard
Change 2903689 on 2016/03/10 by Uriel.Doyon
New "LogHeroMaterials" console command, displaying the current state of materials and textures on the character hero.
#rb marcus.wasmer
#codereview marcus.wassmer
#tests editor, playing PC games, trying the new command
Change 2903669 on 2016/03/10 by Aaron.McLeran
OR-17180 Make stat soundcues and stat soundwaves NOT display zero volume sounds
- Change only effects debug stat commands for audio guys
#rb none
#tests played paragon with new debug stat commands, confirms doesn't show zero-volume sounds
Change 2903625 on 2016/03/10 by John.Pollard
XB1 Oodle SDK
#rb none
#tests none
#codereview Jeff.Campeau
Change 2903577 on 2016/03/10 by Ben.Marsh
Remaking latest build scripts from //UE4/Main @ 2900980.
Change 2903560 on 2016/03/10 by Ben.Marsh
Initial version of BuildGraph scripts - used to create build processes in UE4 which can be run locally or in parallel across a build farm (assuming synchronization and resource allocation implemented by a separate system). Intended to supersede GUBP.
Build graphs are declared using an XML script using syntax similar to MSBuild, ANT or NAnt, and consist of the following components:
* Tasks: Building blocks which can be executed as part of the build process. Many predefined tasks are provided (<Cook>, <Compile>, <Copy>, <Stage>, <Log>, <PakFile>, etc...), and additional tasks may be added be declaring classes derived from AutomationTool.CustomTask in other UAT modules.
* Nodes: A named sequence of tasks which is executed to produce outputs. Nodes may have input dependencies on other nodes before they can be executed. Declared with the <Node> element in scripts.
* Agent Groups: A set of nodes nodes which is executed on the same machine if running as part of a build system. Has no effect when building locally. Declared with the <Group> element in scripts.
* Triggers: Container for groups which should only be executed when explicitly triggered (using the -Trigger=<Name> or -SkipTriggers command line argument). Declared with the <Trigger> element in scripts.
* Notifiers: Specifies email recipients for failures in one or more nodes, whether they should receive notifications on warnings, and so on.
Properties can be passed in to a script on the command line, or set procedurally with the <Property Name="Foo" Value="Bar"/> syntax. Properties referenced with the $(Property Name) notation are valid within all strings, and will be expanded as macros when the script is read. If a property name is not set explicitly, it defaults to the contents of an environment variable with the same name.
Local properties, which only affect the scope of the containing XML element (node, group, etc...) are declared with the <Local Name="Foo" Value="Bar"/> element, and will override a similarly named global property for the local property's scope.
Any elements can be conditionally defined via the "If" attribute, and are largely identical to MSBuild conditions. Literals in conditions may be quoted with single (') or double (") quotes, or an unquoted sequence of letters, digits and underscore characters. All literals are considered identical regardless of how they are declared, and are considered case-insensitive for comparisons (so true equals 'True', equals "TRUE"). Available operators are "==", "!=", "And", "Or", "!", "(...)", "Exists(...)" and "HasTrailingSlash(...)". A full grammar is written up in Condition.cs.
File manipulation is done using wildcards and tags. Any attribute that accepts a list of files may consist of: a Perforce-style wildcard (matching any number of "...", "*" and "?" patterns in any location), a full path name, or a reference to a tagged collection of files, denoted by prefixing with a '#' character. Files may be added to a tag set using the <Tag> Task, which also allows performing set union/difference style operations. Each node can declare multiple outputs in the form of a list of named tags, which other nodes can then depend on.
Build graphs may be executed in parallel as part build system. To do so, the initial graph configuration is generated by running with the -Export=<Filename> argument (producing a JSON file listing the nodes and dependencies to execute). Each participating agent should be synced to the same changelist, and UAT should be re-run with the appropriate -Node=<Name> argument. Outputs from different nodes are transferred between agents via shared storage, typically a network share, the path to which can be specified on the command line using the -SharedStorageDir=<Path> argument. Note that the allocation of machines, and coordination between them, is assumed to be managed by an external system.
A schema for the known set of tasks can be generated by running UAT with the "-Schema=<FileName>" option. Generating a schema and referencing it from a BuildGraph script allows Visual Studio to validate and auto-complete elements as you type.
#rb none
#codereview Marc.Audy, Wes.Hunt, Matthew.Griffin, Richard.Fawcett
#tests local only so far, but not part of any build process yet
Change 2903539 on 2016/03/10 by John.Pollard
Improve replay playback debugging of character movement
#rb none
#tests replays
Change 2903526 on 2016/03/10 by Ben.Marsh
Remake changes from //UE4/Main without integration history, to add support for BuildGraph tasks.
#rb none
#tests none
Change 2903512 on 2016/03/10 by Dan.Youhon
Modify minimum Duration values for JumpForce and MoveToForce ability tasks so that having minimum Duration values doesn't trigger check()s
#rb None
#tests Compiles
Change 2903474 on 2016/03/10 by Marc.Audy
Fix crash if ChildActor is null
#rb None
#tests None
Change 2903314 on 2016/03/10 by Marc.Audy
Fix ParentComponent not being persisted and fixup content that was saved in the window it was broken
#rb James.Golding
#tests Selection of child actors works as expected
#jira UE-28201
Change 2903298 on 2016/03/10 by Simon.Tovey
Disabling the trails optimization.
#tests none
#rb none
#codereview Olaf.Piesche
Change 2903124 on 2016/03/10 by Robert.Manuszewski
Small refactor to pak signing to help with exe protection
#rb none
#tests none
[CL 2907678 by Andrew Grant in Main branch]
970 lines
29 KiB
C++
970 lines
29 KiB
C++
// Copyright 1998-2016 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 )
|
|
{
|
|
// Suspend async loading thread so that it doesn't start queueing render commands
|
|
// while the render thread is suspended.
|
|
SuspendAsyncLoading();
|
|
|
|
bRecreateThread = bInRecreateThread;
|
|
bUseRenderingThread = GUseThreadedRendering;
|
|
bWasRenderingThreadRunning = GIsThreadedRendering;
|
|
if ( bRecreateThread )
|
|
{
|
|
StopRenderingThread();
|
|
// GUseThreadedRendering should be set to false after StopRenderingThread call since
|
|
// otherwise a wrong context could be used.
|
|
GUseThreadedRendering = false;
|
|
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 );
|
|
}
|
|
ResumeAsyncLoading();
|
|
}
|
|
|
|
|
|
/**
|
|
* Tick all rendering thread tickable objects
|
|
*/
|
|
|
|
/** Static array of tickable objects that are ticked from rendering thread*/
|
|
FTickableObjectRenderThread::FRenderingThreadTickableObjectsArray FTickableObjectRenderThread::RenderingThreadTickableObjects;
|
|
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;
|
|
}
|
|
|
|
void TickRenderingTickables()
|
|
{
|
|
static double LastTickTime = FPlatformTime::Seconds();
|
|
|
|
|
|
// calc how long has passed since last tick
|
|
double CurTime = FPlatformTime::Seconds();
|
|
float DeltaSeconds = CurTime - LastTickTime;
|
|
|
|
TickHighFrequencyTickables(CurTime);
|
|
|
|
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_SlightlyBelowNormal,
|
|
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
|
|
|
|
FCoreDelegates::PostRenderingThreadCreated.Broadcast();
|
|
check(GIsThreadedRendering);
|
|
FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RenderThread);
|
|
FPlatformMisc::MemoryBarrier();
|
|
check(!GIsThreadedRendering);
|
|
FCoreDelegates::PreRenderingThreadDestroyed.Broadcast();
|
|
|
|
#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
|
|
|
|
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
|
|
}
|
|
FThreadStats::AddMessage(FStatConstants::AdvanceFrame.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;
|
|
}
|
|
|
|
#if PLATFORM_WINDOWS && !PLATFORM_SEH_EXCEPTIONS_DISABLED
|
|
static int32 FlushRHILogsAndReportCrash(LPEXCEPTION_POINTERS ExceptionInfo)
|
|
{
|
|
if (GDynamicRHI)
|
|
{
|
|
GDynamicRHI->FlushPendingLogs();
|
|
}
|
|
|
|
return ReportCrash(ExceptionInfo);
|
|
}
|
|
#endif
|
|
|
|
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(FlushRHILogsAndReportCrash(GetExceptionInformation()))
|
|
{
|
|
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 );
|
|
}
|
|
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(bool& Dest, bool NewValue)
|
|
{
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
|
|
OnCVarChange2,
|
|
bool&, Dest, Dest,
|
|
bool, 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)
|
|
{
|
|
FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
|
|
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;
|
|
|
|
GRHICommandList.LatchBypass();
|
|
|
|
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)
|
|
{
|
|
GErrorHist[0] = 0;
|
|
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(ENamedThreads::GameThread);
|
|
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 TLockFreePointerListUnordered<FDeferredCleanupInterface, PLATFORM_CACHE_LINE_SIZE> 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;
|
|
}
|