// Copyright Epic Games, Inc. All Rights Reserved. #include "LandscapeSubsystem.h" #include "Engine/Engine.h" #include "UObject/UObjectGlobals.h" #include "Engine/EngineBaseTypes.h" #include "Engine/World.h" #include "Modules/ModuleManager.h" #include "ContentStreaming.h" #include "HAL/IConsoleManager.h" #include "Landscape.h" #include "LandscapeEditTypes.h" #include "LandscapeProxy.h" #include "LandscapeStreamingProxy.h" #include "LandscapeInfo.h" #include "LandscapeInfoMap.h" #include "LandscapeModule.h" #include "LandscapeRender.h" #include "LandscapePrivate.h" #include "LandscapeSettings.h" #include "LandscapeGrassMapsBuilder.h" #include "LandscapeTextureStorageProvider.h" #include "LandscapeComponent.h" #include "LandscapeGroup.h" #include "Logging/StructuredLog.h" #include "ProfilingDebugging/CsvProfiler.h" #include "WorldPartition/WorldPartitionSubsystem.h" #include "ActorPartition/ActorPartitionSubsystem.h" #include "Engine/World.h" #include "Math/IntRect.h" #include "LandscapeNotification.h" #include "LandscapeConfigHelper.h" #include "Engine/Canvas.h" #include "EngineUtils.h" #include "Misc/ScopedSlowTask.h" #include "Algo/ForEach.h" #include "Algo/Transform.h" #include "Algo/RemoveIf.h" #include "Algo/Unique.h" #include "Algo/Partition.h" #include "Misc/App.h" #include "Misc/DateTime.h" #include "AssetCompilingManager.h" #include "VisualLogger/VisualLogger.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(LandscapeSubsystem) // enables debug spew //#define ENABLE_LANDSCAPE_SUBSYSTEM_DEBUG_SPEW 1 #ifdef ENABLE_LANDSCAPE_SUBSYSTEM_DEBUG_SPEW #define SUBSYSTEM_DEBUG_LOG(...) UE_LOG(LogLandscape, Warning, __VA_ARGS__) #define SUBSYSTEM_DEBUG_LOG_REGISTER(...) UE_LOG(LogLandscape, Warning, __VA_ARGS__) #else #define SUBSYSTEM_DEBUG_LOG(...) UE_LOG(LogLandscape, Verbose, __VA_ARGS__) #define SUBSYSTEM_DEBUG_LOG_REGISTER(...) do {} while(0) #endif // ENABLE_LANDSCAPE_EDGE_FIXUP_DEBUG_SPEW #if WITH_EDITOR #include "ActionableMessageSubsystem.h" #include "Async/ParallelFor.h" #include "FileHelpers.h" #include "Editor.h" #endif static int32 GUseStreamingManagerForCameras = 1; static FAutoConsoleVariableRef CVarUseStreamingManagerForCameras( TEXT("grass.UseStreamingManagerForCameras"), GUseStreamingManagerForCameras, TEXT("1: Use Streaming Manager; 0: Use ViewLocationsRenderedLastFrame")); static TAutoConsoleVariable CVarGrassVisualLogCameraLocations( TEXT("grass.VisualLog.CameraLocations"), false, TEXT("Allows to visualize the cameras used for landscape grass in the visual logger ")); static FAutoConsoleVariable CVarMaxAsyncNaniteProxiesPerSecond( TEXT("landscape.Nanite.MaxAsyncProxyBuildsPerSecond"), 6.0f, TEXT("Number of Async nanite proxies to dispatch per second")); int32 LiveRebuildNaniteOnModification = 0; static FAutoConsoleVariableRef CVarLiveRebuildNaniteOnModification( TEXT("landscape.Nanite.LiveRebuildOnModification"), LiveRebuildNaniteOnModification, TEXT("Trigger a rebuild of Nanite representation immediately when a modification is performed (World Partition Maps Only)")); int32 LandscapeMultithreadNaniteBuild = 1; static FAutoConsoleVariableRef CVarLandscapeMultithreadNaniteBuild( TEXT("landscape.Nanite.MultithreadBuild"), LandscapeMultithreadNaniteBuild, TEXT("Multithread nanite landscape build in (World Partition Maps Only)")); int32 LandscapeMaxSimultaneousMultithreadNaniteBuilds = -1; static FAutoConsoleVariableRef CVarLandscapeMaxSimultaneousMultithreadNaniteBuilds( TEXT("landscape.Nanite.MaxSimultaneousMultithreadBuilds"), LandscapeMaxSimultaneousMultithreadNaniteBuilds, TEXT("Max number of simultaneous Nanite static mesh tasks (-1 = unlimited )")); extern int32 GGrassMapUseRuntimeGeneration; DECLARE_CYCLE_STAT(TEXT("LandscapeSubsystem Tick"), STAT_LandscapeSubsystemTick, STATGROUP_Landscape); #define LOCTEXT_NAMESPACE "LandscapeSubsystem" namespace UE::Landscape { static void DumpLandscapeWeightmapAllocations(const TArray& Args) { bool bDumpDetails = false; for (const FString& Arg : Args) { if (FParse::Param(*Arg, TEXT("details"))) { bDumpDetails = true; } } // Command supports editor and cooked games, so get worlds from Engine instead of Editor for (const FWorldContext& WorldContext : GEngine->GetWorldContexts()) { if (UWorld* World = WorldContext.World()) { UE_LOG(LogLandscape, Log, TEXT("In World: %s"), *World->GetFullName()); if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->ForEachLandscapeInfo([&](ULandscapeInfo* LandscapeInfo) { TMap TargetLayerToNumComponents; const FString& LandscapeName = LandscapeInfo->GetLandscapeProxy()->GetActorNameOrLabel(); UE_LOG(LogLandscape, Log, TEXT("- Landscape: %s"), *LandscapeName); LandscapeInfo->ForEachLandscapeProxy([&TargetLayerToNumComponents, &bDumpDetails, &LandscapeName](ALandscapeProxy* LandscapeProxy) { for (ULandscapeComponent* Component : LandscapeProxy->LandscapeComponents) { TSet ComponentLayerNameSet; for (const FWeightmapLayerAllocationInfo& AllocInfo : Component->GetWeightmapLayerAllocations()) { if (AllocInfo.IsAllocated()) { FName LayerName = AllocInfo.LayerInfo->LayerName; TargetLayerToNumComponents.FindOrAdd(LayerName)++; ComponentLayerNameSet.Add(LayerName); } } // Detailed print option if (bDumpDetails) { const FString& ComponentName = Component->GetName(); const FString& ProxyName = LandscapeProxy->GetActorNameOrLabel(); UE_LOG(LogLandscape, Log, TEXT(" Proxy: %s, Component: %s [%s]"), *ProxyName, *ComponentName, *Component->GetSectionBase().ToString()); UE_LOG(LogLandscape, Log, TEXT(" - Target Layers: %d"), ComponentLayerNameSet.Num()); ComponentLayerNameSet.Sort(FNameLexicalLess()); for (const FName& LayerName : ComponentLayerNameSet) { UE_LOG(LogLandscape, Log, TEXT(" - %s"), *LayerName.ToString()); } } } return true; }); // Print the total layers and their names UE_LOG(LogLandscape, Log, TEXT("Number of unique Target Layers: %d"), TargetLayerToNumComponents.Num()); TargetLayerToNumComponents.KeySort(FNameLexicalLess()); for (const TPair& Pair : TargetLayerToNumComponents) { UE_LOG(LogLandscape, Log, TEXT("- %s, used in %d landscape components"), *Pair.Key.ToString(), Pair.Value); } return true; }); } } } } static FAutoConsoleCommand CmdDumpLandscapeWeightmapAllocations( TEXT("landscape.DumpTargetLayerAllocations"), TEXT("[optional: -details] - Dumps a report of target layers allocated for every landscape. \n" "-details shows a detailed report of allocated target layers for each individual landscape component. \n"), FConsoleCommandWithArgsDelegate::CreateStatic(&DumpLandscapeWeightmapAllocations) ); #if WITH_EDITOR bool HasModifiedLandscapes() { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { return LandscapeSubsystem->HasModifiedLandscapes(); } } } return false; } void SaveModifiedLandscapes(UE::Landscape::EBuildFlags InBuildFlags) { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->SaveModifiedLandscapes(InBuildFlags); } } } } // Deprecated void MarkModifiedLandscapesAsDirty() { MarkModifiedLandscapesAsDirty(EBuildFlags::None); } void MarkModifiedLandscapesAsDirty(EBuildFlags InBuildFlags) { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->MarkModifiedLandscapesAsDirty(InBuildFlags); } } } } // Deprecated void BuildGrassMaps() { BuildGrassMaps(EBuildFlags::None); } void BuildGrassMaps(EBuildFlags InBuildFlags) { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->BuildGrassMaps(InBuildFlags); } } } } // Deprecated void BuildPhysicalMaterial() { BuildPhysicalMaterial(EBuildFlags::None); } void BuildPhysicalMaterial(EBuildFlags InBuildFlags) { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->BuildPhysicalMaterial(InBuildFlags); } } } } // Deprecated void BuildNanite() { BuildNanite(EBuildFlags::None); } void BuildNanite(EBuildFlags InBuildFlags) { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->BuildNanite(InBuildFlags); } } } } // Deprecated void BuildAll() { BuildAll(EBuildFlags::None); } void BuildAll(EBuildFlags InBuildFlags) { if (GEditor) { if (UWorld* World = GEditor->GetEditorWorldContext().World()) { if (ULandscapeSubsystem* LandscapeSubsystem = World->GetSubsystem()) { LandscapeSubsystem->BuildAll(InBuildFlags); } } } } static FAutoConsoleCommand CmdLandscapeRebuildPhysicalMaterial( TEXT("landscape.RebuildPhysicalMaterial"), TEXT("Force a rebuild of the physical material data for all landscapes."), FConsoleCommandDelegate::CreateStatic(BuildPhysicalMaterial, EBuildFlags::ForceRebuild) ); #endif //WITH_EDITOR } ULandscapeSubsystem::ULandscapeSubsystem() { } ULandscapeSubsystem::~ULandscapeSubsystem() { } void ULandscapeSubsystem::RegisterActor(ALandscapeProxy* Proxy) { check(Proxy != nullptr); SUBSYSTEM_DEBUG_LOG_REGISTER(TEXT("ULandscapeSubsystem::RegisterActor %p %s (%s) to subsystem %p in world %p (%s)"), Proxy, *Proxy->GetFullName(), *Proxy->GetClass()->GetName(), this, GetWorld(), *GetWorld()->GetName()); TObjectPtr ProxyPtr(Proxy); // in editor we can get multiple registration calls, so ensure we don't register more than once if (Proxy->bIsRegisteredWithSubsystem) { check(Proxy->RegisteredToSubsystem == this); check(Proxies.Contains(ProxyPtr)); return; } check(Proxy->RegisteredToSubsystem == nullptr); check(!Proxies.Contains(ProxyPtr)); Proxies.Add(ProxyPtr); if (ALandscape* LandscapeActor = Cast(Proxy)) { TObjectPtr LandscapeActorPtr(LandscapeActor); LandscapeActors.AddUnique(LandscapeActorPtr); } else if (!IsRunningCookCommandlet()) { if (ALandscapeStreamingProxy* StreamingProxy = Cast(Proxy)) { // We want to know when a streaming proxy is moved, so we can adjust the map if (USceneComponent* RootComponent = StreamingProxy->GetRootComponent()) { RootComponent->TransformUpdated.AddUObject(this, &ULandscapeSubsystem::OnProxyMoved); } } } Proxy->bIsRegisteredWithSubsystem = true; Proxy->RegisteredToSubsystem = this; } void ULandscapeSubsystem::UnregisterActor(ALandscapeProxy* Proxy) { check(Proxy != nullptr); SUBSYSTEM_DEBUG_LOG_REGISTER(TEXT("ULandscapeSubsystem::UnregisterActor %p %s (%s) to subsystem %p in world %p (%s)"), Proxy, *Proxy->GetFullName(), *Proxy->GetClass()->GetName(), this, GetWorld(), *GetWorld()->GetName()); TObjectPtr ProxyPtr(Proxy); // in editor we can get multiple unregistration calls, so ensure we don't register more than once if (!Proxy->bIsRegisteredWithSubsystem) { check(Proxy->RegisteredToSubsystem == nullptr); check(!Proxies.Contains(ProxyPtr)); return; } if (Proxy->RegisteredToSubsystem != this) { UE_LOG(LogLandscape, Warning, TEXT("Landscape Proxy %s was registered to world '%s' but is being unregistered from world '%s', this may indicate that worlds were changed without re-registering actors, which may cause issues in the landscape system. We will assume it should be unregistered from the original world."), *Proxy->GetFullName(), *Proxy->RegisteredToSubsystem->GetWorld()->GetName(), *GetWorld()->GetName()); Proxy->RegisteredToSubsystem->UnregisterActor(Proxy); return; } int32 RemovedCount = Proxies.Remove(ProxyPtr); check(RemovedCount == 1); if (ALandscape* LandscapeActor = Cast(Proxy)) { TObjectPtr LandscapeActorPtr(LandscapeActor); LandscapeActors.Remove(LandscapeActorPtr); } else if (!IsRunningCookCommandlet()) { if (ALandscapeStreamingProxy* StreamingProxy = Cast(Proxy)) { StreamingProxiesNeedingReregister.Remove(StreamingProxy); // We want to know when a streaming proxy is moved, so we can adjust the map if (USceneComponent* RootComponent = StreamingProxy->GetRootComponent()) { RootComponent->TransformUpdated.RemoveAll(this); } } } Proxy->bIsRegisteredWithSubsystem = false; Proxy->RegisteredToSubsystem = nullptr; } void ULandscapeSubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { Super::AddReferencedObjects(InThis, Collector); ULandscapeSubsystem* const TypedThis = Cast(InThis); for (auto Pair : TypedThis->Groups) { FLandscapeGroup::AddReferencedObjects(Pair.Value, Collector); } } void ULandscapeSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); if (UWorld* World = GetWorld()) { if (AWorldSettings* WorldSettings = World->GetWorldSettings()) { OnNaniteWorldSettingsChangedHandle = WorldSettings->OnNaniteSettingsChanged.AddUObject(this, &ULandscapeSubsystem::OnNaniteWorldSettingsChanged); } } static IConsoleVariable* NaniteEnabledCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite")); if (NaniteEnabledCVar && !NaniteEnabledCVar->OnChangedDelegate().IsBoundToObject(this)) { NaniteEnabledCVar->OnChangedDelegate().AddUObject(this, &ULandscapeSubsystem::OnNaniteEnabledChanged); } static IConsoleVariable* LandscapeNaniteEnabledCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("landscape.RenderNanite")); if (LandscapeNaniteEnabledCVar && !LandscapeNaniteEnabledCVar->OnChangedDelegate().IsBoundToObject(this)) { LandscapeNaniteEnabledCVar->OnChangedDelegate().AddUObject(this, &ULandscapeSubsystem::OnNaniteEnabledChanged); } TextureStreamingManager = new FLandscapeTextureStreamingManager(); check(TextureStreamingManager); GrassMapsBuilder = new FLandscapeGrassMapsBuilder(GetWorld(), *TextureStreamingManager); #if WITH_EDITOR PhysicalMaterialBuilder = new FLandscapePhysicalMaterialBuilder(GetWorld()); if (!IsRunningCommandlet()) { NotificationManager = new FLandscapeNotificationManager(); } #endif OnScalabilityChangedHandle = Scalability::OnScalabilitySettingsChanged.AddLambda([](const Scalability::FQualityLevels& QualityLevels) { for (auto* LandscapeComponent : TObjectRange(RF_ClassDefaultObject | RF_ArchetypeObject, true, EInternalObjectFlags::Garbage)) { LandscapeComponent->MarkRenderStateDirty(); } }); FCoreUObjectDelegates::GetPostGarbageCollect().AddUObject(this, &ULandscapeSubsystem::HandlePostGarbageCollect); } void ULandscapeSubsystem::Deinitialize() { FCoreUObjectDelegates::GetPostGarbageCollect().RemoveAll(this); if (OnNaniteWorldSettingsChangedHandle.IsValid()) { UWorld* World = GetWorld(); check(World != nullptr); AWorldSettings* WorldSettings = World->GetWorldSettings(); check(WorldSettings != nullptr); WorldSettings->OnNaniteSettingsChanged.Remove(OnNaniteWorldSettingsChangedHandle); OnNaniteWorldSettingsChangedHandle.Reset(); } static IConsoleVariable* NaniteEnabledCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite")); if (NaniteEnabledCVar) { NaniteEnabledCVar->OnChangedDelegate().RemoveAll(this); } static IConsoleVariable* LandscapeNaniteEnabledCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("landscape.RenderNanite")); if (LandscapeNaniteEnabledCVar) { LandscapeNaniteEnabledCVar->OnChangedDelegate().RemoveAll(this); } Scalability::OnScalabilitySettingsChanged.Remove(OnScalabilityChangedHandle); #if WITH_EDITOR const bool bAllNaniteBuildsDone = FinishAllNaniteBuildsInFlightNow(ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::Default); // Not passing ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::AllowCancel, so there should be no way that FinishAllNaniteBuildsInFlightNow returns false : check(bAllNaniteBuildsDone && (NaniteBuildsInFlight == 0)); delete PhysicalMaterialBuilder; delete NotificationManager; #endif delete GrassMapsBuilder; delete TextureStreamingManager; // cleanup landscape groups for (auto Pair : Groups) { FLandscapeGroup* Group = Pair.Value; delete Group; } Groups.Empty(); StreamingProxiesNeedingReregister.Empty(); Proxies.Empty(); LandscapeActors.Empty(); Super::Deinitialize(); } void ULandscapeSubsystem::HandlePostGarbageCollect() { ALandscapeProxy::RemoveInvalidExclusionBoxes(); GetTextureStreamingManager()->CleanupPostGarbageCollect(); } void ULandscapeSubsystem::OnProxyMoved(USceneComponent* MovedComponent, EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) { AActor* Owner = MovedComponent->GetOwner(); if (ALandscapeStreamingProxy* StreamingProxy = Cast(Owner)) { StreamingProxiesNeedingReregister.Add(StreamingProxy); } } TStatId ULandscapeSubsystem::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(ULandscapeSubsystem, STATGROUP_Tickables); } FLandscapeGroup* ULandscapeSubsystem::GetLandscapeGroupForProxy(ALandscapeProxy* Proxy) { check(Proxy); // use LODGroupKey instead of LandscapeGUID when LODGroupKey is non-zero uint32 LandscapeGroupKey = (Proxy->LODGroupKey != 0) ? Proxy->LODGroupKey : GetTypeHash(Proxy->GetLandscapeGuid()); FLandscapeGroup*& GroupPtr = Groups.FindOrAdd(LandscapeGroupKey); if (GroupPtr == nullptr) { GroupPtr = new FLandscapeGroup(LandscapeGroupKey); } return GroupPtr; } FLandscapeGroup* ULandscapeSubsystem::GetLandscapeGroupForComponent(ULandscapeComponent* Component) { return GetLandscapeGroupForProxy(Component->GetLandscapeProxy()); } void ULandscapeSubsystem::RegisterComponent(ULandscapeComponent* Component) { if (FLandscapeGroup* Group = GetLandscapeGroupForComponent(Component)) { Group->RegisterComponent(Component); } GetGrassMapBuilder()->RegisterComponent(Component); } void ULandscapeSubsystem::UnregisterComponent(ULandscapeComponent* Component) { GetGrassMapBuilder()->UnregisterComponent(Component); if (FLandscapeGroup* Group = Component->RegisteredLandscapeGroup) { Group->UnregisterComponent(Component); } } void ULandscapeSubsystem::RemoveGrassInstances(const TSet* ComponentsToRemoveGrassInstances) { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::RemoveGrassInstances); for (TObjectPtr ProxyPtr : Proxies) { ALandscapeProxy* Proxy = ProxyPtr.Get(); check(IsValid(Proxy)); // Validate our expectation that proxies will have Unregister() called before a Proxy is flagged as garbage. Proxy->FlushGrassComponents(ComponentsToRemoveGrassInstances, /*bFlushGrassMaps = */false); } } void ULandscapeSubsystem::RegenerateGrass(bool bInFlushGrass, bool bInForceSync, TOptional> InOptionalCameraLocations) { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::RegenerateGrass); if (Proxies.IsEmpty()) { return; } UWorld* World = GetWorld(); if (bInFlushGrass) { RemoveGrassInstances(); } { TRACE_CPUPROFILER_EVENT_SCOPE(UpdateGrass); TArray CameraLocations; if (InOptionalCameraLocations.IsSet()) { CameraLocations = *InOptionalCameraLocations; } else { if (GUseStreamingManagerForCameras == 0) { CameraLocations = World->ViewLocationsRenderedLastFrame; } else if (int32 Num = IStreamingManager::Get().GetNumViews()) { CameraLocations.Reserve(Num); for (int32 Index = 0; Index < Num; Index++) { const FStreamingViewInfo& ViewInfo = IStreamingManager::Get().GetViewInformation(Index); CameraLocations.Add(ViewInfo.ViewOrigin); } } } UE_IFVLOG( { if (CVarGrassVisualLogCameraLocations.GetValueOnGameThread()) { for (const FVector& CameraLocation : CameraLocations) { UE_VLOG_LOCATION(this, LogLandscape, Log, CameraLocation, 10.0f, FColor::Green, TEXT("Grass Camera Location")); } } }); // Update the grass near the specified location(s) : for (TObjectPtr ProxyPtr : Proxies) { ALandscapeProxy* Proxy = ProxyPtr.Get(); Proxy->UpdateGrass(CameraLocations, bInForceSync); } } } ETickableTickType ULandscapeSubsystem::GetTickableTickType() const { return HasAnyFlags(RF_ClassDefaultObject) || !GetWorld() || GetWorld()->IsNetMode(NM_DedicatedServer) ? ETickableTickType::Never : ETickableTickType::Always; } bool ULandscapeSubsystem::DoesSupportWorldType(const EWorldType::Type WorldType) const { // we also support inactive worlds -- they are used when the world is already saved, but SaveAs renames it: // then it duplicates the world (producing an inactive world), which we then need to update Landscapes in during OnPreSave() return Super::DoesSupportWorldType(WorldType) || WorldType == EWorldType::Inactive; } void ULandscapeSubsystem::Tick(float DeltaTime) { SCOPE_CYCLE_COUNTER(STAT_LandscapeSubsystemTick); TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::Tick); CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Landscape); LLM_SCOPE(ELLMTag::Landscape); Super::Tick(DeltaTime); UWorld* World = GetWorld(); bool bIsGameWorld = World->IsGameWorld(); #if WITH_EDITOR AppCurrentDateTime = FDateTime::Now(); uint32 FrameNumber = World->Scene->GetFrameNumber(); const bool bIsTimeOnlyTick = (FrameNumber == LastTickFrameNumber); ILandscapeModule& LandscapeModule = FModuleManager::GetModuleChecked("Landscape"); // Check if we need to start or stop creating Collision SceneProxies. Don't do this on time-only ticks as the viewport (therefore the scenes) are not drawn in that case, which would lead to wrongly // assume that no view needed collision this frame if (!bIsTimeOnlyTick) { int32 NumViewsWithShowCollision = LandscapeModule.GetLandscapeSceneViewExtension()->GetNumViewsWithShowCollision(); const bool bNewShowCollisions = NumViewsWithShowCollision > 0; const bool bShowCollisionChanged = (bNewShowCollisions != bAnyViewShowCollisions); bAnyViewShowCollisions = bNewShowCollisions; if (bShowCollisionChanged) { for (ULandscapeHeightfieldCollisionComponent* LandscapeHeightfieldCollisionComponent : TObjectRange(RF_ClassDefaultObject | RF_ArchetypeObject, true, EInternalObjectFlags::Garbage)) { LandscapeHeightfieldCollisionComponent->MarkRenderStateDirty(); } } } #endif // WITH_EDITOR // Double check requested textures are in the right state TextureStreamingManager->CheckRequestedTextures(); static TArray OldCameras; TArray* Cameras = nullptr; if (GUseStreamingManagerForCameras == 0) { if (OldCameras.Num() || World->ViewLocationsRenderedLastFrame.Num()) { // there is a bug here, which often leaves us with no cameras in the editor -- try to fall back to previous camera position(s) if (World->ViewLocationsRenderedLastFrame.Num()) { check(IsInGameThread()); OldCameras = World->ViewLocationsRenderedLastFrame; } Cameras = &OldCameras; } } else { int32 Num = IStreamingManager::Get().GetNumViews(); if (Num) { OldCameras.Reset(Num); for (int32 Index = 0; Index < Num; Index++) { auto& ViewInfo = IStreamingManager::Get().GetViewInformation(Index); OldCameras.Add(ViewInfo.ViewOrigin); } Cameras = &OldCameras; } } // run early update on Proxies, and determine if all of the proxies are ready for grass generation to start bool bAllProxiesReadyForGrassMapGeneration = true; bool bAllProxiesRuntimeGrassMapsDisabled = true; #if WITH_EDITOR TSet DisallowedGrassTickLandscapes; for (TObjectPtr ActorPtr : LandscapeActors) { ALandscape* Landscape = ActorPtr.Get(); if (ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo()) { const bool bLandscapeIsUpToDate = Landscape->IsUpToDate(); const bool bLandscapeSupportsEditing = LandscapeInfo->SupportsLandscapeEditing(); const bool bLandscapeUpdateAllowed = bLandscapeSupportsEditing && (Landscape->GetWorld()->GetFeatureLevel() >= ERHIFeatureLevel::SM5); // if either of these things are true, then we wait for them to complete before running ANY grass map updates.. bool bLandscapeToolIsModifyingLandscape = !Landscape->bGrassUpdateEnabled; // Don't allow grass to tick if landscape is not up to date -- unless landscape update is not possible (preview or level instanced modes) bool bAllowGrassTick = bLandscapeIsUpToDate || !bLandscapeUpdateAllowed; if (bLandscapeToolIsModifyingLandscape || !bAllowGrassTick) { bAllProxiesReadyForGrassMapGeneration = false; DisallowedGrassTickLandscapes.Add(Landscape); } } } #endif // WITH_EDITOR static TArray ActiveProxies; { ActiveProxies.Reset(Proxies.Num()); for (TObjectPtr ProxyPtr : Proxies) { ALandscapeProxy* Proxy = ProxyPtr.Get(); ActiveProxies.Add(Proxy); // Update the proxies proxy { if (!Proxy->GetDisableRuntimeGrassMapGeneration()) { bAllProxiesRuntimeGrassMapsDisabled = false; } #if WITH_EDITOR if (!bIsGameWorld) { // in editor, automatically update component grass types if the material changes for (ULandscapeComponent* Component : Proxy->LandscapeComponents) { Component->UpdateGrassTypes(); } } #endif // WITH_EDITOR // Update the grass type summary if necessary if (!Proxy->IsGrassTypeSummaryValid()) { Proxy->UpdateGrassTypeSummary(); } } } // Move the parent ALandscapes to the front of the list. Better to be consistent with TickLayers running on those first before doing other work on the proxies. Algo::Partition(ActiveProxies, [](ALandscapeProxy* Proxy) { return Cast(Proxy) != nullptr; }); } bool bGrassMapGenerationDisabled = bAllProxiesRuntimeGrassMapsDisabled; #if WITH_EDITOR if (GIsEditor && !bIsGameWorld) { bGrassMapGenerationDisabled = false; } #endif // WITH_EDITOR bool bAllowStartGrassMapGeneration = bAllProxiesReadyForGrassMapGeneration && !bGrassMapGenerationDisabled && (!bIsGameWorld || GGrassMapUseRuntimeGeneration); GrassMapsBuilder->AmortizedUpdateGrassMaps(Cameras ? *Cameras : TArray(), bIsGrassCreationPrioritized, bAllowStartGrassMapGeneration); // run edge fixup for streaming proxies, which only exist in partitioned worlds // TODO [chris.tchou] : remove this check once we support edge fixup on non-partitioned worlds (requires removing heightmap sharing between components) bool bRunEdgeFixup = UWorld::IsPartitionedWorld(GetWorld()); if (bRunEdgeFixup) { TickEdgeFixup(); } #if WITH_EDITOR int32 NumProxiesUpdated = 0; int32 NumMeshesToUpdate = 0; NumNaniteMeshUpdatesAvailable += CVarMaxAsyncNaniteProxiesPerSecond->GetFloat() * DeltaTime; if (NumNaniteMeshUpdatesAvailable > 1.0f) { NumMeshesToUpdate = NumNaniteMeshUpdatesAvailable; NumNaniteMeshUpdatesAvailable -= NumMeshesToUpdate; } #endif // WITH_EDITOR for (ALandscapeProxy* Proxy : ActiveProxies) { #if WITH_EDITOR if (GIsEditor && !World->IsPlayInEditor() && GEditor->PlayWorld == nullptr) { if (ALandscape* Landscape = Cast(Proxy)) { Landscape->TickLayers(DeltaTime); } Proxy->UpdatePhysicalMaterialTasks(); } Proxy->GetAsyncWorkMonitor().Tick(DeltaTime); if (IsLiveNaniteRebuildEnabled()) { if (NumProxiesUpdated < NumMeshesToUpdate && Proxy->GetAsyncWorkMonitor().CheckIfUpdateTriggeredAndClear(FAsyncWorkMonitor::EAsyncWorkType::BuildNaniteMeshes)) { NumProxiesUpdated++; Proxy->UpdateNaniteRepresentation(/* const ITargetPlatform* = */nullptr); } } #endif //WITH_EDITOR // TODO [chris.tchou] : this stops all async task processing if cameras go away, which might leave tasks dangling bool bShouldTickGrass = Proxy->ShouldTickGrass(); #if WITH_EDITOR bShouldTickGrass &= !DisallowedGrassTickLandscapes.Contains(Proxy->GetLandscapeActor()); #endif // WITH_EDITOR if (bShouldTickGrass && (Cameras != nullptr)) { int32 InOutNumComponentsCreated = 0; Proxy->UpdateGrass(*Cameras, InOutNumComponentsCreated); } } ActiveProxies.Reset(); ALandscapeProxy::DebugDrawExclusionBoxes(World); #if WITH_EDITOR if (GIsEditor && !World->IsPlayInEditor()) { LandscapePhysicalMaterial::GarbageCollectTasks(); if (NotificationManager) { NotificationManager->Tick(); } } for (auto It = NaniteMeshBuildStates.CreateIterator(); It; ++It) { UE::Landscape::Nanite::FAsyncBuildData& BuildState = It->Get(); if (BuildState.BuildCompleteEvent->IsComplete()) { It.RemoveCurrentSwap(); } else if (BuildState.CheckForStallAndWarn()) { // build may be stalled (bug). User has been warned, no need to do anything else (it may complete eventually...) } } LastTickFrameNumber = FrameNumber; UActionableMessageSubsystem* ActionableMessageSubsystem = World->GetSubsystem(); if ((ActionableMessageSubsystem != nullptr) && GIsEditor) { FActionableMessage ActionableMessage; const FName LandscapeMessageProvider = TEXT("Landscape"); if (!(GEditor->bIsSimulatingInEditor || (GEditor->PlayWorld != nullptr)) && GetActionableMessage(ActionableMessage)) { ActionableMessageSubsystem->SetActionableMessage(LandscapeMessageProvider, ActionableMessage); } else { ActionableMessageSubsystem->ClearActionableMessage(LandscapeMessageProvider); } } TickNaniteFinalizeBuildEvents(); #endif // WITH_EDITOR } void ULandscapeSubsystem::ForEachLandscapeInfo(TFunctionRef ForEachLandscapeInfoFunc) const { if (ULandscapeInfoMap* LandscapeInfoMap = ULandscapeInfoMap::FindLandscapeInfoMap(GetWorld())) { for (const auto& Pair : LandscapeInfoMap->Map) { if (ULandscapeInfo* LandscapeInfo = Pair.Value) { if (!ForEachLandscapeInfoFunc(LandscapeInfo)) { return; } } } } } void ULandscapeSubsystem::OnNaniteEnabledChanged(IConsoleVariable*) { QUICK_SCOPE_CYCLE_COUNTER(STAT_Landscape_OnNaniteEnabledChanged); for (TObjectPtr& ProxyPtr : Proxies) { ALandscapeProxy* Proxy = ProxyPtr.Get(); Proxy->UpdateRenderingMethod(); } } void ULandscapeSubsystem::TickEdgeFixup() { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::TickEdgeFixup); // apply any requested re-registrations first if (StreamingProxiesNeedingReregister.Num() > 0) { int32 ComponentsUnregistered = 0; int32 ComponentsRegistered = 0; // unregister all first, from whatever group they are registered to for (ALandscapeProxy* StreamingProxy : StreamingProxiesNeedingReregister) { for (ULandscapeComponent* Component : StreamingProxy->LandscapeComponents) { if (FLandscapeGroup* Group = Component->RegisteredLandscapeGroup) { Group->UnregisterComponent(Component); ComponentsUnregistered++; } } } // then re-register them to the group they should be in for (ALandscapeProxy* StreamingProxy : StreamingProxiesNeedingReregister) { FLandscapeGroup* NewGroup = GetLandscapeGroupForProxy(StreamingProxy); for (ULandscapeComponent* Component : StreamingProxy->LandscapeComponents) { NewGroup->RegisterComponent(Component); ComponentsRegistered++; } } StreamingProxiesNeedingReregister.Empty(); } // tick each group const bool bForcePatchAll = UE::Landscape::ShouldPatchAllLandscapeComponentEdges(/* bResetForNext = */ true); for (auto Pair : Groups) { FLandscapeGroup* Group = Pair.Value; Group->TickEdgeFixup(this, bForcePatchAll); } } #if WITH_EDITOR // Deprecated void ULandscapeSubsystem::BuildAll() { BuildAll(UE::Landscape::EBuildFlags::None); } void ULandscapeSubsystem::BuildAll(UE::Landscape::EBuildFlags InBuildFlags) { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::BuildAll); // This is a deliberate action, make sure to flush all packages that are 'pending dirty' : MarkModifiedLandscapesAsDirty(InBuildFlags); BuildGrassMaps(InBuildFlags); BuildPhysicalMaterial(InBuildFlags); BuildNanite(InBuildFlags); if (EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::WriteFinalLog)) { UE_LOGFMT_LOC(LogLandscape, Log, "BuildAllFinalLog", "Build All operation done."); } } // Deprecated void ULandscapeSubsystem::BuildGrassMaps() { BuildGrassMaps(UE::Landscape::EBuildFlags::None); } void ULandscapeSubsystem::BuildGrassMaps(UE::Landscape::EBuildFlags InBuildFlags) { GrassMapsBuilder->Build(InBuildFlags); } // Deprecated void ULandscapeSubsystem::BuildPhysicalMaterial() { BuildPhysicalMaterial(UE::Landscape::EBuildFlags::None); } void ULandscapeSubsystem::BuildPhysicalMaterial(UE::Landscape::EBuildFlags InBuildFlags) { PhysicalMaterialBuilder->Build(InBuildFlags); } TSet ULandscapeSubsystem::GetDirtyLandscapeProxyPackages() const { TSet DirtyPackages; ForEachLandscapeInfo([&](ULandscapeInfo* LandscapeInfo) { LandscapeInfo->ForEachLandscapeProxy([&](const ALandscapeProxy* LandscapeProxy) { UPackage* Package = LandscapeProxy->GetPackage(); if (Package->IsDirty()) { DirtyPackages.Add(Package); } return true; }); return true; }); return DirtyPackages; } // Deprecated : TArray ULandscapeSubsystem::GetOutdatedProxies(UE::Landscape::EOutdatedDataFlags InMatchingOutdatedDataFlags, bool bInMustMatchAllFlags) const { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::GetOutdatedProxies); UWorld* World = GetWorld(); if (!World || World->IsGameWorld()) { return {}; } TArray FinalProxiesToBuild; Algo::TransformIf(Proxies, FinalProxiesToBuild, [InMatchingOutdatedDataFlags, bInMustMatchAllFlags](const TObjectPtr& InProxyPtr) { UE::Landscape::EOutdatedDataFlags ProxyOutdatedDataFlags = InProxyPtr->GetOutdatedDataFlags(); return bInMustMatchAllFlags ? EnumHasAllFlags(ProxyOutdatedDataFlags, InMatchingOutdatedDataFlags) : EnumHasAnyFlags(ProxyOutdatedDataFlags, InMatchingOutdatedDataFlags); }, [](const TWeakObjectPtr& InProxyPtr) { return InProxyPtr.Get(); }); return FinalProxiesToBuild; } TArray> ULandscapeSubsystem::GetOutdatedProxyDetails(UE::Landscape::EOutdatedDataFlags InMatchingOutdatedDataFlags, bool bInMustMatchAllFlags) const { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::GetOutdatedProxyDetails); UWorld* World = GetWorld(); if (!World || World->IsGameWorld()) { return {}; } using ProxyAndFlagsArray = TArray>; // Parallelize the retrieval of the outdated proxies and their flags by "fork and join". Each task in the parallel-for handles a certain number of proxies so task contexts simply consists in an array of proxy+flags : TArray TaskContexts; ParallelForWithTaskContext(TaskContexts, Proxies.Num(), [this, InMatchingOutdatedDataFlags, bInMustMatchAllFlags](ProxyAndFlagsArray& InTaskContext, int32 InIndex) { FTaskTagScope Scope(ETaskTag::EParallelGameThread); ALandscapeProxy* ValidProxy = Proxies[InIndex].Get(); const UE::Landscape::EOutdatedDataFlags ProxyOutdatedDataFlags = ValidProxy->GetOutdatedDataFlags(); if ((bInMustMatchAllFlags && EnumHasAllFlags(ProxyOutdatedDataFlags, InMatchingOutdatedDataFlags)) || (!bInMustMatchAllFlags && EnumHasAnyFlags(ProxyOutdatedDataFlags, InMatchingOutdatedDataFlags))) { InTaskContext.Add({ ValidProxy, ProxyOutdatedDataFlags }); } }); // Join all outdated proxies that have been found by the different tasks : ProxyAndFlagsArray OutdatedProxies; OutdatedProxies.Reserve(Proxies.Num()); Algo::ForEach(TaskContexts, [&OutdatedProxies](ProxyAndFlagsArray& InTaskContext) { OutdatedProxies.Append(InTaskContext); }); return OutdatedProxies; } // Deprecated void ULandscapeSubsystem::BuildNanite(TArrayView InProxiesToBuild, bool bForceRebuild) { BuildNanite(bForceRebuild ? UE::Landscape::EBuildFlags::ForceRebuild : UE::Landscape::EBuildFlags::None, InProxiesToBuild); } void ULandscapeSubsystem::BuildNanite(UE::Landscape::EBuildFlags InBuildFlags, TArrayView InProxiesToBuild) { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::BuildNanite); const bool bForceRebuild = EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::ForceRebuild); UWorld* World = GetWorld(); if (!World || World->IsGameWorld()) { return; } if (InProxiesToBuild.IsEmpty() && Proxies.IsEmpty()) { return; } TArray FinalProxiesToBuild; if (InProxiesToBuild.IsEmpty()) { Algo::Transform(Proxies, FinalProxiesToBuild, [](const TObjectPtr& InProxyPtr) { return InProxyPtr.Get(); }); } else { for (ALandscapeProxy* ProxyToBuild : InProxiesToBuild) { FinalProxiesToBuild.Add(ProxyToBuild); // Build all streaming proxies in the case of a ALandscape : if (ALandscape* Landscape = Cast(ProxyToBuild)) { ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); if (LandscapeInfo != nullptr) { Algo::Transform(LandscapeInfo->StreamingProxies, FinalProxiesToBuild, [](const TWeakObjectPtr& InStreamingProxy) { return InStreamingProxy.Get(); }); } } } } // Only keep unique copies : FinalProxiesToBuild.Sort(); FinalProxiesToBuild.SetNum(Algo::Unique(FinalProxiesToBuild)); // Don't keep those that are null or already up to date : FinalProxiesToBuild.SetNum(Algo::RemoveIf(FinalProxiesToBuild, [bForceRebuild](ALandscapeProxy* InProxy) { return (InProxy == nullptr) || (!bForceRebuild && InProxy->IsNaniteMeshUpToDate()); })); bool bDoFinishAllNaniteBuildsInFlightNow = false; for (ALandscapeProxy* LandscapeProxy : FinalProxiesToBuild) { // reset the nanite content guid so we force rebuild nanite if (ULandscapeNaniteComponent* NaniteComponent = LandscapeProxy->GetComponentByClass(); NaniteComponent != nullptr && bForceRebuild) { UE_LOG(LogLandscape, Log, TEXT("Reset proxy: '%s'"), *LandscapeProxy->GetActorNameOrLabel()); NaniteComponent->SetProxyContentId(FGuid()); } if (LandscapeProxy->IsNaniteMeshUpToDate()) { continue; } FGraphEventRef GraphEvent = LandscapeProxy->UpdateNaniteRepresentationAsync(nullptr); bDoFinishAllNaniteBuildsInFlightNow |= GraphEvent.IsValid(); } if (bDoFinishAllNaniteBuildsInFlightNow) { const bool bAllNaniteBuildsDone = FinishAllNaniteBuildsInFlightNow(ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::DisplaySlowTaskDialog); // Not passing ULandscapeSubsystem::EFinishAllNaniteBuildsInFlightFlags::AllowCancel, so there should be no way that FinishAllNaniteBuildsInFlightNow returns false : check(bAllNaniteBuildsDone); } check(NaniteBuildsInFlight == 0); if (EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::WriteFinalLog)) { UE_LOGFMT_LOC(LogLandscape, Log, "BuildNaniteFinalLog", "Build Nanite: {NumProxies} landscape {NumProxies}|plural(one=proxy,other=proxies) built.", ("NumProxies", FinalProxiesToBuild.Num())); } } bool ULandscapeSubsystem::GetDirtyOnlyInMode() const { const ULandscapeSettings* Settings = GetDefault(); return (Settings->LandscapeDirtyingMode == ELandscapeDirtyingMode::InLandscapeModeOnly) || (Settings->LandscapeDirtyingMode == ELandscapeDirtyingMode::InLandscapeModeAndUserTriggeredChanges); } // Deprecated void ULandscapeSubsystem::SaveModifiedLandscapes() { SaveModifiedLandscapes(UE::Landscape::EBuildFlags::None); } void ULandscapeSubsystem::SaveModifiedLandscapes(UE::Landscape::EBuildFlags InBuildFlags) { TSet SetDirtyPackages; TSet PackagesToSave; const bool bSkipDirty = false; // Gather list of packages to save and make them dirty so they are considered by FEditorFileUtils::SaveDirtyPackages. ForEachLandscapeInfo([&](ULandscapeInfo* LandscapeInfo) { for(UPackage* ModifiedPackage : LandscapeInfo->GetModifiedPackages()) { PackagesToSave.Add(ModifiedPackage->GetFName()); if (!ModifiedPackage->IsDirty()) { SetDirtyPackages.Add(ModifiedPackage); ModifiedPackage->SetDirtyFlag(true); } } return true; }); const bool bPromptUserToSave = true; const bool bSaveMapPackages = true; const bool bSaveContentPackages = true; const bool bFastSave = false; const bool bNotifyNoPackagesSaved = false; const bool bCanBeDeclined = true; FEditorFileUtils::SaveDirtyPackages(bPromptUserToSave, bSaveMapPackages, bSaveContentPackages, bFastSave, bNotifyNoPackagesSaved, bCanBeDeclined, nullptr, [PackagesToSave](UPackage* DirtyPackage) { if (PackagesToSave.Contains(DirtyPackage->GetFName())) { return false; } return true; }); // If Package wasn't saved it is still in the LandscapeInfo ModifiedPackage list, set its dirty flag back to false. int32 NumPackagesNotProcessed = 0; ForEachLandscapeInfo([&](ULandscapeInfo* LandscapeInfo) { for (UPackage* ModifiedPackage : LandscapeInfo->GetModifiedPackages()) { if (SetDirtyPackages.Contains(ModifiedPackage)) { ModifiedPackage->SetDirtyFlag(false); ++NumPackagesNotProcessed; } } return true; }); if (EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::WriteFinalLog)) { check(NumPackagesNotProcessed <= PackagesToSave.Num()); UE_LOGFMT_LOC(LogLandscape, Log, "SaveModifiedLandscapesFinalLog", "Save Modified Landscapes : {NumPackagesToSave} landscape {NumPackagesToSave}|plural(one=proxy,other=proxies) considered : {NumPackagesSaved} saved ({NumPackagesNotProcessed} skipped).", ("NumPackagesToSave", PackagesToSave.Num()), ("NumPackagesSaved", (PackagesToSave.Num() - NumPackagesNotProcessed)), ("NumPackagesNotProcessed", NumPackagesNotProcessed)); } } // Deprecated void ULandscapeSubsystem::MarkModifiedLandscapesAsDirty() { MarkModifiedLandscapesAsDirty(UE::Landscape::EBuildFlags::None); } void ULandscapeSubsystem::MarkModifiedLandscapesAsDirty(UE::Landscape::EBuildFlags InBuildFlags) { int32 NumDirtied = 0; // Flush all packages that are pending mark for dirty : ForEachLandscapeInfo([InBuildFlags, &NumDirtied](ULandscapeInfo* LandscapeInfo) { NumDirtied += LandscapeInfo->MarkModifiedPackagesAsDirty(); return true; }); if (EnumHasAnyFlags(InBuildFlags, UE::Landscape::EBuildFlags::WriteFinalLog)) { UE_LOGFMT_LOC(LogLandscape, Log, "MarkModifiedLandscapesAsDirtyFinalLog", "Mark Modified Landscapes Dirty : {NumProxies} {NumProxies}|plural(one=proxy,other=proxies) made dirty.", ("NumProxies", NumDirtied)); } } bool ULandscapeSubsystem::HasModifiedLandscapes() const { bool bHasModifiedLandscapes = false; ForEachLandscapeInfo([&](ULandscapeInfo* LandscapeInfo) { if (LandscapeInfo->GetModifiedPackageCount() > 0) { bHasModifiedLandscapes = true; return false; } return true; }); return bHasModifiedLandscapes; } bool ULandscapeSubsystem::IsGridBased() const { return UWorld::IsPartitionedWorld(GetWorld()); } void ULandscapeSubsystem::ChangeGridSize(ULandscapeInfo* LandscapeInfo, uint32 GridSizeInComponents) { TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::ChangeGridSize); if (!IsGridBased()) { return; } TSet ActorsToDelete; FLandscapeConfigHelper::ChangeGridSize(LandscapeInfo, GridSizeInComponents, ActorsToDelete); // This code path is used for converting a non grid based Landscape to a gridbased so it shouldn't delete any actors check(!ActorsToDelete.Num()); } ALandscapeProxy* ULandscapeSubsystem::FindOrAddLandscapeProxy(ULandscapeInfo* LandscapeInfo, const FIntPoint& SectionBase) { if (!IsGridBased()) { return LandscapeInfo->GetCurrentLevelLandscapeProxy(true); } return FLandscapeConfigHelper::FindOrAddLandscapeStreamingProxy(LandscapeInfo, SectionBase); } void ULandscapeSubsystem::DisplayMessages(FCanvas* Canvas, float& XPos, float& YPos) { } bool ULandscapeSubsystem::GetActionableMessage(FActionableMessage& OutActionableMessage) { using namespace UE::Landscape; TRACE_CPUPROFILER_EVENT_SCOPE(ULandscapeSubsystem::GetActionableMessage); static const FText DefaultTooltip = LOCTEXT("DefaultLandscapeModified.ToolTip", "Assets that affect the Landscape may have changed. Rebuild the Landscape to see the results."); static const FText DefaultActionMessage = LOCTEXT("DefaultLandscapeModified.Action", "Rebuild"); const TArray> OutdatedProxies = GetOutdatedProxyDetails(EOutdatedDataFlags::All, /*bInMustMatchAllFlags = */false); TArray NumOutdatedProxyPerFlag; NumOutdatedProxyPerFlag.AddDefaulted(GetOutdatedDataFlagIndex(EOutdatedDataFlags::Last) + 1); int32 NumTotalOutdatedProxy = 0; EOutdatedDataFlags OutdatedFlagsUnion = EOutdatedDataFlags::None; Algo::ForEach(OutdatedProxies, [&OutdatedProxies, &NumOutdatedProxyPerFlag, &NumTotalOutdatedProxy, &OutdatedFlagsUnion](const TTuple& ProxyAndFlag) { ALandscape* ParentLandscape = ProxyAndFlag.Key->GetLandscapeActor(); // Don't display any message for this landscape when it's being edited : we consider the landscape to be in "WIP state" while editing. // This avoids flickering of the message while the async stuff (grass, Nanite, ...) gets updated in the background if ((ParentLandscape == nullptr) || !ParentLandscape->HasLandscapeEdMode()) { ++NumTotalOutdatedProxy; OutdatedFlagsUnion |= ProxyAndFlag.Value; uint32 RemainingFlags = static_cast(ProxyAndFlag.Value); while (RemainingFlags != 0) { uint32 FlagIndex = FBitSet::GetAndClearNextBit(RemainingFlags); ++NumOutdatedProxyPerFlag[FlagIndex]; } } }); auto LogFinalNumberOfDirtyProxies = [this]() { const int32 NumDirtyProxies = GetDirtyLandscapeProxyPackages().Num(); UE_LOGFMT_LOC(LogLandscape, Log, "BuildFinalNumberOfDirtyProxies", "{NumProxies} landscape {NumProxies}|plural(one=proxy,other=proxies) now need to be saved.", ("NumProxies", NumDirtyProxies)); }; // If more than 1 action is required, go with a BuildAll action if (FMath::CountBits(static_cast(OutdatedFlagsUnion)) > 1) { OutActionableMessage.Message = FText::Format(LOCTEXT("SeveralLandscapeDataOutdated.Message", "{0} Landscape {0}|plural(one=actor,other=actors) {0}|plural(one=is,other=are) out of date and {0}|plural(one=needs,other=need) to be rebuilt"), NumTotalOutdatedProxy);; OutActionableMessage.Tooltip = DefaultTooltip; OutActionableMessage.ActionMessage = DefaultActionMessage; OutActionableMessage.ActionCallback = [LogFinalNumberOfDirtyProxies]() { UE::Landscape::BuildAll(UE::Landscape::EBuildFlags::WriteFinalLog); LogFinalNumberOfDirtyProxies(); }; return true; } if (int32 OutdatedProxiesCount = NumOutdatedProxyPerFlag[GetOutdatedDataFlagIndex(EOutdatedDataFlags::GrassMaps)]) { OutActionableMessage.Message = FText::Format(LOCTEXT("GRASS_MAPS_NEED_TO_BE_REBUILT_FMT", "{0} Landscape {0}|plural(one=actor,other=actors) with grass maps {0}|plural(one=needs,other=need) to be rebuilt"), OutdatedProxiesCount); OutActionableMessage.Tooltip = DefaultTooltip; OutActionableMessage.ActionMessage = DefaultActionMessage; OutActionableMessage.ActionCallback = [LogFinalNumberOfDirtyProxies]() { UE::Landscape::BuildGrassMaps(UE::Landscape::EBuildFlags::WriteFinalLog); LogFinalNumberOfDirtyProxies(); }; return true; } if (int32 OutdatedProxiesCount = NumOutdatedProxyPerFlag[GetOutdatedDataFlagIndex(EOutdatedDataFlags::PhysicalMaterials)]) { OutActionableMessage.Message = FText::Format(LOCTEXT("LANDSCAPE_PHYSICALMATERIAL_NEED_TO_BE_REBUILT_FMT", "{0} Landscape {0}|plural(one=actor,other=actors) with physical materials {0}|plural(one=needs,other=need) to be rebuilt"), OutdatedProxiesCount); OutActionableMessage.Tooltip = DefaultTooltip; OutActionableMessage.ActionMessage = DefaultActionMessage; OutActionableMessage.ActionCallback = [LogFinalNumberOfDirtyProxies]() { UE::Landscape::BuildPhysicalMaterial(UE::Landscape::EBuildFlags::WriteFinalLog); LogFinalNumberOfDirtyProxies(); }; return true; } if (int32 OutdatedProxiesCount = NumOutdatedProxyPerFlag[GetOutdatedDataFlagIndex(EOutdatedDataFlags::NaniteMeshes)]) { OutActionableMessage.Message = FText::Format(LOCTEXT("LANDSCAPE_NANITE_MESHES_NEED_TO_BE_REBUILT_FMT", "{0} Landscape {0}|plural(one=actor,other=actors) with Nanite meshes {0}|plural(one=needs,other=need) to be rebuilt"), OutdatedProxiesCount); OutActionableMessage.Tooltip = DefaultTooltip; OutActionableMessage.ActionMessage = DefaultActionMessage; OutActionableMessage.ActionCallback = [LogFinalNumberOfDirtyProxies]() { UE::Landscape::BuildNanite(UE::Landscape::EBuildFlags::WriteFinalLog); LogFinalNumberOfDirtyProxies(); }; return true; } if (int32 OutdatedProxiesCount = NumOutdatedProxyPerFlag[GetOutdatedDataFlagIndex(EOutdatedDataFlags::PackageModified)]) { OutActionableMessage.Message = FText::Format(LOCTEXT("LandscapeModified.Message", "{0} Landscape {0}|plural(one=actor,other=actors) {0}|plural(one=is,other=are) out of date and {0}|plural(one=needs,other=need) to be rebuilt"), OutdatedProxiesCount); OutActionableMessage.Tooltip = LOCTEXT("LandscapeModified.Tooltip", "The Landscape actors visible in your level have been modified as a result of changes to other assets.\nThese changes need to be applied to the Landscape assets."); OutActionableMessage.ActionMessage = LOCTEXT("LandscapeModified.Action", "Update"); OutActionableMessage.ActionCallback = [LogFinalNumberOfDirtyProxies]() { UE::Landscape::MarkModifiedLandscapesAsDirty(UE::Landscape::EBuildFlags::WriteFinalLog); LogFinalNumberOfDirtyProxies(); }; return true; } return false; } FDateTime ULandscapeSubsystem::GetAppCurrentDateTime() { return AppCurrentDateTime; } // deprecated void ULandscapeSubsystem::AddAsyncEvent(FGraphEventRef GraphEventRef) { } TSharedRef ULandscapeSubsystem::CreateTrackedNaniteBuildState( ALandscapeProxy* LandscapeProxy, int32 InLODToExport, const TArray& InComponentsToExport) { check(Proxies.Contains(LandscapeProxy)); // proxies should be registered before attempting to build nanite TSharedRef AsyncBuildData = LandscapeProxy->MakeAsyncNaniteBuildData(InLODToExport, InComponentsToExport); AsyncBuildData->BuildCompleteEvent = FGraphEvent::CreateGraphEvent(); AsyncBuildData->TimeStamp_Requested = FPlatformTime::Seconds(); NaniteMeshBuildStates.Add(AsyncBuildData); return AsyncBuildData; } void ULandscapeSubsystem::AddNaniteFinalizeBuildEvent(FGraphEventRef InNaniteFinalizeBuildEvent) { NaniteFinalizeBuildEvents.Add(InNaniteFinalizeBuildEvent); } bool ULandscapeSubsystem::FinishAllNaniteBuildsInFlightNow(EFinishAllNaniteBuildsInFlightFlags FinishFlags) { check(IsInGameThread()); const int32 TotalMeshes = NaniteBuildsInFlight.load(); const bool bAllowCancel = EnumHasAnyFlags(FinishFlags, EFinishAllNaniteBuildsInFlightFlags::AllowCancel); TOptional SlowTask; if (EnumHasAnyFlags(FinishFlags, EFinishAllNaniteBuildsInFlightFlags::DisplaySlowTaskDialog)) { SlowTask.Emplace(TotalMeshes, LOCTEXT("Landscape_BuildNanite", "Building Nanite Landscape Meshes")); const bool bShowCancelButton = bAllowCancel; SlowTask.GetValue().Initialize(); SlowTask.GetValue().MakeDialog(bShowCancelButton); } TArray IncompleteMeshes; // we have to drain the game thread tasks and static mesh builds bool bCancelled = false; int32 LastRemainingMeshes = TotalMeshes; while (AreNaniteBuildsInProgress()) { int32 Remaining = NaniteBuildsInFlight.load(); int32 MeshesProcessed = LastRemainingMeshes - Remaining; LastRemainingMeshes = Remaining; if (SlowTask.IsSet()) { SlowTask.GetValue().EnterProgressFrame(MeshesProcessed, FText::Format(LOCTEXT("Landscape_BuildNaniteProgress", "Building Nanite Landscape Mesh ({0} of {1})"), FText::AsNumber(TotalMeshes - LastRemainingMeshes), FText::AsNumber(TotalMeshes))); } IncompleteMeshes.Reset(); bool bAnyStalled = false; bool bAnyNotStalled = false; for (TSharedRef AsyncBuildData : NaniteMeshBuildStates) { if (!AsyncBuildData->bIsComplete) // event IsComplete? { if (AsyncBuildData->CheckForStallAndWarn()) { bAnyStalled = true; } else { bAnyNotStalled = true; } IncompleteMeshes.Add(AsyncBuildData->NaniteStaticMesh.Get()); } } if (IncompleteMeshes.Num() > 0) { FAssetCompilingManager::Get().FinishCompilationForObjects(IncompleteMeshes); } const bool bCancelWhenAllStalled = false; // this is disabled (stall detection is not perfect) - for now we remain in the loop hoping it will complete. if (bCancelWhenAllStalled && bAnyStalled && !bAnyNotStalled) { // all remaining meshes are stalled.. UE_LOG(LogLandscape, Warning, TEXT("Cancelling Nanite Build because it is taking too long, nanite meshes may not be up to date")); bCancelled = true; break; } if (bAllowCancel && SlowTask.IsSet() && SlowTask.GetValue().ShouldCancel()) { // TODO [chris.tchou] Currently this just closes the dialog, but lets the tasks continue -- we should set the cancel flag in the outstanding tasks // to early out, and test to make sure this doesn't leave anything hanging or in a bad state. bCancelled = true; break; } // Make sure we have also executed all deferred "finalize Nanite build tasks" // Nanite finalize build events run on the game thread so drain all game thread tasks here FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); // Tick NaniteFinalizeBuildEvents in order to empty the list of those that have been processed, that will allow us to eventually leave that while // statement by having AreNaniteBuildsInProgress return true : TickNaniteFinalizeBuildEvents(); } if (SlowTask.IsSet()) { SlowTask.GetValue().Destroy(); SlowTask.Reset(); // should destroy and teardown the slow task } return !bCancelled && !AreNaniteBuildsInProgress(); } void ULandscapeSubsystem::TickNaniteFinalizeBuildEvents() { check(IsInGameThread()); NaniteFinalizeBuildEvents.RemoveAllSwap([](const FGraphEventRef& Ref) -> bool { return Ref->IsComplete(); }); } bool ULandscapeSubsystem::IsMultithreadedNaniteBuildEnabled() { return LandscapeMultithreadNaniteBuild > 0; } bool ULandscapeSubsystem::IsLiveNaniteRebuildEnabled() { return LiveRebuildNaniteOnModification > 0; } bool ULandscapeSubsystem::AreNaniteBuildsInProgress() const { check(IsInGameThread()); return (NaniteBuildsInFlight.load() > 0) || !NaniteFinalizeBuildEvents.IsEmpty(); } void ULandscapeSubsystem::IncNaniteBuild() { NaniteBuildsInFlight++; } void ULandscapeSubsystem::DecNaniteBuild() { NaniteBuildsInFlight--; NaniteStaticMeshesInFlight--; } void ULandscapeSubsystem::WaitLaunchNaniteBuild() { ON_SCOPE_EXIT { ++NaniteStaticMeshesInFlight; }; if (LandscapeMultithreadNaniteBuild != 0 && LandscapeMaxSimultaneousMultithreadNaniteBuilds == -1) { return; } const int32 MaxNaniteBuilds = LandscapeMultithreadNaniteBuild ? LandscapeMaxSimultaneousMultithreadNaniteBuilds : 1; if (MaxNaniteBuilds < 0) { return; } while(NaniteStaticMeshesInFlight >= MaxNaniteBuilds) { FPlatformProcess::Sleep(0.05f); } check((LandscapeMultithreadNaniteBuild != 0) || (NaniteStaticMeshesInFlight <= 1)); } ULandscapeSubsystem::FDelegateAccess::FDelegateAccess(FOnHeightmapStreamedDelegate& InOnHeightmapStreamed, FOnLandscapeProxyComponentDataChanged& InOnLandscapeProxyComponentDataChanged, FOnLandscapeProxyMaterialChanged& InOnLandscapeProxyMaterialChanged) : OnHeightmapStreamedDelegate(InOnHeightmapStreamed) , OnLandscapeProxyComponentDataChangedDelegate(InOnLandscapeProxyComponentDataChanged) , OnLandscapeProxyMaterialChangedDelegate(InOnLandscapeProxyMaterialChanged) {} ULandscapeSubsystem::FDelegateAccess ULandscapeSubsystem::GetDelegateAccess() const { return FDelegateAccess(OnHeightmapStreamedDelegate, OnLandscapeProxyComponentDataChangedDelegate, OnLandscapeProxyMaterialChangedDelegate); } #endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE