// Copyright Epic Games, Inc. All Rights Reserved. #include "Render/DisplayClusterRenderManager.h" #include "Config/IPDisplayClusterConfigManager.h" #include "Engine/GameViewportClient.h" #include "Engine/GameEngine.h" #include "Misc/DisplayClusterGlobals.h" #include "Misc/DisplayClusterHelpers.h" #include "Misc/DisplayClusterLog.h" #include "Misc/DisplayClusterStrings.h" #include "DisplayClusterConfigurationStrings.h" #include "Components/DisplayClusterCameraComponent.h" #include "DisplayClusterViewportClient.h" #include "Config/IPDisplayClusterConfigManager.h" #include "DisplayClusterConfigurationTypes.h" #include "Game/IPDisplayClusterGameManager.h" #include "Render/Device/DisplayClusterRenderDeviceFactoryInternal.h" #include "Render/Device/IDisplayClusterRenderDeviceFactory.h" #include "Render/Monitoring/DisplayClusterVblankMonitor.h" #include "Render/PostProcess/IDisplayClusterPostProcess.h" #include "Render/Projection/IDisplayClusterProjectionPolicyFactory.h" #include "Render/Projection/IDisplayClusterProjectionPolicy.h" #include "Render/Synchronization/IDisplayClusterRenderSyncPolicyFactory.h" #include "Render/Containers/DisplayClusterRender_MeshComponent.h" #include "Render/Containers/DisplayClusterRender_Texture.h" #include "Render/Presentation/DisplayClusterPresentationNative.h" #include "Render/Synchronization/DisplayClusterRenderSyncPolicyFactoryInternal.h" #include "Render/Synchronization/DisplayClusterRenderSyncPolicyNone.h" #include "Framework/Application/SlateApplication.h" #include "UnrealClient.h" #include "Kismet/GameplayStatics.h" #include "CineCameraComponent.h" #include "Engine/Scene.h" #include "DisplayClusterRootActor.h" #if PLATFORM_WINDOWS #include "Windows/AllowWindowsPlatformTypes.h" #include "Windows.h" #include "Windows/HideWindowsPlatformTypes.h" #endif static TAutoConsoleVariable CVarSyncDiagnosticsVBlankMonitoring( TEXT("nDisplay.sync.diag.VBlankMonitoring"), 0, TEXT("Sync diagnostics: V-blank monitoring\n") TEXT("0 : disabled\n") TEXT("1 : enabled (if policy supports only)\n") , ECVF_ReadOnly ); #include "Misc/DisplayClusterDataCache.h" /** * The cache for DC texture objects. (Singleton) * Allows you to reuse textures with the same unique name. */ class FDisplayClusterRenderTextureCache : public TDisplayClusterDataCache { public: static TSharedPtr GetOrCreateRenderTexture(const FString& InTextureName) { static FDisplayClusterRenderTextureCache TextureCacheSingleton; const FString UniqueName = HashString(InTextureName); TSharedPtr TextureRef = TextureCacheSingleton.Find(UniqueName); if (!TextureRef.IsValid()) { TextureRef = MakeShared(UniqueName); TextureCacheSingleton.Add(TextureRef); } return TextureRef; } }; //--------------------------------------------------- // FDisplayClusterRenderManager //--------------------------------------------------- FDisplayClusterRenderManager::FDisplayClusterRenderManager() { // Instantiate and register internal render device factory TSharedPtr NewRenderDeviceFactory(new FDisplayClusterRenderDeviceFactoryInternal); RegisterRenderDeviceFactory(DisplayClusterStrings::args::dev::Mono, NewRenderDeviceFactory); RegisterRenderDeviceFactory(DisplayClusterStrings::args::dev::QBS, NewRenderDeviceFactory); RegisterRenderDeviceFactory(DisplayClusterStrings::args::dev::SbS, NewRenderDeviceFactory); RegisterRenderDeviceFactory(DisplayClusterStrings::args::dev::TB, NewRenderDeviceFactory); // Instantiate and register internal sync policy factory TSharedPtr NewSyncPolicyFactory(new FDisplayClusterRenderSyncPolicyFactoryInternal); RegisterSynchronizationPolicyFactory(DisplayClusterConfigurationStrings::config::cluster::render_sync::None, NewSyncPolicyFactory); // None RegisterSynchronizationPolicyFactory(DisplayClusterConfigurationStrings::config::cluster::render_sync::Ethernet, NewSyncPolicyFactory); // Ethernet RegisterSynchronizationPolicyFactory(DisplayClusterConfigurationStrings::config::cluster::render_sync::EthernetBarrier, NewSyncPolicyFactory); // Ethernet_Simple RegisterSynchronizationPolicyFactory(DisplayClusterConfigurationStrings::config::cluster::render_sync::Nvidia, NewSyncPolicyFactory); // NVIDIA // Instantiate V-blank monitor (it won't auto-start polling) VBlankMonitor = MakeShared(); } FDisplayClusterRenderManager::~FDisplayClusterRenderManager() { } ////////////////////////////////////////////////////////////////////////////////////////////// // IPDisplayClusterManager ////////////////////////////////////////////////////////////////////////////////////////////// bool FDisplayClusterRenderManager::Init(EDisplayClusterOperationMode OperationMode) { CurrentOperationMode = OperationMode; return true; } void FDisplayClusterRenderManager::Release() { //@note: No need to release our RenderDevice. It will be released in a safe way by TSharedPtr. } bool FDisplayClusterRenderManager::StartSession(UDisplayClusterConfigurationData* InConfigData, const FString& InNodeId) { if (CurrentOperationMode == EDisplayClusterOperationMode::Disabled) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Operation mode is 'Disabled' so no initialization will be performed")); return true; } // Set callback on viewport created. We want to make sure the DisplayClusterViewportClient is used. UGameViewportClient::OnViewportCreated().AddRaw(this, &FDisplayClusterRenderManager::OnViewportCreatedHandler_CheckViewportClass); // Create synchronization object UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating synchronization policy object...")); SyncPolicy = CreateRenderSyncPolicy(); if (SyncPolicy) { SyncPolicy->Initialize(); } // Instantiate render device TSharedPtr NewRenderDevice; UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating stereo device...")); NewRenderDevice = CreateRenderDevice(); // Set new device as the engine's stereoscopic device if (GEngine && NewRenderDevice.IsValid()) { GEngine->StereoRenderingDevice = StaticCastSharedPtr(NewRenderDevice); RenderDevicePtr = NewRenderDevice.Get(); } // Start v-blank monitoring if requested if (!!CVarSyncDiagnosticsVBlankMonitoring.GetValueOnGameThread()) { VBlankMonitor->StartMonitoring(); } // When session is starting in Editor the device won't be initialized so we avoid nullptr access here. //@todo Now we always have a device, even for Editor. Change the condition working on the EditorDevice. return (RenderDevicePtr ? RenderDevicePtr->Initialize() : true); } void FDisplayClusterRenderManager::EndSession() { #if WITH_EDITOR if (GIsEditor && RenderDevicePtr) { // Since we can run multiple PIE sessions we have to clean device before the next one. GEngine->StereoRenderingDevice.Reset(); RenderDevicePtr = nullptr; } #endif SyncPolicy.Reset(); } bool FDisplayClusterRenderManager::StartScene(UWorld* InWorld) { if (RenderDevicePtr) { RenderDevicePtr->StartScene(InWorld); } return true; } void FDisplayClusterRenderManager::EndScene() { if (RenderDevicePtr) { RenderDevicePtr->EndScene(); } } #if PLATFORM_WINDOWS static bool SimulateMouseClick(HWND WindowHandle) { RECT WindowRect; // Window rect in screen coordinates if (!GetWindowRect(WindowHandle, &WindowRect)) { return false; } const double ScreenWidth = double(::GetSystemMetrics(SM_CXSCREEN)); const double ScreenHeight = double(::GetSystemMetrics(SM_CYSCREEN)); check(ScreenWidth > 0.5); check(ScreenHeight > 0.5); INPUT Inputs[3] = {{ 0 }}; // Expected coordinates are normalized to screen dimensions of 65535x65535 const double Left = double(WindowRect.left); const double Right = double(WindowRect.right); const double Top = double(WindowRect.top); const double Bottom = double(WindowRect.bottom); Inputs[0].type = INPUT_MOUSE; Inputs[0].mi.dx = LONG((Left + (Right - Left)/2.0) * (65535.0 / ScreenWidth)); Inputs[0].mi.dy = LONG((Top + (Bottom - Top)/2.0) * (65535.0 / ScreenHeight)); Inputs[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; Inputs[1].type = INPUT_MOUSE; Inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; Inputs[2].type = INPUT_MOUSE; Inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTUP; return !!::SendInput(3, Inputs, sizeof(INPUT)); } #endif //PLATFORM_WINDOWS void FDisplayClusterRenderManager::PreTick(float DeltaSeconds) { static const bool bIsRenderingOffscreen = FParse::Param(FCommandLine::Get(), TEXT("RenderOffscreen")); if(!bIsRenderingOffscreen && !bWasWindowFocused) { if (UGameViewportClient* GameViewportClient = GEngine->GameViewport) { if (TSharedPtr Window = GameViewportClient->GetWindow()) { if (TSharedPtr NativeWindow = Window->GetNativeWindow()) { if (const void* WindowHandle = NativeWindow->GetOSWindowHandle()) { #if PLATFORM_WINDOWS const HWND GameHWND = (HWND)WindowHandle; ::SetWindowPos(GameHWND, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); ::SetForegroundWindow(GameHWND); ::SetCapture(GameHWND); ::SetFocus(GameHWND); ::SetActiveWindow(GameHWND); SimulateMouseClick(GameHWND); #endif FSlateApplication::Get().SetAllUserFocusToGameViewport(); bWasWindowFocused = true; } } } } } if (RenderDevicePtr) { RenderDevicePtr->PreTick(DeltaSeconds); } } ////////////////////////////////////////////////////////////////////////////////////////////// // IDisplayClusterRenderManager ////////////////////////////////////////////////////////////////////////////////////////////// IDisplayClusterRenderDevice* FDisplayClusterRenderManager::GetRenderDevice() const { return RenderDevicePtr; } bool FDisplayClusterRenderManager::RegisterRenderDeviceFactory(const FString& InDeviceType, TSharedPtr& InFactory) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Registering factory for rendering device type: %s"), *InDeviceType); if (!InFactory.IsValid()) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Invalid factory object")); return false; } { FScopeLock Lock(&CritSecInternals); if (RenderDeviceFactories.Contains(InDeviceType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Setting a new factory for '%s' rendering device type"), *InDeviceType); } RenderDeviceFactories.Emplace(InDeviceType, InFactory); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Registered factory for rendering device type: %s"), *InDeviceType); return true; } bool FDisplayClusterRenderManager::UnregisterRenderDeviceFactory(const FString& InDeviceType) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistering factory for rendering device type: %s"), *InDeviceType); { FScopeLock Lock(&CritSecInternals); if (!RenderDeviceFactories.Contains(InDeviceType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A factory for '%s' rendering device type not found"), *InDeviceType); return false; } RenderDeviceFactories.Remove(InDeviceType); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistered factory for rendering device type: %s"), *InDeviceType); return true; } bool FDisplayClusterRenderManager::RegisterSynchronizationPolicyFactory(const FString& InSyncPolicyType, TSharedPtr& InFactory) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Registering factory for synchronization policy: %s"), *InSyncPolicyType); if (!InFactory.IsValid()) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Invalid factory object")); return false; } { FScopeLock Lock(&CritSecInternals); if (SyncPolicyFactories.Contains(InSyncPolicyType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A new factory for '%s' synchronization policy was set"), *InSyncPolicyType); } SyncPolicyFactories.Emplace(InSyncPolicyType, InFactory); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Registered factory for synchronization policy: %s"), *InSyncPolicyType); return true; } bool FDisplayClusterRenderManager::UnregisterSynchronizationPolicyFactory(const FString& InSyncPolicyType) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistering factory for synchronization policy: %s"), *InSyncPolicyType); { FScopeLock Lock(&CritSecInternals); if (!SyncPolicyFactories.Contains(InSyncPolicyType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A factory for '%s' synchronization policy not found"), *InSyncPolicyType); return false; } SyncPolicyFactories.Remove(InSyncPolicyType); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistered factory for synchronization policy: %s"), *InSyncPolicyType); return true; } TSharedPtr FDisplayClusterRenderManager::GetCurrentSynchronizationPolicy() { FScopeLock Lock(&CritSecInternals); return SyncPolicy; } //------------------------------------------------------------------------------------------------------------------------------ // Projection Policy //------------------------------------------------------------------------------------------------------------------------------ bool FDisplayClusterRenderManager::RegisterProjectionPolicyFactory(const FString& InProjectionType, TSharedPtr& InFactory) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Registering factory for projection type: %s"), *InProjectionType); if (!InFactory.IsValid()) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Invalid factory object")); return false; } { FScopeLock Lock(&CritSecInternals); if (ProjectionPolicyFactories.Contains(InProjectionType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A new factory for '%s' projection policy was set"), *InProjectionType); } ProjectionPolicyFactories.Emplace(InProjectionType, InFactory); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Registered factory for projection type: %s"), *InProjectionType); return true; } bool FDisplayClusterRenderManager::UnregisterProjectionPolicyFactory(const FString& InProjectionType) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistering factory for projection policy: %s"), *InProjectionType); { FScopeLock Lock(&CritSecInternals); if (!ProjectionPolicyFactories.Contains(InProjectionType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A handler for '%s' projection type not found"), *InProjectionType); return false; } ProjectionPolicyFactories.Remove(InProjectionType); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistered factory for projection policy: %s"), *InProjectionType); return true; } TSharedPtr FDisplayClusterRenderManager::GetProjectionPolicyFactory(const FString& InProjectionType) { FScopeLock Lock(&CritSecInternals); TSharedPtr Factory; if (!DisplayClusterHelpers::map::template ExtractValue(ProjectionPolicyFactories, InProjectionType, Factory)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("No factory found for projection policy: %s"), *InProjectionType); } return Factory; } void FDisplayClusterRenderManager::GetRegisteredProjectionPolicies(TArray& OutPolicyIDs) const { FScopeLock Lock(&CritSecInternals); ProjectionPolicyFactories.GetKeys(OutPolicyIDs); } //------------------------------------------------------------------------------------------------------------------------------ // PostProcess //------------------------------------------------------------------------------------------------------------------------------ bool FDisplayClusterRenderManager::RegisterPostProcessFactory(const FString& InPostProcessType, TSharedPtr& InFactory) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Registering factory for postprocess type: %s"), *InPostProcessType); if (!InFactory.IsValid()) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Invalid factory object")); return false; } { FScopeLock Lock(&CritSecInternals); if (PostProcessFactories.Contains(InPostProcessType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A new factory for '%s' postprocess was set"), *InPostProcessType); } PostProcessFactories.Emplace(InPostProcessType, InFactory); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Registered factory for postprocess type: %s"), *InPostProcessType); return true; } bool FDisplayClusterRenderManager::UnregisterPostProcessFactory(const FString& InPostProcessType) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistering factory for postprocess: %s"), *InPostProcessType); { FScopeLock Lock(&CritSecInternals); if (!PostProcessFactories.Contains(InPostProcessType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A handler for '%s' postprocess type not found"), *InPostProcessType); return false; } PostProcessFactories.Remove(InPostProcessType); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistered factory for postprocess: %s"), *InPostProcessType); return true; } TSharedPtr FDisplayClusterRenderManager::GetPostProcessFactory(const FString& InPostProcessType) { FScopeLock Lock(&CritSecInternals); TSharedPtr Factory; if (!DisplayClusterHelpers::map::template ExtractValue(PostProcessFactories, InPostProcessType, Factory)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("No factory found for postprocess: %s"), *InPostProcessType); } return Factory; } void FDisplayClusterRenderManager::GetRegisteredPostProcess(TArray& OutPostProcessIDs) const { FScopeLock Lock(&CritSecInternals); PostProcessFactories.GetKeys(OutPostProcessIDs); } //------------------------------------------------------------------------------------------------------------------------------ // Warp Policy //------------------------------------------------------------------------------------------------------------------------------ bool FDisplayClusterRenderManager::RegisterWarpPolicyFactory(const FString& InWarpPolicyType, TSharedPtr& InFactory) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Registering factory for warp policy type: %s"), *InWarpPolicyType); if (!InFactory.IsValid()) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Invalid factory object")); return false; } { FScopeLock Lock(&CritSecInternals); if (WarpPolicyFactories.Contains(InWarpPolicyType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A new factory for '%s' warp policy was set"), *InWarpPolicyType); } WarpPolicyFactories.Emplace(InWarpPolicyType, InFactory); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Registered factory for warp policy type: %s"), *InWarpPolicyType); return true; } bool FDisplayClusterRenderManager::UnregisterWarpPolicyFactory(const FString& InWarpPolicyType) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistering factory for warp policy: %s"), *InWarpPolicyType); { FScopeLock Lock(&CritSecInternals); if (!WarpPolicyFactories.Contains(InWarpPolicyType)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("A handler for '%s' warp policy type not found"), *InWarpPolicyType); return false; } WarpPolicyFactories.Remove(InWarpPolicyType); } UE_LOG(LogDisplayClusterRender, Log, TEXT("Unregistered factory for warp policy: %s"), *InWarpPolicyType); return true; } TSharedPtr FDisplayClusterRenderManager::GetWarpPolicyFactory(const FString& InWarpPolicyType) { FScopeLock Lock(&CritSecInternals); TSharedPtr Factory; if (!DisplayClusterHelpers::map::template ExtractValue(WarpPolicyFactories, InWarpPolicyType, Factory)) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("No factory found for warp policy: %s"), *InWarpPolicyType); } return Factory; } void FDisplayClusterRenderManager::GetRegisteredWarpPolicies(TArray& OutWarpPolicyIDs) const { FScopeLock Lock(&CritSecInternals); WarpPolicyFactories.GetKeys(OutWarpPolicyIDs); } //------------------------------------------------------------------------------------------------------------------------------ // Resources //------------------------------------------------------------------------------------------------------------------------------ TSharedPtr FDisplayClusterRenderManager::CreateMeshComponent() const { return MakeShared(); } TSharedPtr FDisplayClusterRenderManager::GetOrCreateCachedTexture(const FString& InUniqueTextureName) const { return FDisplayClusterRenderTextureCache::GetOrCreateRenderTexture(InUniqueTextureName); } IDisplayClusterViewportManager* FDisplayClusterRenderManager::GetViewportManager() const { ADisplayClusterRootActor* RootActor = GDisplayCluster->GetGameMgr()->GetRootActor(); if (RootActor) { return RootActor->GetViewportManager(); } return nullptr; } ////////////////////////////////////////////////////////////////////////////////////////////// // FDisplayClusterRenderManager ////////////////////////////////////////////////////////////////////////////////////////////// TSharedPtr FDisplayClusterRenderManager::CreateRenderDevice() const { TSharedPtr NewRenderDevice; if (CurrentOperationMode == EDisplayClusterOperationMode::Cluster) { if (GDynamicRHI == nullptr) { UE_LOG(LogDisplayClusterRender, Error, TEXT("GDynamicRHI is null. Cannot detect RHI name.")); return nullptr; } // Monoscopic if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::Mono)) { NewRenderDevice = RenderDeviceFactories[DisplayClusterStrings::args::dev::Mono]->Create(DisplayClusterStrings::args::dev::Mono); } // Quad buffer stereo else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::QBS)) { NewRenderDevice = RenderDeviceFactories[DisplayClusterStrings::args::dev::QBS]->Create(DisplayClusterStrings::args::dev::QBS); } // Side-by-side else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::SbS)) { NewRenderDevice = RenderDeviceFactories[DisplayClusterStrings::args::dev::SbS]->Create(DisplayClusterStrings::args::dev::SbS); } // Top-bottom else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::TB)) { NewRenderDevice = RenderDeviceFactories[DisplayClusterStrings::args::dev::TB]->Create(DisplayClusterStrings::args::dev::TB); } // Leave native render but inject custom present for cluster synchronization else { UE_LOG(LogDisplayClusterRender, Warning, TEXT("No rendering device specified! A native present handler will be instantiated when viewport is available")); UGameViewportClient::OnViewportCreated().AddRaw(const_cast(this), &FDisplayClusterRenderManager::OnViewportCreatedHandler_SetCustomPresent); } } else if (CurrentOperationMode == EDisplayClusterOperationMode::Editor) { #if 0 UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating DX11 mono device for PIE")); NewRenderDevice = MakeShared(); #endif } else if (CurrentOperationMode == EDisplayClusterOperationMode::Disabled) { // Stereo device is not needed UE_LOG(LogDisplayClusterRender, Log, TEXT("No need to instantiate stereo device")); } else { UE_LOG(LogDisplayClusterRender, Warning, TEXT("Unknown operation mode")); } if (!NewRenderDevice.IsValid()) { UE_LOG(LogDisplayClusterRender, Log, TEXT("No stereo device created")); } return NewRenderDevice; } TSharedPtr FDisplayClusterRenderManager::CreateRenderSyncPolicy() const { if (CurrentOperationMode != EDisplayClusterOperationMode::Cluster) { UE_LOG(LogDisplayClusterRender, Log, TEXT("Synchronization policy is not available for current operation mode")); return nullptr; } if (GDynamicRHI == nullptr) { UE_LOG(LogDisplayClusterRender, Error, TEXT("GDynamicRHI is null. Cannot detect RHI name.")); return nullptr; } const UDisplayClusterConfigurationData* ConfigData = GDisplayCluster->GetPrivateConfigMgr()->GetConfig(); if (!ConfigData) { UE_LOG(LogDisplayClusterRender, Error, TEXT("Couldn't get configuration data")); return nullptr; } FString SyncPolicyType; TMap* SyncPolicyParams = nullptr; // Always use 'none' sync policy while rendering headless if (FParse::Param(FCommandLine::Get(), TEXT("RenderOffscreen"))) { SyncPolicyType = DisplayClusterConfigurationStrings::config::cluster::render_sync::HeadlessRenderingSyncPolicy; SyncPolicyParams = nullptr; UE_LOG(LogDisplayClusterRender, Log, TEXT("Headless rendering requested. Using '%s' sync policy."), *SyncPolicyType); } // Otherwise use sync policy from config else { SyncPolicyType = ConfigData->Cluster->Sync.RenderSyncPolicy.Type; SyncPolicyParams = &ConfigData->Cluster->Sync.RenderSyncPolicy.Parameters; UE_LOG(LogDisplayClusterRender, Log, TEXT("Requested sync policy is '%s'"), *SyncPolicyType); } // Instantiate policy instance TSharedPtr NewSyncPolicy; if (SyncPolicyFactories.Contains(SyncPolicyType)) { UE_LOG(LogDisplayClusterRender, Log, TEXT("A factory for the requested synchronization policy <%s> was found"), *SyncPolicyType); NewSyncPolicy = SyncPolicyFactories[SyncPolicyType]->Create(SyncPolicyType, SyncPolicyParams ? *SyncPolicyParams : TMap()); } // Fallback if requested policy is not available else { const FString DefaultPolicy = DisplayClusterConfigurationStrings::config::cluster::render_sync::EthernetBarrier; UE_LOG(LogDisplayClusterRender, Log, TEXT("No factory found for the requested synchronization policy <%s>. Default '%s' policy will be used."), *SyncPolicyType, *DefaultPolicy); NewSyncPolicy = SyncPolicyFactories[DefaultPolicy]->Create(DefaultPolicy, TMap()); } return NewSyncPolicy; } void FDisplayClusterRenderManager::ResizeWindow(int32 WinX, int32 WinY, int32 ResX, int32 ResY) { UGameEngine* Engine = Cast(GEngine); TSharedPtr Window = Engine->GameViewportWindow.Pin(); check(Window.IsValid()); UE_LOG(LogDisplayClusterRender, Log, TEXT("Adjusting game window: pos [%d, %d], size [%d x %d]"), WinX, WinY, ResX, ResY); // Adjust window position/size Window->ReshapeWindow(FVector2D(WinX, WinY), FVector2D(ResX, ResY)); } void FDisplayClusterRenderManager::OnViewportCreatedHandler_SetCustomPresent() const { if (GEngine && GEngine->GameViewport) { if (!GEngine->GameViewport->Viewport->GetViewportRHI().IsValid()) { GEngine->GameViewport->OnBeginDraw().AddRaw(this, &FDisplayClusterRenderManager::OnBeginDrawHandler); } } } void FDisplayClusterRenderManager::OnViewportCreatedHandler_CheckViewportClass() const { if (GEngine && GEngine->GameViewport) { UDisplayClusterViewportClient* const GameViewport = Cast(GEngine->GameViewport); if (!GameViewport) { UE_LOG(LogDisplayClusterRender, Warning, TEXT("DisplayClusterViewportClient is not set as a default GameViewport class")); } } } void FDisplayClusterRenderManager::OnBeginDrawHandler() const { static bool initialized = false; if (!initialized && GEngine->GameViewport->Viewport->GetViewportRHI().IsValid()) { FDisplayClusterPresentationNative* const NativePresentHandler = new FDisplayClusterPresentationNative(GEngine->GameViewport->Viewport, SyncPolicy); check(NativePresentHandler); GEngine->GameViewport->Viewport->GetViewportRHI().GetReference()->SetCustomPresent(NativePresentHandler); initialized = true; } }