Files
UnrealEngineUWP/Engine/Source/Runtime/PreLoadScreen/Private/PreLoadSlateThreading.cpp
Marc Audy 68150e0be7 Merge UE5/Release-Engine-Staging to UE5/Main @ 14611496
This represents UE4/Main @ 14594913

[CL 14612291 by Marc Audy in ue5-main branch]
2020-10-29 13:38:15 -04:00

268 lines
8.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PreLoadSlateThreading.h"
#include "PreLoadScreenManager.h"
#include "Framework/Application/SlateApplication.h"
#include "HAL/Event.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/PlatformAtomics.h"
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "Input/HittestGrid.h"
#include "Misc/ScopeLock.h"
#include "Misc/StringBuilder.h"
#include "Rendering/SlateDrawBuffer.h"
#include "RenderingThread.h"
#include "Widgets/SVirtualWindow.h"
TAtomic<int32> FPreLoadScreenSlateSynchMechanism::LoadingThreadInstanceCounter(0);
bool FPreLoadScreenSlateThreadTask::Init()
{
// First thing to do is set the slate loading thread ID
// This guarantees all systems know that a slate thread exists
const int32 PreviousValue = FPlatformAtomics::InterlockedCompareExchange((int32*)&GSlateLoadingThreadId, FPlatformTLS::GetCurrentThreadId(), 0);
check(PreviousValue == 0);
return true;
}
uint32 FPreLoadScreenSlateThreadTask::Run()
{
SyncMechanism->RunMainLoop_SlateThread();
return 0;
}
void FPreLoadScreenSlateThreadTask::Exit()
{
// Tear down the slate loading thread ID
const int32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
const int32 PreviousThreadId = FPlatformAtomics::InterlockedCompareExchange((int32*)&GSlateLoadingThreadId, 0, CurrentThreadId);
check(PreviousThreadId == CurrentThreadId);
}
FPreLoadSlateWidgetRenderer::FPreLoadSlateWidgetRenderer(TSharedPtr<SWindow> InMainWindow, TSharedPtr<SVirtualWindow> InVirtualRenderWindow, FSlateRenderer* InRenderer)
: MainWindow(InMainWindow.Get())
, VirtualRenderWindow(InVirtualRenderWindow.ToSharedRef())
, SlateRenderer(InRenderer)
{
HittestGrid = MakeShareable(new FHittestGrid);
}
void FPreLoadSlateWidgetRenderer::DrawWindow(float DeltaTime)
{
// If the engine has requested exit, early out the draw loop, several
// of the things inside of here are not safe to perform while shutting down.
if (IsEngineExitRequested())
{
return;
}
// FPreLoadScreenSlateSynchMechanism need to be shutdown before we release the FSlateApplication
if (ensure(FSlateApplication::IsInitialized()))
{
FSlateRenderer* MainSlateRenderer = FSlateApplication::Get().GetRenderer();
FScopeLock ScopeLock(MainSlateRenderer->GetResourceCriticalSection());
const FVector2D DrawSize = VirtualRenderWindow->GetClientSizeInScreen();
FSlateApplication::Get().Tick(ESlateTickType::Time);
const float Scale = 1.0f;
FGeometry WindowGeometry = FGeometry::MakeRoot(DrawSize, FSlateLayoutTransform(Scale));
VirtualRenderWindow->SlatePrepass(WindowGeometry.Scale);
FSlateRect ClipRect = WindowGeometry.GetLayoutBoundingRect();
HittestGrid->SetHittestArea(VirtualRenderWindow->GetPositionInScreen(), VirtualRenderWindow->GetViewportSize());
HittestGrid->Clear();
// Get the free buffer & add our virtual window
FSlateDrawBuffer& DrawBuffer = SlateRenderer->GetDrawBuffer();
FSlateWindowElementList& WindowElementList = DrawBuffer.AddWindowElementList(VirtualRenderWindow);
WindowElementList.SetRenderTargetWindow(MainWindow);
int32 MaxLayerId = 0;
{
FPaintArgs PaintArgs(nullptr, *HittestGrid, FVector2D::ZeroVector, FSlateApplication::Get().GetCurrentTime(), FSlateApplication::Get().GetDeltaTime());
// Paint the window
MaxLayerId = VirtualRenderWindow->Paint(
PaintArgs,
WindowGeometry, ClipRect,
WindowElementList,
0,
FWidgetStyle(),
VirtualRenderWindow->IsEnabled());
}
SlateRenderer->DrawWindows(DrawBuffer);
DrawBuffer.ViewOffset = FVector2D::ZeroVector;
}
}
FPreLoadScreenSlateSynchMechanism::FPreLoadScreenSlateSynchMechanism(TSharedPtr<FPreLoadSlateWidgetRenderer, ESPMode::ThreadSafe> InWidgetRenderer)
: bIsRunningSlateMainLoop(false)
, SlateLoadingThread(nullptr)
, SlateRunnableTask(nullptr)
, SleepEvent(nullptr)
, WidgetRenderer(InWidgetRenderer)
{
}
FPreLoadScreenSlateSynchMechanism::~FPreLoadScreenSlateSynchMechanism()
{
DestroySlateThread();
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().OnWindowBeingDestroyed().RemoveAll(this);
FSlateApplication::Get().OnPreShutdown().RemoveAll(this);
}
}
void FPreLoadScreenSlateSynchMechanism::Initialize()
{
check(IsInGameThread());
// Initialize should only be called once
if (ensure(FSlateApplication::IsInitialized() && bIsRunningSlateMainLoop == 0))
{
// Prevent the application from closing while we are rendering from another thread
FSlateApplication::Get().OnPreShutdown().AddRaw(this, &FPreLoadScreenSlateSynchMechanism::DestroySlateThread);
FSlateApplication::Get().OnWindowBeingDestroyed().AddRaw(this, &FPreLoadScreenSlateSynchMechanism::HandleWindowBeingDestroyed);
bIsRunningSlateMainLoop = true;
check(SlateLoadingThread == nullptr);
TStringBuilder<32> ThreadName;
ThreadName.Append(TEXT("SlateLoadingThread"));
ThreadName.Appendf(TEXT("%d"), LoadingThreadInstanceCounter.Load());
LoadingThreadInstanceCounter++;
SleepEvent = FGenericPlatformProcess::GetSynchEventFromPool(false);
SlateRunnableTask = new FPreLoadScreenSlateThreadTask(*this);
SlateLoadingThread = FRunnableThread::Create(SlateRunnableTask, ThreadName.GetData());
}
}
void FPreLoadScreenSlateSynchMechanism::DestroySlateThread()
{
check(IsInGameThread());
if (bIsRunningSlateMainLoop)
{
check(SlateLoadingThread != nullptr);
bIsRunningSlateMainLoop = false;
SleepEvent->Trigger();
SlateLoadingThread->WaitForCompletion();
FGenericPlatformProcess::ReturnSynchEventToPool(SleepEvent);
delete SlateLoadingThread;
delete SlateRunnableTask;
SlateLoadingThread = nullptr;
SlateRunnableTask = nullptr;
}
}
void FPreLoadScreenSlateSynchMechanism::HandleWindowBeingDestroyed(const SWindow& WindowBeingDestroyed)
{
check(IsInGameThread());
if (WidgetRenderer && WidgetRenderer->GetMainWindow_GameThread() == &WindowBeingDestroyed)
{
// wait until the render has completed its task
DestroySlateThread();
}
}
bool FPreLoadScreenSlateSynchMechanism::IsSlateMainLoopRunning_AnyThread() const
{
return bIsRunningSlateMainLoop && !IsEngineExitRequested();
}
void FPreLoadScreenSlateSynchMechanism::RunMainLoop_SlateThread()
{
FTaskTagScope Scope(ETaskTag::ESlateThread);
double LastTime = FPlatformTime::Seconds();
TAtomic<int32> SlateDrawEnqueuedCounter(0);
FEvent* EnqueueRenderEvent = FGenericPlatformProcess::GetSynchEventFromPool(false);
while (IsSlateMainLoopRunning_AnyThread())
{
// Test to ensure that we are still the SlateLoadingThread
checkCode(
const int32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
const int32 PreviousThreadId = FPlatformAtomics::InterlockedCompareExchange((int32*)&GSlateLoadingThreadId, CurrentThreadId, CurrentThreadId);
check(PreviousThreadId == CurrentThreadId)
);
double CurrentTime = FPlatformTime::Seconds();
double DeltaTime = CurrentTime - LastTime;
{
// 60 fps max
const double MaxTickRate = 1.0 / 60.0f;
double TimeToWait = MaxTickRate - DeltaTime;
if (TimeToWait > 0.0)
{
SleepEvent->Wait(FTimespan::FromSeconds(TimeToWait));
CurrentTime = FPlatformTime::Seconds();
DeltaTime = CurrentTime - LastTime;
}
}
// Wait for the current render command to be executed
while (IsSlateMainLoopRunning_AnyThread() && SlateDrawEnqueuedCounter != 0)
{
EnqueueRenderEvent->Wait(FTimespan::FromMilliseconds(1));
}
if (IsSlateMainLoopRunning_AnyThread())
{
// This avoids crashes if we Suspend rendering whilst the loading screen is up
// as we don't want Slate to submit any more draw calls until we Resume.
if (GDynamicRHI && !GDynamicRHI->RHIIsRenderingSuspended())
{
FScopeLock Lock(&FPreLoadScreenManager::AcquireCriticalSection);
if (IsSlateMainLoopRunning_AnyThread() && FPreLoadScreenManager::bRenderingEnabled)
{
WidgetRenderer->DrawWindow(DeltaTime);
++SlateDrawEnqueuedCounter;
//Queue up a render tick every time we tick on this sync thread.
FPreLoadScreenSlateSynchMechanism* SyncMech = this;
ENQUEUE_RENDER_COMMAND(PreLoadScreenRenderTick)(
[SyncMech, &SlateDrawEnqueuedCounter, EnqueueRenderEvent](FRHICommandListImmediate& RHICmdList)
{
if (SyncMech->IsSlateMainLoopRunning_AnyThread())
{
FPreLoadScreenManager::StaticRenderTick_RenderThread();
}
--SlateDrawEnqueuedCounter;
EnqueueRenderEvent->Trigger();
}
);
}
}
}
LastTime = CurrentTime;
}
// Need to wait because the enqueued command has references this & SlateDrawEnqueuedCounter
while (SlateDrawEnqueuedCounter > 0)
{
EnqueueRenderEvent->Wait(FTimespan::FromMilliseconds(1));
}
FGenericPlatformProcess::ReturnSynchEventToPool(EnqueueRenderEvent);
}