// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. #include "DisplayClusterDeviceBase.h" #include "Cluster/IPDisplayClusterClusterManager.h" #include "Cluster/Controller/IPDisplayClusterNodeController.h" #include "Config/IPDisplayClusterConfigManager.h" #include "Game/IPDisplayClusterGameManager.h" #include "DisplayClusterScreenComponent.h" #include "RHIStaticStates.h" #include "Slate/SceneViewport.h" #include "Misc/DisplayClusterHelpers.h" #include "Misc/DisplayClusterLog.h" #include "DisplayClusterGlobals.h" #include "IPDisplayCluster.h" #include <utility> FDisplayClusterDeviceBase::FDisplayClusterDeviceBase() : FRHICustomPresent() { UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT(".ctor FDisplayClusterDeviceBase")); } FDisplayClusterDeviceBase::~FDisplayClusterDeviceBase() { UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT(".dtor FDisplayClusterDeviceBase")); } bool FDisplayClusterDeviceBase::Initialize() { if (GDisplayCluster->GetOperationMode() == EDisplayClusterOperationMode::Disabled) { return false; } UE_LOG(LogDisplayClusterRender, Log, TEXT("Use swap interval: %d"), SwapInterval); return true; } void FDisplayClusterDeviceBase::WaitForBufferSwapSync(int32& InOutSyncInterval) { // Perform SW synchronization UE_LOG(LogDisplayClusterRender, Verbose, TEXT("Waiting for swap sync...")); // Policies below are available for any render device type switch (SwapSyncPolicy) { case EDisplayClusterSwapSyncPolicy::None: { exec_BarrierWait(); InOutSyncInterval = 0; break; } default: { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Swap sync policy drop: %d"), (int)SwapSyncPolicy); InOutSyncInterval = 0; break; } } } void FDisplayClusterDeviceBase::UpdateProjectionScreenDataForThisFrame() { UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("UpdateProjectionScreenDataForThisFrame")); check(IsInGameThread()); if (GDisplayCluster->GetOperationMode() == EDisplayClusterOperationMode::Disabled) { return; } // Store transformations of active projection screen UDisplayClusterScreenComponent* pScreen = GDisplayCluster->GetPrivateGameMgr()->GetActiveScreen(); if (pScreen) { ProjectionScreenLoc = pScreen->GetComponentLocation(); ProjectionScreenRot = pScreen->GetComponentRotation(); ProjectionScreenSize = pScreen->GetScreenSize(); } } void FDisplayClusterDeviceBase::exec_BarrierWait() { if (GDisplayCluster->GetOperationMode() == EDisplayClusterOperationMode::Disabled) { return; } double tTime = 0.f; double bTime = 0.f; IPDisplayClusterNodeController* const pController = GDisplayCluster->GetPrivateClusterMgr()->GetController(); if (pController) { pController->WaitForSwapSync(&tTime, &bTime); } UE_LOG(LogDisplayClusterRender, Verbose, TEXT("Render barrier wait: t=%lf b=%lf"), tTime, bTime); } ////////////////////////////////////////////////////////////////////////////////////////////// // IStereoRendering ////////////////////////////////////////////////////////////////////////////////////////////// bool FDisplayClusterDeviceBase::IsStereoEnabled() const { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("IsStereoEnabled")); return true; } bool FDisplayClusterDeviceBase::IsStereoEnabledOnNextFrame() const { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("IsStereoEnabledOnNextFrame")); return true; } bool FDisplayClusterDeviceBase::EnableStereo(bool stereo /*= true*/) { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("EnableStereo")); return true; } void FDisplayClusterDeviceBase::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const { X = ViewportArea.GetLocation().X; SizeX = ViewportArea.GetSize().X; Y = ViewportArea.GetLocation().Y; SizeY = ViewportArea.GetSize().Y; } void FDisplayClusterDeviceBase::CalculateStereoViewOffset(const enum EStereoscopicPass StereoPassType, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation) { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("CalculateStereoViewOffset")); check(IsInGameThread()); check(WorldToMeters > 0.f); UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("OLD ViewLoc: %s, ViewRot: %s"), *ViewLocation.ToString(), *ViewRotation.ToString()); UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WorldToMeters: %f"), WorldToMeters); CurrentWorldToMeters = WorldToMeters; // View vector must be orthogonal to the projection plane. ViewRotation = ProjectionScreenRot; const float ScaledEyeDist = EyeDist * CurrentWorldToMeters; const float EyeOffset = ScaledEyeDist / 2.f; const float PassOffset = ((StereoPassType == EStereoscopicPass::eSSP_LEFT_EYE || GDisplayCluster->GetPrivateClusterMgr()->GetNodeId().Contains("left_eye")) ? -EyeOffset : EyeOffset); const float PassOffsetSwap = (bEyeSwap == true ? -PassOffset : PassOffset); // offset eye position along Y (right) axis of camera UDisplayClusterCameraComponent* pCamera = GDisplayCluster->GetPrivateGameMgr()->GetActiveCamera(); if(pCamera) { const FString nodeId = GDisplayCluster->GetPrivateClusterMgr()->GetNodeId(); const FQuat eyeQuat = pCamera->GetComponentQuat(); ViewLocation += eyeQuat.RotateVector(FVector(0.0f, PassOffsetSwap, 0.0f)); } const int eyeIdx = (StereoPassType == EStereoscopicPass::eSSP_LEFT_EYE ? 0 : 1); EyeLoc[eyeIdx] = ViewLocation; EyeRot[eyeIdx] = ViewRotation; UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("NEW ViewLoc: %s, ViewRot: %s"), *ViewLocation.ToString(), *ViewRotation.ToString()); } FMatrix FDisplayClusterDeviceBase::GetStereoProjectionMatrix(const enum EStereoscopicPass StereoPassType) const { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetStereoProjectionMatrix")); check(IsInGameThread()); check(StereoPassType != EStereoscopicPass::eSSP_FULL); const float n = NearClipPlane; const float f = FarClipPlane; // Half-size const float hw = ProjectionScreenSize.X / 2.f * CurrentWorldToMeters; const float hh = ProjectionScreenSize.Y / 2.f * CurrentWorldToMeters; UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("StereoProjectionMatrix math: hw:%f hh:%f"), hw, hh); // Screen corners const FVector pa = ProjectionScreenLoc + ProjectionScreenRot.Quaternion().RotateVector(GetProjectionScreenGeometryLBC(StereoPassType, hw, hh)); // left bottom corner const FVector pb = ProjectionScreenLoc + ProjectionScreenRot.Quaternion().RotateVector(GetProjectionScreenGeometryRBC(StereoPassType, hw, hh)); // right bottom corner const FVector pc = ProjectionScreenLoc + ProjectionScreenRot.Quaternion().RotateVector(GetProjectionScreenGeometryLTC(StereoPassType, hw, hh)); // left top corner // Screen vectors FVector vr = pb - pa; // lb->rb normilized vector, right axis of projection screen vr.Normalize(); FVector vu = pc - pa; // lb->lt normilized vector, up axis of projection screen vu.Normalize(); FVector vn = -FVector::CrossProduct(vr, vu); // Projection plane normal. Use minus because of left-handed coordinate system vn.Normalize(); const int eyeIdx = (StereoPassType == EStereoscopicPass::eSSP_LEFT_EYE ? 0 : 1); const FVector pe = EyeLoc[eyeIdx]; const FVector va = pa - pe; // camera -> lb const FVector vb = pb - pe; // camera -> rb const FVector vc = pc - pe; // camera -> lt const float d = -FVector::DotProduct(va, vn); // distance from eye to screen const float ndifd = n / d; const float l = FVector::DotProduct(vr, va) * ndifd; // distance to left screen edge const float r = FVector::DotProduct(vr, vb) * ndifd; // distance to right screen edge const float b = FVector::DotProduct(vu, va) * ndifd; // distance to bottom screen edge const float t = FVector::DotProduct(vu, vc) * ndifd; // distance to top screen edge const float mx = 2.f * n / (r - l); const float my = 2.f * n / (t - b); const float ma = -(r + l) / (r - l); const float mb = -(t + b) / (t - b); const float mc = f / (f - n); const float md = -(f * n) / (f - n); const float me = 1.f; // Normal LHS const FMatrix pm = FMatrix( FPlane(mx, 0, 0, 0), FPlane(0, my, 0, 0), FPlane(ma, mb, mc, me), FPlane(0, 0, md, 0)); // Invert Z-axis (UE4 uses Z-inverted LHS) const FMatrix flipZ = FMatrix( FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, -1, 0), FPlane(0, 0, 1, 1)); const FMatrix result(pm * flipZ); return result; } void FDisplayClusterDeviceBase::InitCanvasFromView(class FSceneView* InView, class UCanvas* Canvas) { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("InitCanvasFromView")); } void FDisplayClusterDeviceBase::UpdateViewport(bool bUseSeparateRenderTarget, const class FViewport& Viewport, class SViewport* ViewportWidget) { //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("UpdateViewport")); check(IsInGameThread()); // Update projection screen data UpdateProjectionScreenDataForThisFrame(); // Save current dimensions ViewportSize = Viewport.GetSizeXY(); BackBuffSize = Viewport.GetRenderTargetTextureSizeXY(); // If no custom area specified the full viewport area will be used if (ViewportArea.IsValid() == false) { ViewportArea.SetLocation(FIntPoint::ZeroValue); ViewportArea.SetSize(Viewport.GetSizeXY()); } // Store viewport CurrentViewport = (FViewport*)&Viewport; Viewport.GetViewportRHI()->SetCustomPresent(this); } void FDisplayClusterDeviceBase::CalculateRenderTargetSize(const class FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) { //UE_LOG(LogDisplayClusterRender, Log, TEXT("FDisplayClusterDeviceBase::CalculateRenderTargetSize")); check(IsInGameThread()); InOutSizeX = Viewport.GetSizeXY().X; // Add one pixel height line for right eye (will be skipped on copy) InOutSizeY = Viewport.GetSizeXY().Y; check(InOutSizeX > 0 && InOutSizeY > 0); } ////////////////////////////////////////////////////////////////////////////////////////////// // FRHICustomPresent ////////////////////////////////////////////////////////////////////////////////////////////// void FDisplayClusterDeviceBase::OnBackBufferResize() { UE_LOG(LogDisplayClusterRender, Verbose, TEXT("OnBackBufferResize")); //@todo: see comment below // if we are in the middle of rendering: prevent from calling EndFrame //if (RenderContext.IsValid()) //{ // RenderContext->bFrameBegun = false; //} } bool FDisplayClusterDeviceBase::Present(int32& InOutSyncInterval) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Present - default handler implementation. Check stereo device instantiation.")); // Default behavior // Return false to force clean screen. This will indicate that something is going wrong // or particular stereo device hasn't been implemented appropriately yet. return false; } ////////////////////////////////////////////////////////////////////////////////////////////// // IDisplayClusterStereoDevice ////////////////////////////////////////////////////////////////////////////////////////////// void FDisplayClusterDeviceBase::SetViewportArea(const FIntPoint& loc, const FIntPoint& size) { UE_LOG(LogDisplayClusterRender, Log, TEXT("SetViewportArea: loc=%s size=%s"), *loc.ToString(), *size.ToString()); FScopeLock lock(&InternalsSyncScope); ViewportArea.SetLocation(loc); ViewportArea.SetSize(size); } void FDisplayClusterDeviceBase::SetDesktopStereoParams(float FOV) { UE_LOG(LogDisplayClusterRender, Log, TEXT("SetDesktopStereoParams: FOV=%f"), FOV); //@todo } void FDisplayClusterDeviceBase::SetDesktopStereoParams(const FVector2D& screenSize, const FIntPoint& screenRes, float screenDist) { UE_LOG(LogDisplayClusterRender, Log, TEXT("SetDesktopStereoParams")); FVector2D size = screenSize; float dist = screenDist; //@todo: } void FDisplayClusterDeviceBase::SetInterpupillaryDistance(float dist) { UE_LOG(LogDisplayClusterRender, Log, TEXT("SetInterpupillaryDistance: %f"), dist); FScopeLock lock(&InternalsSyncScope); EyeDist = dist; } float FDisplayClusterDeviceBase::GetInterpupillaryDistance() const { UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetInterpupillaryDistance: %f"), EyeDist); FScopeLock lock(&InternalsSyncScope); return EyeDist; } void FDisplayClusterDeviceBase::SetEyesSwap(bool swap) { UE_LOG(LogDisplayClusterRender, Log, TEXT("SetEyesSwap: %s"), DisplayClusterHelpers::str::BoolToStr(swap)); FScopeLock lock(&InternalsSyncScope); bEyeSwap = swap; } bool FDisplayClusterDeviceBase::GetEyesSwap() const { UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetEyesSwap: %s"), DisplayClusterHelpers::str::BoolToStr(bEyeSwap)); FScopeLock lock(&InternalsSyncScope); return bEyeSwap; } bool FDisplayClusterDeviceBase::ToggleEyesSwap() { { FScopeLock lock(&InternalsSyncScope); bEyeSwap = !bEyeSwap; } UE_LOG(LogDisplayClusterRender, Log, TEXT("ToggleEyesSwap: swap=%s"), DisplayClusterHelpers::str::BoolToStr(bEyeSwap)); return bEyeSwap; } void FDisplayClusterDeviceBase::SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Swap sync policy: %d"), (int)policy); // Since not all our devices are opengl compatible in terms of implementation // we have to perform some wrapping logic for the policies. switch (policy) { // Policies below are available for any render device type case EDisplayClusterSwapSyncPolicy::None: SwapSyncPolicy = policy; break; default: UE_LOG(LogDisplayClusterRender, Error, TEXT("Unsupported policy type: %d"), (int)policy); SwapSyncPolicy = EDisplayClusterSwapSyncPolicy::None; break; } } EDisplayClusterSwapSyncPolicy FDisplayClusterDeviceBase::GetSwapSyncPolicy() const { UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetSwapSyncPolicy: policy=%d"), (int)SwapSyncPolicy); return SwapSyncPolicy; } void FDisplayClusterDeviceBase::GetCullingDistance(float& NearDistance, float& FarDistance) const { FScopeLock lock(&InternalsSyncScope); NearDistance = NearClipPlane; FarDistance = FarClipPlane; } void FDisplayClusterDeviceBase::SetCullingDistance(float NearDistance, float FarDistance) { UE_LOG(LogDisplayClusterRender, Log, TEXT("New culling distance: NCP=%f, FCP=%f"), NearDistance, FarDistance); FScopeLock lock(&InternalsSyncScope); NearClipPlane = NearDistance; FarClipPlane = FarDistance; }