Files
jordi rovira 445183bccc [mutable] Fix crash with the use of passthrough components.
- Make sure the component name is added.
- Make sure some LODs exist.
- Initialize internal skeleton array with the instance components count and not the object component count.
- Add some defensive early outs in case of other internal problems.
#jira UE-227958
#rnx
[REVIEW] [at]gerard.martin, [at]alexei.lebedev
#rb alexei.lebedev

[CL 37267473 by jordi rovira in 5.5 branch]
2024-10-18 11:22:30 -04:00

861 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "LogBenchmarkUtil.h"
#include "Containers/Queue.h"
#include "MuCO/DescriptorHash.h"
#include "MuCO/CustomizableObject.h"
#include "MuCO/CustomizableObjectPrivate.h"
#include "MuCO/CustomizableObjectInstance.h"
#include "MuCO/CustomizableObjectExtension.h"
#include "MuCO/CustomizableInstanceLODManagement.h"
#include "Containers/Ticker.h"
#include "MuCO/CustomizableObjectInstanceDescriptor.h"
#include "MuR/Mesh.h"
#include "MuR/Parameters.h"
#include "MuR/System.h"
#include "MuR/Skeleton.h"
#include "MuR/Image.h"
#include "UObject/GCObject.h"
#include "WorldCollision.h"
#include "Engine/StreamableManager.h"
#include "Framework/Notifications/NotificationManager.h"
#include "MuCO/FMutableTaskGraph.h"
#include "AssetRegistry/AssetData.h"
#include "ContentStreaming.h"
#include "Animation/MorphTarget.h"
#include "UObject/StrongObjectPtr.h"
#include "Tasks/Task.h"
#include "CustomizableObjectSystemPrivate.generated.h"
#if WITH_EDITORONLY_DATA
class UEditorImageProvider;
#endif
class UCustomizableObjectSystem;
namespace LowLevelTasks { enum class ETaskPriority : int8; }
struct FTexturePlatformData;
// Split StreamedBulkData into chunks smaller than MUTABLE_STREAMED_DATA_MAXCHUNKSIZE
#define MUTABLE_STREAMED_DATA_MAXCHUNKSIZE (512 * 1024 * 1024)
/** End a Customizable Object Instance Update. All code paths of an update have to end here. */
void FinishUpdateGlobal(const TSharedRef<FUpdateContextPrivate>& Context);
class FMutableUpdateCandidate
{
public:
// The Instance to possibly update
UCustomizableObjectInstance* CustomizableObjectInstance;
EQueuePriorityType Priority = EQueuePriorityType::Med;
// These are the LODs that would be applied if this candidate is chosen
int32 MinLOD = 0;
/** Array of RequestedLODs per component to generate if this candidate is chosen */
TArray<uint16> RequestedLODLevels;
FMutableUpdateCandidate(UCustomizableObjectInstance* InCustomizableObjectInstance);
FMutableUpdateCandidate(const UCustomizableObjectInstance* InCustomizableObjectInstance, const int32 InMinLOD,
const TArray<uint16>& InRequestedLODLevels) :
CustomizableObjectInstance(const_cast<UCustomizableObjectInstance*>(InCustomizableObjectInstance)), MinLOD(InMinLOD),
RequestedLODLevels(InRequestedLODLevels) {}
bool HasBeenIssued() const;
void Issue();
void ApplyLODUpdateParamsToInstance(FUpdateContextPrivate& Context);
private:
/** If true it means that EnqueueUpdateSkeletalMesh has decided this update should be performed, if false it should be ignored. Just used for consistency checks */
bool bHasBeenIssued = false;
};
struct FMutablePendingInstanceUpdate
{
TSharedRef<FUpdateContextPrivate> Context;
FMutablePendingInstanceUpdate(const TSharedRef<FUpdateContextPrivate>& InContext);
bool operator==(const FMutablePendingInstanceUpdate& Other) const;
bool operator<(const FMutablePendingInstanceUpdate& Other) const;
};
inline uint32 GetTypeHash(const FMutablePendingInstanceUpdate& Update);
struct FPendingInstanceUpdateKeyFuncs : BaseKeyFuncs<FMutablePendingInstanceUpdate, TWeakObjectPtr<const UCustomizableObjectInstance>>
{
FORCEINLINE static TWeakObjectPtr<const UCustomizableObjectInstance> GetSetKey(const FMutablePendingInstanceUpdate& PendingUpdate);
FORCEINLINE static bool Matches(const TWeakObjectPtr<const UCustomizableObjectInstance>& A, const TWeakObjectPtr<const UCustomizableObjectInstance>& B);
FORCEINLINE static uint32 GetKeyHash(const TWeakObjectPtr<const UCustomizableObjectInstance>& Identifier);
};
struct FMutablePendingInstanceDiscard
{
TWeakObjectPtr<UCustomizableObjectInstance> CustomizableObjectInstance;
FMutablePendingInstanceDiscard(UCustomizableObjectInstance* InCustomizableObjectInstance)
{
CustomizableObjectInstance = InCustomizableObjectInstance;
}
friend bool operator ==(const FMutablePendingInstanceDiscard& A, const FMutablePendingInstanceDiscard& B)
{
return A.CustomizableObjectInstance.HasSameIndexAndSerialNumber(B.CustomizableObjectInstance);
}
};
inline uint32 GetTypeHash(const FMutablePendingInstanceDiscard& Discard)
{
return GetTypeHash(Discard.CustomizableObjectInstance.GetWeakPtrTypeHash());
}
struct FPendingInstanceDiscardKeyFuncs : BaseKeyFuncs<FMutablePendingInstanceUpdate, TWeakObjectPtr<UCustomizableObjectInstance>>
{
FORCEINLINE static const TWeakObjectPtr<UCustomizableObjectInstance>& GetSetKey(const FMutablePendingInstanceDiscard& PendingDiscard)
{
return PendingDiscard.CustomizableObjectInstance;
}
FORCEINLINE static bool Matches(const TWeakObjectPtr<UCustomizableObjectInstance>& A, const TWeakObjectPtr<UCustomizableObjectInstance>& B)
{
return A.HasSameIndexAndSerialNumber(B);
}
FORCEINLINE static uint32 GetKeyHash(const TWeakObjectPtr<UCustomizableObjectInstance>& Identifier)
{
return GetTypeHash(Identifier.GetWeakPtrTypeHash());
}
};
/** Instance updates queue.
*
* The queues will only contain a single operation per UCustomizableObjectInstance.
* If there is already an operation it will be replaced. */
class FMutablePendingInstanceWork
{
TSet<FMutablePendingInstanceUpdate, FPendingInstanceUpdateKeyFuncs> PendingInstanceUpdates;
TSet<FMutablePendingInstanceDiscard, FPendingInstanceDiscardKeyFuncs> PendingInstanceDiscards;
TSet<mu::Instance::ID> PendingIDsToRelease;
public:
// Returns the number of pending instance updates, LOD Updates and discards.
int32 Num() const;
// Adds a new instance update
void AddUpdate(const FMutablePendingInstanceUpdate& UpdateToAdd);
// Removes an instance update
void RemoveUpdate(const TWeakObjectPtr<UCustomizableObjectInstance>& Instance);
#if WITH_EDITOR
void RemoveUpdatesForObject(const UCustomizableObject* InObject);
#endif
const FMutablePendingInstanceUpdate* GetUpdate(const TWeakObjectPtr<const UCustomizableObjectInstance>& Instance) const;
TSet<FMutablePendingInstanceUpdate, FPendingInstanceUpdateKeyFuncs>::TIterator GetUpdateIterator()
{
return PendingInstanceUpdates.CreateIterator();
}
TSet<FMutablePendingInstanceDiscard, FPendingInstanceDiscardKeyFuncs>::TIterator GetDiscardIterator()
{
return PendingInstanceDiscards.CreateIterator();
}
TSet<mu::Instance::ID>::TIterator GetIDsToReleaseIterator()
{
return PendingIDsToRelease.CreateIterator();
}
void AddDiscard(const FMutablePendingInstanceDiscard& TaskToEnqueue);
void AddIDRelease(mu::Instance::ID IDToRelease);
void RemoveAllUpdatesAndDiscardsAndReleases();
};
struct FMutableImageCacheKey
{
mu::FResourceID Resource = 0;
int32 SkippedMips = 0;
FMutableImageCacheKey() {};
FMutableImageCacheKey(mu::FResourceID InResource, int32 InSkippedMips)
: Resource(InResource), SkippedMips(InSkippedMips) {}
inline bool operator==(const FMutableImageCacheKey& Other) const
{
return Resource == Other.Resource && SkippedMips == Other.SkippedMips;
}
};
inline uint32 GetTypeHash(const FMutableImageCacheKey& Key)
{
return HashCombine(GetTypeHash(Key.Resource), GetTypeHash(Key.SkippedMips));
}
// Cache of weak references to generated resources for one single model
struct FMutableResourceCache
{
TWeakObjectPtr<const UCustomizableObject> Object;
TMap<mu::FResourceID, TWeakObjectPtr<USkeletalMesh> > Meshes;
TMap<FMutableImageCacheKey, TWeakObjectPtr<UTexture2D> > Images;
void Clear()
{
Meshes.Reset();
Images.Reset();
}
};
USTRUCT()
struct FGeneratedTexture
{
GENERATED_USTRUCT_BODY();
FMutableImageCacheKey Key;
UPROPERTY(Category = NoCategory, VisibleAnywhere)
FString Name;
UPROPERTY(Category = NoCategory, VisibleAnywhere)
TObjectPtr<UTexture> Texture = nullptr;
bool operator==(const FGeneratedTexture& Other) const = default;
};
USTRUCT()
struct FGeneratedMaterial
{
GENERATED_USTRUCT_BODY();
UPROPERTY(Category = NoCategory, VisibleAnywhere)
TObjectPtr<UMaterialInterface> MaterialInterface;
UPROPERTY(Category = NoCategory, VisibleAnywhere)
TArray< FGeneratedTexture > Textures;
// Surface or SharedSurface Id
uint32 SurfaceId = 0;
// Index of the material to instantiate (UCustomizableObject::ReferencedMaterials)
uint32 MaterialIndex = 0;
bool operator==(const FGeneratedMaterial& Other) const { return SurfaceId == Other.SurfaceId && MaterialIndex == Other.MaterialIndex; };
};
DECLARE_DELEGATE(FMutableTaskDelegate);
struct FMutableTask
{
/** Actual function to perform in this task. */
FMutableTaskDelegate Function;
TArray<UE::Tasks::FTask, TFixedAllocator<2>> Dependencies;
/** Check if all the dependencies of this task have been completed. */
FORCEINLINE bool AreDependenciesComplete() const
{
for (const UE::Tasks::FTask& Dependency : Dependencies)
{
if (!Dependency.IsCompleted())
{
return false;
}
}
return true;
}
/** Free the handles for any dependency of this task. */
FORCEINLINE void ClearDependencies()
{
Dependencies.Empty();
}
};
// Mutable data generated during the update steps.
// We keep it from begin to end update, and it is used in several steps.
struct FInstanceUpdateData
{
struct FImage
{
FName Name;
mu::FResourceID ImageID;
// LOD of the ImageId. If the texture is shared between LOD, first LOD where this image can be found.
int32 BaseLOD;
int32 BaseMip;
uint16 FullImageSizeX, FullImageSizeY;
mu::Ptr<const mu::Image> Image;
TWeakObjectPtr<UTexture2D> Cached;
bool bIsPassThrough = false;
bool bIsNonProgressive = false;
};
struct FVector
{
FName Name;
FLinearColor Vector;
};
struct FScalar
{
FName Name;
float Scalar;
};
struct FSurface
{
/** Range in the Images array */
uint16 FirstImage = 0;
uint16 ImageCount = 0;
/** Range in the Vectors array */
uint16 FirstVector = 0;
uint16 VectorCount = 0;
/** Range in the Scalar array */
uint16 FirstScalar = 0;
uint16 ScalarCount = 0;
/** Index of the material in the referenced materials array of the CO.
* A negative value means that the material of this surface slot of the mesh doesn't need to be changed.
* This is valid for pass-through meshes.
*/
int32 MaterialIndex = -1;
/** Id of the surface in the mutable core instance. */
uint32 SurfaceId = 0;
/** Id of the metadata associated with this surface. */
uint32 SurfaceMetadataId = 0;
};
struct FLOD
{
mu::FResourceID MeshID;
mu::Ptr<const mu::Mesh> Mesh;
/** Range in the Surfaces array */
uint16 FirstSurface = 0;
uint16 SurfaceCount = 0;
/** Range in the external Bones array */
uint32 FirstActiveBone = 0;
uint32 ActiveBoneCount = 0;
/** Range in the external Bones array */
uint32 FirstBoneMap = 0;
uint32 BoneMapCount = 0;
// True if the Mesh is valid
bool bGenerated = false;
};
struct FComponent
{
uint16 Id = 0;
/** Range in the LODs array */
uint16 FirstLOD = 0;
uint16 LODCount = 0;
};
TArray<FComponent> Components;
TArray<FLOD> LODs;
TArray<FSurface> Surfaces;
TArray<FImage> Images;
TArray<FVector> Vectors;
TArray<FScalar> Scalars;
TArray<mu::FBoneName> ActiveBones;
TArray<mu::FBoneName> BoneMaps;
struct FBone
{
mu::FBoneName Name;
FMatrix44f MatrixWithScale;
bool operator==(const mu::FBoneName& OtherName) const { return Name == OtherName; };
};
struct FMorphTargetMeshData
{
TArray<FName> NameResolutionMap;
TArray<FMorphTargetVertexData> Data;
};
TMap<uint32, FMorphTargetMeshData> RealTimeMorphTargetMeshData;
struct FRealTimeMorphsComponentData
{
int32 ObjectComponentIndex = INDEX_NONE;
TArray<FName> RealTimeMorphTargetNames;
TArray<TArray<FMorphTargetLODModel>> RealTimeMorphsLODData;
};
TArray<FRealTimeMorphsComponentData> RealTimeMorphTargets;
struct FClothingMeshData
{
int32 ClothingAssetIndex = INDEX_NONE;
int32 ClothingAssetLOD = INDEX_NONE;
int32 PhysicsAssetIndex = INDEX_NONE;
TArray<FCustomizableObjectMeshToMeshVertData> Data;
};
TMap<uint32, FClothingMeshData> ClothingMeshData;
struct FSkeletonData
{
TArray<uint16> SkeletonIds;
TArray<FBone> BonePose;
TMap<mu::FBoneName, TPair<FName, uint16>> BoneInfoMap;
};
// Access by instance component index
TArray<FSkeletonData> Skeletons;
struct FNamedExtensionData
{
mu::ExtensionDataPtrConst Data;
FName Name;
};
TArray<FNamedExtensionData> ExtendedInputPins;
/** */
void Clear()
{
LODs.Empty();
Components.Empty();
Surfaces.Empty();
Images.Empty();
Scalars.Empty();
Vectors.Empty();
RealTimeMorphTargets.Empty();
Skeletons.Empty();
ExtendedInputPins.Empty();
}
};
/** Update Context.
*
* Alive from the start to the end of the update (both API and LOD update). */
class FUpdateContextPrivate
{
public:
FUpdateContextPrivate(UCustomizableObjectInstance& InInstance, const FCustomizableObjectInstanceDescriptor& Descriptor);
FUpdateContextPrivate(UCustomizableObjectInstance& InInstance);
~FUpdateContextPrivate();
int32 GetMinLOD() const;
void SetMinLOD(int32 MinLOD);
/** Return an array of LODs per object component. */
const TArray<uint16>& GetRequestedLODs() const;
void SetRequestedLODs(TArray<uint16>& RequestedLODs);
const FCustomizableObjectInstanceDescriptor& GetCapturedDescriptor() const;
const FDescriptorHash& GetCapturedDescriptorHash() const;
const FCustomizableObjectInstanceDescriptor&& MoveCommittedDescriptor();
EQueuePriorityType PriorityType = EQueuePriorityType::Low;
FInstanceUpdateDelegate UpdateCallback;
FInstanceUpdateNativeDelegate UpdateNativeCallback;
/** Weak reference to the instance we are operating on.
*It is weak because we don't want to lock it in case it becomes irrelevant in the game while operations are pending and it needs to be destroyed. */
TWeakObjectPtr<UCustomizableObjectInstance> Instance;
private:
/** Descriptor which the update will be performed on. */
FCustomizableObjectInstanceDescriptor CapturedDescriptor;
/** Hash of the descriptor. */
FDescriptorHash CapturedDescriptorHash;
public:
/** Instance parameters at the time of the operation request. */
mu::Ptr<mu::Parameters> Parameters;
mu::Ptr<mu::System> MutableSystem;
bool bOnlyUpdateIfNotGenerated = false;
bool bIgnoreCloseDist = false;
bool bForceHighPriority = false;
FInstanceUpdateData InstanceUpdateData;
TArray<int32> RelevantParametersInProgress;
TArray<FString> LowPriorityTextures;
/** This option comes from the operation request */
bool bNeverStream = false;
/** When this option is enabled it will reuse the Mutable core instance and its temp data between updates. */
bool bLiveUpdateMode = false;
bool bReuseInstanceTextures = false;
bool bUseMeshCache = false;
/** Whether the mesh to generate should support Mesh LOD streaming or not. */
bool bStreamMeshLODs = false;
/** This option comes from the operation request. It is used to reduce the number of mipmaps that mutable must generate for images. */
int32 MipsToSkip = 0;
mu::Instance::ID InstanceID = 0; // Redundant
mu::Ptr<const mu::Instance> MutableInstance;
TSharedPtr<mu::Model> Model;
// Number of possible components in the entire CO
// TODO: Redundant because it is in the CO
uint8 NumObjectComponents = 0;
// Number of components in the instance being generated
// TODO: Redundant to store it here because it is implicit in the size of some arrays or in the MutableInstance while it is valid.
uint8 NumInstanceComponents = 0;
// Index with ObjectComponent index
// \TODO: Wasteful: it only needs to be as big as NumInstanceComponents
TArray<uint8> NumLODsAvailablePerComponent;
uint8 FirstLODAvailable = 0;
uint8 FirstResidentLOD = 0;
TMap<mu::FResourceID, FTexturePlatformData*> ImageToPlatformDataMap;
EUpdateResult UpdateResult = EUpdateResult::Success;
mu::FImageOperator::FImagePixelFormatFunc PixelFormatOverride;
/** Mutable Meshes required for each component. Outermost index is the object component index, inner index is the LOD. */
TArray<TArray<mu::FResourceID>> MeshDescriptors;
/** Used to know if the updated instances' meshes are different from the previous ones.
* The index of the array is the instance component's index.
* @return true if the mesh is new or new to this instance (e.g. mesh cached by another instance). */
TArray<bool> MeshChanged;
bool UpdateStarted = false;
bool bLevelBegunPlay = false;
/** true if the update has been optimized (skips all Tasks and calls FinishUpdateGlobal directly on the Enqueue). */
bool bOptimizedUpdate = false;
// Update stats
double StartQueueTime = 0.0;
double QueueTime = 0.0;
double StartUpdateTime = 0.0;
double UpdateTime = 0.0;
double TaskGetMeshTime = 0.0;
double TaskLockCacheTime = 0.0;
double TaskGetImagesTime = 0.0;
double TaskConvertResourcesTime = 0.0f;
double TaskCallbacksTime = 0.0;
// Update Memory stats
int64 UpdateStartBytes = 0;
int64 UpdateEndPeakBytes = 0;
int64 UpdateEndRealPeakBytes = 0;
/** If a InstanceUsage is in this set it means that its AttachParent has been modified (USkeletalMesh changed, UMaterial changed...). */
TSet<TWeakObjectPtr<UCustomizableObjectInstanceUsage>> AttachedParentUpdated;
/** Hard references to objects. Avoids GC to collect them. */
TArray<TStrongObjectPtr<const UObject>> Objects;
};
/** Runtime data used during a mutable instance update */
struct FMutableReleasePlatformOperationData
{
TMap<mu::FResourceID, FTexturePlatformData*> ImageToPlatformDataMap;
};
USTRUCT()
struct FPendingReleaseSkeletalMeshInfo
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
TObjectPtr<USkeletalMesh> SkeletalMesh = nullptr;
UPROPERTY()
double TimeStamp = 0.0f;
};
UCLASS()
class UCustomizableObjectSystemPrivate : public UObject, public IStreamingManager
{
GENERATED_BODY()
public:
// Singleton for the unreal mutable system.
static UCustomizableObjectSystem* SSystem;
// Pointer to the lower level mutable system that actually does the work.
mu::Ptr<mu::System> MutableSystem;
/** Store the last streaming memory size in bytes, to change it when it is safe. */
uint64 LastWorkingMemoryBytes = 0;
uint32 LastGeneratedResourceCacheSize = 0;
// This object is responsible for streaming data to the MutableSystem.
TSharedPtr<class FUnrealMutableModelBulkReader> Streamer;
TSharedPtr<class FUnrealExtensionDataStreamer> ExtensionDataStreamer;
// This object is responsible for providing custom images and meshes to mutable (for image parameters, etc.)
// This object is called from the mutable thread, and it should only access data already safely submitted from
// the game thread and stored in FUnrealMutableImageProvider::GlobalExternalImages.
TSharedPtr<class FUnrealMutableResourceProvider> ResourceProvider;
// Cache of weak references to generated resources to see if they can be reused.
TArray<FMutableResourceCache> ModelResourcesCache;
// List of textures currently cached and valid for the current object that we are operating on.
// This array gets generated when the object cached resources are protected in SetResourceCacheProtected
// from the game thread, and it is read from the Mutable thread only while updating the instance.
TArray<mu::FResourceID> ProtectedObjectCachedImages;
// The pending instance updates, discards or releases
FMutablePendingInstanceWork MutablePendingInstanceWork;
// Queue of game-thread tasks that need to be executed for the current operation. TQueue is thread safe
TQueue<FMutableTask> PendingTasks;
static int32 EnableMutableProgressiveMipStreaming;
static int32 EnableMutableLiveUpdate;
static int32 EnableReuseInstanceTextures;
static int32 EnableMutableAnimInfoDebugging;
static int32 EnableSkipGenerateResidentMips;
static int32 EnableOnlyGenerateRequestedLODs;
static int32 MaxTextureSizeToGenerate;
static int32 SkeletalMeshMinLodQualityLevel;
#if WITH_EDITOR
mu::FImageOperator::FImagePixelFormatFunc ImageFormatOverrideFunc;
#endif
// IStreamingManager interface
virtual void UpdateResourceStreaming(float DeltaTime, bool bProcessEverything = false) override;
virtual int32 BlockTillAllRequestsFinished(float TimeLimit = 0.0f, bool bLogResults = false) override;
virtual void CancelForcedResources() override {}
virtual void AddLevel(ULevel* Level) override {}
virtual void RemoveLevel(ULevel* Level) override {}
virtual void NotifyLevelChange() override {}
virtual void SetDisregardWorldResourcesForFrames(int32 NumFrames) override {}
virtual void NotifyLevelOffset(ULevel* Level, const FVector& Offset) override {}
UCustomizableObjectSystem* GetPublic() const;
void AddGameThreadTask(const FMutableTask& Task);
// Remove references to cached objects that have been deleted in the unreal
// side, and cannot be cached anyway.
// This should only happen in the game thread
void CleanupCache();
// This should only happen in the game thread
FMutableResourceCache& GetObjectCache(const UCustomizableObject* Object);
void AddTextureReference(const FMutableImageCacheKey& TextureId);
// Returns true if the texture's references become zero
bool RemoveTextureReference(const FMutableImageCacheKey& TextureId);
bool TextureHasReferences(const FMutableImageCacheKey& TextureId) const;
EUpdateRequired IsUpdateRequired(const UCustomizableObjectInstance& Instance, bool bOnlyUpdateIfNotGenerated, bool bOnlyUpdateIfLOD, bool bIgnoreCloseDist) const;
EQueuePriorityType GetUpdatePriority(const UCustomizableObjectInstance& Instance, bool bForceHighPriority) const;
void EnqueueUpdateSkeletalMesh(const TSharedRef<FUpdateContextPrivate>& Context);
// Init an async and safe release of the UE and Mutable resources used by the instance without actually destroying the instance, for example if it's very far away
void InitDiscardResourcesSkeletalMesh(UCustomizableObjectInstance* InCustomizableObjectInstance);
// Init the async release of a Mutable Core Instance ID and all the temp resources associated with it
void InitInstanceIDRelease(mu::Instance::ID IDToRelease);
void GetMipStreamingConfig(const UCustomizableObjectInstance& Instance, bool& bOutNeverStream, int32& OutMipsToSkip) const;
bool IsReplaceDiscardedWithReferenceMeshEnabled() const;
void SetReplaceDiscardedWithReferenceMeshEnabled(bool bIsEnabled);
/** Updated at the beginning of each tick. */
int32 GetNumSkeletalMeshes() const;
bool bReplaceDiscardedWithReferenceMesh = false;
bool bReleaseTexturesImmediately = false;
bool bSupport16BitBoneIndex = false;
TMap<FMutableImageCacheKey, uint32> TextureReferenceCount; // Keeps a count of texture usage to decide if they have to be blocked from GC during an update
UPROPERTY(Transient)
TObjectPtr<UCustomizableObjectInstance> CurrentInstanceBeingUpdated = nullptr;
TSharedPtr<FUpdateContextPrivate> CurrentMutableOperation = nullptr;
// Handle to the registered TickDelegate.
FTSTicker::FDelegateHandle TickDelegateHandle;
FTickerDelegate TickDelegate;
/** Change the current status of Mutable. Enabling/Disabling core features.
* Disabling Mutable will turn off compilation, generation, and streaming and will remove the system ticker. */
static void OnMutableEnabledChanged(IConsoleVariable* CVar = nullptr);
/** Update the last set amount of internal memory Mutable can use to build objects. */
void UpdateMemoryLimit();
bool IsMutableAnimInfoDebuggingEnabled() const;
FUnrealMutableResourceProvider* GetResourceProviderChecked() const;
/** Start the actual work of Update Skeletal Mesh process (Update Skeletal Mesh without the queue). */
void StartUpdateSkeletalMesh(const TSharedRef<FUpdateContextPrivate>& Context);
/** See UCustomizableObjectInstance::IsUpdating. */
bool IsUpdating(const UCustomizableObjectInstance& Instance) const;
/** Update stats at each tick.
* Used for stats that are costly to update. */
void UpdateStats();
void CacheTextureParameters(const TArray<FCustomizableObjectTextureParameterValue>& TextureParameters) const;
void UnCacheTextureParameters(const TArray<FCustomizableObjectTextureParameterValue>& TextureParameters) const;
/** Mutable TaskGraph system (Mutable Thread). */
FMutableTaskGraph MutableTaskGraph;
#if WITH_EDITORONLY_DATA
/** Mutable default image provider. Used by the COIEditor and Instance/Descriptor APIs. */
UPROPERTY(Transient)
TObjectPtr<UEditorImageProvider> EditorImageProvider = nullptr;
#endif
FLogBenchmarkUtil LogBenchmarkUtil;
int32 NumSkeletalMeshes = 0;
bool bAutoCompileCommandletEnabled = false;
UPROPERTY()
TArray<FPendingReleaseSkeletalMeshInfo> PendingReleaseSkeletalMesh;
UPROPERTY(Transient)
TObjectPtr<UCustomizableInstanceLODManagementBase> DefaultInstanceLODManagement = nullptr;
UPROPERTY(Transient)
TObjectPtr<UCustomizableInstanceLODManagementBase> CurrentInstanceLODManagement = nullptr;
// Array where textures are added temporarily while the mutable thread may want to
// reused them for some instance under construction.
UPROPERTY(Transient)
TArray<TObjectPtr<UTexture2D>> ProtectedCachedTextures;
// For async material loading
FStreamableManager StreamableManager;
#if WITH_EDITORONLY_DATA
// Array to keep track of cached objects
TArray<FGuid> UncompiledCustomizableObjectIds;
/** Weak pointer to the Uncompiled Customizable Objects notification */
TWeakPtr<SNotificationItem> UncompiledCustomizableObjectsNotificationPtr;
/** Map used to cache per platform MaxChunkSize. If MaxChunkSize > 0, streamed data will be split in multiple files */
TMap<FString, int64> PlatformMaxChunkSize;
#endif
int32 NumLODUpdatesLastTick = 0;
public:
/**
* Get to know if the settings used by the mutable syustem are optimzed for benchmarking operations or not
* @return true if using benchmarking optimized settings, false otherwise.
*/
static bool IsUsingBenchmarkingSettings();
/**
* Enable or disable the usage of benchmarking optimized settings
* @param bUseBenchmarkingOptimizedSettings True to enable the usage of benchmarking settings, false to disable it.
*/
CUSTOMIZABLEOBJECT_API static void SetUsageOfBenchmarkingSettings(bool bUseBenchmarkingOptimizedSettings);
private:
/** Flag that controls some of the settings used for the generation of instances. */
inline static bool bUseBenchmarkingSettings = false;
};
namespace impl
{
void CreateMutableInstance(const TSharedRef<FUpdateContextPrivate>& Operation);
void FixLODs(const TSharedRef<FUpdateContextPrivate>& Operation);
void Subtask_Mutable_PrepareSkeletonData(const TSharedRef<FUpdateContextPrivate>& OperationData);
void Subtask_Mutable_UpdateParameterRelevancy(const TSharedRef<FUpdateContextPrivate>& OperationData);
void Subtask_Mutable_PrepareTextures(const TSharedRef<FUpdateContextPrivate>& OperationData);
}
/** Set OnlyLOD to -1 to generate all mips */
CUSTOMIZABLEOBJECT_API FTexturePlatformData* MutableCreateImagePlatformData(mu::Ptr<const mu::Image> MutableImage, int32 OnlyLOD, uint16 FullSizeX, uint16 FullSizeY);