/*
 * Copyright 2019,2020,2021 Sony Corporation
 */

#include "SRDisplaySystem.h"

#include "SRDisplayTextResource.h"
#include "RenderTargetManager.h"

#include "Window/SRDisplayWindow.h"

#include <Slate/SceneViewport.h>
#include <Modules/ModuleManager.h>

#if PLATFORM_WINDOWS
#include <Windows/AllowWindowsPlatformTypes.h>
#include <Windows/HideWindowsPlatformTypes.h>
#endif

DEFINE_LOG_CATEGORY(LogSRDisplay);

namespace sr_display
{
	FTexture2DRHIRef DisplayProjectionTextureLeft;
	FTexture2DRHIRef DisplayProjectionTextureRight;
	bool bRuntimeShaderGenerated = false;

	static const float METER_TO_CENTIMETER = 100.f;

	static const float DEFAULT_SRD_DISPLAY_WIDTH = 34.5f;
	static const float DEFAULT_SRD_DISPLAY_HEIGHT = 19.5f;
	static const float DEFAULT_SRD_DISPLAY_TILT = 45.f;

	const float FSRDisplaySystem::DEFAULT_SRD_VIEW_SPACE_SCALE = 1.f;
	const float FSRDisplaySystem::DEFAULT_SRD_FAR_CLIP = 100000000.f;

	FSRDisplaySystem::FSRDisplaySystem(const FAutoRegister& AutoRegister)
		: FHeadMountedDisplayBase(nullptr)
		, FSceneViewExtensionBase(AutoRegister)
		, DisplayResolution(FVector2D(3840, 2560))
	{
		if (!InitializeXrRuntime())
		{
			return;
		}
	}

	FSRDisplaySystem::~FSRDisplaySystem()
	{
		if (RenderTargetManager != nullptr)
		{
			delete RenderTargetManager;
			RenderTargetManager = nullptr;
		}

		FinalizeXrRuntime();
	}

	bool FSRDisplaySystem::IsInitialized() const
	{
		return true;
	}

	bool FSRDisplaySystem::InitializeXrRuntime()
	{
#if WITH_EDITOR
		if (!bIsSessionBegun)
		{
			FinalizeXrRuntime();
		}
#endif
		if (!RuntimeSessionHandle)
		{
			if (!IsProjectGraphicsAPISupported())
			{
				return false;
			}

			// Find SR Display.
#if WITH_EDITOR
			SrdXrDeviceInfo device;
			SrdXrResult result = srd::xr::ext::GetDevice(0, &device);
			if (result == SrdXrResult::SUCCESS)
			{
				isWithoutSRDisplayMode = false;
				DisplayResolution = FVector2D(
						device.target_monitor_rectangle.right - device.target_monitor_rectangle.left,
						device.target_monitor_rectangle.bottom - device.target_monitor_rectangle.top);
			}
			else
			{
				isWithoutSRDisplayMode = true;
				return true;
			}
#else
			SrdXrDeviceInfo device;
			if (!HandleXRRuntimeResult(srd::xr::ext::GetDevice(0, &device)))
			{
				return false;
			}
			isWithoutSRDisplayMode = false;
			DisplayResolution = FVector2D(
					device.target_monitor_rectangle.right - device.target_monitor_rectangle.left,
					device.target_monitor_rectangle.bottom - device.target_monitor_rectangle.top);
#endif

			// Create Session with SR Display
			{
				SrdXrSessionCreateInfo SessionInfo;
				SessionInfo.platform_id = SrdXrPlatformId::SRD;
				SessionInfo.coordinate_system = SrdXrCoordinateSystem::LEFT_Z_UP_X_FORWARD;
				SessionInfo.coordinate_unit = SrdXrCoordinateUnit::CENTIMETER;
				SessionInfo.device = device;
				if (!HandleXRRuntimeResult(srd::xr::CreateSession(&SessionInfo, &RuntimeSessionHandle)))
				{
					return false;
				}
			}

			if (!HandleXRRuntimeResult(srd::xr::ext::SetGraphicsAPI(RuntimeSessionHandle, GetCurrentGraphicsAPI())))
			{
				FinalizeXrRuntime();
				return false;
			}

			{
				static const float UE_GAMMA_CORRECTION_VALUE = 2.2f; // UE4's gamma correction value is always 2.2.

				SrdXrColorManagementSettings Settings;
				Settings.gamma = UE_GAMMA_CORRECTION_VALUE;
				Settings.is_input_texture_gamma_corrected = true;
				if (!HandleXRRuntimeResult(srd::xr::ext::SetColorManagementSettings(RuntimeSessionHandle, &Settings)))
				{
					FinalizeXrRuntime();
					return false;
				}
			}

			{
				FVector4 LeftBottomPosition;
				FVector4 LeftTopPosition;
				FVector4 RightBottomPosition;
				FVector4 RightTopPosition;
				if (!GetDisplayCornersPosition(LeftBottomPosition, LeftTopPosition, RightBottomPosition, RightTopPosition))
				{
					FinalizeXrRuntime();
					return false;
				}
				DisplayProjector = new FDisplayProjector(LeftBottomPosition, LeftTopPosition, RightBottomPosition, RightTopPosition);
			}

			LeftViewProjectionMatrix = GetDefaultProjectionMatrix(EStereoscopicPass::eSSP_LEFT_EYE);
			RightViewProjectionMatrix = GetDefaultProjectionMatrix(EStereoscopicPass::eSSP_RIGHT_EYE);

			// Get SR Display's specs
			if (!HandleXRRuntimeResult(srd::xr::ext::GetPlatformSpecificData(RuntimeSessionHandle, &XrdPlatformSpecificData)))
			{
				FinalizeXrRuntime();
				return false;
			}

			// Register callback function to xr runtime. It will be called when the runtime outs logs.
			srd::xr::ext::SetDebugLogCallback([](const char* MessagePointer, SrdXrLogLevels RuntimeLogLevel)
			{
				FString Message(UTF8_TO_TCHAR(MessagePointer));
				switch (RuntimeLogLevel)
				{
					case SrdXrLogLevels::TRACE:
						UE_LOG(LogSRDisplay, VeryVerbose, TEXT("%s"), *Message);
						break;
					case SrdXrLogLevels::DEBUG:
						UE_LOG(LogSRDisplay, Log, TEXT("%s"), *Message);
						break;
					case SrdXrLogLevels::INFO:
						UE_LOG(LogSRDisplay, Display, TEXT("%s"), *Message);
						break;
					case SrdXrLogLevels::WARN:
						UE_LOG(LogSRDisplay, Warning, TEXT("%s"), *Message);
						break;
					case SrdXrLogLevels::ERR:
						UE_LOG(LogSRDisplay, Error, TEXT("%s"), *Message);
						break;
					case SrdXrLogLevels::CRITICAL:
						UE_LOG(LogSRDisplay, Fatal, TEXT("%s"), *Message);
						break;
					default:
						break;
				}
			});

		}

		return true;
	}

