// 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 "MuCO/MutableStreamableManager.h" #include "UObject/StrongObjectPtr.h" #include "Tasks/Task.h" #include "CustomizableObjectSystemPrivate.generated.h" #define UE_API CUSTOMIZABLEOBJECT_API class FMutableStreamRequest; class UEditorImageProvider; 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) extern TAutoConsoleVariable CVarMutableHighPriorityLoading; /** Serialized Morph data. */ struct FMorphTargetMeshData { /** Names of the Morph Targets. */ TArray NameResolutionMap; /** Vertex data. Each vertex contains an index to the NameResolutionMap. */ TArray Data; }; /** Serialized Cloth data. */ struct FClothingMeshData { int32 ClothingAssetIndex = INDEX_NONE; int32 ClothingAssetLOD = INDEX_NONE; /** Per vertex data. */ TArray Data; }; /** Mapping of FMorphTargetVertexData local names to global names. */ struct FMappedMorphTargetMeshData { /** Index is the local name. Value is the index to a global name table. */ TArray NameResolutionMap; /** Pointer to the original data with indices to local names. */ const TArray* DataView = nullptr; }; /* Reconstruct the final Morph Targets using the global names. * @param Mesh Used to know which MappedMorphTargets vertices must be removed. * @param GlobalNames * @param OutMorphTargets resulting Morph Targets with the indices pointing to GlobalNames. */ void ReconstructMorphTargets(const mu::FMesh& Mesh, const TArray& GlobalNames, const TMap& MappedMorphTargets, TArray& OutMorphTargets); /** Request the Mutable Data Streamer to load Morph Target blocks. Does not stream them. * @param MutableDataStreamer Streamer to request the blocks. * @param Mesh Mutable mesh to obtain which blocks to load. * @param StreamingResult Key is the block. Value is the container where the data will be deserialized once streamed. */ void LoadMorphTargetsData(FMutableStreamRequest& MutableDataStreamer, const TSharedRef& Mesh, TMap& StreamingResult); void LoadMorphTargetsMetadata(const FMutableStreamRequest& MutableDataStreamer, const TSharedRef& Mesh, TMap& StreamingResult); /** Request the Mutable Data Streamer to load Cloth blocks. Does not stream them. * @param MutableDataStreamer Streamer to request the blocks. * @param Mesh Mutable mesh to obtain which blocks to load. * @param StreamingResult Key is the block. Value is the container where the data will be deserialized once streamed. */ void LoadClothing(FMutableStreamRequest& MutableDataStreamer, const TSharedRef& Mesh, TMap& StreamingResult); /** End a Customizable Object Instance Update. All code paths of an update have to end here. */ void FinishUpdateGlobal(const TSharedRef& Context); #if WITH_EDITOR /** * Class used to hold some MutableSystem settings to be used during the update of a given instance. * It will store the settings used by the system during the setup of this object and so later can be reverted back. */ class FMutableSystemSettingsOverrides { public: UE_API FMutableSystemSettingsOverrides(bool bUseProgressiveMipStreaming, bool bOnlyGenerateRequestedLODs, mu::FImageOperator::FImagePixelFormatFunc InImagePixelFormatFunc); /** Apply the settings set in this object to the mutable system. */ UE_API void ApplySettingsOverrides() const; /** Restore the mutable system settings to a state prior to the setup of this object */ UE_API void RestoreSettings() const; private: // Requested values bool bIsProgressiveMipStreamingEnabled = false; bool bIsOnlyGenerateRequestedLODsEnabled = false; mu::FImageOperator::FImagePixelFormatFunc ImagePixelFormatFunc; // previous values. Cached at the time of creating this object bool bOldIsProgressiveMipStreamingEnabled = false; bool bOldIsOnlyGenerateRequestedLODsEnabled = false; mu::FImageOperator::FImagePixelFormatFunc OldImagePixelFormatFunc; }; #endif /** Strongly typed index for the index of a component in a UCustomizableObjectInstance. */ class FCustomizableObjectInstanceComponentIndex { private: int32 Index = 0; public: explicit FCustomizableObjectInstanceComponentIndex(int32 InIndex = 0) : Index(InIndex) { } inline void Invalidate() { Index = INDEX_NONE; } inline bool IsValid() const { return Index != INDEX_NONE; } inline int32 GetValue() const { return Index; } }; 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 TMap MinLOD; // These are the LODs that would be copied to the descriptor to trigger mesh updates on quality setting changes. TMap QualitySettingMinLODs; TMap FirstRequestedLOD; FMutableUpdateCandidate(UCustomizableObjectInstance* InCustomizableObjectInstance); FMutableUpdateCandidate(const UCustomizableObjectInstance* InCustomizableObjectInstance, const TMap& InMinLOD, const TMap& InFirstRequestedLOD) : CustomizableObjectInstance(const_cast(InCustomizableObjectInstance)), MinLOD(InMinLOD), FirstRequestedLOD(InFirstRequestedLOD) {} 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 Context; FMutablePendingInstanceUpdate(const TSharedRef& InContext); bool operator==(const FMutablePendingInstanceUpdate& Other) const; bool operator<(const FMutablePendingInstanceUpdate& Other) const; }; inline uint32 GetTypeHash(const FMutablePendingInstanceUpdate& Update); struct FPendingInstanceUpdateKeyFuncs : BaseKeyFuncs> { FORCEINLINE static TWeakObjectPtr GetSetKey(const FMutablePendingInstanceUpdate& PendingUpdate); FORCEINLINE static bool Matches(const TWeakObjectPtr& A, const TWeakObjectPtr& B); FORCEINLINE static uint32 GetKeyHash(const TWeakObjectPtr& Identifier); }; struct FMutablePendingInstanceDiscard { TWeakObjectPtr 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> { FORCEINLINE static const TWeakObjectPtr& GetSetKey(const FMutablePendingInstanceDiscard& PendingDiscard) { return PendingDiscard.CustomizableObjectInstance; } FORCEINLINE static bool Matches(const TWeakObjectPtr& A, const TWeakObjectPtr& B) { return A.HasSameIndexAndSerialNumber(B); } FORCEINLINE static uint32 GetKeyHash(const TWeakObjectPtr& 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 PendingInstanceUpdates; TSet PendingInstanceDiscards; TSet 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& Instance); #if WITH_EDITOR void RemoveUpdatesForObject(const UCustomizableObject* InObject); #endif const FMutablePendingInstanceUpdate* GetUpdate(const TWeakObjectPtr& Instance) const; TSet::TIterator GetUpdateIterator() { return PendingInstanceUpdates.CreateIterator(); } TSet::TIterator GetDiscardIterator() { return PendingInstanceDiscards.CreateIterator(); } TSet::TIterator GetIDsToReleaseIterator() { return PendingIDsToRelease.CreateIterator(); } void AddDiscard(const FMutablePendingInstanceDiscard& TaskToEnqueue); void AddIDRelease(mu::FInstance::FID 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 Object; TMap > Meshes; TMap > 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 Texture = nullptr; bool operator==(const FGeneratedTexture& Other) const = default; }; USTRUCT() struct FGeneratedMaterial { GENERATED_USTRUCT_BODY(); UPROPERTY(Category = NoCategory, VisibleAnywhere) TObjectPtr 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; #if WITH_EDITORONLY_DATA // Name of the component that contains this material UPROPERTY(Category = NoCategory, VisibleAnywhere) FName ComponentName; #endif bool operator==(const FGeneratedMaterial& Other) const { return SurfaceId == Other.SurfaceId && MaterialIndex == Other.MaterialIndex; }; }; /** Final data per component. */ struct FSkeletalMeshMorphTargets { /** Name of the Morph Target. */ TArray RealTimeMorphTargetNames; /** First index is the Morph Target (in sync with RealTimeMorphTargetNames). Second index is the LOD. */ TArray> RealTimeMorphsLODData; }; // 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; TSharedPtr Image; TWeakObjectPtr Cached; TArray ConstantImagesNeededToGenerate; 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 = TNumericLimits::Max(); TSharedPtr 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; }; struct FComponent { FCustomizableObjectComponentIndex Id = FCustomizableObjectComponentIndex(0); /** Range in the LODs array */ uint16 FirstLOD = 0; uint16 LODCount = 0; /** Overlay material. Only one per mesh, if any. */ int32 OverlayMaterial = INDEX_NONE; }; TArray Components; TArray LODs; TArray Surfaces; TArray Images; TArray Vectors; TArray Scalars; TArray ActiveBones; TArray BoneMaps; struct FBone { mu::FBoneName Name; FMatrix44f MatrixWithScale; bool operator==(const mu::FBoneName& OtherName) const { return Name == OtherName; }; }; /** Key is the Block Id. Value is the LoadAdditionalAssetsAndData read destination. */ TMap RealTimeMorphTargetMeshData; /** Key is the Component Name. Value is the final Morph Target data to copy into the Skeletal Mesh. */ TMap RealTimeMorphTargets; /** Key is the Block Id. Value is the LoadAdditionalAssetsAndData read destination. */ TMap ClothingMeshData; struct FSkeletonData { TArray SkeletonIds; TArray BonePose; TMap> BoneInfoMap; }; // Access by instance component index TArray SkeletonsPerInstanceComponent; struct FNamedExtensionData { TSharedPtr Data; FName Name; }; TArray ExtendedInputPins; /** */ void Clear() { LODs.Empty(); Components.Empty(); Surfaces.Empty(); Images.Empty(); Scalars.Empty(); Vectors.Empty(); RealTimeMorphTargets.Empty(); SkeletonsPerInstanceComponent.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); bool IsContextValid() const; void SetMinLOD(const TMap& MinLOD); /** Return an array of LODs per object component. */ const TMap& GetFirstRequestedLOD() const; void SetFirstRequestedLOD(const TMap& RequestedLODs); void SetQualitySettingMinLODs(const TMap& FirstLODs); 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 Instance; /** Customizable Object we are operating on. It can be destroyed between Game Thread tasks. */ TWeakObjectPtr Object; /** Return the object component index associated with a component in this instance. */ FCustomizableObjectComponentIndex GetObjectComponentIndex(FCustomizableObjectInstanceComponentIndex InstanceComponentIndex); 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. */ TSharedPtr Parameters; TSharedPtr MutableSystem; bool bOnlyUpdateIfNotGenerated = false; bool bIgnoreCloseDist = false; bool bForceHighPriority = false; FInstanceUpdateData InstanceUpdateData; TArray RelevantParametersInProgress; const FInstanceUpdateData::FComponent* GetComponentUpdateData(FCustomizableObjectInstanceComponentIndex InstanceComponentIndex); TArray 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; /** true if the Update has blocked Low Priority Tasks from launching. */ bool bLowPriorityTasksBlocked = false; /** The Context has been successfully created. */ bool bValid = 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::FInstance::FID InstanceID = 0; // Redundant TSharedPtr MutableInstance; TSharedPtr 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; /** List of component names. Index is the ObjectComponentIndex. */ TArray ComponentNames; /** Index of the resource in the StreamedResourceData array of the Model Resources. */ TArray StreamedResourceIndex; /** Index of the resource in the ExtensionStreamedResourceData array of the Model Resources. */ TArray ExtensionStreamedResourceIndex; TMap NumLODsAvailable; /** Copy of UModelResources::ComponentFirstLODAvailable. First compiled LOD per component for the running platform. Constant. */ TMap FirstLODAvailable; TMap FirstResidentLOD; TMap ImageToPlatformDataMap; EUpdateResult UpdateResult = EUpdateResult::Success; mu::FImageOperator::FImagePixelFormatFunc PixelFormatOverride; private: /** Mutable Meshes required for each component. Outermost index is the object component index, inner index is the LOD. */ TArray> MeshDescriptors; public: void InitMeshDescriptors(int32 Size); const TArray>& GetMeshDescriptors() const; TArray* GetMeshDescriptors(FCustomizableObjectComponentIndex Index); /** 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 MeshChangedPerInstanceComponent; 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> AttachedParentUpdated; /** Hard references to objects. Avoids GC to collect them. */ TArray> Objects; #if WITH_EDITORONLY_DATA TSharedPtr UpdateSettingsOverride = nullptr; #endif }; /** Runtime data used during a mutable instance update */ struct FMutableReleasePlatformOperationData { TMap ImageToPlatformDataMap; }; USTRUCT() struct FPendingReleaseSkeletalMeshInfo { GENERATED_USTRUCT_BODY() UPROPERTY() TObjectPtr SkeletalMesh = nullptr; UPROPERTY() double TimeStamp = 0.0f; }; #if WITH_EDITORONLY_DATA UENUM() enum class ECustomizableObjectDDCPolicy : uint8 { None = 0, Local, Default }; // Struct used to keep a copy of the EditorSettings needed to compile Customizable Objects. struct FEditorCompileSettings { // General case bool bIsMutableEnabled = true; // Auto Compile bool bEnableAutomaticCompilation = true; bool bCompileObjectsSynchronously = true; bool bCompileRootObjectsOnStartPIE = false; // DDC settings ECustomizableObjectDDCPolicy EditorDerivedDataCachePolicy = ECustomizableObjectDDCPolicy::Default; ECustomizableObjectDDCPolicy CookDerivedDataCachePolicy = ECustomizableObjectDDCPolicy::Default; }; #endif /** Private part, hidden from outside the plugin. * * UE STREAMING: * * [- NumLODsAvailable -----------------------------] = 8 (State and platform dependent) * [- Stripped --[- Packaged -----------------------] * 0 1 2 3 4 5 6 7 * |------|------|------|------|------|------|------| * [- Streaming --------[- Residents -] * ^ ^ * | | * FirstLODAvailable FirstResidentLOD * FirstRequestedLOD (LODs generated by Core) * * [- NumLODsToStream ----------------] = 5 (Compiled constant, ModelResources) * * * HACKY MUTABLE STREAMING: * * [- NumLODsAvailable -----------------------------] = 8 * [- Stripped --[- Packaged -----------------------] * 0 1 2 3 4 5 6 7 * |------|------|------|------|------|------|------| * [- Residents ----------------------] * [------] Data copied from FirstRequestedLODs. Hacky Mutable LOD Streaming. * ^ ^ * | | * | FirstRequestedLOD (LODs generated by Core) * | * FirstLODAvailable (Compilation constant) * FirstResidentLOD * MinLOD * * NumLODsToStream = 0 * * * DEFINITIONS: * * QualitySettingMinLODs = MinLOD based on the active quality settings. COI Descriptor. * MinLOD = From user. Artificial limit. Skeletal Mesh Component. * FirstLODAvailable = First available lod per platform. Skeletal Mesh Component. * FirstResidentLOD = First LOD generated with geometry. Skeletal Mesh Component. * FirstRequestedLOD = From user. Usually from USkeletalMeshComponent::GetPredictedLODLevel Skeletal Mesh Component. */ 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. TSharedPtr 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 Streamer; // 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 ResourceProvider; // Cache of weak references to generated resources to see if they can be reused. TArray 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 ProtectedObjectCachedImages; // The pending instance updates, discards or releases FMutablePendingInstanceWork MutablePendingInstanceWork; 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; // 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& 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::FInstance::FID 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 TextureReferenceCount; // Keeps a count of texture usage to decide if they have to be blocked from GC during an update UPROPERTY(Transient) TObjectPtr CurrentInstanceBeingUpdated = nullptr; TSharedPtr CurrentMutableOperation = nullptr; // Handle to the registered TickDelegate. FTSTicker::FDelegateHandle TickWarningsDelegateHandle; /** 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& 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(); #if WITH_EDITOR /** Add a CO to the pending - load list.COs need to wait until all related objects are fully loaded before being able * to do things like check-if-up-to-date or compile. */ void AddPendingLoad(UCustomizableObject*); #endif // Unprotect the resources in the instances of this object of being garbage collector // while an instance is being built or updated, so that they can be reused. void ClearResourceCacheProtected(); /** Mutable TaskGraph system (Mutable Thread). */ FMutableTaskGraph MutableTaskGraph; /** Last Mutable task from the previous update. The next update can not start until this task has has completed. */ UE::Tasks::FTask LastUpdateMutableTask = UE::Tasks::MakeCompletedTask(); #if WITH_EDITORONLY_DATA /** List of CustomizableObjects pending to complete loading. */ UPROPERTY(Transient) TArray> ObjectsPendingLoad; #endif FLogBenchmarkUtil LogBenchmarkUtil; int32 NumSkeletalMeshes = 0; bool bAutoCompileCommandletEnabled = false; UPROPERTY() TArray PendingReleaseSkeletalMesh; UPROPERTY(Transient) TObjectPtr DefaultInstanceLODManagement = nullptr; UPROPERTY(Transient) TObjectPtr 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> ProtectedCachedTextures; TSharedRef StreamableManager = MakeShared(); #if WITH_EDITOR // Copy of the Mutable Editor Settings tied to CO compilation. They are updated whenever changed FEditorCompileSettings EditorSettings; #endif #if WITH_EDITORONLY_DATA // Array to keep track of cached objects TArray UncompiledCustomizableObjectIds; /** Weak pointer to the Uncompiled Customizable Objects notification */ TWeakPtr UncompiledCustomizableObjectsNotificationPtr; /** Map used to cache per platform MaxChunkSize. If MaxChunkSize > 0, streamed data will be split in multiple files */ TMap PlatformMaxChunkSize; #endif int32 NumLODUpdatesLastTick = 0; /** Time when the "Started Update Skeletal Mesh Async" log will be unmuted (in seconds). */ float LogStartedUpdateUnmute = 0.0; /** Time of the last "Started Update Skeletal Mesh Async" log (in seconds). */ float LogStartedUpdateLast = 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& Operation); void FixLODs(const TSharedRef& Operation); void Subtask_Mutable_PrepareSkeletonData(const TSharedRef& OperationData); void Subtask_Mutable_UpdateParameterRelevancy(const TSharedRef& OperationData); void Subtask_Mutable_PrepareTextures(const TSharedRef& OperationData); } /** Set OnlyLOD to -1 to generate all mips */ CUSTOMIZABLEOBJECT_API FTexturePlatformData* MutableCreateImagePlatformData(TSharedPtr MutableImage, int32 OnlyLOD, uint16 FullSizeX, uint16 FullSizeY); /** Return true if Streaming is enabled for the given Object. */ bool IsStreamingEnabled(const UCustomizableObject& Object); template Type* ToObject(const FString& Parameter) { return Cast(FSoftObjectPath::ConstructFromStringPath(Parameter).TryLoad()); } template Type* ToObject(const FName& Parameter) { return ToObject(Parameter.ToString()); } #undef UE_API