Files
UnrealEngine/Engine/Source/Runtime/Landscape/Private/LandscapeGrassMapsBuilder.h
2025-05-18 13:04:45 +08:00

300 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Async/AsyncWork.h"
#include "UObject/WeakObjectPtrTemplates.h"
#include "LandscapeComponent.h"
#include "LandscapeTextureStreamingManager.h"
#include "Containers/AllocatorFixedSizeFreeList.h"
#include "Spatial/PointHashGrid3.h"
class FLandscapeGrassWeightExporter;
struct FScopedSlowTask;
namespace UE::Landscape
{
enum class EBuildFlags : uint8;
} // namespace UE::Landscape
class FAsyncFetchTask : public FNonAbandonableTask
{
public:
// non-owned pointer. The lifetime is managed externally and must guarantee this pointer is valid while the task is in flight
TNonNullPtr< FLandscapeGrassWeightExporter> ActiveRender;
TMap<ULandscapeComponent*, TUniquePtr<FLandscapeComponentGrassData>, TInlineSetAllocator<1>> Results;
FAsyncFetchTask(FLandscapeGrassWeightExporter* ActiveRender)
: ActiveRender(ActiveRender)
{
}
void DoWork();
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FAsyncFetchTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
/**
* Helper class used to Build or monitor outdated Grass maps of a world
*/
class FLandscapeGrassMapsBuilder
{
public:
LANDSCAPE_API FLandscapeGrassMapsBuilder(UWorld* InWorld, FLandscapeTextureStreamingManager& InTextureStreamingManager);
~FLandscapeGrassMapsBuilder();
#if WITH_EDITOR
/** Synchronously build all grass maps on all proxies in the world */
UE_DEPRECATED(5.6, "Use the function with the EBuildFlags param")
LANDSCAPE_API void Build();
LANDSCAPE_API void Build(UE::Landscape::EBuildFlags InBuildFlags);
/** Return the number of landscape components in the world that have grass map data that is not up to date (NOT including components missing grass map data altogether) */
LANDSCAPE_API int32 GetOutdatedGrassMapCount(bool bInForceUpdate = true) const;
#endif // WITH_EDITOR
// count components that have valid data but whose generation hash does not match
int32 CountOutdatedGrassMaps(const TArray<TObjectPtr<ULandscapeComponent>>& LandscapeComponents) const;
// called when components are registered to the world
void RegisterComponent(ULandscapeComponent* Component);
// called when components are unregistered from the world.
void UnregisterComponent(const ULandscapeComponent* Component);
// get the number of grass maps that are still waiting to render, as of the last AmortizedUpdateGrassMaps()
int32 GetTotalGrassMapsWaitingToRender() const { return TotalComponentsWaitingCount; }
// Amortized Update of Grass Maps near the specified cameras.
// if Cameras is empty, it considers all distances to be zero for update purposes:
// - it won't evict anything for distance
// - it will not start tracking (building grass maps) for any components
void AmortizedUpdateGrassMaps(const TArray<FVector>& Cameras, bool bPrioritizeCreation, bool bAllowStartGrassMapGeneration);
// synchronous build of grassmaps for a specific set of components
// returns true if it successfully makes all LandscapeComponents have up to date Grass Maps.
bool BuildGrassMapsNowForComponents(TArrayView<TObjectPtr<ULandscapeComponent>> LandscapeComponents, FScopedSlowTask* SlowTask, bool bMarkDirty);
private:
// false if this program instance will never be able to render grass
bool CanEverRender() const;
// false if the world can not currently render the grass (but this may change later, for example if preview modes are modified)
bool CanCurrentlyRender() const;
// Update all of the non-pending components. Returns true if any components changed states.
// If passed an empty Cameras array, distances are calculated as zero (i.e. it won't evict for distance)
// MaxExpensiveUpdateChecksToPerform controls how many expensive component updates are performed (pass ComponentStates.Num() to process all)
// bCancelAndEvictAllImmediately will move all tracked component states to the pending state and block until any in-flight processing is cancelled and cleaned up
// bEvictWhenBeyondEvictionRange will evict any populated components that are beyond the eviction range distance (used to reclaim memory when using runtime generation)
bool UpdateTrackedComponents(const TArray<FVector>& Cameras, int32 LocalMaxRendering, int32 MaxExpensiveUpdateChecksToPerform, bool bCancelAndEvictAllImmediately, bool bEvictWhenBeyondEvictionRange);
// Start the grass map generation process on pending components in priority order
// (based on distance from the given Camera set) -- Cameras must not be empty.
void StartPrioritizedGrassMapGeneration(const TArray<FVector>& Cameras, int32 MaxComponentsToStart, bool bOnlyWhenCloserThanEvictionRange);
enum class EComponentStage : uint8
{
Pending, // initial state; the component is waiting to start the generation process.
NotReady, // tried to start generation process, but either no grass types exist or the material is not ready. wait for that to change.
TextureStreaming, // texture streaming was requested. wait for the mips to be available
Rendering, // GPU render commands were sent -- waiting for async readback to complete
AsyncFetch, // Waiting for the async fetch task to complete
GrassMapsPopulated, // grass maps are built and are ready to create instances
};
struct FComponentState
{
public:
// this pointer is valid as long as the Component is registered
// (the FComponentState itself may continue to exist after unregistration, until all resources are cleaned up)
ULandscapeComponent* Component = nullptr;
EComponentStage Stage = EComponentStage::Pending;
bool bInPendingHeap = false;
// counts the number of ticks this component has remained in the current stage
int32 TickCount = 0;
#if WITH_EDITOR
// the hashes of dependencies when this component's grass map was built, for tracking automatic invalidation
uint32 GrassMapGenerationHash = 0;
uint32 GrassInstanceGenerationHash = 0;
#endif // WITH_EDITOR
// list of textures to stream prior to rendering this component (valid only in TextureSreaming and Rendering stages)
TArray<UTexture*> TexturesToStream;
// the active render (valid only in Rendering stage)
TUniquePtr<FLandscapeGrassWeightExporter> ActiveRender;
// when in AsyncFetch stage, this is async task that we are waiting for
TUniquePtr<FAsyncTask<FAsyncFetchTask>> AsyncFetchTask;
FComponentState(ULandscapeComponent* Component);
bool AreTexturesStreamedIn() const;
bool IsBeyondEvictionRange(const TArray<FVector>& Cameras) const;
};
// components in the Pending state are also tracked in a priority heap, using this structure
struct FPendingComponent
{
FPendingComponent(FComponentState* InComponentState)
: State(InComponentState)
{}
// comparison operator used for Heap functions -- smaller keys have priority over larger keys
bool operator<(const FPendingComponent& Other) const
{
return PriorityKey < Other.PriorityKey;
}
void UpdatePriorityDistance(const TArray<FVector>& Cameras, float MustHaveDistanceScale);
FComponentState* State = nullptr;
double PriorityKey = TNumericLimits<double>::Lowest();
};
// structure to do an amortized update of a set of update elements in a (conceptual) array
struct FAmortizedUpdate
{
// the first index to update this tick (inclusive)
int32 FirstIndex = 0;
// the last index to update this tick (exclusive)
int32 LastIndex = 0;
// Call at the beginning of the update tick to set up how many updates you want to run this tick
// UpdateElementCount is the total number of update elements (to wrap when we hit the end of the possibility space)
void StartUpdateTick(int32 UpdateElementCount, int32 MaxUpdatesThisFrame)
{
check(MaxUpdatesThisFrame >= 0);
if (MaxUpdatesThisFrame >= UpdateElementCount)
{
// if we are updating more than what we have, then just update everything
FirstIndex = 0;
LastIndex = UpdateElementCount;
}
else
{
FirstIndex = LastIndex;
if (FirstIndex >= UpdateElementCount)
{
FirstIndex = 0;
}
check(FirstIndex >= 0);
LastIndex = FirstIndex + MaxUpdatesThisFrame;
}
}
// return true if the given element should update
bool ShouldUpdate(int32 UpdateElementIndex)
{
if ((UpdateElementIndex >= FirstIndex) && (UpdateElementIndex < LastIndex))
{
return true;
}
return false;
}
// call if an update element is deleted from the array, to update the valid ranges
void HandleDeletion(int32 DeletedUpdateElementIndex)
{
if (DeletedUpdateElementIndex < FirstIndex)
{
FirstIndex--;
}
if (DeletedUpdateElementIndex < LastIndex)
{
LastIndex--;
}
}
};
UWorld* World = nullptr;
// counts of how many components are currently in each stage
int32 PendingCount = 0;
int32 NotReadyCount = 0;
int32 StreamingCount = 0;
int32 RenderingCount = 0;
int32 AsyncFetchCount = 0;
int32 PopulatedCount = 0;
// number of components that need to render but are waiting (as of the last call to StartTrackingComponents())
int32 TotalComponentsWaitingCount = 0;
// true if any render thread commands were queued by the last call to UpdateTrackedComponents()
bool bRenderCommandsQueuedByLastUpdate = false;
TAllocatorFixedSizeFreeList<sizeof(FComponentState), 32> StatePoolAllocator;
// store the grass map state of each registered (or recently unregistered) component
TMap<ULandscapeComponent*, FComponentState*> ComponentStates;
// Pending components, in a min heap by distance to streaming cameras
TArray<FPendingComponent> PendingComponentsHeap;
int32 FirstNewPendingComponent = TNumericLimits<int32>::Max();
int32 PendingUpdateAmortizationCounter = 0;
float LastMustHaveDistanceScale = 0.0f; // used to detect spawn distance scales changing
// state to amortize the update of components
FAmortizedUpdate AmortizedUpdate;
// system to manage texture streaming requests
FLandscapeTextureStreamingManager& TextureStreamingManager;
// hash grid storing previously used camera locations (to determine when cameras have jumped)
UE::Geometry::TPointHashGrid3<FVector, double> PreviousCameraHashGrid;
// tries to cancel any in flight operations and transition back to the Pending state
// returns true when the state has been successfully transitioned back to Pending
// if bCancelImmediately is true, it will ensure the component reaches Pending state before returning (possibly blocking on async or gpu tasks)
bool CancelAndEvict(FComponentState& State, bool bCancelImmediately);
// try to kick off the grass map generation pipeline -- returns true if it started the amortized update path, false otherwise.
bool StartGrassMapGeneration(FComponentState& State, bool bForceCompileShaders);
// try to apply fast path transitions to a pending component -- returns true if a fastpath was taken, and the component is no longer pending.
bool TryFastpathsFromPending(FComponentState& State, bool bRecalculateHashes);
// once textures are streamed, this kicks off the grass data render, and async GPU readback
void KickOffRenderAndReadback(FComponentState& State);
// once the GPU readback is complete, this starts processing the data to generate a GrassData structure
void LaunchAsyncFetchTask(FComponentState& State);
void PopulateGrassDataFromAsyncFetchTask(FComponentState& State);
// once the GPU readback is complete, this populates the grass data on the component (and cancels texture stream requests)
void PopulateGrassDataFromReadback(FComponentState& State);
void RemoveTextureStreamingRequests(FComponentState& State);
// state transition helpers
void StreamingToNotReady(FComponentState& State);
void PendingToNotReady(FComponentState& State);
void PendingToPopulatedFastPathAlreadyHasData(FComponentState& State);
void PendingToPopulatedFastPathNoGrass(FComponentState& State);
void PendingToStreaming(FComponentState& State);
// pending component heap add/remove
void AddToPendingComponentHeap(FComponentState* State);
void RemoveFromPendingComponentHeap(FComponentState* State);
void CompleteAllAsyncTasksNow();
#if WITH_EDITOR
// cached count of how many grass maps are outdated, and the last time we calculated that value
mutable int32 OutdatedGrassMapCount = 0;
mutable double GrassMapsLastCheckTime = 0.0;
#endif // WITH_EDITOR
};