Files
UnrealEngineUWP/Engine/Source/Runtime/Landscape/Private/LandscapeEditLayer.cpp
jonathan bard c70b8af6e7 Introducing landscape batched merge (activated by CVar : landscape.EditLayersLocalMerge.Enable 2): complete refactor of the landscape edit layers merge algorithm to solve the taxing GPU memory requirements that global merge has and serve as the basis for future features (new weight-blending methods, partial exports, ...) and performance and workflow improvements (deterministic editing of partially-loaded worlds, local updates, removal of useless weightmap allocations, ...)
Unlike local merge (landscape.EditLayersLocalMerge.Enable 1), batched merge is backwards-compatible with BP brushes and will therefore replace both global merge and local merge in the near future. However, in order to maintain full backwards-compatibility, existing BP brushes will still act as they do now wrt determinism, requiring the entire world to be loaded by default. This means that as long as a landscape uses existing and not-yet converted brushes like Landmass, Water, etc., it will effectively act as global merge, with the same memory constraints as the current ones.
It also supports both vertical (in between edit layers) and horizontal (in between paint layers) blending, which allows legacy weight-blending to still work but opens up several possibilities in term of paint layers blending improvements.

Details:
* ILandscapeEditLayerRenderer is an interface (UInterface, actually) to implement for any element that needs to write heightmaps/weightmaps/visibility maps. These can be anything (edit layers, BP brushes, actors, components, ...), which streamlines the rendering/blending capabilities while maintaining backwards-compatibility
* UE::Landscape::EditLayers::IEditLayerRendererProvider is an interface to implement in order to provide a sequence of ILandscapeEditLayerRenderer to the landscape. This will be called on-demand when merging the landscape, which means the landscape dynamically retrieves its active renderers instead of relying on the landscape actor's data to do so. This solves the issue of having to checkout the landscape when adding/removing an element (e.g. a BP brush) to it. BP brushes are still supported, but things like landscape patch components can now be trivially added to a sequence of render operations without checking out the landscape actor
* The merge operation is now generic and is split into several render batches (instead of one large render, the size of the entire loaded landscape), hence the name. Each batch is sized automatically so that it contains a minimal amount of weightmaps and a minimal amount of landscape components.
* A component dependency analysis is run in order to maximize the amount of components to render in a given batch while trying to respect the optimal batch size (CVar landscape.EditLayersLocalMerge.MaxResolutionPerRenderBatch, default value : 1024) If a given component depends on components outside the batch, though, it will either be rendered in another batch (if possible) or the batches will be made large enough to render these (which allows the system to be compatible with existing BP brushes such as Water, but also prevents batched merge to solve the GPU memory requirements that global merge has)
* The merge is split into the following phases :
1. Retrieval of edit layer renderers and their associated states for the various merge types (supported/enabled for heightmaps/weightmaps/visibility)
At this point, the user can selectively change the enabled state of the supported renderers, which allows to customize the merge operation without mutating the landscape state (e.g. only keeping the layers under edit layer N enabled)
2. Trim the unneeded renderers for the requested merge operation (disabled renderers, unneeded weightmaps, etc.)
3. (weightmap merge only) Analyze the renderers' weightmaps usage and divide the renders into several "render groups", that is, a set of weightmaps rendered together because they have a(n horizontal) dependency. Each render group will be rendered separately (i.e. reduces the memory requirements of weight-blending to the minimum)
4. Each renderer provides a list of "render items", which contain locality information (where does this render item outputs data? what input data does it require? does it output heightmap/weightmap/visibility? what weightmap(s) does it output?) and a full component dependency analysis is then run on all renderers/requested components
5. The work is split into several batches, each batch into several render steps : for each render group, there's one step per active renderer, and one last step : a simple callback, which the user can attach to, which allows to retrieve the data that has been rendered for this render group for this batch, i.e. a portion of the merge landscape data, before the render target it was using gets repurposed for another batch/render group.
Currently, this is where the readback of heightmaps/weightmaps to the final texture is done and eventually, the same merge can be repurposed by using another callback and, for example, copying into a transient render target (partial renders), or an image on disk (partial exports)
6. The list of render batches and render steps is executed. Some renderers are automatically added to mix (e.g. default data rendering, to provide the initial clear, at the beginning, and "legacy" weight-blending and normals rendering, both at the end of the merge process) and each batch also has a list of "validity" render targets (one per target layer), which is nothing else than a binary "stencil" buffer, masking the components that are *not* in a given batch. These are not implemented as stencil buffers, though, because we eventually might need access to them on the BP side, so they are plain UTextureRenderTarget2D.
* Several CVars (landscape.BatchedMerge.VisualLog.*) were added to help debug the merge process
* In order to improve rendering time, a new utility shader (CopyQuadsMultiSourcePS) was made  to copy several (up to 63) source textures into an atlas, which optimizes quite a bit the edit layer->atlas copy operation

