You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change3091951on 2016/08/17 by Chris.Bunner (Duplicate) CL 3090919: Fixed edge case interactions in HLOD ray rejection logic in Lightmass. Change3093162on 2016/08/18 by Ben.Woodhouse Fix minor memory leak (missing delete of RT Heartbeat thread) Change 3093470 on 2016/08/18 by Ben.Woodhouse Fix minor leak in FMonitoredProcess - the Thread member would get leaked if the FMonitoredProcess was cancelled, because it gets NULLed without deleting it. Fix is to add a bool to keep track of whether the thread is running, rather than using the Thread pointer. Also fixes a race condition where the FMonitoredProcess::Thread member could get initialized after the thread had completed. This would cause IsRunning to never return false, even if the thread has completed, and the editor would hang on startup (this was fixed by setting bIsRunning to true before creating the thread) Change 3093698 on 2016/08/18 by Daniel.Wright Translucent lighting volume draw event cleanup Change 3093700 on 2016/08/18 by Daniel.Wright Clamp on box reflection capture transition distance visualizer Change 3093755 on 2016/08/18 by Ryan.Vance Merging stereo planar reflections from Odin. Change 3094060 on 2016/08/18 by Daniel.Wright Fully featured base pass reflection captures with blending and parallax correction * Used in the forward renderer when materials opt-in to 'High Quality Reflections' * Used in the deferred renderer for translucent 'Surface ForwardShading' materials * Reflection captures are culled to a frustum space grid using the same reverse linked-list method as lights in the forward renderer * Fixed grid culling in stereo / splitscreen * The ReflectionEnvironment compute shader used in the deferred path also uses the culled grid now which reduces its cost from .93ms -> .70ms on 970 GTX. PS4 cost is about the same. * Capsule indirect self-shadowing is now reduced in the forward path to match deferred, and both are controlled by r.CapsuleIndirectShadowSelfShadowIntensity * SetupHZB is now skipped when SSAO / SSR / HZB are all disabled Change 3094160 on 2016/08/18 by Daniel.Wright CIS fixes Change 3094899 on 2016/08/19 by Ben.Woodhouse Batching optimization for dragging components onto blueprints, reported on UDN. Adding 2300 static mesh actors now takes 3 seconds instead of 40 minutes. https://udn.unrealengine.com/questions/305821/suspected-rhi-uniform-buffer-leak-when-adding-stat.html #jira UE-34937 Change 3095256 on 2016/08/19 by Daniel.Wright Disabled ISR warning spamming CIS Change 3095468 on 2016/08/19 by Daniel.Wright Fixed refcounting on hit proxy render targets Change 3095470 on 2016/08/19 by Daniel.Wright Added bVisibleInReflectionCaptures to primitive component, which is useful for hiding objects too close to the capture point Change 3096274 on 2016/08/22 by Rolando.Caloca DR - vk - added missing BC4 Change 3096291 on 2016/08/22 by Rolando.Caloca DR - vk - Fix image views for some rendertarget formats - Fix ImageViews on sub mips Change 3096579 on 2016/08/22 by Rolando.Caloca DR - vk - Fix rendering for shaders with no descriptors Change 3096584 on 2016/08/22 by Rolando.Caloca DR - vk - Fix 3d texture update Change 3096813 on 2016/08/22 by Rolando.Caloca DR - Fix GL linking errors PR #2615 Change 3097062 on 2016/08/22 by Rolando.Caloca DR - vk - Added unified mem flag - Added Mip index into UAV - Switched compute descriptor set index 0 Change 3097065 on 2016/08/22 by Rolando.Caloca DR - vk - Framebuffer barriers now wait on STAGE_FRAGMENT_SHADER instead of STAGE_BOTTOM_OF_PIPE Change 3097084 on 2016/08/22 by Daniel.Wright Enabled r.VertexFoggingForOpaque by default to match other forward renderer choices (fast by default) Change 3097086 on 2016/08/22 by Rolando.Caloca DR - vk - Missed file Change 3097943 on 2016/08/23 by Rolando.Caloca DR - hlslcc - Remove duplicated definitions out into a common header Change 3098166 on 2016/08/23 by Rolando.Caloca DR - Custom Renderer callback after getting SceneColor Change 3098418 on 2016/08/23 by Olaf.Piesche Moving vertex factory dirtying to always happen in-editor for mesh emitters on dynamic data reinitialization; there are several cases in which this needs to happen (some material changes, mesh reimports...) which are difficult to track, so in-editor we just always recreate the mesh particle vertex factory with the dynamic data. #jira UE-34838 Change 3098448 on 2016/08/23 by Rolando.Caloca DR - vk - fixes for depth/stencil descriptors - Minor debug dump improvement Change 3098463 on 2016/08/23 by Daniel.Wright Static lights with MinRoughness = 1.0 don't get their source shapes drawn into reflection captures, since they are being used as virtual area lights Change 3098556 on 2016/08/23 by Daniel.Wright Lightmass area shadows only mark texels as mapped inside the light's influence, which fixes multiple stationary lights with bUseAreaShadowsForStationaryLight interfering. Change 3098672 on 2016/08/23 by Rolando.Caloca DR - vk - Fixed crash when using vertex shaders with no descriptors Change 3099173 on 2016/08/24 by Ben.Woodhouse Fixed various issues with subsurface profile, for checkerboard and non-checkerboard code paths - Re-enable non-checkerboard skin by default - Checkerboard issues fixed: - Emissive lighting was being applied twice due to not taking checkerboard pattern into account - Emissive lighting was modulated by basecolor in the recombine - Metallic materials were contributing specular lighting to the diffuse channel - Non-checkerboard fixes: - Fix write mask during SkyLightDiffuse so alpha is updated correctly - Metallic specular lighting was broken (specularColor was lerping to white instead of baseColor) - Optimisation: Fall back to default lit for pixels where the opacity is 0. - For non-checkerboard, this gives better handling of metallic/emissive for pixels where SSS is not required (non-CB RGBA encoding for diffuse/spec doesn't cope well with colored specular or emissive) - For checkerboard, this gives similar results in terms of shading, but we get full-resolution shading on non SSS pixels #jira UE-34561 Change 3099673 on 2016/08/24 by Daniel.Wright Removed unused reflection shape variables Change 3099674 on 2016/08/24 by Daniel.Wright Fixed translucent materials not working in DrawMaterialToRenderTarget (fallout from cl 3089208) Fixed ensure with FRendererModule::DrawTile in the forward renderer, trying to bind light attenuation texture Change 3099700 on 2016/08/24 by Daniel.Wright Disabled log spam when a Rift is connected but not being used Change 3099730 on 2016/08/24 by Daniel.Wright MSAA depth resolve uses depth of closest surface, hides some artifacts with dynamic shadowing against the skybox Change 3099789 on 2016/08/24 by Brian.Karis FloatRGB is now always supported. If 11:11:10 isn't supported by hardware this format by definition will map to a different format meaning it is always supported. Change 3099987 on 2016/08/24 by Daniel.Wright Fixed light grid debug asserts on PS4 * Always creating the local light buffer, even if it won't be used by the shader * Transition ViewState FRWBuffers to writable at the beginning of a new frame Change 3100120 on 2016/08/24 by Rolando.Caloca DR - vk - Use 256MB pages for GPU memory Change 3100151 on 2016/08/24 by Daniel.Wright PS4 gracefully falls back to Temporal AA when MSAA is requested, as the GNM RHI doesn't support MSAA yet Change 3100302 on 2016/08/24 by Rolando.Caloca DR - vk - Mem system changes - Now allocates a readback heap from GPU->CPU - Removed bad total memory on heap/type - Added fallback to another mem type if it's OOM Change 3101110 on 2016/08/25 by Rolando.Caloca DR - vk - Remove r.Vulkan.UseGLSL Change 3101121 on 2016/08/25 by Rolando.Caloca DR - vk - Initial support for HiResShot Change 3101450 on 2016/08/25 by Rolando.Caloca DR - vk - Remove imagelayout from textures; renamed a method for clarity Change 3101462 on 2016/08/25 by Daniel.Wright Planar reflections no longer update GPU particles, fixes Scene Depth particle collision Change 3101525 on 2016/08/25 by Frank.Fella Niagara - Remove public include modules from niagara, and remove the public include dependency on niagara from UnrealEd, and fix up fallout. Change 3101613 on 2016/08/25 by Rolando.Caloca DR - vk - Fix static analysis warning Change 3101686 on 2016/08/25 by Frank.Fella Niagara - Move asset type actions into the niagara module. Change 3101865 on 2016/08/25 by Rolando.Caloca DR - vk - Fix compile issue when enabling dump layer Change 3101946 on 2016/08/25 by Frank.Fella Orion - Fix include error caused by niagara include fixup. Change 3101999 on 2016/08/25 by Frank.Fella Fortnite - Fix include error caused by niagara include fixup. Change 3102035 on 2016/08/25 by Frank.Fella Ocean - Fix include error caused by niagara include fixup. Change 3102047 on 2016/08/25 by Frank.Fella UnrealTournament - Fix include error caused by niagara include fixup. Change 3102627 on 2016/08/26 by Frank.Fella Niagara - Move stats group declaration to the niagara module and move the stats declarations in the niagara module into the cpp files. Change 3102654 on 2016/08/26 by Ben.Woodhouse Fix for D3D error with mismatched vertex/pixel shader registers for SV_POSITION input. Remove unused PixelPosition attribute from interpolators #jira UE-33424 Change 3102780 on 2016/08/26 by Ben.Woodhouse Make shadow culling take FOV into account, via LODDistanceFactor Also set the LODDistanceFactorSquared member of the view, which was previously uninitialized #jira UE-33873 Change 3102930 on 2016/08/26 by Rolando.Caloca DR - vk - Do not require backbuffer at start, like Metal Change 3103061 on 2016/08/26 by Rolando.Caloca DR - vk - More debug dump to help track down issues Change 3103143 on 2016/08/26 by Rolando.Caloca DR - vk - Added partial image view for each texture for Depth/Stencil - Removed some unused members from textures Change 3104162 on 2016/08/29 by Gil.Gribb Merging //UE4/Dev-Main@3104155 to Dev-Rendering (//UE4/Dev-Rendering) Change 3104491 on 2016/08/29 by Rolando.Caloca DR - vk - Fix merge issue Change 3104500 on 2016/08/29 by Rolando.Caloca DR - Rebuilt hlslcc libs after merge Change 3104978 on 2016/08/29 by John.Billon -Moved Particle Cutouts to the Required Module -Pre-existing SubUVAnimation data is automatically moved to required on Init. -Added Default Particle Cutouts project setting that will attempt to find and use a texture on a particle's material for a cutout by default. Change 3105249 on 2016/08/29 by John.Billon Fixing non-editor compile error. Change 3105326 on 2016/08/29 by Zabir.Hoque SIMD Movie Player on XB1 Change 3105813 on 2016/08/30 by John.Billon Fixing static analysis warning. Change 3106322 on 2016/08/30 by Matt.Kuhlenschmidt Removed duplicated view uniform shader parameters initialization between slate and scene rendering. Moved all the duped initialization into a single shared method. The shared method should be where new parameters are initialized if they are required for the view to work properly. Change 3106350 on 2016/08/30 by Rolando.Caloca DR - vk - Added missing texture formats - Added texture debug name Change 3106547 on 2016/08/30 by Rolando.Caloca DR - Added ESimpleRenderTargetMode::EExistingColorAndClearDepth Change 3106631 on 2016/08/30 by Uriel.Doyon Dirty Texture Streaming Build do not dirty maps anymore. #jira UE-35241 Change 3106919 on 2016/08/30 by Rolando.Caloca DR - Temp workaround to get Vulkan up & running, might require hlslcc fix Change 3106974 on 2016/08/30 by Uriel.Doyon Changed lightmass exports version from GUID to INT in order to shorten filenames. Change 3106988 on 2016/08/30 by Uriel.Doyon New project specific config value r.Streaming.CheckBuildStatus used to specify whether the engine should check if the "Texture Streaming Build" is dirty (false by default). #jira UE-35227 Change 3107927 on 2016/08/31 by John.Billon -Duplicating OpenGL4 ClearUAV Implementation from 4.13 -Fixed uav clear format. #Jira UE-35345 Change 3108095 on 2016/08/31 by Marc.Olano Restore initialization of noise textures, accidentally removed in @3106322 #jira UE-35369 Change 3108557 on 2016/08/31 by John.Billon Fixing HTML5 compile error [CL 3109297 by Gil Gribb in Main branch]
1030 lines
30 KiB
C++
1030 lines
30 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
RenderingThread.cpp: Rendering thread implementation.
|
|
=============================================================================*/
|
|
|
|
#include "RenderCorePrivatePCH.h"
|
|
#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.
|
|
if (IsAsyncLoadingMultithreaded())
|
|
{
|
|
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 );
|
|
}
|
|
if (IsAsyncLoadingMultithreaded())
|
|
{
|
|
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()
|
|
{
|
|
FMemory::SetupTLSCachesOnCurrentThread();
|
|
FTaskGraphInterface::Get().AttachToThread(ENamedThreads::RHIThread);
|
|
FTaskGraphInterface::Get().ProcessThreadUntilRequestReturn(ENamedThreads::RHIThread);
|
|
FMemory::ClearAndDisableTLSCachesOnCurrentThread();
|
|
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
|
|
{
|
|
FMemory::SetupTLSCachesOnCurrentThread();
|
|
FPlatformProcess::SetupRenderThread();
|
|
|
|
#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 );
|
|
}
|
|
FMemory::ClearAndDisableTLSCachesOnCurrentThread();
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* If the rendering thread is in its idle loop (which ticks rendering tickables
|
|
*/
|
|
volatile bool GRunRenderingThreadHeartbeat = false;
|
|
|
|
FThreadSafeCounter OutstandingHeartbeats;
|
|
/** The rendering thread heartbeat runnable object. */
|
|
class FRenderingThreadTickHeartbeat : public FRunnable
|
|
{
|
|
public:
|
|
|
|
// FRunnable interface.
|
|
virtual bool Init(void)
|
|
{
|
|
OutstandingHeartbeats.Reset();
|
|
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 && OutstandingHeartbeats.GetValue() < 4)
|
|
{
|
|
OutstandingHeartbeats.Increment();
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND(
|
|
HeartbeatTickTickables,
|
|
{
|
|
OutstandingHeartbeats.Decrement();
|
|
// 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();
|
|
delete GRenderingThreadHeartbeat;
|
|
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())
|
|
{
|
|
if (!GIsCriticalError)
|
|
{
|
|
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);
|
|
|
|
CompletionEvent = TGraphTask<FNullGraphTask>::CreateTask(NULL, 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)
|
|
{
|
|
SCOPE_TIME_GUARD(TEXT("GameThreadWaitForTask"));
|
|
|
|
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()
|
|
{
|
|
if (!GIsRHIInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
{
|
|
UE_LOG(LogRendererCore, Display, TEXT("Usage: r.RHIThread.Enable 0/1; Currently %d"), (int32)GUseRHIThread);
|
|
}
|
|
}
|
|
|
|
static FAutoConsoleCommand CVarRHIThreadEnable(
|
|
TEXT("r.RHIThread.Enable"),
|
|
TEXT("Enables/disabled the RHI Thread\n"),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&HandleRHIThreadEnableChanged)
|
|
);
|