VRTowerDef/Plugins/MetaXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.cpp

667 lines
19 KiB
C++
Raw Normal View History

2024-05-29 08:53:41 +00:00
// @lint-ignore-every LICENSELINT
// Copyright Epic Games, Inc. All Rights Reserved.
#include "OculusXRHMD_Splash.h"
#if OCULUS_HMD_SUPPORTED_PLATFORMS
#include "OculusXRHMD.h"
#include "RenderingThread.h"
#include "Misc/ScopeLock.h"
#include "OculusXRHMDRuntimeSettings.h"
#include "StereoLayerFunctionLibrary.h"
#include "TextureResource.h"
#if PLATFORM_ANDROID
#include "Android/AndroidJNI.h"
#include "Android/AndroidEGL.h"
#include "Android/AndroidApplication.h"
#include "OculusXRHMDTypes.h"
#endif
namespace OculusXRHMD
{
//-------------------------------------------------------------------------------------------------
// FSplash
//-------------------------------------------------------------------------------------------------
FSplash::FSplash(FOculusXRHMD* InOculusXRHMD)
: OculusXRHMD(InOculusXRHMD), CustomPresent(InOculusXRHMD->GetCustomPresent_Internal()), FramesOutstanding(0), NextLayerId(1), bInitialized(false), bIsShown(false), bNeedSplashUpdate(false), bShouldShowSplash(false), SystemDisplayInterval(1 / 90.0f)
{
// Create empty quad layer for UE layer
{
IStereoLayers::FLayerDesc LayerDesc;
LayerDesc.QuadSize = FVector2D(0.01f, 0.01f);
LayerDesc.Priority = 0;
LayerDesc.PositionType = IStereoLayers::TrackerLocked;
LayerDesc.Texture = nullptr;
UELayer = MakeShareable(new FLayer(NextLayerId++));
UELayer->SetDesc(LayerDesc);
}
}
FSplash::~FSplash()
{
// Make sure RenTicker is freed in Shutdown
check(!Ticker.IsValid())
}
void FSplash::Tick_RenderThread(float DeltaTime)
{
CheckInRenderThread();
if (FramesOutstanding > 0)
{
UE_LOG(LogHMD, VeryVerbose, TEXT("Splash skipping frame; too many frames outstanding"));
return;
}
const double TimeInSeconds = FPlatformTime::Seconds();
const double DeltaTimeInSeconds = TimeInSeconds - LastTimeInSeconds;
if (DeltaTimeInSeconds > 2.f * SystemDisplayInterval && Layers_RenderThread_DeltaRotation.Num() > 0)
{
FScopeLock ScopeLock(&RenderThreadLock);
for (TTuple<FLayerPtr, FQuat>& Info : Layers_RenderThread_DeltaRotation)
{
FLayerPtr Layer = Info.Key;
const FQuat& DeltaRotation = Info.Value;
check(Layer.IsValid());
check(!DeltaRotation.Equals(FQuat::Identity)); // Only layers with non-zero delta rotation should be in the DeltaRotation array.
IStereoLayers::FLayerDesc LayerDesc = Layer->GetDesc();
LayerDesc.Transform.SetRotation(LayerDesc.Transform.GetRotation() * DeltaRotation);
LayerDesc.Transform.NormalizeRotation();
Layer->SetDesc(LayerDesc);
}
LastTimeInSeconds = TimeInSeconds;
}
RenderFrame_RenderThread(FRHICommandListExecutor::GetImmediateCommandList());
}
void FSplash::LoadSettings()
{
UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>();
check(HMDSettings);
ClearSplashes();
for (const FOculusXRSplashDesc& SplashDesc : HMDSettings->SplashDescs)
{
AddSplash(SplashDesc);
}
if (HMDSettings->bAutoEnabled)
{
if (!PreLoadLevelDelegate.IsValid())
{
PreLoadLevelDelegate = FCoreUObjectDelegates::PreLoadMap.AddSP(this, &FSplash::OnPreLoadMap);
}
if (!PostLoadLevelDelegate.IsValid())
{
PostLoadLevelDelegate = FCoreUObjectDelegates::PostLoadMapWithWorld.AddSP(this, &FSplash::OnPostLoadMap);
}
}
else
{
if (PreLoadLevelDelegate.IsValid())
{
FCoreUObjectDelegates::PreLoadMap.Remove(PreLoadLevelDelegate);
PreLoadLevelDelegate.Reset();
}
if (PostLoadLevelDelegate.IsValid())
{
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadLevelDelegate);
PostLoadLevelDelegate.Reset();
}
}
}
void FSplash::OnPreLoadMap(const FString&)
{
DoShow();
}
void FSplash::OnPostLoadMap(UWorld* LoadedWorld)
{
// Don't auto-hide splash if show loading screen is called explicitly
if (!bShouldShowSplash)
{
UE_LOG(LogHMD, Log, TEXT("FSplash::OnPostLoadMap Hide Auto Splash"));
HideLoadingScreen();
}
}
#if WITH_EDITOR
void FSplash::OnPieBegin(bool bIsSimulating)
{
LoadSettings();
}
#endif
void FSplash::Startup()
{
CheckInGameThread();
if (!bInitialized)
{
Settings = OculusXRHMD->CreateNewSettings();
Frame = OculusXRHMD->CreateNewGameFrame();
// keep units in meters rather than UU (because UU make not much sense).
Frame->WorldToMetersScale = 1.0f;
float SystemDisplayFrequency;
if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayFrequency2(&SystemDisplayFrequency)))
{
SystemDisplayInterval = 1.0f / SystemDisplayFrequency;
}
LoadSettings();
OculusXRHMD->InitDevice();
#if WITH_EDITOR
PieBeginDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FSplash::OnPieBegin);
#else
UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>();
check(HMDSettings);
if (HMDSettings->bAutoEnabled)
{
UE_LOG(LogHMD, Log, TEXT("FSplash::Startup Show Splash on Startup"));
DoShow();
}
#endif
OculusXRHMD->Settings_RenderThread = OculusXRHMD->Settings->Clone();
bInitialized = true;
}
}
void FSplash::StopTicker()
{
CheckInGameThread();
if (!bIsShown)
{
ExecuteOnRenderThread([this]() {
if (Ticker.IsValid())
{
Ticker->Unregister();
Ticker = nullptr;
}
});
UnloadTextures();
}
}
void FSplash::StartTicker()
{
CheckInGameThread();
if (!Ticker.IsValid())
{
Ticker = MakeShareable(new FTicker(this));
ExecuteOnRenderThread([this]() {
LastTimeInSeconds = FPlatformTime::Seconds();
Ticker->Register();
});
}
}
void FSplash::RenderFrame_RenderThread(FRHICommandListImmediate& RHICmdList)
{
CheckInRenderThread();
FScopeLock ScopeLock(&RenderThreadLock);
// RenderFrame
FSettingsPtr XSettings = Settings->Clone();
FGameFramePtr XFrame = Frame->Clone();
XFrame->FrameNumber = OculusXRHMD->NextFrameNumber;
XFrame->ShowFlags.Rendering = true;
TArray<FLayerPtr> XLayers = Layers_RenderThread_Input;
ensure(XLayers.Num() != 0);
ovrpResult Result;
if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OculusXRHMD->WaitFrameNumber != XFrame->FrameNumber)
{
UE_LOG(LogHMD, Verbose, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u"), XFrame->FrameNumber);
if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame(XFrame->FrameNumber)))
{
UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u failed (%d)"), XFrame->FrameNumber, Result);
XFrame->ShowFlags.Rendering = false;
}
else
{
OculusXRHMD->WaitFrameNumber = XFrame->FrameNumber;
OculusXRHMD->NextFrameNumber = XFrame->FrameNumber + 1;
FPlatformAtomics::InterlockedIncrement(&FramesOutstanding);
}
}
else
{
XFrame->ShowFlags.Rendering = false;
}
if (XFrame->ShowFlags.Rendering)
{
if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, XFrame->FrameNumber, 0.0)))
{
UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().Update3 %u failed (%d)"), XFrame->FrameNumber, Result);
}
}
{
int32 LayerIndex = 0;
int32 LayerIndex_RenderThread = 0;
while (LayerIndex < XLayers.Num() && LayerIndex_RenderThread < Layers_RenderThread.Num())
{
uint32 LayerIdA = XLayers[LayerIndex]->GetId();
uint32 LayerIdB = Layers_RenderThread[LayerIndex_RenderThread]->GetId();
if (LayerIdA < LayerIdB)
{
XLayers[LayerIndex++]->Initialize_RenderThread(XSettings.Get(), CustomPresent, &OculusXRHMD->DeferredDeletion, RHICmdList);
}
else if (LayerIdA > LayerIdB)
{
OculusXRHMD->DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
}
else
{
XLayers[LayerIndex++]->Initialize_RenderThread(XSettings.Get(), CustomPresent, &OculusXRHMD->DeferredDeletion, RHICmdList, Layers_RenderThread[LayerIndex_RenderThread++].Get());
}
}
while (LayerIndex < XLayers.Num())
{
XLayers[LayerIndex++]->Initialize_RenderThread(XSettings.Get(), CustomPresent, &OculusXRHMD->DeferredDeletion, RHICmdList);
}
while (LayerIndex_RenderThread < Layers_RenderThread.Num())
{
OculusXRHMD->DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
}
}
Layers_RenderThread = XLayers;
for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++)
{
Layers_RenderThread[LayerIndex]->UpdateTexture_RenderThread(XSettings.Get(), CustomPresent, RHICmdList);
}
// This submit is required since splash happens before the game is rendering, so layers won't be submitted with game render commands
CustomPresent->SubmitGPUCommands_RenderThread(RHICmdList);
// RHIFrame
for (int32 LayerIndex = 0; LayerIndex < XLayers.Num(); LayerIndex++)
{
XLayers[LayerIndex] = XLayers[LayerIndex]->Clone();
}
ExecuteOnRHIThread_DoNotWait([this, XSettings, XFrame, XLayers]() {
ovrpResult ResultT;
if (XFrame->ShowFlags.Rendering)
{
UE_LOG(LogHMD, Verbose, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u"), XFrame->FrameNumber);
if (OVRP_FAILURE(ResultT = FOculusXRHMDModule::GetPluginWrapper().BeginFrame4(XFrame->FrameNumber, CustomPresent->GetOvrpCommandQueue())))
{
UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u failed (%d)"), XFrame->FrameNumber, ResultT);
XFrame->ShowFlags.Rendering = false;
}
}
FPlatformAtomics::InterlockedDecrement(&FramesOutstanding);
Layers_RHIThread = XLayers;
Layers_RHIThread.Sort(FLayerPtr_ComparePriority());
if (XFrame->ShowFlags.Rendering)
{
TArray<const ovrpLayerSubmit*> LayerSubmitPtr;
LayerSubmitPtr.SetNum(Layers_RHIThread.Num());
for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++)
{
LayerSubmitPtr[LayerIndex] = Layers_RHIThread[LayerIndex]->UpdateLayer_RHIThread(XSettings.Get(), XFrame.Get(), LayerIndex);
}
UE_LOG(LogHMD, Verbose, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u"), XFrame->FrameNumber);
if (OVRP_FAILURE(ResultT = FOculusXRHMDModule::GetPluginWrapper().EndFrame4(XFrame->FrameNumber, LayerSubmitPtr.GetData(), LayerSubmitPtr.Num(), CustomPresent->GetOvrpCommandQueue())))
{
UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u failed (%d)"), XFrame->FrameNumber, ResultT);
}
else
{
for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++)
{
Layers_RHIThread[LayerIndex]->IncrementSwapChainIndex_RHIThread(CustomPresent);
}
}
}
});
}
void FSplash::ReleaseResources_RHIThread()
{
for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++)
{
Layers_RenderThread[LayerIndex]->ReleaseResources_RHIThread();
}
for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++)
{
Layers_RHIThread[LayerIndex]->ReleaseResources_RHIThread();
}
Layers_RenderThread.Reset();
Layers_RHIThread.Reset();
}
void FSplash::PreShutdown()
{
CheckInGameThread();
}
void FSplash::Shutdown()
{
CheckInGameThread();
#if WITH_EDITOR
if (PieBeginDelegateHandle.IsValid())
{
FEditorDelegates::BeginPIE.Remove(PieBeginDelegateHandle);
PieBeginDelegateHandle.Reset();
}
#endif
if (PreLoadLevelDelegate.IsValid())
{
FCoreUObjectDelegates::PreLoadMap.Remove(PreLoadLevelDelegate);
PreLoadLevelDelegate.Reset();
}
if (PostLoadLevelDelegate.IsValid())
{
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadLevelDelegate);
PostLoadLevelDelegate.Reset();
}
if (bInitialized)
{
ExecuteOnRenderThread([this]() {
if (Ticker)
{
Ticker->Unregister();
Ticker = nullptr;
}
ExecuteOnRHIThread([this]() {
SplashLayers.Reset();
Layers_RenderThread.Reset();
Layers_RenderThread_Input.Reset();
Layers_RHIThread.Reset();
});
});
bInitialized = false;
}
}
int FSplash::AddSplash(const FOculusXRSplashDesc& Desc)
{
CheckInGameThread();
FScopeLock ScopeLock(&RenderThreadLock);
return SplashLayers.Add(FSplashLayer(Desc));
}
void FSplash::AddSplash(const FSplashDesc& Splash)
{
FOculusXRSplashDesc OculusDesc;
OculusDesc.TransformInMeters = Splash.Transform;
OculusDesc.QuadSizeInMeters = Splash.QuadSize;
OculusDesc.DeltaRotation = Splash.DeltaRotation;
OculusDesc.bNoAlphaChannel = Splash.bIgnoreAlpha;
OculusDesc.bIsDynamic = Splash.bIsDynamic || Splash.bIsExternal;
OculusDesc.TextureOffset = Splash.UVRect.Min;
OculusDesc.TextureScale = Splash.UVRect.Max;
OculusDesc.LoadedTexture = Splash.Texture;
AddSplash(OculusDesc);
}
void FSplash::ClearSplashes()
{
CheckInGameThread();
FScopeLock ScopeLock(&RenderThreadLock);
SplashLayers.Reset();
}
bool FSplash::GetSplash(unsigned InSplashLayerIndex, FOculusXRSplashDesc& OutDesc)
{
CheckInGameThread();
FScopeLock ScopeLock(&RenderThreadLock);
if (InSplashLayerIndex < unsigned(SplashLayers.Num()))
{
OutDesc = SplashLayers[int32(InSplashLayerIndex)].Desc;
return true;
}
return false;
}
IStereoLayers::FLayerDesc FSplash::StereoLayerDescFromOculusSplashDesc(FOculusXRSplashDesc OculusDesc)
{
IStereoLayers::FLayerDesc LayerDesc;
if (OculusDesc.LoadedTexture->GetTextureCube() != nullptr)
{
LayerDesc.SetShape<FCubemapLayer>();
}
// else LayerDesc.Shape defaults to FQuadLayer
LayerDesc.Transform = OculusDesc.TransformInMeters * FTransform(OculusXRHMD->GetSplashRotation().Quaternion());
LayerDesc.QuadSize = OculusDesc.QuadSizeInMeters;
LayerDesc.UVRect = FBox2D(OculusDesc.TextureOffset, OculusDesc.TextureOffset + OculusDesc.TextureScale);
LayerDesc.Priority = INT32_MAX - (int32)(OculusDesc.TransformInMeters.GetTranslation().X * 1000.f);
LayerDesc.PositionType = IStereoLayers::TrackerLocked;
LayerDesc.Texture = OculusDesc.LoadedTexture;
LayerDesc.Flags = IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO | (OculusDesc.bNoAlphaChannel ? IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL : 0) | (OculusDesc.bIsDynamic ? IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE : 0);
return LayerDesc;
}
void FSplash::DoShow()
{
CheckInGameThread();
OculusXRHMD->SetSplashRotationToForward();
// Create new textures
UnloadTextures();
// Make sure all UTextures are loaded and contain Resource->TextureRHI
bool bWaitForRT = false;
for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); ++SplashLayerIndex)
{
FSplashLayer& SplashLayer = SplashLayers[SplashLayerIndex];
if (SplashLayer.Desc.TexturePath.IsValid())
{
// load temporary texture (if TexturePath was specified)
LoadTexture(SplashLayer);
}
if (SplashLayer.Desc.LoadingTexture && SplashLayer.Desc.LoadingTexture->IsValidLowLevel())
{
SplashLayer.Desc.LoadingTexture->UpdateResource();
bWaitForRT = true;
}
}
FlushRenderingCommands();
for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); ++SplashLayerIndex)
{
FSplashLayer& SplashLayer = SplashLayers[SplashLayerIndex];
//@DBG BEGIN
if (SplashLayer.Desc.LoadingTexture->IsValidLowLevel())
{
if (SplashLayer.Desc.LoadingTexture->GetResource() && SplashLayer.Desc.LoadingTexture->GetResource()->TextureRHI)
{
SplashLayer.Desc.LoadedTexture = SplashLayer.Desc.LoadingTexture->GetResource()->TextureRHI;
}
else
{
UE_LOG(LogHMD, Warning, TEXT("Splash, %s - no Resource"), *SplashLayer.Desc.LoadingTexture->GetDesc());
}
}
//@DBG END
if (SplashLayer.Desc.LoadedTexture)
{
SplashLayer.Layer = MakeShareable(new FLayer(NextLayerId++));
SplashLayer.Layer->SetDesc(StereoLayerDescFromOculusSplashDesc(SplashLayer.Desc));
}
}
{
//add oculus-generated layers through the OculusVR settings area
FScopeLock ScopeLock(&RenderThreadLock);
Layers_RenderThread_DeltaRotation.Reset();
Layers_RenderThread_Input.Reset();
for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); SplashLayerIndex++)
{
const FSplashLayer& SplashLayer = SplashLayers[SplashLayerIndex];
if (SplashLayer.Layer.IsValid())
{
FLayerPtr ClonedLayer = SplashLayer.Layer->Clone();
Layers_RenderThread_Input.Add(ClonedLayer);
// Register layers that need to be rotated every n ticks
if (!SplashLayer.Desc.DeltaRotation.Equals(FQuat::Identity))
{
Layers_RenderThread_DeltaRotation.Emplace(ClonedLayer, SplashLayer.Desc.DeltaRotation);
}
}
}
//add UE VR splash screen
FOculusXRSplashDesc UESplashDesc = OculusXRHMD->GetUESplashScreenDesc();
if (UESplashDesc.LoadedTexture != nullptr)
{
UELayer.Reset();
UELayer = MakeShareable(new FLayer(NextLayerId++));
UELayer->SetDesc(StereoLayerDescFromOculusSplashDesc(UESplashDesc));
Layers_RenderThread_Input.Add(UELayer->Clone());
}
Layers_RenderThread_Input.Sort(FLayerPtr_CompareId());
}
if (Layers_RenderThread_Input.Num() > 0)
{
// If no textures are loaded, this will push black frame
StartTicker();
bIsShown = true;
UE_LOG(LogHMD, Log, TEXT("FSplash::DoShow"));
}
else
{
UE_LOG(LogHMD, Log, TEXT("No splash layers in FSplash::DoShow"));
}
}
void FSplash::DoHide()
{
CheckInGameThread();
UE_LOG(LogHMD, Log, TEXT("FSplash::DoHide"));
bIsShown = false;
StopTicker();
}
void FSplash::UpdateLoadingScreen_GameThread()
{
if (bNeedSplashUpdate)
{
if (bShouldShowSplash)
{
DoShow();
}
else
{
DoHide();
}
bNeedSplashUpdate = false;
}
}
void FSplash::ShowLoadingScreen()
{
bShouldShowSplash = true;
// DoShow will be called from UpdateSplashScreen_Gamethread().
// This can can happen if the splashes are already being shown, as it will reset the relative positions and delta rotations of the layers.
bNeedSplashUpdate = true;
}
void FSplash::HideLoadingScreen()
{
bShouldShowSplash = false;
bNeedSplashUpdate = bIsShown; // no need to call DoHide when the splash is already hidden
}
void FSplash::UnloadTextures()
{
CheckInGameThread();
// unload temporary loaded textures
FScopeLock ScopeLock(&RenderThreadLock);
for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); ++SplashLayerIndex)
{
if (SplashLayers[SplashLayerIndex].Desc.TexturePath.IsValid())
{
UnloadTexture(SplashLayers[SplashLayerIndex]);
}
}
}
void FSplash::LoadTexture(FSplashLayer& InSplashLayer)
{
CheckInGameThread();
UnloadTexture(InSplashLayer);
UE_LOG(LogLoadingSplash, Log, TEXT("Loading texture for splash %s..."), *InSplashLayer.Desc.TexturePath.GetAssetName());
InSplashLayer.Desc.LoadingTexture = Cast<UTexture>(InSplashLayer.Desc.TexturePath.TryLoad());
if (InSplashLayer.Desc.LoadingTexture != nullptr)
{
UE_LOG(LogLoadingSplash, Log, TEXT("...Success. "));
}
InSplashLayer.Desc.LoadedTexture = nullptr;
InSplashLayer.Layer.Reset();
}
void FSplash::UnloadTexture(FSplashLayer& InSplashLayer)
{
CheckInGameThread();
InSplashLayer.Desc.LoadingTexture = nullptr;
InSplashLayer.Desc.LoadedTexture = nullptr;
InSplashLayer.Layer.Reset();
}
} // namespace OculusXRHMD
#endif // OCULUS_HMD_SUPPORTED_PLATFORMS