	bool FSRDisplaySystem::FinalizeXrRuntime()
	{
		StopXrRuntime();

		if (RuntimeSessionHandle)
		{
			srd::xr::DestroySession(&RuntimeSessionHandle);
			RuntimeSessionHandle = nullptr;
		}

		if (DisplayProjector)
		{
			delete DisplayProjector;
			DisplayProjector = nullptr;
		}

		return true;
	}

	FString FSRDisplaySystem::GetVersionString() const
	{
		// FIXME: Get version string from M/W.
		FString Version = FString::Printf(TEXT("SRDisplay - %s, built %s, %s"), *FEngineVersion::Current().ToString(),
				UTF8_TO_TCHAR(__DATE__), UTF8_TO_TCHAR(__TIME__));

		return Version;
	}

#if ENGINE_MINOR_VERSION >= 26
	int32 FSRDisplaySystem::GetXRSystemFlags() const
	{
		return EXRSystemFlags::IsHeadMounted;
	}
#endif

	bool FSRDisplaySystem::EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type)
	{
		if (Type == EXRTrackedDeviceType::Any || Type == EXRTrackedDeviceType::HeadMountedDisplay)
		{
			OutDevices.Add(IXRTrackingSystem::HMDDeviceId);
			return true;
		}

		return false;
	}

	bool FSRDisplaySystem::GetCurrentPose(int32 DeviceId, FQuat& OutOrientation, FVector& OutPosition)
	{
		FScopeLock SLGetCurrentPose(&CSGetCurrentPose);

		OutOrientation = FQuat::Identity;
		OutPosition = FVector::ZeroVector;

		if (DeviceId != IXRTrackingSystem::HMDDeviceId)
		{
			return false;
		}

		if (IsInRenderingThread())
		{
			return false;
		}

		// Get head positions from XR Runtime.
		SrdXrViewLocateInfo Info;
		Info.view_configuration_type = SrdXrViewConfigurationType::PRIMARY_STEREO;
		SrdXrView View[3];
		if (!HandleXRRuntimeResult_LocateViews(srd::xr::LocateViews(RuntimeSessionHandle, &Info, View)))
		{
			return false;
		}

		// View[0] is head position. View[1] is left eye and View[2] is right eye.
		ConvertPoseFromXrRuntimeToGameWorld(View[0].pose, OutOrientation, OutPosition);

		return true;
	}

	void FSRDisplaySystem::ConvertPoseFromXrRuntimeToGameWorld(SrdXrPosef SrcPose, FQuat& DstOrientation, FVector& DstPosition) const
	{
		FTransform Transform = FTransform(
				FQuat(SrcPose.orientation.x, SrcPose.orientation.y, SrcPose.orientation.z, SrcPose.orientation.w),
				FVector(SrcPose.position.x, SrcPose.position.y, SrcPose.position.z),
				FVector(1.f, 1.f, 1.f));

		DstOrientation = SRDisplayTransform.TransformRotation(Transform.GetRotation());
		DstPosition = SRDisplayTransform.GetRotation().RotateVector(Transform.GetLocation() * SRDisplayViewSpaceScale) + SRDisplayTransform.GetLocation();
	}

	bool FSRDisplaySystem::IsHeadTrackingAllowed() const
	{
		return bIsSessionBegun;
	}

	void FSRDisplaySystem::OnBeginPlay(FWorldContext& InWorldContext)
	{
		// initialize parameter
		SRDisplayManager = nullptr;
		SRDisplayViewSpaceScale = DEFAULT_SRD_VIEW_SPACE_SCALE;
		bQuitApplication = false;
		ShowCameraWindow = false;

		if (!IsProjectPlayModeSupported())
		{
			bQuitApplication = true;
			return;
		}
#if WITH_EDITOR
		if (!InitializeXrRuntime())
		{
			bQuitApplication = true;
			return;
		}
#else
		if (!RuntimeSessionHandle)
		{
			bQuitApplication = true;
			return;
		}
#endif
		if (!isWithoutSRDisplayMode)
		{
			if (!StartXrRuntime())
			{
				bQuitApplication = true;
				return;
			}
		}

		EnableStereo(!isWithoutSRDisplayMode);

		SrdXrCrosstalkCorrectionSettings_v1_1_0 settings_v1_0_0;
		SrdXrResult result = srd::xr::ext::SetCrosstalkCorrectionSettings_v1_1_0(RuntimeSessionHandle, &settings_v1_0_0);
		if (result == SrdXrResult::ERROR_FUNCTION_UNSUPPORTED)
		{
			UE_LOG(LogSRDisplay, Warning, TEXT("Crosstalk correction type is not supported in the installed SR Display SDK. Please update the SDK to the latest version."));
		}
	}

	void FSRDisplaySystem::OnEndPlay(FWorldContext& InWorldContext)
	{
		EnableStereo(false);

		DisplayProjectionTextureRight.SafeRelease();
		DisplayProjectionTextureLeft.SafeRelease();
		bRuntimeShaderGenerated = false;

		StopXrRuntime();

		SRDisplayManager = nullptr;
	}

	bool FSRDisplaySystem::StartXrRuntime()
	{
		// If this application hasn't begun XR runtime session yet, begin it.
		if (!bIsSessionBegun)
		{
			SrdXrSessionBeginInfo SessionBeginInfo;
			SessionBeginInfo.primary_view_configuration_type = SrdXrViewConfigurationType::PRIMARY_STEREO;
			if (!HandleXRRuntimeResult(srd::xr::BeginSession(RuntimeSessionHandle, &SessionBeginInfo)))
			{
				bRuntimeShaderGenerated = false;
				return false;
			}
			bIsSessionBegun = true;
		}

		return true;
	}

	bool FSRDisplaySystem::StopXrRuntime()
	{
		if (bIsSessionBegun)
		{
			srd::xr::EndSession(RuntimeSessionHandle);
			bIsSessionBegun = false;
		}

		return true;
	}

	bool FSRDisplaySystem::OnStartGameFrame(FWorldContext& WorldContext)
	{
		check(WorldContext.World());

		if (bQuitApplication)
		{
			if (GWorld->GetWorld()->GetFirstPlayerController())
			{
				GWorld->GetWorld()->GetFirstPlayerController()->ConsoleCommand("quit");
			}
			return false;
		}

		if (!bIsSessionBegun)
		{
			return false;
		}

		// Poll XR Runtime envent to check if the session is still connected.
		SrdXrEventDataBuffer EventDataBuffer;
		while (srd::xr::PollEvent(RuntimeSessionHandle, &EventDataBuffer) == SrdXrResult::SUCCESS)
		{
			std::wstring error_message;
			switch (EventDataBuffer.state)
			{
				case SrdXrSessionState::SYNCHRONIZED:
					// Normal case. do nothing.
					break;
				case SrdXrSessionState::LOSS_PENDING:
					bQuitApplication = true;
					error_message = SRD_ERROR_MESSAGE_C021 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					return false;
				default:
					// Unknown.
					return true;
			}
		}

		// Update XR Runtime head tracking info by calling srd::xr::BeginFrame().
		{
			FScopeLock SLGetCurrentPose(&CSGetCurrentPose);
			SrdXrResult Result = srd::xr::BeginFrame(RuntimeSessionHandle);
			if (Result != SrdXrResult::SUCCESS)
			{
				return false;
			}
		}

		UpdateSRDisplayManagerState();
		UpdateSRDisplayPosition();
		UpdateProjectionMatrix(); // Update Projection matrix. Calculation will occur only if it is necessary.

		// Update Tracking-World transformation matrix
		RefreshTrackingToWorldTransform(WorldContext);

		return true;
	}

	void FSRDisplaySystem::OnBeginRendering_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily)
	{
		FHeadMountedDisplayBase::OnBeginRendering_RenderThread(RHICmdList, ViewFamily);

		// Get left and right view projection matrix
		for (const FSceneView* SceneView : ViewFamily.Views)
		{
			if (SceneView->StereoPass == eSSP_LEFT_EYE)
			{
				LeftViewProjectionMatrix_RenderingThread = SceneView->ViewMatrices.GetViewProjectionMatrix();
			}
			else if (SceneView->StereoPass == eSSP_RIGHT_EYE)
			{
				RightViewProjectionMatrix_RenderingThread = SceneView->ViewMatrices.GetViewProjectionMatrix();
			}
		}
	}

	float FSRDisplaySystem::GetWorldToMetersScale() const
	{
		return GWorld ? GWorld->GetWorldSettings()->WorldToMeters : 100.f;
	}

	bool FSRDisplaySystem::IsHMDConnected()
	{
		return RuntimeSessionHandle ? true : false;
	}

	bool FSRDisplaySystem::GetHMDMonitorInfo(MonitorInfo& MonitorDesc)
	{
		static const FString MONITOR_NAME = TEXT("SR Display");
		static const size_t MONITOR_ID = 0;
		static const int DESKTOP_X = 0;
		static const int DESKTOP_Y = 1;

		if (bQuitApplication)
		{
			return false;
		}

		MonitorDesc.MonitorName = MONITOR_NAME;
		MonitorDesc.MonitorId = MONITOR_ID;
		MonitorDesc.DesktopX = DESKTOP_X;
		MonitorDesc.DesktopY = DESKTOP_Y;
		MonitorDesc.ResolutionX = XrdPlatformSpecificData.display_resolution.width * 2;
		MonitorDesc.ResolutionY = XrdPlatformSpecificData.display_resolution.height;
		return true;
	}

	FIntPoint FSRDisplaySystem::GetIdealRenderTargetSize() const
	{
		HWND WindowHandle = (HWND)GEngine->GameViewport->GetWindow()->GetNativeWindow()->GetOSWindowHandle();
		RECT WindowRect;
		GetWindowRect(WindowHandle, &WindowRect);

		return FIntPoint(WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top);
	}

	bool FSRDisplaySystem::IsStereoEnabled() const
	{
		return bStereoEnabled;
	}

	bool FSRDisplaySystem::EnableStereo(bool stereo)
	{
		if (stereo)
		{
			FSceneViewport* SceneViewport = nullptr;
			if (!bStereoEnabled && IsStereoRenderingAllowed(&SceneViewport))
			{
				bStereoEnabled = true;
				CreateWindowOnSRDisplay();
			}
		}
		else
		{
			DestroyWindowOnSRDisplay();
			bStereoEnabled = false;
		}

		return bStereoEnabled;
	}

	void FSRDisplaySystem::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const
	{
		SizeX = SizeX / 2;

		if (StereoPass == eSSP_RIGHT_EYE)
		{
			X += SizeX;
		}
	}

	void FSRDisplaySystem::CalculateStereoViewOffset(const enum EStereoscopicPass StereoPassType, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation)
	{
		if (StereoPassType == eSSP_FULL || StereoPassType == eSSP_LEFT_EYE_SIDE || StereoPassType == eSSP_RIGHT_EYE_SIDE)
		{
			return;
		}

		SrdXrViewLocateInfo Info;
		Info.view_configuration_type = SrdXrViewConfigurationType::PRIMARY_STEREO;
		SrdXrView View[3];
		if (!HandleXRRuntimeResult_LocateViews(srd::xr::LocateViews(RuntimeSessionHandle, &Info, View)))
		{
			return;
		}

		SrdXrPosef Pose;
		switch (StereoPassType)
		{
			case eSSP_LEFT_EYE:
				Pose = View[1].pose;
				break;
			case eSSP_RIGHT_EYE:
				Pose = View[2].pose;
				break;
		}
		FQuat ViewOrientation;
		ConvertPoseFromXrRuntimeToGameWorld(Pose, ViewOrientation, ViewLocation);
		ViewRotation = ViewOrientation.Rotator();
	}

	FMatrix FSRDisplaySystem::GetStereoProjectionMatrix(const enum EStereoscopicPass StereoPassType) const
	{
		if (StereoPassType == eSSP_LEFT_EYE)
		{
			return LeftViewProjectionMatrix;
		}
		else
		{
			return RightViewProjectionMatrix;
		}
	}

	void FSRDisplaySystem::RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture2D* BackBuffer, FRHITexture2D* SrcTexture, FVector2D WindowSize) const
	{
		if (!FSRDisplayWindow::GetPlayWindow())
		{
			return;
		}

		uint32 SourceWidth = SrcTexture->GetSizeX();
		uint32 SourceHeight = SrcTexture->GetSizeY();

		// Create offscreen rendering texture for display projection.
		if (DisplayProjectionTextureLeft == nullptr)
		{
			FRHIResourceCreateInfo Info;
			DisplayProjectionTextureLeft = RHICreateTexture2D(
					SourceWidth / 2, SourceHeight, PF_A2B10G10R10, 1, 1,
					TexCreate_None | TexCreate_ShaderResource | TexCreate_RenderTargetable, Info);
		}
		if (DisplayProjectionTextureRight == nullptr)
		{
			FRHIResourceCreateInfo Info;
			DisplayProjectionTextureRight = RHICreateTexture2D(
					SourceWidth / 2, SourceHeight, PF_A2B10G10R10, 1, 1,
					TexCreate_None | TexCreate_ShaderResource | TexCreate_RenderTargetable, Info);
		}

		if (bShowStereoImages && StereoTexture.IsValid())
		{
			// Use stereo 2D image instead of 3D game scene.
			StereoTexture.Overwrite(DisplayProjectionTextureLeft, DisplayProjectionTextureRight,
				RHICmdList, SourceWidth, SourceHeight);
		}
		else
		{
			// Display projection of 3D game scene.
			if (!DisplayProjector->ExecuteDisplayProjection(
					RHICmdList, eSSP_LEFT_EYE, DisplayProjectionTextureLeft, SrcTexture,
					DisplayProjectionTextureLeft->GetSizeXY(), LeftViewProjectionMatrix_RenderingThread, SourceWidth, SourceHeight) ||
				!DisplayProjector->ExecuteDisplayProjection(
					RHICmdList, eSSP_RIGHT_EYE, DisplayProjectionTextureRight, SrcTexture,
					DisplayProjectionTextureRight->GetSizeXY(), RightViewProjectionMatrix_RenderingThread, SourceWidth, SourceHeight))
			{
				return;
			}
		}

		// Composite stereo images
		{
			FRHIRenderPassInfo RPInfoTemp(
				FSRDisplayWindow::GetPlayWindow()->GetRenderTarget()->GetRenderTargetResource()->GetRenderTargetTexture(),
				ERenderTargetActions::Load_Store);
			RHICmdList.BeginRenderPass(RPInfoTemp, TEXT("FSRDisplaySystem_RenderTexture_RenderThread"));
			{
				// RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);  // TODO: When it turns out to be necessary, release comment out
				CompositeStereoImages(RHICmdList,
					FSRDisplayWindow::GetPlayWindow()->GetRenderTarget()->GetRenderTargetResource()->GetRenderTargetTexture(),
					DisplayProjectionTextureLeft, DisplayProjectionTextureRight, SourceWidth, SourceHeight);
			}
			RHICmdList.EndRenderPass();
			FSRDisplayWindow::GetPlayWindow()->RenderTexture_RenderThread(RHICmdList);
		}
	}

	IStereoRenderTargetManager* FSRDisplaySystem::GetRenderTargetManager()
	{
		if (RenderTargetManager == nullptr)
		{
			RenderTargetManager = new FRenderTargetManager();
		}

		return RenderTargetManager;
	}

	void FSRDisplaySystem::SetupViewFamily(FSceneViewFamily& InViewFamily)
	{
		InViewFamily.EngineShowFlags.MotionBlur = 0;
		InViewFamily.EngineShowFlags.HMDDistortion = false;
		InViewFamily.EngineShowFlags.ScreenPercentage = 1.f;
		InViewFamily.EngineShowFlags.StereoRendering = IsStereoEnabled();
	}

	void FSRDisplaySystem::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView)
	{
		InView.BaseHmdOrientation = FQuat(FRotator(0.f, 0.f, 0.f));
		InView.BaseHmdLocation = FVector(0.f);
	}

	void FSRDisplaySystem::BeginRenderViewFamily(FSceneViewFamily& InViewFamily)
	{
	}

	void FSRDisplaySystem::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily)
	{
	}

	void FSRDisplaySystem::PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView)
	{
	}

	bool FSRDisplaySystem::IsActiveThisFrame(FViewport* InViewport) const
	{
		return GEngine && GEngine->IsStereoscopic3D(InViewport);
	}

	/***********************************************************************************/
	/* SR Display APIs.                                                                */
	/***********************************************************************************/
	bool FSRDisplaySystem::GetMousePosition(float& LocationX, float& LocationY)
	{
		bool bGotMousePosition = false;

		if (FSRDisplayWindow::GetPlayWindow())
		{
			FVector2D MousePosition;

			bGotMousePosition = FSRDisplayWindow::GetPlayWindow()->GetMousePosition(MousePosition);

			if (bGotMousePosition)
			{
				LocationX = MousePosition.X;
				LocationY = MousePosition.Y;
			}
		}

		return bGotMousePosition;
	}

	bool FSRDisplaySystem::DeprojectScreenToWorld(APlayerController const* Player, const FVector2D& ScreenPosition, FVector& WorldPosition, FVector& WorldDirection, FVector& CameraPosition)
	{
		ULocalPlayer* const LocalPlayer = Player ? Player->GetLocalPlayer() : nullptr;
		if (LocalPlayer && FSRDisplayWindow::GetPlayWindow() && FSRDisplayWindow::GetPlayWindow()->GetViewport())
		{
			// get the projection data
			FSceneViewProjectionData ProjectionData;
			if (LocalPlayer->GetProjectionData(FSRDisplayWindow::GetPlayWindow()->GetViewport(), eSSP_LEFT_EYE, /*out*/ ProjectionData))
			{
				// Calculate WorldPosition
				FVector4 LeftBottomPosition;
				FVector4 LeftTopPosition;
				FVector4 RightBottomPosition;
				FVector4 RightTopPosition;
				if (!GetDisplayCornersPosition(LeftBottomPosition, LeftTopPosition, RightBottomPosition, RightTopPosition))
				{
					WorldPosition = FVector::ZeroVector;
					WorldDirection = FVector::ZeroVector;
					return false;
				}

				FVector2D screenRate = FVector2D(ScreenPosition.X / DisplayResolution.X, ScreenPosition.Y / DisplayResolution.Y);
				FVector4 yDirection = RightTopPosition - LeftTopPosition;
				FVector4 zDirection = LeftBottomPosition - LeftTopPosition;
				WorldPosition = LeftTopPosition + yDirection * screenRate.X + zDirection * screenRate.Y;

				// Calculate WorldDirection
				WorldDirection = (WorldPosition - ProjectionData.ViewOrigin).GetSafeNormal();

				// Calculate CameraPosition
				CameraPosition = ProjectionData.ViewOrigin;

				return true;
			}
		}

		// something went wrong, zero things and return false
		WorldPosition = FVector::ZeroVector;
		WorldDirection = FVector::ZeroVector;
		return false;
	}

	void FSRDisplaySystem::SetStereoImages(FTexture2DRHIRef Left, FTexture2DRHIRef Right)
	{
		StereoTexture.SetLeft(Left);
		StereoTexture.SetRight(Right);
	}

	void FSRDisplaySystem::ShowStereoImages(bool bShow)
	{
		bShowStereoImages = bShow;
	}

	bool FSRDisplaySystem::IsStereoImagesShown()
	{
		return bShowStereoImages;
	}

	void FSRDisplaySystem::GetDisplaySpec(float& Width, float& Height, float& Tilt)
	{
		if (!RuntimeSessionHandle)
		{
			Width = DEFAULT_SRD_DISPLAY_WIDTH;
			Height = DEFAULT_SRD_DISPLAY_HEIGHT;
			Tilt = DEFAULT_SRD_DISPLAY_TILT;
			return;
		}

		Width = XrdPlatformSpecificData.display_size.width_m * METER_TO_CENTIMETER;
		Height = XrdPlatformSpecificData.display_size.height_m * METER_TO_CENTIMETER;
		Tilt = FMath::RadiansToDegrees(XrdPlatformSpecificData.display_tilt_rad);
	}
	/***********************************************************************************/
	/* Private subroutines.                                                            */
	/***********************************************************************************/
	bool FSRDisplaySystem::IsStereoRenderingAllowed(FSceneViewport** OutSceneViewport)
	{
		if (!GIsEditor)
		{
			UGameEngine* GameEngine = Cast<UGameEngine>(GEngine);
			if (GameEngine != nullptr && GameEngine->SceneViewport.IsValid())
			{
				*OutSceneViewport = GameEngine->SceneViewport.Get();
				return GameEngine->SceneViewport.Get()->IsStereoRenderingAllowed();
			}
		}
#if WITH_EDITOR
		else
		{
			// Return true if automation test
			if (GIsAutomationTesting)
			{
				*OutSceneViewport = nullptr;
				return true;
			}

			UEditorEngine* EditorEngine = CastChecked<UEditorEngine>(GEngine);
			FSceneViewport* PIEViewport = (FSceneViewport*)EditorEngine->GetPIEViewport();
			if (PIEViewport != nullptr)
			{
				*OutSceneViewport = PIEViewport;
				return PIEViewport->IsStereoRenderingAllowed();
			}
			else
			{
				// Check to see if the active editor viewport is drawing in stereo mode
				// @todo VR Editor: Should work with even non-active viewport!
				FSceneViewport* EditorViewport = (FSceneViewport*)EditorEngine->GetActiveViewport();
				if (EditorViewport != nullptr)
				{
					*OutSceneViewport = EditorViewport;
					return EditorViewport->IsStereoRenderingAllowed();
				}
			}
		}
#endif
		*OutSceneViewport = nullptr;
		return false;
	}

	bool FSRDisplaySystem::IsProjectPlayModeSupported()
	{
#if WITH_EDITOR
		if (GWorld->GetWorld()->IsPlayInMobilePreview())
		{
			if (bQuitApplication)
			{
				return false;
			}

			std::wstring error_message = SRD_ERROR_MESSAGE_UE022 + L"\n" + SRD_ERROR_MESSAGE_C017;
			UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
			FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
			return false;
		}
#endif
		return true;
	}

	bool FSRDisplaySystem::IsProjectGraphicsAPISupported()
	{
		FString RHIModuleName = GetSelectedDynamicRHIModuleName();
		if (RHIModuleName.Find("D3D11RHI") != INDEX_NONE)
		{
			return true;
		}

		if (bQuitApplication)
		{
			return false;
		}

		std::wstring error_message = SRD_ERROR_MESSAGE_UE023 + L"\n" + SRD_ERROR_MESSAGE_C017;
		UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
		FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));

		UE_LOG(LogSRDisplay, Error, TEXT("RHIModule used in this module is %s"), *RHIModuleName);
		return false;
	}

	SrdXrGraphicsAPI FSRDisplaySystem::GetCurrentGraphicsAPI()
	{
		FString RHIModuleName = GetSelectedDynamicRHIModuleName();
		if (RHIModuleName.Find("OpenGL") != INDEX_NONE)
		{
			return SrdXrGraphicsAPI::OpenGL;
		}
		else if (RHIModuleName.Find("D3D12RHI") != INDEX_NONE)
		{
			return SrdXrGraphicsAPI::DirectX_12;
		}
		else if (RHIModuleName.Find("D3D11RHI") != INDEX_NONE)
		{
			return SrdXrGraphicsAPI::DirectX_11;
		}
		else
		{
			return SrdXrGraphicsAPI::DirectX_11;
		}
	}

	ASRDisplayManager* FSRDisplaySystem::GetSRDisplayManagerActor()
	{
		TArray<AActor*> FoundActors;
		UGameplayStatics::GetAllActorsOfClass(GWorld->GetWorld(), ASRDisplayManager::StaticClass(), FoundActors);
		if (FoundActors.Num() <= 0)
		{
			return nullptr;
		}

		if (FoundActors.Num() > 1)
		{
			UE_LOG(LogSRDisplay, Warning, TEXT("There are more than two SRDisplayManager placed on the level. Only one of them is available."));
		}

		ASRDisplayManager* Display = Cast<ASRDisplayManager>(FoundActors[FoundActors.Num() - 1]);
		if (!Display->HasActorBegunPlay())
		{
			return nullptr;
		}

		return Display;
	}

	void FSRDisplaySystem::UpdateSRDisplayManagerState()
	{
		static const float FAR_CLIP_MIN = 1.f;

		SRDisplayManager = GetSRDisplayManagerActor();
		if (SRDisplayManager)
		{
			SRDisplayViewSpaceScale = SRDisplayManager->GetRealToVirtualScale();
			SRDisplayTransform = SRDisplayManager->GetActorTransform();
			SpatialClipping = SRDisplayManager->SpatialClipping;
			SRDisplayFarClip = SRDisplayManager->FarClip;
			if (SRDisplayFarClip < FAR_CLIP_MIN)
			{
				SRDisplayFarClip = FAR_CLIP_MIN;
			}

			if (ShowCameraWindow != SRDisplayManager->ShowCameraWindow)
			{
				ShowCameraWindow = SRDisplayManager->ShowCameraWindow;
				srd::xr::ext::ShowCapturedImageWindow(RuntimeSessionHandle, ShowCameraWindow);
			}

			bool enableCrosstalkCorrection = SRDisplayManager->CrosstalkCorrection;
			SrdXrCrosstalkCorrectionSettings_v1_1_0 settings_v1_0_0;
			switch (SRDisplayManager->CorrectionType)
			{
				case ECrosstalkCorrectionType::GRADATION_CORRECTION_MEDIUM:
					settings_v1_0_0.correction_level = SrdXrCrosstalkCorrectionLevel::GRADATION_CORRECTION_MEDIUM;
					break;
				case ECrosstalkCorrectionType::GRADATION_CORRECTION_ALL:
					settings_v1_0_0.correction_level = SrdXrCrosstalkCorrectionLevel::GRADATION_CORRECTION_ALL;
					break;
				case ECrosstalkCorrectionType::GRADATION_CORRECTION_HIGH_PRECISE:
					settings_v1_0_0.correction_level = SrdXrCrosstalkCorrectionLevel::GRADATION_CORRECTION_HIGH_PRECISE;
					break;
				default:
					settings_v1_0_0.correction_level = SrdXrCrosstalkCorrectionLevel::GRADATION_CORRECTION_MEDIUM;
					break;
			}
			settings_v1_0_0.compensation_enabled = enableCrosstalkCorrection;
			SrdXrResult result = srd::xr::ext::SetCrosstalkCorrectionSettings_v1_1_0(RuntimeSessionHandle, &settings_v1_0_0);
			if (result == SrdXrResult::ERROR_FUNCTION_UNSUPPORTED)
			{
				SrdXrCrosstalkCorrectionSettings settings(enableCrosstalkCorrection);
				SrdXrResult ret = srd::xr::ext::SetCrosstalkCorrectionSettings(RuntimeSessionHandle, &settings);
			}

		}
	}

	void FSRDisplaySystem::UpdateSRDisplayPosition()
	{
		FVector4 LeftBottomPosition;
		FVector4 LeftTopPosition;
		FVector4 RightBottomPosition;
		FVector4 RightTopPosition;
		if (GetDisplayCornersPosition(LeftBottomPosition, LeftTopPosition, RightBottomPosition, RightTopPosition))
		{
			DisplayProjector->SetDisplayCornersPosition(LeftBottomPosition, LeftTopPosition, RightBottomPosition, RightTopPosition);
		}
	}

	bool FSRDisplaySystem::GetDisplayCornersPosition(FVector4& LeftBottomPosition, FVector4& LeftTopPosition, FVector4& RightBottomPosition, FVector4& RightTopPosition) const
	{
		float DisplayWidth = XrdPlatformSpecificData.display_size.width_m * METER_TO_CENTIMETER;
		float DisplayHeight = XrdPlatformSpecificData.display_size.height_m * METER_TO_CENTIMETER;

		DisplayWidth *= SRDisplayViewSpaceScale;
		DisplayHeight *= SRDisplayViewSpaceScale;

		const float LFBDepthByTilt = DisplayHeight * FMath::Sin(XrdPlatformSpecificData.display_tilt_rad);
		const float LFBHeightByTilt = DisplayHeight * FMath::Cos(XrdPlatformSpecificData.display_tilt_rad);

		LeftBottomPosition = FVector4(0.f, -DisplayWidth / 2.f, 0.f, 1.f);
		LeftBottomPosition = SRDisplayTransform.GetRotation().RotateVector(LeftBottomPosition) + FVector4(SRDisplayTransform.GetLocation(), 0.f);

		LeftTopPosition = FVector4(LFBDepthByTilt, -DisplayWidth / 2.f, LFBHeightByTilt, 1.f);
		LeftTopPosition = SRDisplayTransform.GetRotation().RotateVector(LeftTopPosition) + FVector4(SRDisplayTransform.GetLocation(), 0.f);

		RightBottomPosition = FVector4(0.f, DisplayWidth / 2.f, 0.f, 1.f);
		RightBottomPosition = SRDisplayTransform.GetRotation().RotateVector(RightBottomPosition) + FVector4(SRDisplayTransform.GetLocation(), 0.f);

		RightTopPosition = FVector4(LFBDepthByTilt, DisplayWidth / 2.f, LFBHeightByTilt, 1.f);
		RightTopPosition = SRDisplayTransform.GetRotation().RotateVector(RightTopPosition) + FVector4(SRDisplayTransform.GetLocation(), 0.f);

		return true;
	}

	void FSRDisplaySystem::UpdateProjectionMatrix()
	{
		static const float DEFAULT_NEAR_Z = 15.f;

		// Get projection matrix from XR Runtime.
		SrdXrProjectionMatrix ProjectionMatrix;
		{
			SrdXrProjectionMatrixInfo ProjectionMatrixInfo;
			ProjectionMatrixInfo.coordinate_system = SrdXrCoordinateSystem::LEFT_Z_UP_X_FORWARD;
			ProjectionMatrixInfo.graphics_api = SrdXrGraphicsAPI::DirectX_11;
			ProjectionMatrixInfo.reversed_z = true;
			ProjectionMatrixInfo.near_clip = DEFAULT_NEAR_Z * SRDisplayViewSpaceScale;
			ProjectionMatrixInfo.far_clip = SRDisplayFarClip * SRDisplayViewSpaceScale;
			if (!HandleXRRuntimeResult_GetProjectionMatrix(srd::xr::ext::GetProjectionMatrix(RuntimeSessionHandle, &ProjectionMatrixInfo, &ProjectionMatrix)))
			{
				return;
			}
		}

		auto leftMatrix = ProjectionMatrix.left_projection.matrix;
		LeftViewProjectionMatrix =
			FMatrix(
				FPlane(leftMatrix[0][0], leftMatrix[0][1], leftMatrix[0][2], leftMatrix[0][3]),
				FPlane(leftMatrix[1][0], leftMatrix[1][1], leftMatrix[1][2], leftMatrix[1][3]),
				FPlane(leftMatrix[2][0], leftMatrix[2][1], leftMatrix[2][2], leftMatrix[2][3]),
				FPlane(leftMatrix[3][0], leftMatrix[3][1], leftMatrix[3][2], leftMatrix[3][3])
			);

		auto rightMatrix = ProjectionMatrix.right_projection.matrix;
		RightViewProjectionMatrix =
			FMatrix(
				FPlane(rightMatrix[0][0], rightMatrix[0][1], rightMatrix[0][2], rightMatrix[0][3]),
				FPlane(rightMatrix[1][0], rightMatrix[1][1], rightMatrix[1][2], rightMatrix[1][3]),
				FPlane(rightMatrix[2][0], rightMatrix[2][1], rightMatrix[2][2], rightMatrix[2][3]),
				FPlane(rightMatrix[3][0], rightMatrix[3][1], rightMatrix[3][2], rightMatrix[3][3])
			);

		FMatrix LeftViewMatrix;
		FMatrix RightViewMatrix;
		if (SpatialClipping && GetViewMatrix(LeftViewMatrix, RightViewMatrix))
		{
			FVector4 LeftBottomPosition = FVector4(0.f, -1.f * XrdPlatformSpecificData.display_size.width_m * METER_TO_CENTIMETER / 2.f, 0.f, 1.f);
			LeftBottomPosition = SRDisplayTransform.GetRotation().RotateVector(LeftBottomPosition) + FVector4(SRDisplayTransform.GetLocation(), 0.f);

			static const FVector4 ClipPlaneOffset(5.f, 0.f, 0.f, 1.f);
			FVector ClipPlanePositionInWorldCoord = LeftBottomPosition - ClipPlaneOffset;
			FVector ClipPlaneNormalVecInWorldCoord = SRDisplayTransform.GetRotation().RotateVector(FVector(1, 0, 0));

			LeftViewProjectionMatrix = GetSpatialClippedProjectionMatrix(ClipPlanePositionInWorldCoord, ClipPlaneNormalVecInWorldCoord, LeftViewMatrix, LeftViewProjectionMatrix);
			RightViewProjectionMatrix = GetSpatialClippedProjectionMatrix(ClipPlanePositionInWorldCoord, ClipPlaneNormalVecInWorldCoord, RightViewMatrix, RightViewProjectionMatrix);
		}
	}

	FMatrix FSRDisplaySystem::GetSpatialClippedProjectionMatrix(FVector ClipPlanePositionInWorldCoord, FVector ClipPlaneNormalVecInWorldCoord, FMatrix ViewMatrix, FMatrix ProjectionMatrix)
	{
		// Caluculate clipping plane(view)
		FVector NormalInCameraCoord = ViewMatrix.TransformVector(ClipPlaneNormalVecInWorldCoord);
		NormalInCameraCoord.Normalize();
		FVector PosInCameraCoord = ViewMatrix.TransformPosition(ClipPlanePositionInWorldCoord);
		FVector4 ClipPlane = FVector4(NormalInCameraCoord.X, NormalInCameraCoord.Y, NormalInCameraCoord.Z,
				-FVector::DotProduct(PosInCameraCoord, NormalInCameraCoord));

		return GetNearClipPlaneConvertedProjectionMatrix(ProjectionMatrix, ClipPlane);
	}

	float sgn(float a)
	{
		if (a > 0.f) { return (1.f); }
		if (a < 0.f) { return (-1.f); }
		return (0.f);
	}

	FMatrix FSRDisplaySystem::GetNearClipPlaneConvertedProjectionMatrix(FMatrix SrcProjMat, FVector4 PlaneInViewSpace)
	{
		FMatrix ZReverseMatrix;
		ZReverseMatrix.SetIdentity();
		ZReverseMatrix.M[2][2] = -1.f;
		ZReverseMatrix.M[3][2] = 1.f;

		FMatrix Test = ZReverseMatrix * ZReverseMatrix;
		FMatrix UnZReversedProjMat = SrcProjMat * ZReverseMatrix;

		FVector4 CornerPlane(
			(sgn(PlaneInViewSpace.X) - UnZReversedProjMat.M[2][0]) / UnZReversedProjMat.M[0][0],
			(sgn(PlaneInViewSpace.Y) - UnZReversedProjMat.M[2][1]) / UnZReversedProjMat.M[1][1],
			1.f,
			(1.f - UnZReversedProjMat.M[2][2]) / UnZReversedProjMat.M[3][2]
		);

		FVector4 ProjPlane(PlaneInViewSpace * (1.f / Dot4(PlaneInViewSpace, CornerPlane)));

		UnZReversedProjMat.M[0][2] = ProjPlane.X;
		UnZReversedProjMat.M[1][2] = ProjPlane.Y;
		UnZReversedProjMat.M[2][2] = ProjPlane.Z;
		UnZReversedProjMat.M[3][2] = ProjPlane.W;

		return UnZReversedProjMat * ZReverseMatrix;
	}

	FMatrix FSRDisplaySystem::GetDefaultProjectionMatrix(enum EStereoscopicPass StereoPassType) const
	{
		// XR tracking session has not established yet. Use defalut projection matrix.
		const float DEFAULT_NEAR_Z = 15.f;
		const float DEFAULT_FAR_Z = 1000.f;
		const float DEFAULT_FOV = 30.f;

		// Convert from degrees to radian.
		const float HalfFov = DEFAULT_FOV / 180.f * 3.14f;
		const int ViewportWidth = XrdPlatformSpecificData.display_resolution.width * 2;
		const int ViewportHeight = XrdPlatformSpecificData.display_resolution.height;

		// Translate into DirectX coordinate system (Z-forward).
		const float M0_0 = ViewportHeight / tan(HalfFov / 2.f) / (ViewportWidth / 2.f);
		const float M1_1 = 1.f / tan(HalfFov / 2.f);
		const float M2_2 = DEFAULT_NEAR_Z / (DEFAULT_NEAR_Z - DEFAULT_FAR_Z);
		const float M2_3 = 1.f;
		const float M3_2 = (-DEFAULT_NEAR_Z * DEFAULT_FAR_Z) / (DEFAULT_NEAR_Z - DEFAULT_FAR_Z);

		const FMatrix DefaultProjectionMatrix(
			FPlane(M0_0, 0.f, 0.f, 0.f),
			FPlane(0.f, M1_1, 0.f, 0.f),
			FPlane(0.f, 0.f, M2_2, M2_3),
			FPlane(0.f, 0.f, M3_2, 0.f)
		);

		return DefaultProjectionMatrix;
	}

	bool FSRDisplaySystem::GetViewMatrix(FMatrix& LeftViewMatrix, FMatrix& RightViewMatrix)
	{
		if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWorld())
		{
			APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GEngine->GameViewport->GetWorld(), 0);
			ULocalPlayer* const LocalPlayer = PlayerController ? PlayerController->GetLocalPlayer() : nullptr;
			if (LocalPlayer && FSRDisplayWindow::GetPlayWindow() && FSRDisplayWindow::GetPlayWindow()->GetViewport())
			{
				FSceneViewProjectionData LeftProjectionData;
				FSceneViewProjectionData RightProjectionData;
				if (LocalPlayer->GetProjectionData(FSRDisplayWindow::GetPlayWindow()->GetViewport(), eSSP_LEFT_EYE, /*out*/ LeftProjectionData)
					&& LocalPlayer->GetProjectionData(FSRDisplayWindow::GetPlayWindow()->GetViewport(), eSSP_RIGHT_EYE, /*out*/ RightProjectionData))
				{
					LeftViewMatrix = FTranslationMatrix(-LeftProjectionData.ViewOrigin) * LeftProjectionData.ViewRotationMatrix;
					RightViewMatrix = FTranslationMatrix(-RightProjectionData.ViewOrigin) * RightProjectionData.ViewRotationMatrix;

					return true;
				}
			}
		}

		return false;
	}

	void FSRDisplaySystem::CreateWindowOnSRDisplay()
	{
		if (!RuntimeSessionHandle || bQuitApplication)
		{
			return;
		}

		SrdXrDeviceInfo DeviceInfo;
		if (!HandleXRRuntimeResult(srd::xr::ext::GetDevice(0, &DeviceInfo)))
		{
			bQuitApplication = true;
			return;
		}

		FSRDisplayWindow::Initialize(
			FVector2D(XrdPlatformSpecificData.display_resolution.width, XrdPlatformSpecificData.display_resolution.height),
			FIntRect(DeviceInfo.target_monitor_rectangle.left, DeviceInfo.target_monitor_rectangle.top,
				DeviceInfo.target_monitor_rectangle.right, DeviceInfo.target_monitor_rectangle.bottom));
	}

	void FSRDisplaySystem::DestroyWindowOnSRDisplay()
	{
		FSRDisplayWindow::Finalize();
	}

	void FSRDisplaySystem::CompositeStereoImages(FRHICommandListImmediate& RHICmdList, FRHITexture2D* BackBuffer, FRHITexture2D* SrcLeftTexture, FRHITexture2D* SrcRightTexture, uint32 SourceWidth, uint32 SourceHeight) const
	{
		// Set rendering device created by UE to XR Runtime.
		{
			SrdXrResult Result = srd::xr::ext::SetRenderDevice(RuntimeSessionHandle, RHIGetNativeDevice());
			if (Result != SrdXrResult::SUCCESS)
			{
				return;
			}
		}

		// Let XR Runtime to create textures for offscreen rendering and shaders.
		if (!bRuntimeShaderGenerated)
		{
			SrdXrTexture LeftTexture;
			SrdXrTexture RightTexture;
			SrdXrTexture TargetTexture;
			LeftTexture.texture = (void*)SrcLeftTexture->GetNativeResource();
			LeftTexture.width = SrcLeftTexture->GetSizeX();
			LeftTexture.height = SrcLeftTexture->GetSizeY();
			RightTexture.texture = (void*)SrcRightTexture->GetNativeResource();
			RightTexture.width = SrcRightTexture->GetSizeX();
			RightTexture.height = SrcRightTexture->GetSizeY();
			TargetTexture.texture = (void*)BackBuffer->GetNativeResource();
			TargetTexture.width = BackBuffer->GetSizeX();
			TargetTexture.height = BackBuffer->GetSizeY();

			SrdXrResult Result = srd::xr::ext::GenerateTextureAndShaders(RuntimeSessionHandle, &LeftTexture, &RightTexture, &TargetTexture);
			if (Result != SrdXrResult::SUCCESS)
			{
				return;
			}
			bRuntimeShaderGenerated = true;
		}

		// Call EndFrame() and let XR Runtime composite stereo images.
		{
			SrdXrFrameEndInfo EndFrameInfo;
			SrdXrResult Result = srd::xr::EndFrame(RuntimeSessionHandle, &EndFrameInfo);
			if (Result != SrdXrResult::SUCCESS)
			{
				return;
			}
		}
	}

	bool FSRDisplaySystem::HandleXRRuntimeResult(SrdXrResult Result)
	{
		if (Result != SrdXrResult::SUCCESS)
		{
			if (bQuitApplication)
			{
				return false;
			}
#if WITH_EDITOR
			UEditorEngine* EditorEngine = Cast<UEditorEngine>(GEngine);
			if (EditorEngine)
			{
				FSceneViewport* EditorViewport = (FSceneViewport*)EditorEngine->GetActiveViewport();
				if (EditorViewport == nullptr)
				{
					return false;
				}
			}
#endif
			std::wstring error_message;
			switch (Result)
			{
				case SrdXrResult::ERROR_HANDLE_INVALID:
					// TBD
					break;
				case SrdXrResult::ERROR_SESSION_RUNNING:
					error_message = SRD_ERROR_MESSAGE_C023 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
				case SrdXrResult::ERROR_RUNTIME_NOT_FOUND:
					error_message = SRD_ERROR_MESSAGE_C022 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
				case SrdXrResult::ERROR_DEVICE_NOT_FOUND:
					error_message = SRD_ERROR_MESSAGE_C020 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
				case SrdXrResult::ERROR_DISPLAY_NOT_FOUND:
					error_message = SRD_ERROR_MESSAGE_C018 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
				case SrdXrResult::ERROR_USB_NOT_CONNECTED:
					error_message = SRD_ERROR_MESSAGE_C019 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
				case SrdXrResult::ERROR_USB_3_NOT_CONNECTED:
					error_message = SRD_ERROR_MESSAGE_C030 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
				default:
					error_message = SRD_ERROR_MESSAGE_C016 + L"\n" + SRD_ERROR_MESSAGE_C017;
					UE_LOG(LogSRDisplay, Error, TEXT("%s"), error_message.c_str());
					FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, error_message.c_str(), *(this->GetSystemName().ToString()));
					break;
			}

			return false;
		}

		return true; // SrdXrResult::SUCCESS
	}

	bool FSRDisplaySystem::HandleXRRuntimeResult_LocateViews(SrdXrResult Result)
	{
		if (Result != SrdXrResult::SUCCESS)
		{
			switch (Result)
			{
				case SrdXrResult::ERROR_POSE_INVALID:
					return true;
				default:
					return HandleXRRuntimeResult(Result);
			}
		}
		return true;
	}

	bool FSRDisplaySystem::HandleXRRuntimeResult_GetProjectionMatrix(SrdXrResult Result)
	{
		if (Result != SrdXrResult::SUCCESS)
		{
			switch (Result)
			{
				case SrdXrResult::ERROR_SESSION_NOT_RUNNING: // no break.
				case SrdXrResult::ERROR_RUNTIME_FAILURE:
					return false;
				default:
					return HandleXRRuntimeResult(Result);
			}
		}
		return true;
	}

} // namespace sr_display
