// Copyright Epic Games, Inc. All Rights Reserved. #include "DisplayClusterScenePreviewModule.h" #include "DisplayClusterScenePreviewProxyManager.h" #include "Blueprints/DisplayClusterBlueprintLib.h" #include "CanvasTypes.h" #include "Components/DisplayClusterScreenComponent.h" #include "DisplayClusterChromakeyCardActor.h" #include "DisplayClusterLightCardActor.h" #include "DisplayClusterRootActor.h" #include "Engine/Blueprint.h" #include "Engine/Canvas.h" #include "Engine/TextureRenderTarget2D.h" #include "Misc/TransactionObjectEvent.h" #include "TextureResource.h" #if WITH_EDITOR #include "LevelEditorViewport.h" #endif #define LOCTEXT_NAMESPACE "DisplayClusterScenePreview" static TAutoConsoleVariable CVarDisplayClusterScenePreviewRenderTickDelay( TEXT("nDisplay.ScenePreview.RenderTickDelay"), 0.1f, TEXT("The number of seconds to wait between processing queued renders.") ); void FDisplayClusterScenePreviewModule::StartupModule() { } void FDisplayClusterScenePreviewModule::ShutdownModule() { Release(); } void FDisplayClusterScenePreviewModule::Release() { if (RenderTickerHandle.IsValid()) { FTSTicker::GetCoreTicker().RemoveTicker(RenderTickerHandle); RenderTickerHandle.Reset(); } TArray RendererIds; RendererConfigs.GenerateKeyArray(RendererIds); for (const int32 RendererId : RendererIds) { DestroyRenderer(RendererId); } // Release the RootActor and PreviewWorld proxies. ProxyManagerPtr.Reset(); } #if WITH_EDITOR void FDisplayClusterScenePreviewModule::OnEditorClosed() { // On editor close, Exit() should run to clean up, but this happens very late. Release(); if (EditorClosedEventHandle.IsValid() && GEditor) { GEditor->OnEditorClose().Remove(EditorClosedEventHandle); } } #endif // #if WITH_EDITOR FDisplayClusterScenePreviewProxyManager& FDisplayClusterScenePreviewModule::GetProxyManager() { #if WITH_EDITOR if (!EditorClosedEventHandle.IsValid() && GEditor) { EditorClosedEventHandle = GEditor->OnEditorClose().AddRaw(this, &FDisplayClusterScenePreviewModule::OnEditorClosed); } #endif //#if WITH_EDITOR if (!ProxyManagerPtr.IsValid()) { ProxyManagerPtr = MakeUnique(); } check(ProxyManagerPtr.IsValid()); return *ProxyManagerPtr; } int32 FDisplayClusterScenePreviewModule::CreateRenderer() { FRendererConfig& Config = RendererConfigs.Add(NextRendererId); Config.Renderer = MakeShared(); return NextRendererId++; } bool FDisplayClusterScenePreviewModule::DestroyRenderer(int32 RendererId) { if (FRendererConfig* Config = RendererConfigs.Find(RendererId)) { // Release the proxy resources that were used by this renderer. GetProxyManager().SetSceneRootActorForRenderer(RendererId, nullptr, EDisplayClusterScenePreviewFlags::None); RegisterRootActorEvents(RendererId, *Config, false /* bShouldRegister */); // Remove this config. RendererConfigs.Remove(RendererId); RegisterOrUnregisterGlobalActorEvents(); return true; } return false; } bool FDisplayClusterScenePreviewModule::SetRendererRootActorPath(int32 RendererId, const FString& ActorPath, const FDisplayClusterRootActorPropertyOverrides& InPropertyOverrides, const EDisplayClusterScenePreviewFlags PreviewFlags) { if (FRendererConfig* RendererConfig = RendererConfigs.Find(RendererId)) { // Use custom properties on root actor RendererConfig->RootActorPropertyOverrides = InPropertyOverrides; EnumAddFlags(RendererConfig->Flags, EDisplayClusterRendererConfigFlags::RootActorPropertyOverridesModified); // Determine these values before we update the config's RootActor if (RendererConfig->PreviewFlags != PreviewFlags) { RendererConfig->PreviewFlags = PreviewFlags; EnumAddFlags(RendererConfig->Flags, EDisplayClusterRendererConfigFlags::PreviewFlagsModified); } // Update root actor path if (RendererConfig->RootActorPath != ActorPath) { RendererConfig->RootActorPath = ActorPath; EnumAddFlags(RendererConfig->Flags, EDisplayClusterRendererConfigFlags::RootActorPathModified); } InternalUpdateRendererConfig(RendererId, *RendererConfig); return true; } return false; } bool FDisplayClusterScenePreviewModule::SetRendererRootActor(int32 RendererId, ADisplayClusterRootActor* Actor, const FDisplayClusterRootActorPropertyOverrides& InPropertyOverrides, const EDisplayClusterScenePreviewFlags PreviewFlags) { if (FRendererConfig* RendererConfig = RendererConfigs.Find(RendererId)) { // Use custom properties on root actor RendererConfig->RootActorPropertyOverrides = InPropertyOverrides; EnumAddFlags(RendererConfig->Flags, EDisplayClusterRendererConfigFlags::RootActorPropertyOverridesModified); // Determine these values before we update the config's RootActor if (RendererConfig->PreviewFlags != PreviewFlags) { RendererConfig->PreviewFlags = PreviewFlags; EnumAddFlags(RendererConfig->Flags, EDisplayClusterRendererConfigFlags::PreviewFlagsModified); } // Clear root actor path RendererConfig->RootActorPath.Empty(); InternalSetRendererRootActor(RendererId, *RendererConfig, Actor); InternalUpdateRendererConfig(RendererId, *RendererConfig); return true; } return false; } ADisplayClusterRootActor* FDisplayClusterScenePreviewModule::GetRendererRootActor(int32 RendererId) { if (FRendererConfig* Config = RendererConfigs.Find(RendererId)) { return InternalGetRendererRootActor(RendererId, *Config); } return nullptr; } ADisplayClusterRootActor* FDisplayClusterScenePreviewModule::GetRendererRootActorOrProxy(int32 RendererId) { if (FRendererConfig* Config = RendererConfigs.Find(RendererId)) { return InternalGetRendererRootActorOrProxy(RendererId, *Config); } return nullptr; } bool FDisplayClusterScenePreviewModule::Render(int32 RendererId, FDisplayClusterMeshProjectionRenderSettings& RenderSettings, FCanvas& Canvas) { if (FRendererConfig* Config = RendererConfigs.Find(RendererId)) { return InternalRenderImmediate(RendererId, *Config, RenderSettings, Canvas); } return false; } bool FDisplayClusterScenePreviewModule::RenderQueued(int32 RendererId, FDisplayClusterMeshProjectionRenderSettings& RenderSettings, const FIntPoint& Size, FRenderResultDelegate ResultDelegate) { return InternalRenderQueued(RendererId, RenderSettings, nullptr, Size, ResultDelegate); } bool FDisplayClusterScenePreviewModule::RenderQueued(int32 RendererId, FDisplayClusterMeshProjectionRenderSettings& RenderSettings, const TWeakPtr Canvas, FRenderResultDelegate ResultDelegate) { if (!Canvas.IsValid()) { return false; } FRenderTarget* RenderTarget = Canvas.Pin()->GetRenderTarget(); if (!RenderTarget) { return false; } return InternalRenderQueued(RendererId, RenderSettings, Canvas, RenderTarget->GetSizeXY(), ResultDelegate); } bool FDisplayClusterScenePreviewModule::InternalRenderQueued(int32 RendererId, FDisplayClusterMeshProjectionRenderSettings& RenderSettings, TWeakPtr Canvas, const FIntPoint& Size, FRenderResultDelegate ResultDelegate) { if (FRendererConfig* Config = RendererConfigs.Find(RendererId)) { RenderQueue.Enqueue(FPreviewRenderJob(RendererId, RenderSettings, Size, Canvas, ResultDelegate)); if (!RenderTickerHandle.IsValid()) { RenderTickerHandle = FTSTicker::GetCoreTicker().AddTicker( FTickerDelegate::CreateRaw(this, &FDisplayClusterScenePreviewModule::OnTick), CVarDisplayClusterScenePreviewRenderTickDelay.GetValueOnGameThread() ); } return true; } return false; } bool FDisplayClusterScenePreviewModule::IsRealTimePreviewEnabled() const { return bIsRealTimePreviewEnabled; } void FDisplayClusterScenePreviewModule::InternalSetRendererRootActor(int32 RendererId, FRendererConfig& RendererConfig, ADisplayClusterRootActor* Actor) { const bool bRootChanged = !RendererConfig.IsRootActorEquals(Actor); if (bRootChanged) { // Unregister events for current root actor RegisterRootActorEvents(RendererId, RendererConfig, false /* bShouldRegister */); RendererConfig.SetRootActor(Actor); } } ADisplayClusterRootActor* FDisplayClusterScenePreviewModule::InternalGetRendererRootActor(int32 RendererId, FRendererConfig& RendererConfig) { ADisplayClusterRootActor* SceneRootActor = RendererConfig.GetRootActor(); const bool bRequiredRootActor = !IsValid(SceneRootActor) || !RendererConfig.IsDefinedRootActor() || EnumHasAnyFlags(RendererConfig.Flags, EDisplayClusterRendererConfigFlags::RootActorPathModified); if (bRequiredRootActor && !RendererConfig.RootActorPath.IsEmpty()) { // If we don't have a RootActor, but we do have a RootActorPath, use it to find the RootActor object. ADisplayClusterRootActor* RootActor = FindObject(nullptr, *RendererConfig.RootActorPath); InternalSetRendererRootActor(RendererId, RendererConfig, RootActor); SceneRootActor = RendererConfig.GetRootActor(); } return IsValid(SceneRootActor) ? SceneRootActor : nullptr; } ADisplayClusterRootActor* FDisplayClusterScenePreviewModule::InternalGetRendererRootActorOrProxy(int32 RendererId, FRendererConfig& RendererConfig) { // Note: we call this function first because it can assign a new root actor and proxy. ADisplayClusterRootActor* RootActor = InternalGetRendererRootActor(RendererId, RendererConfig); // When proxy is required, get it from ProxyManager. const bool bUseRootActorProxy = EnumHasAnyFlags(RendererConfig.PreviewFlags, EDisplayClusterScenePreviewFlags::UseRootActorProxy); return bUseRootActorProxy ? GetProxyManager().GetProxyRootActor(RendererId) : RootActor; } void FDisplayClusterScenePreviewModule::InternalUpdateRendererConfig(int32 RendererId, FRendererConfig& RendererConfig) { // Get RootActor from scene // Call this function at the very beginning because it can internally change the RendererConfig.Flags. ADisplayClusterRootActor* SceneRootActor = InternalGetRendererRootActor(RendererId, RendererConfig); // Check that the RootActor proxy still has the same reference to the RootActor in the scene as the renderer. if (EnumHasAnyFlags(RendererConfig.PreviewFlags, EDisplayClusterScenePreviewFlags::UseRootActorProxy)) { ADisplayClusterRootActor* PrevSceneRootActor = GetProxyManager().GetSceneRootActor(RendererId); if (PrevSceneRootActor != SceneRootActor) { // The RootActor proxy is no longer valid, have to be re-created EnumAddFlags(RendererConfig.Flags, EDisplayClusterRendererConfigFlags::UpdateRootActorRef); } } const bool bAutoUpdateStageActors = EnumHasAnyFlags(RendererConfig.PreviewFlags, EDisplayClusterScenePreviewFlags::AutoUpdateStageActors); if (bAutoUpdateStageActors) { // If the RootActor is missing or any changes have occurred, we must clear the rendering scene if (!SceneRootActor || RendererConfig.Flags != EDisplayClusterRendererConfigFlags::None) { RendererConfig.ClearRendererScene(); } } // If the flags have not changed, do nothing if (RendererConfig.Flags == EDisplayClusterRendererConfigFlags::None) { return; } // Optionally, update the proxy when change the RootActor. if (EnumHasAnyFlags(RendererConfig.Flags, EDisplayClusterRendererConfigFlags::UpdateRootActorRef | EDisplayClusterRendererConfigFlags::PreviewFlagsModified | EDisplayClusterRendererConfigFlags::RootActorBeingModified)) { // Updates the proxy for the new root actor. if (EnumHasAnyFlags(RendererConfig.PreviewFlags, EDisplayClusterScenePreviewFlags::UseRootActorProxy)) { // Create new RootActor proxy GetProxyManager().SetSceneRootActorForRenderer(RendererId, SceneRootActor, RendererConfig.PreviewFlags); EnumAddFlags(RendererConfig.Flags, EDisplayClusterRendererConfigFlags::UpdateRootActorProxyRef); } else { // Remove proxy if exists. GetProxyManager().SetSceneRootActorForRenderer(RendererId, nullptr, RendererConfig.PreviewFlags); } // Update global events RegisterRootActorEvents(RendererId, RendererConfig, true /* bShouldRegister */); RegisterOrUnregisterGlobalActorEvents(); } // RootActor property overrides has been changed if (EnumHasAnyFlags(RendererConfig.Flags, EDisplayClusterRendererConfigFlags::RootActorPropertyOverridesModified | EDisplayClusterRendererConfigFlags::UpdateRootActorRef | EDisplayClusterRendererConfigFlags::UpdateRootActorProxyRef)) { if (ADisplayClusterRootActor* RendererRootActorOrProxy = InternalGetRendererRootActorOrProxy(RendererId, RendererConfig)) { RendererRootActorOrProxy->OverrideRootActorProperties(RendererConfig.RootActorPropertyOverrides); } } AutoPopulateScene(RendererId, RendererConfig); // Reset the flags at the end. RendererConfig.Flags = EDisplayClusterRendererConfigFlags::None; } void FDisplayClusterScenePreviewModule::AutoPopulateScene(int32 RendererId, FRendererConfig& RendererConfig) { // The renderer can use a proxy. ADisplayClusterRootActor* RootActor = InternalGetRendererRootActor(RendererId, RendererConfig); ADisplayClusterRootActor* RootActorProxy = InternalGetRendererRootActorOrProxy(RendererId, RendererConfig); if (RootActor && RootActorProxy) { TArray ProjectionMeshNames; if (UDisplayClusterConfigurationData* Config = RootActor->GetConfigData()) { Config->GetReferencedMeshNames(ProjectionMeshNames); } RendererConfig.AddActorToRenderer(RootActorProxy, [&ProjectionMeshNames](const UPrimitiveComponent* PrimitiveComponent) { // Filter out any primitive component that isn't a projection mesh (a static mesh that has a Mesh projection configured for it) or a screen component const bool bIsProjectionMesh = PrimitiveComponent->IsA() && ProjectionMeshNames.Contains(PrimitiveComponent->GetName()); const bool bIsScreen = PrimitiveComponent->IsA(); return bIsProjectionMesh || bIsScreen; }); const bool bAutoUpdateStageActors = EnumHasAnyFlags(RendererConfig.PreviewFlags, EDisplayClusterScenePreviewFlags::AutoUpdateStageActors); if (bAutoUpdateStageActors) { // Automatically add the lightcards found on this actor TSet LightCards; UDisplayClusterBlueprintLib::FindLightCardsForRootActor(RootActor, LightCards); TSet ChromaKeyCards; UDisplayClusterBlueprintLib::FindChromakeyCardsForRootActor(RootActor, ChromaKeyCards); LightCards.Append(reinterpret_cast&>(ChromaKeyCards)); TSet Actors; Actors.Reserve(LightCards.Num()); for (ADisplayClusterLightCardActor* LightCard : LightCards) { Actors.Add(LightCard); } // Also check for any non-lightcard actors in the world that are valid to control from ICVFX editors if (UWorld* World = RootActor->GetWorld()) { for (const TWeakObjectPtr WeakActor : TActorRange(World)) { if (WeakActor.IsValid() && WeakActor->Implements() && !WeakActor->IsA()) { Actors.Add(WeakActor.Get()); } } } for (AActor* Actor : Actors) { RendererConfig.AddActorToRenderer(Actor, true); } } } } bool FDisplayClusterScenePreviewModule::InternalRenderImmediate(int32 RendererId, FRendererConfig& RendererConfig, FDisplayClusterMeshProjectionRenderSettings& RenderSettings, FCanvas& Canvas) { if (!RendererConfig.Renderer.IsValid()) { return false; } // Update this so that whoever gets the callback can immediately check whether the nDisplay preview may be out of date UpdateIsRealTimePreviewEnabled(); InternalUpdateRendererConfig(RendererId, RendererConfig); // Get the Root Actor or proxy for rendering previews. ADisplayClusterRootActor* ProxyRootActor = InternalGetRendererRootActorOrProxy(RendererId, RendererConfig); ADisplayClusterRootActor* SceneRootActor = InternalGetRendererRootActor(RendererId, RendererConfig); UWorld* PreviewWorld = ProxyRootActor ? ProxyRootActor->GetWorld() : nullptr; UWorld* SceneWorld = SceneRootActor ? SceneRootActor->GetWorld() : nullptr; if (!PreviewWorld || !SceneWorld) { return false; } // Push any deferred render state updates to ensure that light card positions, preview meshes modified above, etc. are up to date PreviewWorld->SendAllEndOfFrameUpdates(); // FDisplayClusterMeshProjectionRenderer uses references to UPrimitiveComponent. // These primitives are taken from RootActorProxy in PreviewWorld and StageActors in SceneWorld. // But we can't render UPrimitiveComponent belonging to different scenes at the same time, // because they reference the internal data of the scene they belong to. // The FDisplayClusterMeshProjectionRenderer::RenderScenes() function implements a new approach that // allows us to render a UPrimitiveComponent from multiple worlds. if (PreviewWorld != SceneWorld) { // Rendering is performed for all scenes in the array in the same order. // First, the RootActorProxy geometries from PreviewWorld are drawn. // Then StageActors from SceneWorld are drawn. // Here is the rendering order: ClearRTT-> RootActorProxy -> StageActors. RendererConfig.Renderer->RenderScenes(&Canvas, { PreviewWorld->Scene, SceneWorld->Scene }, RenderSettings); } else { RendererConfig.Renderer->Render(&Canvas, PreviewWorld->Scene, RenderSettings); } return true; } void FDisplayClusterScenePreviewModule::RegisterOrUnregisterGlobalActorEvents() { // Check whether any of our configs need actor events bool bShouldBeRegistered = false; for (const TPair& ConfigPair : RendererConfigs) { const bool bAutoUpdateStageActors = EnumHasAnyFlags(ConfigPair.Value.PreviewFlags, EDisplayClusterScenePreviewFlags::AutoUpdateStageActors); if (bAutoUpdateStageActors) { bShouldBeRegistered = true; break; } } #if WITH_EDITOR if (bShouldBeRegistered && !bIsRegisteredForActorEvents) { // Register for events FCoreUObjectDelegates::OnObjectPropertyChanged.AddRaw(this, &FDisplayClusterScenePreviewModule::OnActorPropertyChanged); FCoreUObjectDelegates::OnObjectTransacted.AddRaw(this, &FDisplayClusterScenePreviewModule::OnObjectTransacted); if (GEngine != nullptr) { GEngine->OnLevelActorDeleted().AddRaw(this, &FDisplayClusterScenePreviewModule::OnLevelActorDeleted); GEngine->OnLevelActorAdded().AddRaw(this, &FDisplayClusterScenePreviewModule::OnLevelActorAdded); } } else if (!bShouldBeRegistered && bIsRegisteredForActorEvents) { // Unregister for events FCoreUObjectDelegates::OnObjectPropertyChanged.RemoveAll(this); FCoreUObjectDelegates::OnObjectTransacted.RemoveAll(this); if (GEngine != nullptr) { GEngine->OnLevelActorDeleted().RemoveAll(this); GEngine->OnLevelActorAdded().RemoveAll(this); } } #endif } void FDisplayClusterScenePreviewModule::RegisterRootActorEvents(int32 RendererId, FRendererConfig& RendererConfig, bool bShouldRegister) { #if WITH_EDITOR ADisplayClusterRootActor* Actor = RendererConfig.GetRootActor(); if (!Actor) { return; } const uint8* GenericThis = reinterpret_cast(this); // Register/unregister for Blueprint events if (UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(Actor->GetClass())) { // Store a reference to the BP class. // This reference is used to look up FRendererConfig from UBlueprint ptr in the OnBlueprintCompiled(UBlueprint* Blueprint) function. RendererConfig.RootActorBlueprintClass = Blueprint; Blueprint->OnCompiled().RemoveAll(this); if (bShouldRegister) { Blueprint->OnCompiled().AddRaw(this, &FDisplayClusterScenePreviewModule::OnBlueprintCompiled); } } #endif } bool FDisplayClusterScenePreviewModule::UpdateIsRealTimePreviewEnabled() { #if WITH_EDITOR bIsRealTimePreviewEnabled = false; if (GIsEditor && !IsValid(GEditor)) { return false; } for (const FLevelEditorViewportClient* LevelViewport : GEditor->GetLevelViewportClients()) { if (LevelViewport && LevelViewport->IsRealtime()) { bIsRealTimePreviewEnabled = true; break; } } return bIsRealTimePreviewEnabled; #else return false; #endif } bool FDisplayClusterScenePreviewModule::OnTick(float DeltaTime) { GetProxyManager().TickPreviewWorld(DeltaTime); // This loop should break when we either run out of jobs or complete a single job while (!RenderQueue.IsEmpty()) { FPreviewRenderJob Job; if (!RenderQueue.Dequeue(Job)) { break; } ensure(Job.ResultDelegate.IsBound()); if (FRendererConfig* Config = RendererConfigs.Find(Job.RendererId)) { if (Job.bWasCanvasProvided) { // We were provided a canvas for this render job, so use it if possible if (!Job.Canvas.IsValid()) { Job.ResultDelegate.Execute(nullptr); continue; } TSharedPtr Canvas = Job.Canvas.Pin(); FRenderTarget* RenderTarget = Canvas->GetRenderTarget(); if (!RenderTarget) { Job.ResultDelegate.Execute(nullptr); continue; } InternalRenderImmediate(Job.RendererId, *Config, Job.Settings, *Canvas); Job.ResultDelegate.Execute(RenderTarget); break; } ADisplayClusterRootActor* RootActor = InternalGetRendererRootActorOrProxy(Job.RendererId, *Config); if (UWorld* World = RootActor ? RootActor->GetWorld() : nullptr) { // We need to provide the render target for this job UTextureRenderTarget2D* RenderTarget = Config->RenderTarget.Get(); if (!RenderTarget) { // Create a new render target (which will be reused for this config in the future) RenderTarget = NewObject(); RenderTarget->InitCustomFormat(Job.Size.X, Job.Size.Y, PF_B8G8R8A8, true); Config->RenderTarget = TStrongObjectPtr(RenderTarget); } else if (RenderTarget->SizeX != Job.Size.X || RenderTarget->SizeY != Job.Size.Y) { // Resize to match the new size RenderTarget->ResizeTarget(Job.Size.X, Job.Size.Y); // Flush commands so target is immediately ready to render at the new size FlushRenderingCommands(); } FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); FCanvas Canvas(RenderTargetResource, nullptr, FGameTime::GetTimeSinceAppStart(), World->Scene->GetFeatureLevel()); InternalRenderImmediate(Job.RendererId, *Config, Job.Settings, Canvas); Job.ResultDelegate.Execute(RenderTargetResource); break; } // No canvas and no world, so try the next render } // Config no longer exists, so try the next render Job.ResultDelegate.Execute(nullptr); } if (RenderQueue.IsEmpty()) { RenderTickerHandle.Reset(); return false; } return true; } void FDisplayClusterScenePreviewModule::OnActorPropertyChanged(UObject* ObjectBeingModified, FPropertyChangedEvent& PropertyChangedEvent) { for (TPair& RendererConfigIt : RendererConfigs) { const bool bAutoUpdateStageActors = EnumHasAnyFlags(RendererConfigIt.Value.PreviewFlags, EDisplayClusterScenePreviewFlags::AutoUpdateStageActors); if (bAutoUpdateStageActors) { ADisplayClusterRootActor* RootActorPtr = RendererConfigIt.Value.GetRootActor(); if (RootActorPtr == ObjectBeingModified) { EnumAddFlags(RendererConfigIt.Value.Flags, EDisplayClusterRendererConfigFlags::RootActorBeingModified); } else if (UActorComponent* Component = Cast(ObjectBeingModified)) { if (Component->GetOwner() == RootActorPtr) { EnumAddFlags(RendererConfigIt.Value.Flags, EDisplayClusterRendererConfigFlags::LevelActorBeingModified); } } } } } void FDisplayClusterScenePreviewModule::OnLevelActorDeleted(AActor* Actor) { for (TPair& RendererConfigIt : RendererConfigs) { if (RendererConfigIt.Value.AutoPopulateActors.Contains(Actor)) { EnumAddFlags(RendererConfigIt.Value.Flags, EDisplayClusterRendererConfigFlags::LevelActorDeleted); } } } void FDisplayClusterScenePreviewModule::OnLevelActorAdded(AActor* Actor) { if (!Actor || !Actor->Implements()) { return; } // The actor won't be added to a root actor yet, so we can't check who it belongs to. Easier to just mark all configs as dirty. for (TPair& RendererConfigIt : RendererConfigs) { EnumAddFlags(RendererConfigIt.Value.Flags, EDisplayClusterRendererConfigFlags::LevelActorAdded); } } void FDisplayClusterScenePreviewModule::OnBlueprintCompiled(UBlueprint* Blueprint) { #if WITH_EDITOR for (TPair& RendererConfigIt : RendererConfigs) { if(RendererConfigIt.Value.IsBlueprintMatchesRendererRootActor(Blueprint)) { EnumAddFlags(RendererConfigIt.Value.Flags, EDisplayClusterRendererConfigFlags::BlueprintCompiled); } } #endif } void FDisplayClusterScenePreviewModule::OnObjectTransacted(UObject* Object, const FTransactionObjectEvent& TransactionObjectEvent) { if (TransactionObjectEvent.GetEventType() == ETransactionObjectEventType::UndoRedo) { for (TPair& RendererConfigIt : RendererConfigs) { EnumAddFlags(RendererConfigIt.Value.Flags, EDisplayClusterRendererConfigFlags::ObjectTransacted); } } } bool FDisplayClusterScenePreviewModule::FRendererConfig::SetRootActor(ADisplayClusterRootActor* InRootActorPtr) { if (!IsRootActorEquals(InRootActorPtr)) { // Update flags EnumAddFlags(Flags, EDisplayClusterRendererConfigFlags::UpdateRootActorRef); // Store ref to root actor. This type of link supports BP and recompile. RootActorRef.SetSceneActor(InRootActorPtr); return true; } return false; } bool FDisplayClusterScenePreviewModule::FRendererConfig::IsRootActorEquals(ADisplayClusterRootActor* InRootActorPtr) const { const TWeakObjectPtr CurrentRootActor = RootActorRef.GetSceneActorWeakPtr().Get(); return CurrentRootActor == InRootActorPtr; } ADisplayClusterRootActor* FDisplayClusterScenePreviewModule::FRendererConfig::GetRootActor() { const bool bDefinedSceneActor = RootActorRef.IsDefinedSceneActor(); const TWeakObjectPtr RootActorWeakPtr = RootActorRef.GetSceneActorWeakPtr(); if (ADisplayClusterRootActor* RootActorPtr = Cast(RootActorRef.GetOrFindSceneActor())) { const TWeakObjectPtr NewRootActorWeakPtr = RootActorRef.GetSceneActorWeakPtr(); if (NewRootActorWeakPtr != RootActorWeakPtr) { // The reference to the RootActor has been changed. EnumAddFlags(Flags, EDisplayClusterRendererConfigFlags::UpdateRootActorRef); } if (IsValid(RootActorPtr)) { return RootActorPtr; } } if (bDefinedSceneActor) { // We lost the reference to the RootActor object. EnumAddFlags(Flags, EDisplayClusterRendererConfigFlags::LostRootActorRef); } return nullptr; } bool FDisplayClusterScenePreviewModule::FRendererConfig::ClearRendererScene() { if (Renderer.IsValid()) { Renderer->ClearScene(); } AddedActors.Empty(); AutoPopulateActors.Empty(); return true; } bool FDisplayClusterScenePreviewModule::FRendererConfig::AddActorToRenderer(AActor* Actor, bool bAutoPopulate) { if (!Renderer.IsValid()) { return false; } if (!AddedActors.Contains(Actor)) { Renderer->AddActor(Actor); AddedActors.Add(Actor); if (bAutoPopulate) { AutoPopulateActors.Add(Actor); } } return true; } bool FDisplayClusterScenePreviewModule::FRendererConfig::AddActorToRenderer(AActor* Actor, const TFunctionRef& PrimitiveFilter, bool bAutoPopulate) { if (!Renderer.IsValid()) { return false; } if (!AddedActors.Contains(Actor)) { Renderer->AddActor(Actor, PrimitiveFilter); AddedActors.Add(Actor); if (bAutoPopulate) { AutoPopulateActors.Add(Actor); } } return true; } bool FDisplayClusterScenePreviewModule::FRendererConfig::RemoveActorFromRenderer(AActor* Actor) { if (!Renderer.IsValid()) { return false; } if (!AddedActors.Contains(Actor)) { return false; } Renderer->RemoveActor(Actor); AddedActors.Remove(Actor); return true; } bool FDisplayClusterScenePreviewModule::FRendererConfig::GetActorsInRendererScene(bool bIncludeRoot, TArray& OutActors) { if (bIncludeRoot) { if (ADisplayClusterRootActor* RootActorPtr = GetRootActor()) { OutActors.Add(RootActorPtr); } } for (const TWeakObjectPtr& Actor : AddedActors) { if (!Actor.IsValid()) { continue; } OutActors.Add(Actor.Get()); } return true; } bool FDisplayClusterScenePreviewModule::FRendererConfig::SetRendererActorSelectedDelegate(FDisplayClusterMeshProjectionRenderer::FSelection ActorSelectedDelegate) { if (Renderer.IsValid()) { Renderer->ActorSelectedDelegate = ActorSelectedDelegate; return true; } return false; } bool FDisplayClusterScenePreviewModule::FRendererConfig::SetRendererRenderSimpleElementsDelegate(FDisplayClusterMeshProjectionRenderer::FSimpleElementPass RenderSimpleElementsDelegate) { if (Renderer.IsValid()) { Renderer->RenderSimpleElementsDelegate = RenderSimpleElementsDelegate; return true; } return false; } bool FDisplayClusterScenePreviewModule::FRendererConfig::IsBlueprintMatchesRendererRootActor(UBlueprint* Blueprint) const { #if WITH_EDITOR UBlueprint* RootActorBlueprint = RootActorBlueprintClass.Get(); if (Blueprint == RootActorBlueprint) { return true; } #endif return false; } IMPLEMENT_MODULE(FDisplayClusterScenePreviewModule, DisplayClusterScenePreview); #undef LOCTEXT_NAMESPACE