Files
UnrealEngine/Engine/Source/Runtime/Experimental/GeometryCollectionEngine/Public/GeometryCollection/GeometryCollectionISMPoolComponent.h
2025-05-18 13:04:45 +08:00

406 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Components/SceneComponent.h"
#include "Containers/Map.h"
#include "InstancedStaticMeshDelegates.h"
#include "Materials/MaterialInterface.h"
#include "InstanceDataTypes.h"
#include "GeometryCollectionISMPoolComponent.generated.h"
class AActor;
class UE_DEPRECATED(5.6, "UGeometryCollectionISMPoolComponent is deprecated, please use UISMPoolComponent instead.") UGeometryCollectionISMPoolComponent;
class UInstancedStaticMeshComponent;
/**
* Structure containing a set of allocated instance ranges in an FGeometryCollectionISM which is the manager for a single ISM component.
* The instance ranges don't change once allocated, and aren't the same as the actual render indices in the ISM.
* The reason that we don't store the the actual ISM render indices is that ISM component is free to reorder its instances whenever it likes.
*/
struct FInstanceGroups
{
using FInstanceGroupId = int32;
/** A single continuous range associated with an FInstanceGroupId. */
struct FInstanceGroupRange
{
FInstanceGroupRange(int32 InStart, int32 InCount)
: Start(InStart)
, Count(InCount)
{
}
int32 Start = 0;
int32 Count = 0;
};
/** Reset all contents. */
void Reset()
{
TotalInstanceCount = 0;
TotalFreeInstanceCount = 0;
GroupRanges.Empty();
FreeList.Empty();
}
/** Returns true if no groups ranges are in use. */
bool IsEmpty() const
{
return GroupRanges.Num() == FreeList.Num();
}
/** Returns the maximum instance index that we have allocated in a group. */
int32 GetMaxInstanceIndex() const
{
return TotalInstanceCount;
}
/** Add a new group range. */
FInstanceGroupId AddGroup(int32 Count)
{
// First check to see if we can recycle a group from the free list.
for (int32 Index = 0; Index < FreeList.Num(); ++Index)
{
const FInstanceGroupId GroupId = FreeList[Index];
// todo: Could also allow allocating a subrange if Count is less than the group range count.
if (Count == GroupRanges[GroupId].Count)
{
TotalFreeInstanceCount -= Count;
FreeList.RemoveAtSwap(Index, EAllowShrinking::No);
return GroupId;
}
}
// Create a new group.
const int32 StartIndex = TotalInstanceCount;
TotalInstanceCount += Count;
const FInstanceGroupId GroupId = GroupRanges.Add(FInstanceGroupRange(StartIndex, Count));
return GroupId;
}
/** Remove a group range. */
void RemoveGroup(FInstanceGroupId GroupId)
{
// Remove the group by putting on free list for reuse.
// Actually removing it would require too much shuffling of the render instance index remapping.
TotalFreeInstanceCount += GroupRanges[GroupId].Count;
FreeList.Add(GroupId);
}
int32 TotalInstanceCount = 0;
int32 TotalFreeInstanceCount = 0;
TArray<FInstanceGroupRange> GroupRanges;
TArray<FInstanceGroupId> FreeList;
};
/**
* A description for an ISM component.
*/
struct FISMComponentDescription
{
enum EFlags
{
UseHISM = 1 << 1, // HISM is no longer supported. This flag is ignored.
GpuLodSelection = 1 << 2,
ReverseCulling = 1 << 3,
StaticMobility = 1 << 4,
WorldPositionOffsetWritesVelocity = 1 << 5,
EvaluateWorldPositionOffset = 1 << 6,
AffectShadow = 1 << 7,
AffectDistanceFieldLighting = 1 << 8,
AffectDynamicIndirectLighting = 1 << 9,
AffectFarShadow = 1 << 10,
DistanceCullPrimitive = 1 << 11,
};
uint32 Flags = WorldPositionOffsetWritesVelocity|EvaluateWorldPositionOffset|AffectShadow;
int32 NumCustomDataFloats = 0;
FVector Position = FVector::ZeroVector;
int32 StartCullDistance = 0;
int32 EndCullDistance = 0;
int32 MinLod = 0;
uint32 GroupHash = 0; // Optional, allows identical SMs to be separated into different groups for finer grained culling
float LodScale = 1.f;
TArray<FName> Tags;
FName StatsCategory;
bool operator==(const FISMComponentDescription& Other) const
{
return Flags == Other.Flags &&
NumCustomDataFloats == Other.NumCustomDataFloats &&
Position == Other.Position &&
StartCullDistance == Other.StartCullDistance &&
EndCullDistance == Other.EndCullDistance &&
MinLod == Other.MinLod &&
LodScale == Other.LodScale &&
Tags == Other.Tags &&
GroupHash == Other.GroupHash &&
StatsCategory == Other.StatsCategory;
}
};
FORCEINLINE uint32 GetTypeHash(const FISMComponentDescription& Desc)
{
uint32 Hash = HashCombineFast(GetTypeHash(Desc.Flags), GetTypeHash(Desc.NumCustomDataFloats));
Hash = HashCombineFast(Hash, GetTypeHash(Desc.Position));
Hash = HashCombineFast(Hash, GetTypeHash(Desc.StartCullDistance));
Hash = HashCombineFast(Hash, GetTypeHash(Desc.EndCullDistance));
Hash = HashCombineFast(Hash, GetTypeHash(Desc.MinLod));
Hash = HashCombineFast(Hash, GetTypeHash(Desc.LodScale));
Hash = HashCombineFast(Hash, GetTypeHash(Desc.GroupHash));
Hash = HashCombineFast(Hash, GetArrayHash(Desc.Tags.GetData(), Desc.Tags.Num()));
return HashCombineFast(Hash, GetTypeHash(Desc.StatsCategory));
}
/**
* A mesh with potentially overriden materials and ISM property description.
* We batch instances into ISMs that have equivalent values for this structure.
*/
struct FGeometryCollectionStaticMeshInstance
{
TWeakObjectPtr<UStaticMesh> StaticMesh;
TArray<TWeakObjectPtr<UMaterialInterface>> MaterialsOverrides;
TArray<float> CustomPrimitiveData;
FISMComponentDescription Desc;
bool operator==(const FGeometryCollectionStaticMeshInstance& Other) const
{
if (!(Desc == Other.Desc))
{
return false;
}
if (!StaticMesh.HasSameIndexAndSerialNumber(Other.StaticMesh))
{
return false;
}
if (MaterialsOverrides.Num() != Other.MaterialsOverrides.Num())
{
return false;
}
for (int32 MatIndex = 0; MatIndex < MaterialsOverrides.Num(); MatIndex++)
{
if (!MaterialsOverrides[MatIndex].HasSameIndexAndSerialNumber(Other.MaterialsOverrides[MatIndex]))
{
return false;
}
}
if (CustomPrimitiveData.Num() != Other.CustomPrimitiveData.Num())
{
return false;
}
for (int32 DataIndex = 0; DataIndex < CustomPrimitiveData.Num(); DataIndex++)
{
if (CustomPrimitiveData[DataIndex] != Other.CustomPrimitiveData[DataIndex])
{
return false;
}
}
return true;
}
};
FORCEINLINE uint32 GetTypeHash(const FGeometryCollectionStaticMeshInstance& MeshInstance)
{
uint32 CombinedHash = GetTypeHash(MeshInstance.StaticMesh);
CombinedHash = HashCombineFast(CombinedHash, GetTypeHash(MeshInstance.MaterialsOverrides.Num()));
for (const TWeakObjectPtr<UMaterialInterface> Material: MeshInstance.MaterialsOverrides)
{
CombinedHash = HashCombineFast(CombinedHash, GetTypeHash(Material));
}
for (const float CustomFloat : MeshInstance.CustomPrimitiveData)
{
CombinedHash = HashCombineFast(CombinedHash, GetTypeHash(CustomFloat));
}
CombinedHash = HashCombineFast(CombinedHash, GetTypeHash(MeshInstance.Desc));
return CombinedHash;
}
/** Describes a group of instances within an ISM. */
struct FGeometryCollectionMeshInfo
{
int32 ISMIndex;
FInstanceGroups::FInstanceGroupId InstanceGroupIndex;
TArray<float> CustomData;
TArrayView<const float> CustomDataSlice(int32 InstanceIndex, int32 NumCustomDataFloatsPerInstance);
void ShadowCopyCustomData(int32 InstanceCount, int32 NumCustomDataFloatsPerInstance, TArrayView<const float> CustomDataFloats);
};
struct FGeometryCollectionISMPool;
/**
* A mesh group which is a collection of meshes and their related FGeometryCollectionMeshInfo.
* We group these with a single handle with the expectation that a client will want to own multiple meshs and release them together.
*/
struct FGeometryCollectionMeshGroup
{
using FMeshId = int32;
/** Adds a new mesh with instance count. We expect to only add a unique mesh instance once to each group. Returns a ID that can be used to update the instances. */
FMeshId AddMesh(const FGeometryCollectionStaticMeshInstance& MeshInstance, int32 InstanceCount, const FGeometryCollectionMeshInfo& ISMInstanceInfo, TArrayView<const float> CustomDataFloats);
/** Update instance transforms for a group of instances. */
bool BatchUpdateInstancesTransforms(FGeometryCollectionISMPool& ISMPool, FMeshId MeshId, int32 StartInstanceIndex, TArrayView<const FTransform> NewInstancesTransforms, bool bWorldSpace, bool bMarkRenderStateDirty, bool bTeleport);
void BatchUpdateInstanceCustomData(FGeometryCollectionISMPool& ISMPool, int32 CustomFloatIndex, float CustomFloatValue);
/** Remove all of our managed meshes and associated instances. */
void RemoveAllMeshes(FGeometryCollectionISMPool& ISMPool);
/** Array of allocated mesh infos. */
TArray<FGeometryCollectionMeshInfo> MeshInfos;
/** Flag for whether we allow removal of instances when transform scale is set to zero. */
bool bAllowPerInstanceRemoval = false;
};
/** Structure containting all info for a single ISM. */
struct FGeometryCollectionISM
{
/** Create the ISMComponent according to settings on the mesh instance. */
void CreateISM(USceneComponent* InOwningComponent);
/** Initialize the ISMComponent according to settings on the mesh instance. */
void InitISM(const FGeometryCollectionStaticMeshInstance& InMeshInstance, bool bKeepAlive, bool bOverrideTransformUpdates = false);
/** Add a group to the ISM. Returns the group index. */
FInstanceGroups::FInstanceGroupId AddInstanceGroup(int32 InstanceCount, TArrayView<const float> CustomDataFloats);
/** Unique description of ISM component settings. */
FGeometryCollectionStaticMeshInstance MeshInstance;
/** Created ISM component. Will be nullptr when this slot has been recycled to FGeometryCollectionISMPool FreeList. */
TObjectPtr<UInstancedStaticMeshComponent> ISMComponent;
/** Groups of instances allocated in the ISM. */
FInstanceGroups InstanceGroups;
/** Id of Instance in ISMC */
TArray<FPrimitiveInstanceId> InstanceIds;
};
/** A pool of ISMs. */
struct FGeometryCollectionISMPool
{
using FISMIndex = int32;
FGeometryCollectionISMPool();
/** Find or add an ISM and return an ISM index handle. */
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FISMIndex GetOrAddISM(UGeometryCollectionISMPoolComponent* OwningComponent, const FGeometryCollectionStaticMeshInstance& MeshInstance, bool& bOutISMCreated);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
/** Remove an ISM. */
void RemoveISM(FISMIndex ISMIndex, bool bKeepAlive, bool bRecycle);
/** Add instances to ISM and return a mesh info handle. */
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FGeometryCollectionMeshInfo AddInstancesToISM(UGeometryCollectionISMPoolComponent* OwningComponent, const FGeometryCollectionStaticMeshInstance& MeshInstance, int32 InstanceCount, TArrayView<const float> CustomDataFloats);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
/** Remove instances from an ISM. */
void RemoveInstancesFromISM(const FGeometryCollectionMeshInfo& MeshInfo);
/** Update ISM contents. */
bool BatchUpdateInstancesTransforms(FGeometryCollectionMeshInfo& MeshInfo, int32 StartInstanceIndex, TArrayView<const FTransform> NewInstancesTransforms, bool bWorldSpace, bool bMarkRenderStateDirty, bool bTeleport, bool bAllowPerInstanceRemoval);
void BatchUpdateInstanceCustomData(FGeometryCollectionMeshInfo const& MeshInfo, int32 CustomFloatIndex, float CustomFloatValue);
/** Clear all ISM components and associated data. */
void Clear();
/** Tick maintenance of free list and preallocation. */
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void Tick(UGeometryCollectionISMPoolComponent* OwningComponent);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
/** Add an ISM description to the preallocation queue. */
void RequestPreallocateMeshInstance(const FGeometryCollectionStaticMeshInstance& MeshInstances);
/** Process the preallocation queue. Processing is timesliced so that only some of the queue will be processed in every call. */
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void ProcessPreallocationRequests(UGeometryCollectionISMPoolComponent* OwningComponent, int32 MaxPreallocations);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void UpdateAbsoluteTransforms(const FTransform& BaseTransform, EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport);
/** Array of ISM objects. */
TArray<FGeometryCollectionISM> ISMs;
/** Mapping from mesh description to ISMs array slot. */
TMap<FGeometryCollectionStaticMeshInstance, FISMIndex> MeshToISMIndex;
/** Set of ISM descriptions that we would like to preallocate. */
TSet<FGeometryCollectionStaticMeshInstance> PrellocationQueue;
/** Free list of indices in ISMs that are empty. */
TArray<int32> FreeList;
/** Free list of indices in ISMs that have registered ISM components. */
TArray<int32> FreeListISM;
// Cached state of lifecycle cvars from the last Tick()
bool bCachedKeepAlive = false;
bool bCachedRecycle = false;
// Whether we force ISMs to use parent bounds and disable transform updates
bool bDisableBoundsAndTransformUpdate = false;
};
/**
* UGeometryCollectionISMPoolComponent.
* Component that manages a pool of ISM components in order to allow multiple client components that use the same meshes to the share ISMs.
*/
class UE_DEPRECATED(5.6, "UGeometryCollectionISMPoolDebugDrawComponent is deprecated, please use UISMPoolDebugDrawComponent instead.") UGeometryCollectionISMPoolDebugDrawComponent;
UCLASS(meta = (BlueprintSpawnableComponent), MinimalAPI)
class UE_DEPRECATED(5.6, "UGeometryCollectionISMPoolComponent is deprecated, please use UISMPoolComponent instead.") UGeometryCollectionISMPoolComponent: public USceneComponent
{
GENERATED_UCLASS_BODY()
public:
using FMeshGroupId = int32;
using FMeshId = int32;
//~ Begin UActorComponent Interface
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
virtual void GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize) override;
//~ End UActorComponent Interface
/**
* Create an Mesh group which represent an arbitrary set of mesh with their instance
* no resources are created until the meshes are added for this group
* return a mesh group Id used to add and update instances
*/
GEOMETRYCOLLECTIONENGINE_API FMeshGroupId CreateMeshGroup(bool bAllowPerInstanceRemoval = false);
/** Destroy a mesh group and its associated resources */
GEOMETRYCOLLECTIONENGINE_API void DestroyMeshGroup(FMeshGroupId MeshGroupId);
/** Add a static mesh for a mesh group */
GEOMETRYCOLLECTIONENGINE_API FMeshId AddMeshToGroup(FMeshGroupId MeshGroupId, const FGeometryCollectionStaticMeshInstance& MeshInstance, int32 InstanceCount, TArrayView<const float> CustomDataFloats);
/** Update transforms for a mesh group */
GEOMETRYCOLLECTIONENGINE_API bool BatchUpdateInstancesTransforms(FMeshGroupId MeshGroupId, FMeshId MeshId, int32 StartInstanceIndex, TArrayView<const FTransform> NewInstancesTransforms, bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
UE_DEPRECATED(5.3, "BatchUpdateInstancesTransforms Array parameter version is deprecated, use the TArrayView version instead")
GEOMETRYCOLLECTIONENGINE_API bool BatchUpdateInstancesTransforms(FMeshGroupId MeshGroupId, FMeshId MeshId, int32 StartInstanceIndex, const TArray<FTransform>& NewInstancesTransforms, bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
/** Update a single slot of custom instance data for all instances in a mesh group */
GEOMETRYCOLLECTIONENGINE_API bool BatchUpdateInstanceCustomData(FMeshGroupId MeshGroupId, int32 CustomFloatIndex, float CustomFloatValue);
/**
* Preallocate an ISM in the pool.
* Doing this early for known mesh instance descriptions can reduce the component registration cost of AddMeshToGroup() for newly discovered mesh descriptions.
*/
GEOMETRYCOLLECTIONENGINE_API void PreallocateMeshInstance(const FGeometryCollectionStaticMeshInstance& MeshInstance);
GEOMETRYCOLLECTIONENGINE_API void SetTickablePoolManagement(bool bEnablePoolManagement);
GEOMETRYCOLLECTIONENGINE_API void SetOverrideTransformUpdates(bool bOverrideUpdates);
GEOMETRYCOLLECTIONENGINE_API void UpdateAbsoluteTransforms(const FTransform& BaseTransform, EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport);
private:
uint32 NextMeshGroupId = 0;
TMap<FMeshGroupId, FGeometryCollectionMeshGroup> MeshGroups;
FGeometryCollectionISMPool Pool;
// Expose internals for debug draw support.
PRAGMA_DISABLE_DEPRECATION_WARNINGS
friend UGeometryCollectionISMPoolDebugDrawComponent;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
};