Misc changes:
* Improvements/fixes to landscape-related GPU tags
* Fixed landscape.DumpWeightmapDiff which would leave txt/png files for weightmap allocations that haven't actually changed if one of the other channels of the weightmap had some data changes

Code details:
* OnFullHeightmapRenderDoneDelegate has been replaced with OnEditLayersMerged and is now called for each batch render
* Non-public API landscape utils have moved to LandscapeUtilsPrivate.h/.cpp
* Fixed edge case of a map that was saved with an invalid ULandscapeEditLayer (e.g. after having uninstalled a plugin where the base class of a layer was defined): we now clear the layer and make sure we don't fall into a situation where there's no edit layer altogether (which is not allowed and usually enforced by the UX)
* Added parameter to ReallocateWeightmaps in order to restrict the re-allocation of weightmaps to a given set of components. This is to avoid a situation where we're trying to add weightmap allocations to components that are not part of the merge. This will eventually go away when we refactor the weightmap allocation system in the editor
* Since the whole thing has to run on the game thread (because of BP calls), a new "scratch render target" system has been added (ULandscapeEditResourcesSubsystem / ULandscapeScratchRenderTarget) in order to help the various merge steps to request/release URenderTargets (2D and 2D array) and manage their RHI access. This would normally be done via RDG but we don't have access to it outside the render thread. This is shared across landscapes, which minimizes the amount of render targets needed when editing multiple landscapes at the same time.

#rb chris.tchou
#jira UE-214800
#tests Editor, cooked game

[CL 35108473 by jonathan bard in ue5-main branch]
2024-07-26 10:30:17 -04:00

90 lines
2.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LandscapeEditLayer.h"
#include "LandscapeEditTypes.h"
#include "Landscape.h"
#define LOCTEXT_NAMESPACE "LandscapeEditLayer"
void ULandscapeEditLayerBase::SetBackPointer(ALandscape* Landscape)
{
OwningLandscape = Landscape;
}
void ULandscapeEditLayerBase::PostLoad()
{
Super::PostLoad();
// TODO[jonathan.bard] Remove
// Needed because we might have saved some layers before we realized we were missing this flag
SetFlags(RF_Transactional);
}
#if WITH_EDITOR
const FLandscapeLayer* ULandscapeEditLayerBase::GetOwningLayer() const
{
if (OwningLandscape != nullptr)
{
TArrayView<const FLandscapeLayer> Layers = OwningLandscape->GetLayers();
return Layers.FindByPredicate([this](const FLandscapeLayer& Struct) { return Struct.EditLayer == this; });
}
return nullptr;
}
#endif // WITH_EDITOR
// ----------------------------------------------------------------------------------
bool ULandscapeEditLayer::SupportsTargetType(ELandscapeToolTargetType InType) const
{
return (InType == ELandscapeToolTargetType::Heightmap) || (InType == ELandscapeToolTargetType::Weightmap) || (InType == ELandscapeToolTargetType::Visibility);
}
// ----------------------------------------------------------------------------------
bool ULandscapeEditLayerSplines::SupportsTargetType(ELandscapeToolTargetType InType) const
{
return (InType == ELandscapeToolTargetType::Heightmap) || (InType == ELandscapeToolTargetType::Weightmap) || (InType == ELandscapeToolTargetType::Visibility);
}
void ULandscapeEditLayerSplines::OnLayerCreated(FLandscapeLayer& Layer)
{
// Splines edit layer is always using alpha blend mode
Layer.BlendMode = LSBM_AlphaBlend;
}
TArray<ULandscapeEditLayerSplines::FEditLayerAction> ULandscapeEditLayerSplines::GetActions() const
{
TArray<FEditLayerAction> Actions;
#if WITH_EDITOR
// Register an "Update Splines" action :
Actions.Add(FEditLayerAction(
LOCTEXT("LandscapeEditLayerSplines_UpdateSplines", "Update Splines"),
FEditLayerAction::FExecuteDelegate::CreateWeakLambda(this, [](const FEditLayerAction::FExecuteParams& InParams)
{
InParams.GetLandscape()->UpdateLandscapeSplines(FGuid(), /*bUpdateOnlySelection = */false, /*bForceUpdate =*/true);
return FEditLayerAction::FExecuteResult(/*bInSuccess = */true);
}),
FEditLayerAction::FCanExecuteDelegate::CreateWeakLambda(this, [](const FEditLayerAction::FExecuteParams& InParams, FText& OutReason)
{
if (InParams.GetLayer()->bLocked)
{
OutReason = FText::Format(LOCTEXT("LandscapeEditLayerSplines_CannotUpdateSplinesOnLockedLayer", "Cannot update splines on layer '{0}' : the layer is currently locked"), FText::FromName(InParams.GetLayer()->Name));
return false;
}
OutReason = LOCTEXT("LandscapeEditLayerSplines_UpdateSplines_Tooltip", "Update Landscape Splines");
return true;
})));
#endif // WITH_EDITOR
return Actions;
}
#undef LOCTEXT_NAMESPACE