// 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 GroupRanges; TArray 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 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 StaticMesh; TArray> MaterialsOverrides; TArray 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 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 CustomData; TArrayView CustomDataSlice(int32 InstanceIndex, int32 NumCustomDataFloatsPerInstance); void ShadowCopyCustomData(int32 InstanceCount, int32 NumCustomDataFloatsPerInstance, TArrayView 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 CustomDataFloats); /** Update instance transforms for a group of instances. */ bool BatchUpdateInstancesTransforms(FGeometryCollectionISMPool& ISMPool, FMeshId MeshId, int32 StartInstanceIndex, TArrayView 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 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 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 ISMComponent; /** Groups of instances allocated in the ISM. */ FInstanceGroups InstanceGroups; /** Id of Instance in ISMC */ TArray 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 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 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 ISMs; /** Mapping from mesh description to ISMs array slot. */ TMap MeshToISMIndex; /** Set of ISM descriptions that we would like to preallocate. */ TSet PrellocationQueue; /** Free list of indices in ISMs that are empty. */ TArray FreeList; /** Free list of indices in ISMs that have registered ISM components. */ TArray 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 CustomDataFloats); /** Update transforms for a mesh group */ GEOMETRYCOLLECTIONENGINE_API bool BatchUpdateInstancesTransforms(FMeshGroupId MeshGroupId, FMeshId MeshId, int32 StartInstanceIndex, TArrayView 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& 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 MeshGroups; FGeometryCollectionISMPool Pool; // Expose internals for debug draw support. PRAGMA_DISABLE_DEPRECATION_WARNINGS friend UGeometryCollectionISMPoolDebugDrawComponent; PRAGMA_ENABLE_DEPRECATION_WARNINGS };