Files
UnrealEngineUWP/Engine/Source/Runtime/PreLoadScreen/Private/PreLoadSlateThreading.cpp
patrick boutot 835727130a Slate: Clear the uncached draw elements and the batches elements once they are rendered. We do this to remove hard references to the Glyph Sequence from the previous frame. Cached draw elements are still cached. An old Glyph Sequence may still be a reference in a cached element but it is the responsibility of the SWidget to do the proper invalidation. Deprecated the FSlateRenderer::GetDrawBuffer to remind the user to call FSlateRenderer::ReleaseDrawBuffer.
#rb matt.kuhlenschmidt, none
#preflight 62066e4554003c49ad37c3e6
#preflight 6214eeab797dbbeb471d43cc
#preflight 621522ff9e113332ba17dc9d
#preflight 62162720104496cff889752a

#ROBOMERGE-OWNER: patrick.boutot
#ROBOMERGE-AUTHOR: patrick.boutot
#ROBOMERGE-SOURCE: CL 19089835 via CL 19089855 via CL 19092359 via CL 19093299 via CL 19101760
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v921-19075845)

[CL 19141573 by patrick boutot in ue5-main branch]
2022-02-24 23:56:41 -05:00

269 lines
8.7 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
FSlateRenderer::FScopedAcquireDrawBuffer ScopedDrawBuffer{ *SlateRenderer };
FSlateWindowElementList& WindowElementList = ScopedDrawBuffer.GetDrawBuffer().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(ScopedDrawBuffer.GetDrawBuffer());
ScopedDrawBuffer.GetDrawBuffer().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);
}