// Copyright Epic Games, Inc. All Rights Reserved. #include "MuCO/CustomizableObjectInstance.h" #include "Algo/Find.h" #include "Algo/MaxElement.h" #include "Animation/AnimClassInterface.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Animation/AnimInstance.h" #include "Animation/Skeleton.h" #include "BoneControllers/AnimNode_RigidBody.h" #include "ClothConfig.h" #include "ClothingAsset.h" #include "MuCO/CustomizableObjectInstanceUsagePrivate.h" #include "Engine/SkeletalMesh.h" #include "Engine/SkeletalMeshSocket.h" #include "Engine/SkeletalMeshLODSettings.h" #include "MaterialDomain.h" #include "MutableStreamRequest.h" #include "Materials/Material.h" #include "Materials/MaterialInstanceDynamic.h" #include "Misc/Optional.h" #include "Modules/ModuleManager.h" #include "Tasks/Task.h" #include "MuCO/CustomizableObjectSystemPrivate.h" #include "MuCO/CustomizableObjectSkeletalMesh.h" #include "MuCO/CustomizableObjectInstancePrivate.h" #include "MuCO/CustomizableObjectExtension.h" #include "MuCO/CustomizableObjectMipDataProvider.h" #include "MuCO/CustomizableObjectPrivate.h" #include "MuCO/CustomizableObjectInstanceUsage.h" #include "MuCO/CustomizableObjectInstanceAssetUserData.h" #include "MuCO/ICustomizableObjectModule.h" #include "MuCO/UnrealConversionUtils.h" #include "MuCO/UnrealPortabilityHelpers.h" #include "MuCO/LogBenchmarkUtil.h" #include "PhysicsEngine/PhysicsAsset.h" #include "Rendering/Texture2DResource.h" #include "RenderingThread.h" #include "SkeletalMergingLibrary.h" #include "MuCO/ICustomizableObjectEditorModule.h" #include "UObject/UObjectIterator.h" #include "Widgets/Notifications/SNotificationList.h" #include "PhysicsEngine/AggregateGeom.h" #include "PhysicsEngine/PhysicsConstraintTemplate.h" #include "PhysicsEngine/SkeletalBodySetup.h" #include "Hash/CityHash.h" #include "MuCO/CustomizableObjectCustomVersion.h" #include "MuCO/CustomizableObjectResourceData.h" #include "MuCO/CustomizableObjectStreamedResourceData.h" #include "MuCO/CustomizableObjectResourceDataTypes.h" #include "MuCO/LoadUtils.h" #include "Serialization/BulkData.h" #include "HAL/PlatformFileManager.h" #include "MuCO/Plugins/IMutableClothingModule.h" #include "Rendering/SkeletalMeshModel.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(CustomizableObjectInstance) #if WITH_EDITOR #include "Logging/MessageLog.h" #include "MessageLogModule.h" #include "UnrealEdMisc.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Application/ThrottleManager.h" #endif namespace { #ifndef REQUIRES_SINGLEUSE_FLAG_FOR_RUNTIME_TEXTURES #define REQUIRES_SINGLEUSE_FLAG_FOR_RUNTIME_TEXTURES !PLATFORM_DESKTOP #endif bool bDisableClothingPhysicsEditsPropagation = false; static FAutoConsoleVariableRef CVarDisableClothingPhysicsEditsPropagation( TEXT("mutable.DisableClothingPhysicsEditsPropagation"), bDisableClothingPhysicsEditsPropagation, TEXT("If set to true, disables clothing physics edits propagation from the render mesh."), ECVF_Default); bool bDisableNotifyComponentsOfTextureUpdates = false; static FAutoConsoleVariableRef CVarDisableNotifyComponentsOfTextureUpdates( TEXT("mutable.DisableNotifyComponentsOfTextureUpdates"), bDisableNotifyComponentsOfTextureUpdates, TEXT("If set to true, disables Mutable notifying the streaming system that a component has had a change in at least one texture of its components."), ECVF_Default); } const FString MULTILAYER_PROJECTOR_PARAMETERS_INVALID = TEXT("Invalid Multilayer Projector Parameters."); const FString NUM_LAYERS_PARAMETER_POSTFIX = FString("_NumLayers"); const FString OPACITY_PARAMETER_POSTFIX = FString("_Opacity"); const FString IMAGE_PARAMETER_POSTFIX = FString("_SelectedImages"); const FString POSE_PARAMETER_POSTFIX = FString("_SelectedPoses"); // Struct used by BuildMaterials() to identify common materials between LODs struct FMutableMaterialPlaceholder { enum class EPlaceHolderParamType { Vector, Scalar, Texture }; struct FMutableMaterialPlaceHolderParam { FName ParamName; EPlaceHolderParamType Type; int32 LayerIndex; // Set to -1 for non-multilayer params float Scalar; FLinearColor Vector; FGeneratedTexture Texture; FMutableMaterialPlaceHolderParam(const FName& InParamName, const int32 InLayerIndex, const FLinearColor& InVector) : ParamName(InParamName), Type(EPlaceHolderParamType::Vector), LayerIndex(InLayerIndex), Vector(InVector) {} FMutableMaterialPlaceHolderParam(const FName& InParamName, const int32 InLayerIndex, const float InScalar) : ParamName(InParamName), Type(EPlaceHolderParamType::Scalar), LayerIndex(InLayerIndex), Scalar(InScalar) {} FMutableMaterialPlaceHolderParam(const FName& InParamName, const int32 InLayerIndex, const FGeneratedTexture& InTexture) : ParamName(InParamName), Type(EPlaceHolderParamType::Texture), LayerIndex(InLayerIndex), Texture(InTexture) {} bool operator<(const FMutableMaterialPlaceHolderParam& Other) const { return Type < Other.Type || ParamName.CompareIndexes(Other.ParamName); } bool operator==(const FMutableMaterialPlaceHolderParam& Other) const = default; }; uint32 ParentMaterialID = 0; int32 MatIndex = -1; private: mutable TArray Params; public: void AddParam(const FMutableMaterialPlaceHolderParam& NewParam) { Params.Add(NewParam); } const TArray& GetParams() const { return Params; } bool operator==(const FMutableMaterialPlaceholder& Other) const; friend uint32 GetTypeHash(const FMutableMaterialPlaceholder& PlaceHolder); }; bool FMutableMaterialPlaceholder::operator==(const FMutableMaterialPlaceholder& Other) const { return ParentMaterialID == Other.ParentMaterialID && Params == Other.Params; } // Return a hash of the material and its parameters uint32 GetTypeHash(const FMutableMaterialPlaceholder& PlaceHolder) { uint32 Hash = GetTypeHash(PlaceHolder.ParentMaterialID); // Sort parameters before building the hash. PlaceHolder.Params.Sort(); for (const FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam& Param : PlaceHolder.Params) { uint32 ParamHash = GetTypeHash(Param.ParamName); ParamHash = HashCombineFast(ParamHash, (uint32)Param.LayerIndex); ParamHash = HashCombineFast(ParamHash, (uint32)Param.Type); switch (Param.Type) { case FMutableMaterialPlaceholder::EPlaceHolderParamType::Vector: ParamHash = HashCombineFast(ParamHash, GetTypeHash(Param.Vector)); break; case FMutableMaterialPlaceholder::EPlaceHolderParamType::Scalar: ParamHash = HashCombineFast(ParamHash, GetTypeHash(Param.Scalar)); break; case FMutableMaterialPlaceholder::EPlaceHolderParamType::Texture: ParamHash = HashCombineFast(ParamHash, Param.Texture.Texture->GetUniqueID()); break; } Hash = HashCombineFast(Hash, ParamHash); } return Hash; } UTexture2D* UCustomizableInstancePrivate::CreateTexture(const FString& TextureName) { UTexture2D* NewTexture = NewObject( GetTransientPackage(), GetData(TextureName), RF_Transient ); UCustomizableObjectSystem::GetInstance()->GetPrivate()->LogBenchmarkUtil.AddTexture(*NewTexture); NewTexture->SetPlatformData( nullptr ); return NewTexture; } void UCustomizableInstancePrivate::SetLastMeshId(FCustomizableObjectComponentIndex ObjectComponentIndex, int32 LODIndex, mu::FResourceID MeshId) { FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); if (ComponentData && ComponentData->LastMeshIdPerLOD.IsValidIndex(LODIndex)) { ComponentData->LastMeshIdPerLOD[LODIndex] = MeshId; } else { check(false); } } void UCustomizableInstancePrivate::InvalidateGeneratedData() { SkeletalMeshStatus = ESkeletalMeshStatus::NotGenerated; SkeletalMeshes.Reset(); CommittedDescriptor = {}; CommittedDescriptorHash = {}; // Init Component Data FCustomizableInstanceComponentData TemplateComponentData; TemplateComponentData.LastMeshIdPerLOD.Init(MAX_uint64, MAX_MESH_LOD_COUNT); ComponentsData.Init(TemplateComponentData, ComponentsData.Num()); GeneratedMaterials.Empty(); } void UCustomizableInstancePrivate::InitCustomizableObjectData(const UCustomizableObject* InCustomizableObject) { InvalidateGeneratedData(); if (!InCustomizableObject || !InCustomizableObject->IsCompiled()) { return; } // Init Component Data FCustomizableInstanceComponentData TemplateComponentData; TemplateComponentData.LastMeshIdPerLOD.Init(MAX_uint64, MAX_MESH_LOD_COUNT); ComponentsData.Init(TemplateComponentData, InCustomizableObject->GetComponentCount()); ExtensionInstanceData.Empty(); } FCustomizableInstanceComponentData* UCustomizableInstancePrivate::GetComponentData(const FName& ComponentName) { UCustomizableObject* Object = GetPublic()->GetCustomizableObject(); if (!Object || !Object->IsCompiled()) { return nullptr; } const int32 ObjectComponentIndex = Object->GetPrivate()->GetModelResourcesChecked().ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName); if (ObjectComponentIndex == INDEX_NONE) { return nullptr; } if (!ComponentsData.IsValidIndex(ObjectComponentIndex)) { return nullptr; } return &ComponentsData[ObjectComponentIndex]; } FCustomizableInstanceComponentData* UCustomizableInstancePrivate::GetComponentData(FCustomizableObjectComponentIndex ObjectComponentIndex) { return ComponentsData.IsValidIndex(ObjectComponentIndex.GetValue()) ? &ComponentsData[ObjectComponentIndex.GetValue()] : nullptr; } const FCustomizableInstanceComponentData* UCustomizableInstancePrivate::GetComponentData(FCustomizableObjectComponentIndex ObjectComponentIndex) const { return ComponentsData.IsValidIndex(ObjectComponentIndex.GetValue()) ? &ComponentsData[ObjectComponentIndex.GetValue()] : nullptr; } UCustomizableObjectInstance::UCustomizableObjectInstance() { SetFlags(RF_Transactional); } void UCustomizableInstancePrivate::SetDescriptor(const FCustomizableObjectInstanceDescriptor& InDescriptor) { UCustomizableObject* InCustomizableObject = InDescriptor.GetCustomizableObject(); const bool bCustomizableObjectChanged = GetPublic()->Descriptor.GetCustomizableObject() != InCustomizableObject; #if WITH_EDITOR // Bind a lambda to the PostCompileDelegate and unbind from the previous object if any. BindObjectDelegates(GetPublic()->GetCustomizableObject(), InCustomizableObject); #endif GetPublic()->Descriptor = InDescriptor; if (bCustomizableObjectChanged) { InitCustomizableObjectData(InCustomizableObject); } } void UCustomizableInstancePrivate::PrepareForUpdate(const TSharedRef& OperationData) { // Clear the ComponentData from previous updates for (FCustomizableInstanceComponentData& ComponentData : ComponentsData) { ComponentData.AnimSlotToBP.Empty(); ComponentData.AssetUserDataArray.Empty(); ComponentData.Skeletons.Skeleton = nullptr; ComponentData.Skeletons.SkeletonIds.Empty(); ComponentData.Skeletons.SkeletonsToMerge.Empty(); ComponentData.PhysicsAssets.PhysicsAssetToLoad.Empty(); ComponentData.PhysicsAssets.PhysicsAssetsToMerge.Empty(); ComponentData.ClothingPhysicsAssetsToStream.Empty(); ComponentData.StreamedResourceIndex.Empty(); ComponentData.OverlayMaterial = nullptr; #if WITH_EDITORONLY_DATA ComponentData.MeshPartPaths.Empty(); #endif } } #if WITH_EDITOR void UCustomizableInstancePrivate::PostDuplicate(bool bDuplicateForPIE) { Super::PostDuplicate(bDuplicateForPIE); // Invalidate all generated data to avoid modifying resources shared between CO instances. InvalidateGeneratedData(); // Empty after duplicating or ReleasingMutableResources may free textures used by the other CO instance. GeneratedTextures.Empty(); } void UCustomizableInstancePrivate::OnPostCompile() { GetDescriptor().ReloadParameters(); InitCustomizableObjectData(GetPublic()->GetCustomizableObject()); } void UCustomizableInstancePrivate::OnObjectStatusChanged(FCustomizableObjectStatus::EState Previous, FCustomizableObjectStatus::EState Next) { if (Previous != Next && Next == FCustomizableObjectStatus::EState::ModelLoaded) { OnPostCompile(); } } void UCustomizableInstancePrivate::BindObjectDelegates(UCustomizableObject* CurrentCustomizableObject, UCustomizableObject* NewCustomizableObject) { if (CurrentCustomizableObject == NewCustomizableObject) { return; } // Unbind callback from the previous CO if (CurrentCustomizableObject) { CurrentCustomizableObject->GetPrivate()->Status.GetOnStateChangedDelegate().RemoveAll(this); } // Bind callback to the new CO if (NewCustomizableObject) { NewCustomizableObject->GetPrivate()->Status.GetOnStateChangedDelegate().AddUObject(this, &UCustomizableInstancePrivate::OnObjectStatusChanged); } } bool UCustomizableObjectInstance::CanEditChange(const FProperty* InProperty) const { bool bIsMutable = Super::CanEditChange(InProperty); if (bIsMutable && InProperty != NULL) { if (InProperty->GetFName() == TEXT("CustomizationObject")) { bIsMutable = false; } if (InProperty->GetFName() == TEXT("ParameterName")) { bIsMutable = false; } } return bIsMutable; } void UCustomizableObjectInstance::PostTransacted(const FTransactionObjectEvent& TransactionEvent) { Super::PostTransacted(TransactionEvent); GetPrivate()->OnInstanceTransactedDelegate.Broadcast(TransactionEvent); } #endif // WITH_EDITOR bool UCustomizableObjectInstance::IsEditorOnly() const { if (UCustomizableObject* CustomizableObject = GetCustomizableObject()) { return CustomizableObject->IsEditorOnly(); } return false; } void UCustomizableObjectInstance::PostInitProperties() { UObject::PostInitProperties(); if (!HasAllFlags(RF_ClassDefaultObject)) { if (!PrivateData) { PrivateData = NewObject(this, FName("Private")); } else if (PrivateData->GetOuter() != this) { PrivateData = Cast(StaticDuplicateObject(PrivateData, this, FName("Private"))); } } } void UCustomizableObjectInstance::BeginDestroy() { // Release the Live Instance ID if there it hadn't been released before DestroyLiveUpdateInstance(); if (PrivateData) { #if WITH_EDITOR // Unbind Object delegates PrivateData->BindObjectDelegates(GetCustomizableObject(), nullptr); #endif PrivateData.Get()->ReleaseMutableResources(true, *this); } Super::BeginDestroy(); } void UCustomizableObjectInstance::DestroyLiveUpdateInstance() { if (PrivateData && PrivateData->LiveUpdateModeInstanceID) { // If UCustomizableObjectSystemPrivate::SSystem is nullptr it means it has already been destroyed, no point in registering an instanceID release // since the Mutable system has already been destroyed. Just checking UCustomizableObjectSystem::GetInstance() will try to recreate the system when // everything is shutting down, so it's better to check UCustomizableObjectSystemPrivate::SSystem first here if (UCustomizableObjectSystemPrivate::SSystem && UCustomizableObjectSystem::GetInstance() && UCustomizableObjectSystem::GetInstance()->GetPrivate()) { UCustomizableObjectSystem::GetInstance()->GetPrivate()->InitInstanceIDRelease(PrivateData->LiveUpdateModeInstanceID); PrivateData->LiveUpdateModeInstanceID = 0; } } } void UCustomizableInstancePrivate::ReleaseMutableResources(bool bCalledFromBeginDestroy, const UCustomizableObjectInstance& Instance) { GeneratedMaterials.Empty(); if (UCustomizableObjectSystem::IsCreated()) // Need to check this because the object might be destroyed after the CustomizableObjectSystem at shutdown { UCustomizableObjectSystemPrivate* CustomizableObjectSystem = UCustomizableObjectSystem::GetInstance()->GetPrivate(); // Get the cache of resources of all live instances of this object FMutableResourceCache& Cache = CustomizableObjectSystem->GetObjectCache(Instance.GetCustomizableObject()); for (FGeneratedTexture& Texture : GeneratedTextures) { if (CustomizableObjectSystem->RemoveTextureReference(Texture.Key)) { // Do not release textures when called from BeginDestroy, it would produce a texture artifact in the // instance's remaining sk meshes and GC is being performed anyway so it will free the textures if needed if (!bCalledFromBeginDestroy && CustomizableObjectSystem->bReleaseTexturesImmediately) { ReleaseMutableTexture(Texture.Key, Cast(Texture.Texture), Cache); } } } } GeneratedTextures.Empty(); } bool UCustomizableObjectInstance::IsReadyForFinishDestroy() { //return ReleaseResourcesFence.IsFenceComplete(); return true; } void UCustomizableObjectInstance::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FCustomizableObjectCustomVersion::GUID); const int32 CustomizableObjectCustomVersion = GetLinkerCustomVersion(FCustomizableObjectCustomVersion::GUID); if (CustomizableObjectCustomVersion < FCustomizableObjectCustomVersion::GroupProjectorIntToScalarIndex) { TArray IntParametersToMove; // Find the num layer parameters that were int enums for (int32 i = 0; i < IntParameters_DEPRECATED.Num(); ++i) { if (IntParameters_DEPRECATED[i].ParameterName.EndsWith(NUM_LAYERS_PARAMETER_POSTFIX, ESearchCase::CaseSensitive)) { FString ParameterNamePrefix, Aux; const bool bSplit = IntParameters_DEPRECATED[i].ParameterName.Split(NUM_LAYERS_PARAMETER_POSTFIX, &ParameterNamePrefix, &Aux); check(bSplit); // Confirm this is actually a multilayer param by finding the corresponding pose param for (int32 j = 0; j < IntParameters_DEPRECATED.Num(); ++j) { if (i != j) { if (IntParameters_DEPRECATED[j].ParameterName.StartsWith(ParameterNamePrefix, ESearchCase::CaseSensitive) && IntParameters_DEPRECATED[j].ParameterName.EndsWith(POSE_PARAMETER_POSTFIX, ESearchCase::CaseSensitive)) { IntParametersToMove.Add(i); break; } } } } } // Convert them to float params for (int32 i = 0; i < IntParametersToMove.Num(); ++i) { FloatParameters_DEPRECATED.AddDefaulted(); FloatParameters_DEPRECATED.Last().ParameterName = IntParameters_DEPRECATED[IntParametersToMove[i]].ParameterName; FloatParameters_DEPRECATED.Last().ParameterValue = FCString::Atoi(*IntParameters_DEPRECATED[IntParametersToMove[i]].ParameterValueName); FloatParameters_DEPRECATED.Last().Id = IntParameters_DEPRECATED[IntParametersToMove[i]].Id; } // Remove them from the int params in reverse order for (int32 i = IntParametersToMove.Num() - 1; i >= 0; --i) { IntParameters_DEPRECATED.RemoveAt(IntParametersToMove[i]); } } if (CustomizableObjectCustomVersion < FCustomizableObjectCustomVersion::CustomizableObjectInstanceDescriptor) { Descriptor.CustomizableObject = CustomizableObject_DEPRECATED; Descriptor.BoolParameters = BoolParameters_DEPRECATED; Descriptor.IntParameters = IntParameters_DEPRECATED; Descriptor.FloatParameters = FloatParameters_DEPRECATED; Descriptor.TextureParameters = TextureParameters_DEPRECATED; Descriptor.VectorParameters = VectorParameters_DEPRECATED; Descriptor.ProjectorParameters = ProjectorParameters_DEPRECATED; } } void UCustomizableObjectInstance::PostLoad() { Super::PostLoad(); #if WITH_EDITOR PrivateData->BindObjectDelegates(nullptr, GetCustomizableObject()); #endif // Skip the cost of ReloadParameters in the cook commandlet; it will be reloaded during PreSave. For cooked runtime // and editor UI, reload on load because it will not otherwise reload unless the CustomizableObject recompiles. Descriptor.ReloadParameters(); PrivateData->InitCustomizableObjectData(GetCustomizableObject()); } FString UCustomizableObjectInstance::GetDesc() { FString ObjectName = "Missing Object"; if (UCustomizableObject* CustomizableObject = GetCustomizableObject()) { ObjectName = CustomizableObject->GetName(); } return FString::Printf(TEXT("Instance of [%s]"), *ObjectName); } int32 UCustomizableObjectInstance::GetProjectorValueRange(const FString& ParamName) const { return Descriptor.GetProjectorValueRange(ParamName); } int32 UCustomizableObjectInstance::GetIntValueRange(const FString& ParamName) const { return Descriptor.GetIntValueRange(ParamName); } int32 UCustomizableObjectInstance::GetFloatValueRange(const FString& ParamName) const { return Descriptor.GetFloatValueRange(ParamName); } int32 UCustomizableObjectInstance::GetTextureValueRange(const FString& ParamName) const { return Descriptor.GetTextureValueRange(ParamName); } // Only safe to call if the Mutable texture ref count system returns 0 and absolutely sure nobody holds a reference to the texture void UCustomizableInstancePrivate::ReleaseMutableTexture(const FMutableImageCacheKey& MutableTextureKey, UTexture2D* Texture, FMutableResourceCache& Cache) { if (ensure(Texture) && Texture->IsValidLowLevel()) { Texture->ConditionalBeginDestroy(); for (FTexture2DMipMap& Mip : Texture->GetPlatformData()->Mips) { Mip.BulkData.RemoveBulkData(); } } // Must remove texture from cache since it has been released Cache.Images.Remove(MutableTextureKey); } void UCustomizableObjectInstance::SetObject(UCustomizableObject* InObject) { #if WITH_EDITOR // Bind a lambda to the PostCompileDelegate and unbind from the previous object if any. PrivateData->BindObjectDelegates(GetCustomizableObject(), InObject); #endif Descriptor.SetCustomizableObject(InObject); PrivateData->InitCustomizableObjectData(InObject); } UCustomizableObject* UCustomizableObjectInstance::GetCustomizableObject() const { return Descriptor.CustomizableObject; } bool UCustomizableObjectInstance::GetBuildParameterRelevancy() const { return Descriptor.GetBuildParameterRelevancy(); } void UCustomizableObjectInstance::SetBuildParameterRelevancy(bool Value) { Descriptor.SetBuildParameterRelevancy(Value); } int32 UCustomizableInstancePrivate::GetState() const { return GetPublic()->Descriptor.GetState(); } void UCustomizableInstancePrivate::SetState(const int32 InState) { const int32 OldState = GetState(); GetPublic()->Descriptor.SetState(InState); if (OldState != InState) { // State may change texture properties, so invalidate the texture reuse cache TextureReuseCache.Empty(); } } FString UCustomizableObjectInstance::GetCurrentState() const { return Descriptor.GetCurrentState(); } void UCustomizableObjectInstance::SetCurrentState(const FString& StateName) { Descriptor.SetCurrentState(StateName); } bool UCustomizableObjectInstance::IsParameterRelevant(int32 ParameterIndex) const { // This should have been precalculated in the last update if the appropriate flag in the instance was set. return GetPrivate()->RelevantParameters.Contains(ParameterIndex); } bool UCustomizableObjectInstance::IsParameterRelevant(const FString& ParamName) const { UCustomizableObject* CustomizableObject = GetCustomizableObject(); if (!CustomizableObject) { return false; } // This should have been precalculated in the last update if the appropriate flag in the instance was set. int32 ParameterIndexInObject = CustomizableObject->GetPrivate()->FindParameter(ParamName); return GetPrivate()->RelevantParameters.Contains(ParameterIndexInObject); } bool UCustomizableObjectInstance::IsParameterDirty(const FString& ParamName, const int32 RangeIndex) const { switch (Descriptor.CustomizableObject->GetParameterTypeByName(ParamName)) { case EMutableParameterType::None: return false; case EMutableParameterType::Projector: { const FCustomizableObjectProjectorParameterValue* Result = Descriptor.ProjectorParameters.FindByPredicate([&](const FCustomizableObjectProjectorParameterValue& Value) { return Value.ParameterName == ParamName; }); const FCustomizableObjectProjectorParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.ProjectorParameters.FindByPredicate([&](const FCustomizableObjectProjectorParameterValue& Value) { return Value.ParameterName == ParamName; }); if (Result && ResultCommited) { if (RangeIndex == INDEX_NONE) { return Result->Value == ResultCommited->Value; } else { if (Result->RangeValues.IsValidIndex(RangeIndex) && ResultCommited->RangeValues.IsValidIndex(RangeIndex)) { return Result->RangeValues[RangeIndex] == ResultCommited->RangeValues[RangeIndex]; } else { return Result->RangeValues.Num() != ResultCommited->RangeValues.Num(); } } } else { return Result != ResultCommited; } } case EMutableParameterType::Texture: { const FCustomizableObjectTextureParameterValue* Result = Descriptor.TextureParameters.FindByPredicate([&](const FCustomizableObjectTextureParameterValue& Value) { return Value.ParameterName == ParamName; }); const FCustomizableObjectTextureParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.TextureParameters.FindByPredicate([&](const FCustomizableObjectTextureParameterValue& Value) { return Value.ParameterName == ParamName; }); if (Result && ResultCommited) { if (RangeIndex == INDEX_NONE) { return Result->ParameterValue == ResultCommited->ParameterValue; } else { if (Result->ParameterRangeValues.IsValidIndex(RangeIndex) && ResultCommited->ParameterRangeValues.IsValidIndex(RangeIndex)) { return Result->ParameterRangeValues[RangeIndex] == ResultCommited->ParameterRangeValues[RangeIndex]; } else { return Result->ParameterRangeValues.Num() != ResultCommited->ParameterRangeValues.Num(); } } } else { return Result != ResultCommited; } } case EMutableParameterType::Bool: { const FCustomizableObjectBoolParameterValue* Result = Descriptor.BoolParameters.FindByPredicate([&](const FCustomizableObjectBoolParameterValue& Value) { return Value.ParameterName == ParamName; }); const FCustomizableObjectBoolParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.BoolParameters.FindByPredicate([&](const FCustomizableObjectBoolParameterValue& Value) { return Value.ParameterName == ParamName; }); if (Result && ResultCommited) { if (RangeIndex == INDEX_NONE) { return Result->ParameterValue == ResultCommited->ParameterValue; } else { return false; } } else { return Result != ResultCommited; } } case EMutableParameterType::Int: { const FCustomizableObjectIntParameterValue* Result = Descriptor.IntParameters.FindByPredicate([&](const FCustomizableObjectIntParameterValue& Value) { return Value.ParameterName == ParamName; }); const FCustomizableObjectIntParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.IntParameters.FindByPredicate([&](const FCustomizableObjectIntParameterValue& Value) { return Value.ParameterName == ParamName; }); if (Result && ResultCommited) { if (RangeIndex == INDEX_NONE) { return Result->ParameterValueName == ResultCommited->ParameterValueName; } else { if (Result->ParameterRangeValueNames.IsValidIndex(RangeIndex) && ResultCommited->ParameterRangeValueNames.IsValidIndex(RangeIndex)) { return Result->ParameterRangeValueNames[RangeIndex] == ResultCommited->ParameterRangeValueNames[RangeIndex]; } else { return Result->ParameterRangeValueNames.Num() != ResultCommited->ParameterRangeValueNames.Num(); } } } else { return Result != ResultCommited; } } case EMutableParameterType::Float: { const FCustomizableObjectFloatParameterValue* Result = Descriptor.FloatParameters.FindByPredicate([&](const FCustomizableObjectFloatParameterValue& Value) { return Value.ParameterName == ParamName; }); const FCustomizableObjectFloatParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.FloatParameters.FindByPredicate([&](const FCustomizableObjectFloatParameterValue& Value) { return Value.ParameterName == ParamName; }); if (Result && ResultCommited) { if (RangeIndex == INDEX_NONE) { return Result->ParameterValue == ResultCommited->ParameterValue; } else { if (Result->ParameterRangeValues.IsValidIndex(RangeIndex) && ResultCommited->ParameterRangeValues.IsValidIndex(RangeIndex)) { return Result->ParameterRangeValues[RangeIndex] == ResultCommited->ParameterRangeValues[RangeIndex]; } else { return Result->ParameterRangeValues.Num() != ResultCommited->ParameterRangeValues.Num(); } } } else { return Result != ResultCommited; } } case EMutableParameterType::Color: { const FCustomizableObjectVectorParameterValue* Result = Descriptor.VectorParameters.FindByPredicate([&](const FCustomizableObjectVectorParameterValue& Value) { return Value.ParameterName == ParamName; }); const FCustomizableObjectVectorParameterValue* ResultCommited = GetPrivate()->CommittedDescriptor.VectorParameters.FindByPredicate([&](const FCustomizableObjectVectorParameterValue& Value) { return Value.ParameterName == ParamName; }); if (Result && ResultCommited) { if (RangeIndex == INDEX_NONE) { return Result->ParameterValue == ResultCommited->ParameterValue; } else { return false; } } else { return Result != ResultCommited; } } default: unimplemented(); return false; } } void UCustomizableInstancePrivate::PostEditChangePropertyWithoutEditor() { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::PostEditChangePropertyWithoutEditor); for (TTuple>& Tuple : SkeletalMeshes) { USkeletalMesh* SkeletalMesh = Tuple.Get<1>(); if (SkeletalMesh && SkeletalMesh->GetResourceForRendering() && !SkeletalMesh->GetResourceForRendering()->IsInitialized()) { MUTABLE_CPUPROFILER_SCOPE(InitResources); // reinitialize resources SkeletalMesh->InitResources(); } } } bool UCustomizableInstancePrivate::CanUpdateInstance() const { UCustomizableObject* Object = GetPublic()->GetCustomizableObject(); if (!Object) { return false; } #if WITH_EDITOR if (Object->GetPrivate()->IsLocked()) { return false; } if (!Object->IsCompiled()) { return false; } return true; #else return Object->IsCompiled(); #endif } void UCustomizableObjectInstance::UpdateSkeletalMeshAsync(bool bIgnoreCloseDist, bool bForceHighPriority) { UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate(); const TSharedRef Context = MakeShared(*this); Context->bIgnoreCloseDist = bIgnoreCloseDist; Context->bForceHighPriority = bForceHighPriority; SystemPrivate->EnqueueUpdateSkeletalMesh(Context); } void UCustomizableObjectInstance::UpdateSkeletalMeshAsyncResult(FInstanceUpdateDelegate Callback, bool bIgnoreCloseDist, bool bForceHighPriority) { UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate(); const TSharedRef Context = MakeShared(*this); Context->bIgnoreCloseDist = bIgnoreCloseDist; Context->bForceHighPriority = bForceHighPriority; Context->UpdateCallback = Callback; SystemPrivate->EnqueueUpdateSkeletalMesh(Context); } void UCustomizableObjectInstance::UpdateSkeletalMeshAsyncResult(FInstanceUpdateNativeDelegate Callback, bool bIgnoreCloseDist, bool bForceHighPriority) { UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate(); const TSharedRef Context = MakeShared(*this); Context->bIgnoreCloseDist = bIgnoreCloseDist; Context->bForceHighPriority = bForceHighPriority; Context->UpdateNativeCallback = Callback; SystemPrivate->EnqueueUpdateSkeletalMesh(Context); } void UCustomizableInstancePrivate::TickUpdateCloseCustomizableObjects(UCustomizableObjectInstance& Public, FMutableInstanceUpdateMap& InOutRequestedUpdates) { UCustomizableObject* Object = Public.GetCustomizableObject(); if (!Object) { return; } #if WITH_EDITOR if (!Object->IsCompiled() && Object->GetPrivate()->CompilationResult != ECompilationResultPrivate::Errors) // Avoid constantly retry failed compilations. { if (ICustomizableObjectEditorModule* EditorModule = ICustomizableObjectEditorModule::Get()) { EditorModule->CompileCustomizableObject(*Object, nullptr, true, false); } } #endif if (!CanUpdateInstance()) { return; } const UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate(); const EUpdateRequired UpdateRequired = SystemPrivate->IsUpdateRequired(Public, true, true, false); if (UpdateRequired != EUpdateRequired::NoUpdate) // Since this is done in the tick, avoid starting an update that we know for sure that would not be performed. Once started it has some performance implications that we want to avoid. { if (UpdateRequired == EUpdateRequired::Discard) { UCustomizableObjectSystem::GetInstance()->GetPrivate()->InitDiscardResourcesSkeletalMesh(&Public); InOutRequestedUpdates.Remove(&Public); } else if (UpdateRequired == EUpdateRequired::Update) { const EQueuePriorityType Priority = SystemPrivate->GetUpdatePriority(Public, false); FMutableUpdateCandidate* UpdateCandidate = InOutRequestedUpdates.Find(&Public); if (UpdateCandidate) { ensure(HasCOInstanceFlags(PendingLODsUpdate | PendingLODsDowngrade)); UpdateCandidate->Priority = Priority; UpdateCandidate->Issue(); } else { FMutableUpdateCandidate Candidate(&Public); Candidate.Priority = Priority; Candidate.Issue(); InOutRequestedUpdates.Add(&Public, Candidate); } } else { check(false); } } else { InOutRequestedUpdates.Remove(&Public); } ClearCOInstanceFlags(PendingLODsUpdate | PendingLODsDowngrade); } void UCustomizableInstancePrivate::UpdateInstanceIfNotGenerated(UCustomizableObjectInstance& Public, FMutableInstanceUpdateMap& InOutRequestedUpdates) { if (SkeletalMeshStatus != ESkeletalMeshStatus::NotGenerated) { return; } if (!CanUpdateInstance()) { return; } UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate(); const TSharedRef Context = MakeShared(Public); Context->bOnlyUpdateIfNotGenerated = true; SystemPrivate->EnqueueUpdateSkeletalMesh(Context); EQueuePriorityType Priority = SystemPrivate->GetUpdatePriority(Public, false); FMutableUpdateCandidate* UpdateCandidate = InOutRequestedUpdates.Find(&Public); if (UpdateCandidate) { UpdateCandidate->Priority = Priority; UpdateCandidate->Issue(); } else { FMutableUpdateCandidate Candidate(&Public); Candidate.Priority = Priority; Candidate.Issue(); InOutRequestedUpdates.Add(&Public, Candidate); } } #if !UE_BUILD_SHIPPING bool AreSkeletonsCompatible(const TArray>& InSkeletons) { MUTABLE_CPUPROFILER_SCOPE(AreSkeletonsCompatible); if (InSkeletons.IsEmpty()) { return true; } bool bCompatible = true; struct FBoneToMergeInfo { FBoneToMergeInfo(const uint32 InBonePathHash, const uint32 InSkeletonIndex, const uint32 InParentBoneSkeletonIndex) : BonePathHash(InBonePathHash), SkeletonIndex(InSkeletonIndex), ParentBoneSkeletonIndex(InParentBoneSkeletonIndex) {} uint32 BonePathHash = 0; uint32 SkeletonIndex = 0; uint32 ParentBoneSkeletonIndex = 0; }; // Accumulated hierarchy hash from parent-bone to root bone TMap BoneNamesToBoneInfo; BoneNamesToBoneInfo.Reserve(InSkeletons[0] ? InSkeletons[0]->GetReferenceSkeleton().GetNum() : 0); for (int32 SkeletonIndex = 0; SkeletonIndex < InSkeletons.Num(); ++SkeletonIndex) { const TObjectPtr Skeleton = InSkeletons[SkeletonIndex]; check(Skeleton); const FReferenceSkeleton& ReferenceSkeleton = Skeleton->GetReferenceSkeleton(); const TArray& Bones = ReferenceSkeleton.GetRawRefBoneInfo(); const TArray& BonePoses = ReferenceSkeleton.GetRawRefBonePose(); const int32 NumBones = Bones.Num(); for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { const FMeshBoneInfo& Bone = Bones[BoneIndex]; // Retrieve parent bone name and respective hash, root-bone is assumed to have a parent hash of 0 const FName ParentName = Bone.ParentIndex != INDEX_NONE ? Bones[Bone.ParentIndex].Name : NAME_None; const uint32 ParentHash = Bone.ParentIndex != INDEX_NONE ? GetTypeHash(ParentName) : 0; // Look-up the path-hash from root to the parent bone const FBoneToMergeInfo* ParentBoneInfo = BoneNamesToBoneInfo.Find(ParentName); const uint32 ParentBonePathHash = ParentBoneInfo ? ParentBoneInfo->BonePathHash : 0; const uint32 ParentBoneSkeletonIndex = ParentBoneInfo ? ParentBoneInfo->SkeletonIndex : 0; // Append parent hash to path to give full path hash to current bone const uint32 BonePathHash = HashCombine(ParentBonePathHash, ParentHash); // Check if the bone exists in the hierarchy const FBoneToMergeInfo* ExistingBoneInfo = BoneNamesToBoneInfo.Find(Bone.Name); // If the hash differs from the existing one it means skeletons are incompatible if (!ExistingBoneInfo) { // Add path hash to current bone BoneNamesToBoneInfo.Add(Bone.Name, FBoneToMergeInfo(BonePathHash, SkeletonIndex, ParentBoneSkeletonIndex)); } else if (ExistingBoneInfo->BonePathHash != BonePathHash) { if (bCompatible) { // Print the skeletons to merge FString Msg = TEXT("Failed to merge skeletons. Skeletons to merge: "); for (int32 AuxSkeletonIndex = 0; AuxSkeletonIndex < InSkeletons.Num(); ++AuxSkeletonIndex) { if (InSkeletons[AuxSkeletonIndex] != nullptr) { Msg += FString::Printf(TEXT("\n\t- %s"), *InSkeletons[AuxSkeletonIndex].GetName()); } } UE_LOG(LogMutable, Error, TEXT("%s"), *Msg); #if WITH_EDITOR FNotificationInfo Info(FText::FromString(TEXT("Mutable: Failed to merge skeletons. Invalid parent chain detected. Please check the output log for more information."))); Info.bFireAndForget = true; Info.FadeOutDuration = 1.0f; Info.ExpireDuration = 10.0f; FSlateNotificationManager::Get().AddNotification(Info); #endif bCompatible = false; } // Print the first non compatible bone in the bone chain, since all child bones will be incompatible too. if (ExistingBoneInfo->ParentBoneSkeletonIndex != SkeletonIndex) { // Different skeletons can't be used if they are incompatible with the reference skeleton. UE_LOG(LogMutable, Error, TEXT("[%s] parent bone is different in skeletons [%s] and [%s]."), *Bone.Name.ToString(), *InSkeletons[SkeletonIndex]->GetName(), *InSkeletons[ExistingBoneInfo->ParentBoneSkeletonIndex]->GetName()); } } } } return bCompatible; } #endif USkeleton* UCustomizableInstancePrivate::MergeSkeletons(UCustomizableObject& CustomizableObject, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, FCustomizableObjectComponentIndex ObjectComponentIndex, bool& bOutCreatedNewSkeleton) { MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_MergeSkeletons); bOutCreatedNewSkeleton = false; FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); check(ComponentData); FReferencedSkeletons& ReferencedSkeletons = ComponentData->Skeletons; // Merged skeleton found in the cache if (ReferencedSkeletons.Skeleton) { USkeleton* MergedSkeleton = ReferencedSkeletons.Skeleton; ReferencedSkeletons.Skeleton = nullptr; return MergedSkeleton; } // No need to merge skeletons if(ReferencedSkeletons.SkeletonsToMerge.Num() == 1) { const TObjectPtr RefSkeleton = ReferencedSkeletons.SkeletonsToMerge[0]; ReferencedSkeletons.SkeletonIds.Empty(); ReferencedSkeletons.SkeletonsToMerge.Empty(); return RefSkeleton; } #if !UE_BUILD_SHIPPING // Test Skeleton compatibility before attempting the merge to avoid a crash. if (!AreSkeletonsCompatible(ReferencedSkeletons.SkeletonsToMerge)) { return nullptr; } #endif FSkeletonMergeParams Params; Params.SkeletonsToMerge = ReferencedSkeletons.SkeletonsToMerge; USkeleton* FinalSkeleton = USkeletalMergingLibrary::MergeSkeletons(Params); if (!FinalSkeleton) { FString Msg = FString::Printf(TEXT("MergeSkeletons failed for Customizable Object [%s], Instance [%s]. Skeletons involved: "), *CustomizableObject.GetName(), *GetOuter()->GetName()); const int32 SkeletonCount = Params.SkeletonsToMerge.Num(); for (int32 SkeletonIndex = 0; SkeletonIndex < SkeletonCount; ++SkeletonIndex) { Msg += FString::Printf(TEXT(" [%s]"), *Params.SkeletonsToMerge[SkeletonIndex]->GetName()); } UE_LOG(LogMutable, Error, TEXT("%s"), *Msg); } else { #if WITH_EDITOR uint32 CombinedSkeletonHash = INDEX_NONE; #endif // Make the final skeleton compatible with all the merged skeletons and their compatible skeletons. for (USkeleton* Skeleton : Params.SkeletonsToMerge) { if (Skeleton) { FinalSkeleton->AddCompatibleSkeleton(Skeleton); const TArray>& CompatibleSkeletons = Skeleton->GetCompatibleSkeletons(); for (const TSoftObjectPtr& CompatibleSkeleton : CompatibleSkeletons) { FinalSkeleton->AddCompatibleSkeletonSoft(CompatibleSkeleton); } #if WITH_EDITOR const uint32 SkeletonHash = GetTypeHash(Skeleton->GetName()); CombinedSkeletonHash = HashCombine(CombinedSkeletonHash, SkeletonHash); #endif } } // Add the hash based on the sources for the merged skeleton to make its name unique #if WITH_EDITOR FinalSkeleton->Rename(*FString::Printf(TEXT("%s_%lu"), *FinalSkeleton->GetName(), CombinedSkeletonHash)); #endif // Add Skeleton to the cache CustomizableObject.GetPrivate()->SkeletonCache.Add(ReferencedSkeletons.SkeletonIds, FinalSkeleton); ReferencedSkeletons.SkeletonIds.Empty(); bOutCreatedNewSkeleton = true; } return FinalSkeleton; } namespace { FORCEINLINE TObjectPtr ClonePhysicsConstraintTemplate( const TObjectPtr& From, const TObjectPtr& Outer, FName Name = NAME_None) { // We don't use DuplicateObject here beacuse it is too slow. TObjectPtr Result = NewObject(Outer, Name); Result->DefaultInstance = From->DefaultInstance; Result->ProfileHandles = From->ProfileHandles; #if WITH_EDITOR Result->SetDefaultProfile(From->DefaultInstance); #endif return Result; } FKAggregateGeom MakeAggGeomFromMutablePhysics(int32 BodyIndex, const mu::FPhysicsBody* MutablePhysicsBody) { FKAggregateGeom BodyAggGeom; auto GetCollisionEnabledFormFlags = [](uint32 Flags) -> ECollisionEnabled::Type { return ECollisionEnabled::Type(Flags & 0xFF); }; auto GetContributeToMassFromFlags = [](uint32 Flags) -> bool { return static_cast((Flags >> 8) & 1); }; const int32 NumSpheres = MutablePhysicsBody->GetSphereCount(BodyIndex); TArray& AggSpheres = BodyAggGeom.SphereElems; AggSpheres.Empty(NumSpheres); for (int32 I = 0; I < NumSpheres; ++I) { uint32 Flags = MutablePhysicsBody->GetSphereFlags(BodyIndex, I); FString Name = MutablePhysicsBody->GetSphereName(BodyIndex, I); FVector3f Position; float Radius; MutablePhysicsBody->GetSphere(BodyIndex, I, Position, Radius); FKSphereElem& NewElem = AggSpheres.AddDefaulted_GetRef(); NewElem.Center = FVector(Position); NewElem.Radius = Radius; NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags)); NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags)); NewElem.SetName(FName(*Name)); } const int32 NumBoxes = MutablePhysicsBody->GetBoxCount(BodyIndex); TArray& AggBoxes = BodyAggGeom.BoxElems; AggBoxes.Empty(NumBoxes); for (int32 I = 0; I < NumBoxes; ++I) { uint32 Flags = MutablePhysicsBody->GetBoxFlags(BodyIndex, I); FString Name = MutablePhysicsBody->GetBoxName(BodyIndex, I); FVector3f Position; FQuat4f Orientation ; FVector3f Size; MutablePhysicsBody->GetBox(BodyIndex, I, Position, Orientation, Size); FKBoxElem& NewElem = AggBoxes.AddDefaulted_GetRef(); NewElem.Center = FVector(Position); NewElem.Rotation = FRotator(Orientation.Rotator()); NewElem.X = Size.X; NewElem.Y = Size.Y; NewElem.Z = Size.Z; NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags)); NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags)); NewElem.SetName(FName(*Name)); } //const int32 NumConvexes = MutablePhysicsBody->GetConvexCount( BodyIndex ); //TArray& AggConvexes = BodyAggGeom.ConvexElems; //AggConvexes.Empty(); //for (int32 I = 0; I < NumConvexes; ++I) //{ // uint32 Flags = MutablePhysicsBody->GetConvexFlags( BodyIndex, I ); // FString Name = MutablePhysicsBody->GetConvexName( BodyIndex, I ); // const FVector3f* Vertices; // const int32* Indices; // int32 NumVertices; // int32 NumIndices; // FTransform3f Transform; // MutablePhysicsBody->GetConvex( BodyIndex, I, Vertices, NumVertices, Indices, NumIndices, Transform ); // // TArrayView VerticesView( Vertices, NumVertices ); // TArrayView IndicesView( Indices, NumIndices ); //} TArray& AggSphyls = BodyAggGeom.SphylElems; const int32 NumSphyls = MutablePhysicsBody->GetSphylCount(BodyIndex); AggSphyls.Empty(NumSphyls); for (int32 I = 0; I < NumSphyls; ++I) { uint32 Flags = MutablePhysicsBody->GetSphylFlags(BodyIndex, I); FString Name = MutablePhysicsBody->GetSphylName(BodyIndex, I); FVector3f Position; FQuat4f Orientation; float Radius; float Length; MutablePhysicsBody->GetSphyl(BodyIndex, I, Position, Orientation, Radius, Length); FKSphylElem& NewElem = AggSphyls.AddDefaulted_GetRef(); NewElem.Center = FVector(Position); NewElem.Rotation = FRotator(Orientation.Rotator()); NewElem.Radius = Radius; NewElem.Length = Length; NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags)); NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags)); NewElem.SetName(FName(*Name)); } TArray& AggTaperedCapsules = BodyAggGeom.TaperedCapsuleElems; const int32 NumTaperedCapsules = MutablePhysicsBody->GetTaperedCapsuleCount(BodyIndex); AggTaperedCapsules.Empty(NumTaperedCapsules); for (int32 I = 0; I < NumTaperedCapsules; ++I) { uint32 Flags = MutablePhysicsBody->GetTaperedCapsuleFlags(BodyIndex, I); FString Name = MutablePhysicsBody->GetTaperedCapsuleName(BodyIndex, I); FVector3f Position; FQuat4f Orientation; float Radius0; float Radius1; float Length; MutablePhysicsBody->GetTaperedCapsule(BodyIndex, I, Position, Orientation, Radius0, Radius1, Length); FKTaperedCapsuleElem& NewElem = AggTaperedCapsules.AddDefaulted_GetRef(); NewElem.Center = FVector(Position); NewElem.Rotation = FRotator(Orientation.Rotator()); NewElem.Radius0 = Radius0; NewElem.Radius1 = Radius1; NewElem.Length = Length; NewElem.SetContributeToMass(GetContributeToMassFromFlags(Flags)); NewElem.SetCollisionEnabled(GetCollisionEnabledFormFlags(Flags)); NewElem.SetName(FName(*Name)); } return BodyAggGeom; }; TObjectPtr MakePhysicsAssetFromTemplateAndMutableBody( const TSharedRef& OperationData, TObjectPtr TemplateAsset, const mu::FPhysicsBody* MutablePhysics, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex) { check(TemplateAsset); TObjectPtr Result = NewObject(); if (!Result) { return nullptr; } Result->SolverSettings = TemplateAsset->SolverSettings; Result->SolverType = TemplateAsset->SolverType; Result->bNotForDedicatedServer = TemplateAsset->bNotForDedicatedServer; const TMap>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap; TMap BonesInUse; const int32 MutablePhysicsBodyCount = MutablePhysics->GetBodyCount(); BonesInUse.Reserve(MutablePhysicsBodyCount); for ( int32 I = 0; I < MutablePhysicsBodyCount; ++I ) { if (const TPair* BoneInfo = BoneInfoMap.Find(MutablePhysics->GetBodyBoneId(I))) { BonesInUse.Add(BoneInfo->Key, I); } } const int32 PhysicsAssetBodySetupNum = TemplateAsset->SkeletalBodySetups.Num(); bool bTemplateBodyNotUsedFound = false; TArray UsageMap; UsageMap.Init(1, PhysicsAssetBodySetupNum); for (int32 BodySetupIndex = 0; BodySetupIndex < PhysicsAssetBodySetupNum; ++BodySetupIndex) { const TObjectPtr& BodySetup = TemplateAsset->SkeletalBodySetups[BodySetupIndex]; int32* MutableBodyIndex = BonesInUse.Find(BodySetup->BoneName); if (!MutableBodyIndex) { bTemplateBodyNotUsedFound = true; UsageMap[BodySetupIndex] = 0; continue; } TObjectPtr NewBodySetup = NewObject(Result); NewBodySetup->BodySetupGuid = FGuid::NewGuid(); // Copy Body properties NewBodySetup->BoneName = BodySetup->BoneName; NewBodySetup->PhysicsType = BodySetup->PhysicsType; NewBodySetup->bConsiderForBounds = BodySetup->bConsiderForBounds; NewBodySetup->bMeshCollideAll = BodySetup->bMeshCollideAll; NewBodySetup->bDoubleSidedGeometry = BodySetup->bDoubleSidedGeometry; NewBodySetup->bGenerateNonMirroredCollision = BodySetup->bGenerateNonMirroredCollision; NewBodySetup->bSharedCookedData = BodySetup->bSharedCookedData; NewBodySetup->bGenerateMirroredCollision = BodySetup->bGenerateMirroredCollision; NewBodySetup->PhysMaterial = BodySetup->PhysMaterial; NewBodySetup->CollisionReponse = BodySetup->CollisionReponse; NewBodySetup->CollisionTraceFlag = BodySetup->CollisionTraceFlag; NewBodySetup->DefaultInstance = BodySetup->DefaultInstance; NewBodySetup->WalkableSlopeOverride = BodySetup->WalkableSlopeOverride; NewBodySetup->BuildScale3D = BodySetup->BuildScale3D; NewBodySetup->bSkipScaleFromAnimation = BodySetup->bSkipScaleFromAnimation; // PhysicalAnimationProfiles can't be added with the current UPhysicsAsset API outside the editor. // Don't pouplate them for now. //NewBodySetup->PhysicalAnimationData = BodySetup->PhysicalAnimationData; NewBodySetup->AggGeom = MakeAggGeomFromMutablePhysics(*MutableBodyIndex, MutablePhysics); Result->SkeletalBodySetups.Add(NewBodySetup); } if (!bTemplateBodyNotUsedFound) { Result->CollisionDisableTable = TemplateAsset->CollisionDisableTable; const int32 NumConstraints = TemplateAsset->ConstraintSetup.Num(); Result->ConstraintSetup.SetNum(NumConstraints); for (int32 ConstraintIndex = 0; ConstraintIndex < NumConstraints; ++ConstraintIndex) { const TObjectPtr& TemplateConstraint = TemplateAsset->ConstraintSetup[ConstraintIndex]; if (!TemplateConstraint) { continue; } Result->ConstraintSetup[ConstraintIndex] = ClonePhysicsConstraintTemplate(TemplateConstraint, Result); } } else { // Recreate the collision disable entry Result->CollisionDisableTable.Reserve(TemplateAsset->CollisionDisableTable.Num()); for (const TPair& CollisionDisableEntry : TemplateAsset->CollisionDisableTable) { const bool bIndex0Used = UsageMap[CollisionDisableEntry.Key.Indices[0]] > 0; const bool bIndex1Used = UsageMap[CollisionDisableEntry.Key.Indices[1]] > 0; if (bIndex0Used && bIndex1Used) { Result->CollisionDisableTable.Add(CollisionDisableEntry); } } // Only add constraints that are part of the bones used for the mutable physics volumes description. Result->ConstraintSetup.Reserve(TemplateAsset->ConstraintSetup.Num()); for (const TObjectPtr& Constraint : TemplateAsset->ConstraintSetup) { if (!Constraint) { continue; } const FName BoneA = Constraint->DefaultInstance.ConstraintBone1; const FName BoneB = Constraint->DefaultInstance.ConstraintBone2; if (BonesInUse.Find(BoneA) && BonesInUse.Find(BoneB)) { Result->ConstraintSetup.AddDefaulted_GetRef() = ClonePhysicsConstraintTemplate(Constraint, Result); } } } Result->UpdateBodySetupIndexMap(); Result->UpdateBoundsBodiesArray(); #if WITH_EDITORONLY_DATA Result->ConstraintProfiles = TemplateAsset->ConstraintProfiles; #endif return Result; } } UPhysicsAsset* UCustomizableInstancePrivate::GetOrBuildMainPhysicsAsset( const TSharedRef& OperationData, TObjectPtr TemplateAsset, const mu::FPhysicsBody* MutablePhysics, bool bDisableCollisionsBetweenDifferentAssets, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex) { MUTABLE_CPUPROFILER_SCOPE(MergePhysicsAssets); check(MutablePhysics); UPhysicsAsset* Result = nullptr; const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex); if (!Component) { return nullptr; } FCustomizableObjectComponentIndex ObjectComponentIndex = Component->Id; FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); check(ComponentData); TArray>& PhysicsAssets = ComponentData->PhysicsAssets.PhysicsAssetsToMerge; TArray> ValidAssets; const int32 NumPhysicsAssets = ComponentData->PhysicsAssets.PhysicsAssetsToMerge.Num(); for (int32 I = 0; I < NumPhysicsAssets; ++I) { const TObjectPtr& PhysicsAsset = ComponentData->PhysicsAssets.PhysicsAssetsToMerge[I]; if (PhysicsAsset) { ValidAssets.AddUnique(PhysicsAsset); } } if (!ValidAssets.Num()) { return Result; } // Just get the referenced asset if no recontrution or merge is needed. if (ValidAssets.Num() == 1 && !MutablePhysics->bBodiesModified) { return ValidAssets[0]; } TemplateAsset = TemplateAsset ? TemplateAsset : ValidAssets[0]; check(TemplateAsset); Result = NewObject(); if (!Result) { return nullptr; } Result->SolverSettings = TemplateAsset->SolverSettings; Result->SolverType = TemplateAsset->SolverType; Result->bNotForDedicatedServer = TemplateAsset->bNotForDedicatedServer; const TMap>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap; TMap BonesInUse; const int32 MutablePhysicsBodyCount = MutablePhysics->GetBodyCount(); BonesInUse.Reserve(MutablePhysicsBodyCount); for ( int32 I = 0; I < MutablePhysicsBodyCount; ++I ) { if (const TPair* BoneInfo = BoneInfoMap.Find(MutablePhysics->GetBodyBoneId(I))) { BonesInUse.Add(BoneInfo->Key, I); } } // Each array is a set of elements that can collide TArray>> CollisionSets; // {SetIndex, ElementInSetIndex, BodyIndex} using CollisionSetEntryType = TTuple; // Map from BodyName/BoneName to set and index in set. TMap BodySetupSetMap; // {SetIndex0, SetIndex1, BodyIndex} using MultiSetCollisionEnableType = TTuple; // Only for elements that belong to two or more differnet sets. // Contains in which set the elements belong. using MultiSetArrayType = TArray>; TMap MultiCollisionSets; TArray> SetsIndexMap; CollisionSets.SetNum(ValidAssets.Num()); SetsIndexMap.SetNum(CollisionSets.Num()); TMap CollisionDisableTable; // New body index int32 CurrentBodyIndex = 0; for (int32 CollisionSetIndex = 0; CollisionSetIndex < ValidAssets.Num(); ++CollisionSetIndex) { const int32 PhysicsAssetBodySetupNum = ValidAssets[CollisionSetIndex]->SkeletalBodySetups.Num(); SetsIndexMap[CollisionSetIndex].Init(-1, PhysicsAssetBodySetupNum); for (int32 BodySetupIndex = 0; BodySetupIndex < PhysicsAssetBodySetupNum; ++BodySetupIndex) { const TObjectPtr& BodySetup = ValidAssets[CollisionSetIndex]->SkeletalBodySetups[BodySetupIndex]; int32* MutableBodyIndex = BonesInUse.Find(BodySetup->BoneName); if (!MutableBodyIndex) { continue; } CollisionSetEntryType* Found = BodySetupSetMap.Find(BodySetup->BoneName); if (!Found) { TObjectPtr NewBodySetup = NewObject(Result); NewBodySetup->BodySetupGuid = FGuid::NewGuid(); // Copy Body properties NewBodySetup->BoneName = BodySetup->BoneName; NewBodySetup->PhysicsType = BodySetup->PhysicsType; NewBodySetup->bConsiderForBounds = BodySetup->bConsiderForBounds; NewBodySetup->bMeshCollideAll = BodySetup->bMeshCollideAll; NewBodySetup->bDoubleSidedGeometry = BodySetup->bDoubleSidedGeometry; NewBodySetup->bGenerateNonMirroredCollision = BodySetup->bGenerateNonMirroredCollision; NewBodySetup->bSharedCookedData = BodySetup->bSharedCookedData; NewBodySetup->bGenerateMirroredCollision = BodySetup->bGenerateMirroredCollision; NewBodySetup->PhysMaterial = BodySetup->PhysMaterial; NewBodySetup->CollisionReponse = BodySetup->CollisionReponse; NewBodySetup->CollisionTraceFlag = BodySetup->CollisionTraceFlag; NewBodySetup->DefaultInstance = BodySetup->DefaultInstance; NewBodySetup->WalkableSlopeOverride = BodySetup->WalkableSlopeOverride; NewBodySetup->BuildScale3D = BodySetup->BuildScale3D; NewBodySetup->bSkipScaleFromAnimation = BodySetup->bSkipScaleFromAnimation; // PhysicalAnimationProfiles can't be added with the current UPhysicsAsset API outside the editor. // Don't poulate them for now. //NewBodySetup->PhysicalAnimationData = BodySetup->PhysicalAnimationData; NewBodySetup->AggGeom = MakeAggGeomFromMutablePhysics(*MutableBodyIndex, MutablePhysics); Result->SkeletalBodySetups.Add(NewBodySetup); int32 IndexInSet = CollisionSets[CollisionSetIndex].Add(CurrentBodyIndex); BodySetupSetMap.Add(BodySetup->BoneName, {CollisionSetIndex, IndexInSet, CurrentBodyIndex}); SetsIndexMap[CollisionSetIndex][IndexInSet] = CurrentBodyIndex; ++CurrentBodyIndex; } else { int32 FoundCollisionSetIndex = Found->Get<0>(); int32 FoundCollisionSetElemIndex = Found->Get<1>(); int32 FoundBodyIndex = Found->Get<2>(); // No need to add the body again. Volumes that come form mutable are already merged. // here we only need to merge properies. // TODO: check if there is other properties worth merging. In case of conflict select the more restrivtive one? Result->SkeletalBodySetups[FoundBodyIndex]->bConsiderForBounds |= BodySetup->bConsiderForBounds; // Mark as removed so no indices are invalidated. CollisionSets[FoundCollisionSetIndex][FoundCollisionSetElemIndex] = INDEX_NONE; // Add Elem to the set but mark it as removed so we have an index for remapping. int32 IndexInSet = CollisionSets[CollisionSetIndex].Add(INDEX_NONE); SetsIndexMap[CollisionSetIndex][IndexInSet] = FoundBodyIndex; MultiSetArrayType& Sets = MultiCollisionSets.FindOrAdd(FoundBodyIndex); // The first time there is a collision (MultSet is empty), add the colliding element set // as well as the current set. if (!Sets.Num()) { Sets.Add(FoundCollisionSetIndex); } Sets.Add(CollisionSetIndex); } } // Remap collision indices removing invalid ones. CollisionDisableTable.Reserve(CollisionDisableTable.Num() + ValidAssets[CollisionSetIndex]->CollisionDisableTable.Num()); for (const TPair& DisabledCollision : ValidAssets[CollisionSetIndex]->CollisionDisableTable) { int32 MappedIdx0 = SetsIndexMap[CollisionSetIndex][DisabledCollision.Key.Indices[0]]; int32 MappedIdx1 = SetsIndexMap[CollisionSetIndex][DisabledCollision.Key.Indices[1]]; // This will generate correct disables for the case when two shapes from different sets // are meged to the same setup. Will introduce repeated pairs, but this is not a problem. // Currenly if two bodies / bones have disabled collision in one of the merged assets, the collision // will remain disabled even if other merges allow it. if ( MappedIdx0 != INDEX_NONE && MappedIdx1 != INDEX_NONE ) { CollisionDisableTable.Add({MappedIdx0, MappedIdx1}, DisabledCollision.Value); } } // Only add constraints that are part of the bones used for the mutable physics volumes description. Result->ConstraintSetup.Reserve(Result->ConstraintSetup.Num() + ValidAssets[CollisionSetIndex]->ConstraintSetup.Num()); for (const TObjectPtr& Constraint : ValidAssets[CollisionSetIndex]->ConstraintSetup) { if (!Constraint) { continue; } FName BoneA = Constraint->DefaultInstance.ConstraintBone1; FName BoneB = Constraint->DefaultInstance.ConstraintBone2; if (BonesInUse.Find(BoneA) && BonesInUse.Find(BoneB)) { Result->ConstraintSetup.AddDefaulted_GetRef() = ClonePhysicsConstraintTemplate(Constraint, Result); } } #if WITH_EDITORONLY_DATA Result->ConstraintProfiles.Append(ValidAssets[CollisionSetIndex]->ConstraintProfiles); #endif } if (bDisableCollisionsBetweenDifferentAssets) { // Compute collision disable table size upperbound to reduce number of alloactions. int32 CollisionDisableTableSize = 0; for (int32 S0 = 1; S0 < CollisionSets.Num(); ++S0) { for (int32 S1 = 0; S1 < S0; ++S1) { CollisionDisableTableSize += CollisionSets[S1].Num() * CollisionSets[S0].Num(); } } // We already may have elements in the table, but at the moment of // addition we don't know yet the final number of elements. // Now a good number of elements will be added and because we know the final number of elements // an upperbound to the number of interactions can be computed and reserved. CollisionDisableTable.Reserve(CollisionDisableTableSize); // Generate disable collision entry for every element in Set S0 for every element in Set S1 // that are not in multiple sets. for (int32 S0 = 1; S0 < CollisionSets.Num(); ++S0) { for (int32 S1 = 0; S1 < S0; ++S1) { for (int32 Set0Elem : CollisionSets[S0]) { // Element present in more than one set, will be treated later. if (Set0Elem == INDEX_NONE) { continue; } for (int32 Set1Elem : CollisionSets[S1]) { // Element present in more than one set, will be treated later. if (Set1Elem == INDEX_NONE) { continue; } CollisionDisableTable.Add(FRigidBodyIndexPair{Set0Elem, Set1Elem}, false); } } } } // Process elements that belong to multiple sets that have been merged to the same element. for ( const TPair& Sets : MultiCollisionSets ) { for (int32 S = 0; S < CollisionSets.Num(); ++S) { if (!Sets.Value.Contains(S)) { for (int32 SetElem : CollisionSets[S]) { if (SetElem != INDEX_NONE) { CollisionDisableTable.Add(FRigidBodyIndexPair{Sets.Key, SetElem}, false); } } } } } CollisionDisableTable.Shrink(); } Result->CollisionDisableTable = MoveTemp(CollisionDisableTable); Result->UpdateBodySetupIndexMap(); Result->UpdateBoundsBodiesArray(); ComponentData->PhysicsAssets.PhysicsAssetsToMerge.Empty(); return Result; } static float MutableMeshesMinUVChannelDensity = 100.f; FAutoConsoleVariableRef CVarMutableMeshesMinUVChannelDensity( TEXT("Mutable.MinUVChannelDensity"), MutableMeshesMinUVChannelDensity, TEXT("Min UV density to set on generated meshes. This value will influence the requested texture mip to stream in. Higher values will result in higher quality mips being streamed in earlier.")); void SetMeshUVChannelDensity(FMeshUVChannelInfo& UVChannelInfo, float Density = 0.f) { Density = Density > 0.f ? Density : 150.f; Density = FMath::Max(MutableMeshesMinUVChannelDensity, Density); UVChannelInfo.bInitialized = true; UVChannelInfo.bOverrideDensities = false; for (int32 i = 0; i < TEXSTREAM_MAX_NUM_UVCHANNELS; ++i) { UVChannelInfo.LocalUVDensities[i] = Density; } } bool UCustomizableInstancePrivate::DoComponentsNeedUpdate(UCustomizableObjectInstance* Public, const TSharedRef& OperationData, bool& bHasInvalidMesh) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::DoComponentsNeedUpdate); UCustomizableObject* CustomizableObject = Public->GetCustomizableObject(); if (!CustomizableObject) { return false; } const UModelResources& ModelResources = *CustomizableObject->GetPrivate()->GetModelResources(); const int32 NumInstanceComponents = OperationData->InstanceUpdateData.Components.Num(); // To be indexed with instance component index TArray ComponentWithMesh; ComponentWithMesh.Init(false, NumInstanceComponents); TArray MeshIDs; MeshIDs.Init(MAX_uint64, NumInstanceComponents * MAX_MESH_LOD_COUNT); // Gather the Mesh Ids of all components, and validate the integrity of the meshes to generate. for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < NumInstanceComponents; ++InstanceComponentIndex) { const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex]; const FName ComponentName = OperationData->ComponentNames[Component.Id.GetValue()]; for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < Component.LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex]; if (!LOD.Mesh) { continue; } if (LOD.SurfaceCount == 0 && !LOD.Mesh->IsReference()) { continue; } // Unreal does not support empty sections. if (!LOD.Mesh->IsReference() && LOD.Mesh->GetVertexCount() == 0) // else { UE_LOG(LogMutable, Error, TEXT("Failed to generate SkeletalMesh for CO Instance [%s]. CO [%s] has invalid geometry for LOD [%d] Component [%d]."), *Public->GetName(), *CustomizableObject->GetName(), LODIndex, NumInstanceComponents); bHasInvalidMesh = true; continue; } ComponentWithMesh[InstanceComponentIndex] = true; MeshIDs[InstanceComponentIndex * MAX_MESH_LOD_COUNT + LODIndex] = LOD.MeshID; } } // Find which components need an update OperationData->MeshChangedPerInstanceComponent.Init(false, NumInstanceComponents); for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < NumInstanceComponents; ++InstanceComponentIndex) { const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex]; const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id; if (!ObjectComponentIndex.IsValid()) { continue; } const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()]; if (OperationData->bUseMeshCache) { USkeletalMesh* CachedMesh = CustomizableObject->GetPrivate()->MeshCache.Get(*OperationData->GetMeshDescriptors(ObjectComponentIndex)); if (CachedMesh) { TObjectPtr* SkeletalMesh = SkeletalMeshes.Find(ComponentName); const bool bMeshNeedsUpdate = !SkeletalMesh || (*SkeletalMesh != CachedMesh); OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = bMeshNeedsUpdate; ComponentWithMesh[InstanceComponentIndex] = true; continue; } } // Components with mesh must have valid geometry at CurrentMaxLOD TObjectPtr* SkeletalMeshPtr = SkeletalMeshes.Find(ComponentName); const bool bHadSkeletalMesh = SkeletalMeshPtr && *SkeletalMeshPtr; if (Component.LODCount == 0) { // We don't have a mesh in the component, so it has changed if we had one before. OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = bHadSkeletalMesh; continue; } const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD]; bool bIsReferenced = LOD.Mesh && LOD.Mesh->IsReference(); if (!bIsReferenced) { if (ComponentWithMesh[InstanceComponentIndex] && MeshIDs[InstanceComponentIndex * MAX_MESH_LOD_COUNT + OperationData->NumLODsAvailable[ComponentName] - 1] == MAX_uint64) { UE_LOG(LogMutable, Error, TEXT("Failed to generate SkeletalMesh for CO Instance [%s]. CO [%s] is missing geometry for LOD [%d] Object Component [%d]."), *Public->GetName(), *CustomizableObject->GetName(), OperationData->NumLODsAvailable[ComponentName] - 1, ObjectComponentIndex.GetValue()); bHasInvalidMesh = true; continue; } } // If the component wasn't there and now is there, we need to update it. OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = !bHadSkeletalMesh && LOD.Mesh && (LOD.Mesh->GetFaceCount()>0); const FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); if (!ComponentData) // Could be nullptr if the component has not been generated. { continue; } // Update if MeshIDs are different const int32 ComponentOffset = InstanceComponentIndex * MAX_MESH_LOD_COUNT; for (int32 MeshIndex = 0; !OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] && MeshIndex < MAX_MESH_LOD_COUNT; ++MeshIndex) { OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = MeshIDs[ComponentOffset + MeshIndex] != ComponentData->LastMeshIdPerLOD[MeshIndex]; } // If the component SkeletalMesh was build with scalability settings different than the current, we need to update. // The mismatch can happen when the scalability setting is changed and an update is requested with a MinLOD lower // than the new FirstLODAvailable. This is an artifact of our LOD management system setting MinLOD. if (!OperationData->bStreamMeshLODs && !OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] && bHadSkeletalMesh) { const int32 FirstRequestedLOD = OperationData->GetFirstRequestedLOD()[ComponentName]; for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstRequestedLOD; ++LODIndex) { TIndirectArray& LODRenderData = (*SkeletalMeshPtr)->GetResourceForRendering()->LODRenderData; if (LODRenderData[LODIndex].RenderSections.Num() != LODRenderData[FirstRequestedLOD].RenderSections.Num()) { OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex] = true; break; } } } } bool bChanged = OperationData->MeshChangedPerInstanceComponent.Num() != SkeletalMeshes.Num() || OperationData->MeshChangedPerInstanceComponent.Find(true) != INDEX_NONE; // It also changed if we removed a component that we did have before if (!bChanged) { for (const TPair>& OldMesh : SkeletalMeshes) { bool bFound = false; for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < NumInstanceComponents; ++InstanceComponentIndex) { const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex]; const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id; const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()]; if (ComponentName == OldMesh.Key) { bFound = true; break; } } if (!bFound) { bChanged = true; break; } } } return !bHasInvalidMesh && bChanged; } bool UCustomizableInstancePrivate::UpdateSkeletalMesh_PostBeginUpdate0(UCustomizableObjectInstance* Public, const TSharedRef& OperationData) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::UpdateSkeletalMesh_PostBeginUpdate0) bool bHasInvalidMesh = false; bool bUpdateMeshes = DoComponentsNeedUpdate(Public, OperationData, bHasInvalidMesh); UCustomizableObject* CustomizableObject = Public->GetCustomizableObject(); if (!CustomizableObject) { UE_LOG(LogMutable, Warning, TEXT("Failed to generate SkeletalMesh for CO Instance %s. It does not have a CO."), *Public->GetName()); InvalidateGeneratedData(); OperationData->UpdateResult = EUpdateResult::Error; return false; } // We can not handle empty meshes, clear any generated mesh and return if (bHasInvalidMesh) { UE_LOG(LogMutable, Warning, TEXT("Failed to generate SkeletalMesh for CO Instance %s. CO [%s]"), *Public->GetName(), *GetNameSafe(CustomizableObject)); InvalidateGeneratedData(); OperationData->UpdateResult = EUpdateResult::Error; return false; } TextureReuseCache.Empty(); // Sections may have changed, so invalidate the texture reuse cache because it's indexed by section TMap> OldSkeletalMeshes = SkeletalMeshes; const UModelResources& ModelResources = CustomizableObject->GetPrivate()->GetModelResourcesChecked(); // Collate the Extension Data on the instance into groups based on the extension that produced // it, so that we only need to call extension functions such as OnSkeletalMeshCreated once for // each extension. TMap> ExtensionToExtensionData; { const TArrayView AllExtensions = ICustomizableObjectModule::Get().GetRegisteredExtensions(); // Pre-populate ExtensionToExtensionData with empty entries for all extensions. // // This ensures that extension functions such as OnSkeletalMeshCreated are called for each // extension, even if they didn't produce any extension data. Public->GetPrivate()->ExtensionInstanceData.Empty(AllExtensions.Num()); for (const UCustomizableObjectExtension* Extension : AllExtensions) { ExtensionToExtensionData.Add(Extension); } const TArrayView ExtensionPins = ICustomizableObjectModule::Get().GetAdditionalObjectNodePins(); for (FInstanceUpdateData::FNamedExtensionData& ExtensionOutput : OperationData->InstanceUpdateData.ExtendedInputPins) { const FRegisteredObjectNodeInputPin* FoundPin = Algo::FindBy(ExtensionPins, ExtensionOutput.Name, &FRegisteredObjectNodeInputPin::GlobalPinName); if (!FoundPin) { // Failed to find the corresponding pin for this output // // This may indicate that a plugin has been removed or renamed since the CO was compiled UE_LOG(LogMutable, Error, TEXT("Failed to find Object node input pin with name %s"), *ExtensionOutput.Name.ToString()); continue; } const UCustomizableObjectExtension* Extension = FoundPin->Extension.Get(); if (!Extension) { // Extension is not loaded or not found UE_LOG(LogMutable, Error, TEXT("Extension for Object node input pin %s is no longer valid"), *ExtensionOutput.Name.ToString()); continue; } if (ExtensionOutput.Data->Origin == mu::FExtensionData::EOrigin::Invalid) { // Null data was produced // // This can happen if a node produces an FExtensionData but doesn't initialize it UE_LOG(LogMutable, Error, TEXT("Invalid data sent to Object node input pin %s"), *ExtensionOutput.Name.ToString()); continue; } // All registered extensions were added to the map above, so if the extension is still // registered it should be found. TArray* ContainerArray = ExtensionToExtensionData.Find(Extension); if (!ContainerArray) { UE_LOG(LogMutable, Error, TEXT("Object node input pin %s received data for unregistered extension %s"), *ExtensionOutput.Name.ToString(), *Extension->GetPathName()); continue; } const FCustomizableObjectResourceData* ReferencedExtensionData = nullptr; switch (ExtensionOutput.Data->Origin) { case mu::FExtensionData::EOrigin::ConstantAlwaysLoaded: { check(ModelResources.AlwaysLoadedExtensionData.IsValidIndex(ExtensionOutput.Data->Index)); ReferencedExtensionData = &ModelResources.AlwaysLoadedExtensionData[ExtensionOutput.Data->Index]; } break; case mu::FExtensionData::EOrigin::ConstantStreamed: { #if WITH_EDITOR check(ModelResources.StreamedExtensionDataEditor.IsValidIndex(ExtensionOutput.Data->Index)); ReferencedExtensionData = &ModelResources.StreamedExtensionDataEditor[ExtensionOutput.Data->Index]; #else check(ModelResources.StreamedExtensionData.IsValidIndex(ExtensionOutput.Data->Index)); const FCustomizableObjectStreamedResourceData& StreamedData = ModelResources.StreamedExtensionData[ExtensionOutput.Data->Index]; if (!StreamedData.IsLoaded()) { // The data should have been loaded as part of executing the CO program. // // This could indicate a bug in the streaming logic. UE_LOG(LogMutable, Error, TEXT("Customizable Object produced a streamed extension data that is not loaded: %s"), *StreamedData.GetPath().ToString()); continue; } ReferencedExtensionData = &StreamedData.GetLoadedData(); #endif } break; default: unimplemented(); } check(ReferencedExtensionData); ContainerArray->Emplace(FoundPin->InputPin, ReferencedExtensionData->Data); } } // Give each extension the chance to generate Extension Instance Data for (const TPair>& Pair : ExtensionToExtensionData) { FInstancedStruct NewExtensionInstanceData = Pair.Key->GenerateExtensionInstanceData(Pair.Value); if (NewExtensionInstanceData.IsValid()) { FExtensionInstanceData& NewData = Public->GetPrivate()->ExtensionInstanceData.AddDefaulted_GetRef(); NewData.Extension = Pair.Key; NewData.Data = MoveTemp(NewExtensionInstanceData); } } // None of the current meshes requires a mesh update. Continue to BuildMaterials if (!bUpdateMeshes) { return true; } SkeletalMeshes.Reset(); const int32 NumInstanceComponents = OperationData->InstanceUpdateData.Components.Num(); for (int32 InstanceComponentIndexValue = 0; InstanceComponentIndexValue < NumInstanceComponents; ++InstanceComponentIndexValue) { FCustomizableObjectInstanceComponentIndex InstanceComponentIndex(InstanceComponentIndexValue); const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData( InstanceComponentIndex ); if (!Component) { continue; } const FCustomizableObjectComponentIndex ObjectComponentIndex = Component->Id; if (!ModelResources.ComponentNamesPerObjectComponent.IsValidIndex(ObjectComponentIndex.GetValue())) { continue; } const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()]; FCustomizableInstanceComponentData* ComponentData = GetComponentData(ComponentName); if (!ComponentData) { ensure(false); InvalidateGeneratedData(); return false; } // If the component doesn't need an update copy the previously generated mesh. if (!OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex.GetValue()]) { if (TObjectPtr* Result = OldSkeletalMeshes.Find(ComponentName)) { SkeletalMeshes.Add(ComponentName, *Result); } continue; } if (OperationData->bUseMeshCache) { TArray* MeshDescriptors = OperationData->GetMeshDescriptors(ObjectComponentIndex); if (!MeshDescriptors) { continue; } if (USkeletalMesh* CachedMesh = CustomizableObject->GetPrivate()->MeshCache.Get(*MeshDescriptors)) { check(MeshDescriptors->Num() == MAX_MESH_LOD_COUNT); ComponentData->LastMeshIdPerLOD = *MeshDescriptors; SkeletalMeshes.Add(ComponentName, CachedMesh); continue; } } // Reset last mesh IDs. ComponentData->LastMeshIdPerLOD.Init(MAX_uint64, MAX_MESH_LOD_COUNT); // We need the first valid mesh. get it from the component, considering that some LOSs may have been skipped. TSharedPtr ComponentMesh; uint32 MeshHash = INDEX_NONE; for (int32 FirstValidLODIndex = Component->FirstLOD; FirstValidLODIndex < OperationData->InstanceUpdateData.LODs.Num() && !ComponentMesh; ++FirstValidLODIndex) { ComponentMesh = OperationData->InstanceUpdateData.LODs[FirstValidLODIndex].Mesh; MeshHash = HashCombine(MeshHash,GetTypeHash(OperationData->InstanceUpdateData.LODs[FirstValidLODIndex].MeshID)); } if (!ComponentMesh) { continue; } if (ComponentMesh->GetSurfaceCount() == 0 && !ComponentMesh->IsReference()) { continue; } // If it is a referenced resource, only the first LOD is relevant. if (ComponentMesh->IsReference()) { int32 ReferenceID = ComponentMesh->GetReferencedMesh(); TSoftObjectPtr Ref = ModelResources.PassThroughMeshes[ReferenceID]; if (!Ref.IsValid()) { // This shouldn't happen here synchronosuly. It should have been requested as an async load. UE_LOG(LogMutable, Error, TEXT("Referenced mesh [%s] was not pre-loaded. It will be sync-loaded probably causing a hitch. CO [%s]"), *Ref.ToString(), *GetNameSafe(CustomizableObject)); } UStreamableRenderAsset* Asset = MutablePrivate::LoadObject(Ref); USkeletalMesh* SkeletalMesh = Cast(Asset); if (SkeletalMesh) { SkeletalMeshes.Add(ComponentName, SkeletalMesh); } else { // Pass-through static meshes not implemented yet. UE_LOG(LogMutable, Error, TEXT("Referenced static meshes [%s] are not supported yet. CO [%s]"), *Ref.ToString(), *GetNameSafe(CustomizableObject)); } continue; } if (!ModelResources.ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex.GetValue())) { InvalidateGeneratedData(); return false; } // Create and initialize the SkeletalMesh for this component MUTABLE_CPUPROFILER_SCOPE(ConstructMesh); USkeletalMesh* SkeletalMesh = nullptr; FString SkeletalMeshName = FString::Printf (TEXT("SK_%s_%s_%lu"), *Public->GetCustomizableObject()->GetName(), *ComponentName.ToString(), MeshHash); SkeletalMeshName = MakeUniqueObjectName(GetTransientPackage(), USkeletalMesh::StaticClass(), *SkeletalMeshName, EUniqueObjectNameOptions::GloballyUnique).ToString(); if (OperationData->bStreamMeshLODs) { SkeletalMesh = NewObject(GetTransientPackage(), FName(*SkeletalMeshName), RF_Transient); } else { SkeletalMesh = NewObject(GetTransientPackage(), FName(*SkeletalMeshName), RF_Transient); } check(SkeletalMesh); SkeletalMeshes.Add(ComponentName, SkeletalMesh); const FMutableRefSkeletalMeshData& RefSkeletalMeshData = ModelResources.ReferenceSkeletalMeshesData[ObjectComponentIndex.GetValue()]; // Set up the default information any mesh from this component will have (LODArrayInfos, RenderData, Mesh settings, etc). InitSkeletalMeshData(OperationData, SkeletalMesh, RefSkeletalMeshData, *CustomizableObject, ObjectComponentIndex); // Construct a new skeleton, fix up ActiveBones and Bonemap arrays and recompute the RefInvMatrices const bool bBuildSkeletonDataSuccess = BuildSkeletonData(OperationData, *SkeletalMesh, RefSkeletalMeshData, *CustomizableObject, FCustomizableObjectInstanceComponentIndex(InstanceComponentIndex) ); if (!bBuildSkeletonDataSuccess) { InvalidateGeneratedData(); return false; } // Build PhysicsAsset merging physics assets coming from SubMeshes of the newly generated Mesh if (TSharedPtr MutablePhysics = ComponentMesh->GetPhysicsBody()) { constexpr bool bDisallowCollisionBetweenAssets = true; UPhysicsAsset* PhysicsAssetResult = GetOrBuildMainPhysicsAsset( OperationData, RefSkeletalMeshData.PhysicsAsset, MutablePhysics.Get(), bDisallowCollisionBetweenAssets, FCustomizableObjectInstanceComponentIndex(InstanceComponentIndex) ); SkeletalMesh->SetPhysicsAsset(PhysicsAssetResult); #if WITH_EDITORONLY_DATA if (PhysicsAssetResult && PhysicsAssetResult->GetPackage() == GetTransientPackage()) { constexpr bool bMarkAsDirty = false; PhysicsAssetResult->SetPreviewMesh(SkeletalMesh, bMarkAsDirty); } #endif } const int32 NumAdditionalPhysicsNum = ComponentMesh->AdditionalPhysicsBodies.Num(); for (int32 I = 0; I < NumAdditionalPhysicsNum; ++I) { TSharedPtr AdditionalPhysiscBody = ComponentMesh->AdditionalPhysicsBodies[I]; check(AdditionalPhysiscBody); if (!AdditionalPhysiscBody->bBodiesModified) { continue; } const int32 PhysicsBodyExternalId = ComponentMesh->AdditionalPhysicsBodies[I]->CustomId; const FAnimBpOverridePhysicsAssetsInfo& Info = ModelResources.AnimBpOverridePhysiscAssetsInfo[PhysicsBodyExternalId]; // Make sure the AnimInstance class is loaded. It is expected to be already loaded at this point though. UClass* AnimInstanceClassLoaded = MutablePrivate::LoadClass(Info.AnimInstanceClass); TSubclassOf AnimInstanceClass = TSubclassOf(AnimInstanceClassLoaded); if (!ensureAlways(AnimInstanceClass)) { continue; } FAnimBpGeneratedPhysicsAssets& PhysicsAssetsUsedByAnimBp = AnimBpPhysicsAssets.FindOrAdd(AnimInstanceClass); TObjectPtr PhysicsAssetTemplate = TObjectPtr(Info.SourceAsset.Get()); check(PhysicsAssetTemplate); FAnimInstanceOverridePhysicsAsset& Entry = PhysicsAssetsUsedByAnimBp.AnimInstancePropertyIndexAndPhysicsAssets.Emplace_GetRef(); Entry.PropertyIndex = Info.PropertyIndex; Entry.PhysicsAsset = MakePhysicsAssetFromTemplateAndMutableBody( OperationData,PhysicsAssetTemplate, AdditionalPhysiscBody.Get(), InstanceComponentIndex); } // Add sockets from the SkeletalMesh of reference and from the MutableMesh BuildMeshSockets(OperationData, SkeletalMesh, ModelResources, RefSkeletalMeshData, ComponentMesh); for (const TPair>& Pair : ExtensionToExtensionData) { PRAGMA_DISABLE_DEPRECATION_WARNINGS Pair.Key->OnSkeletalMeshCreated(Pair.Value, ObjectComponentIndex.GetValue(), SkeletalMesh); PRAGMA_ENABLE_DEPRECATION_WARNINGS Pair.Key->OnSkeletalMeshCreated(Pair.Value, ComponentName, SkeletalMesh); } // Mesh to copy data from if possible. TObjectPtr* OldSkeletalMeshPtr = OldSkeletalMeshes.Find(ComponentName); USkeletalMesh* OldSkeletalMesh = OldSkeletalMeshPtr ? *OldSkeletalMeshPtr : nullptr; BuildOrCopyElementData(OperationData, SkeletalMesh, *CustomizableObject, InstanceComponentIndex); bool const bCopyRenderDataSuccess = BuildOrCopyRenderData(OperationData, SkeletalMesh, OldSkeletalMesh, Public, InstanceComponentIndex); if (!bCopyRenderDataSuccess) { InvalidateGeneratedData(); return false; } BuildOrCopyMorphTargetsData(OperationData, SkeletalMesh, OldSkeletalMesh, *CustomizableObject, InstanceComponentIndex); BuildOrCopyClothingData(OperationData, SkeletalMesh, ModelResources, InstanceComponentIndex, ClothingPhysicsAssets); FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering(); ensure(RenderData && RenderData->LODRenderData.Num() > 0); ensure(SkeletalMesh->GetLODNum() > 0); if(RenderData) { for (FSkeletalMeshLODRenderData& LODResource : RenderData->LODRenderData) { UnrealConversionUtils::UpdateSkeletalMeshLODRenderDataBuffersSize(LODResource); } } if (OperationData->bUseMeshCache) { const TArray* MeshId = OperationData->GetMeshDescriptors(ObjectComponentIndex); if (MeshId) { CustomizableObject->GetPrivate()->MeshCache.Add(*MeshId, SkeletalMesh); } } if (UCustomizableObjectSkeletalMesh* StreamableMesh = Cast(SkeletalMesh)) { StreamableMesh->InitMutableStreamingData(OperationData, ComponentName, Component->FirstLOD, Component->LODCount); } } return true; } UCustomizableObjectInstance* UCustomizableObjectInstance::Clone() { MUTABLE_CPUPROFILER_SCOPE(UCustomizableObjectInstance::Clone); // Default Outer is the transient package. UCustomizableObjectInstance* NewInstance = NewObject(); check(NewInstance->PrivateData); NewInstance->CopyParametersFromInstance(this); return NewInstance; } UCustomizableObjectInstance* UCustomizableObjectInstance::CloneStatic(UObject* Outer) { UCustomizableObjectInstance* NewInstance = NewObject(Outer, GetClass()); NewInstance->CopyParametersFromInstance(this); NewInstance->GetPrivate()->bShowOnlyRuntimeParameters = false; return NewInstance; } void UCustomizableObjectInstance::CopyParametersFromInstance(UCustomizableObjectInstance* Instance) { GetPrivate()->SetDescriptor(Instance->GetPrivate()->GetDescriptor()); } int32 UCustomizableObjectInstance::AddValueToIntRange(const FString& ParamName) { return Descriptor.AddValueToIntRange(ParamName); } int32 UCustomizableObjectInstance::AddValueToFloatRange(const FString& ParamName) { return Descriptor.AddValueToFloatRange(ParamName); } int32 UCustomizableObjectInstance::AddValueToProjectorRange(const FString& ParamName) { return Descriptor.AddValueToProjectorRange(ParamName); } int32 UCustomizableObjectInstance::RemoveValueFromIntRange(const FString& ParamName, int32 RangeIndex) { return Descriptor.RemoveValueFromIntRange(ParamName, RangeIndex); } int32 UCustomizableObjectInstance::RemoveValueFromFloatRange(const FString& ParamName, const int32 RangeIndex) { return Descriptor.RemoveValueFromFloatRange(ParamName, RangeIndex); } int32 UCustomizableObjectInstance::RemoveValueFromProjectorRange(const FString& ParamName, const int32 RangeIndex) { return Descriptor.RemoveValueFromProjectorRange(ParamName, RangeIndex); } int32 UCustomizableObjectInstance::MultilayerProjectorNumLayers(const FName& ProjectorParamName) const { return Descriptor.NumProjectorLayers(ProjectorParamName); } void UCustomizableObjectInstance::MultilayerProjectorCreateLayer(const FName& ProjectorParamName, int32 Index) { Descriptor.CreateLayer(ProjectorParamName, Index); } void UCustomizableObjectInstance::MultilayerProjectorRemoveLayerAt(const FName& ProjectorParamName, int32 Index) { Descriptor.RemoveLayerAt(ProjectorParamName, Index); } FMultilayerProjectorLayer UCustomizableObjectInstance::MultilayerProjectorGetLayer(const FName& ProjectorParamName, int32 Index) const { return Descriptor.GetLayer(ProjectorParamName, Index); } void UCustomizableObjectInstance::MultilayerProjectorUpdateLayer(const FName& ProjectorParamName, int32 Index, const FMultilayerProjectorLayer& Layer) { Descriptor.UpdateLayer(ProjectorParamName, Index, Layer); } void UCustomizableObjectInstance::SaveDescriptor(FArchive &Ar, bool bUseCompactDescriptor) { Descriptor.SaveDescriptor(Ar, bUseCompactDescriptor); } void UCustomizableObjectInstance::LoadDescriptor(FArchive &Ar) { Descriptor.LoadDescriptor(Ar); } const FString& UCustomizableObjectInstance::GetIntParameterSelectedOption(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetIntParameterSelectedOption(ParamName, RangeIndex); } void UCustomizableObjectInstance::SetIntParameterSelectedOption(int32 IntParamIndex, const FString& SelectedOption, const int32 RangeIndex) { Descriptor.SetIntParameterSelectedOption(IntParamIndex, SelectedOption, RangeIndex); } void UCustomizableObjectInstance::SetIntParameterSelectedOption(const FString& ParamName, const FString& SelectedOptionName, const int32 RangeIndex) { Descriptor.SetIntParameterSelectedOption(ParamName, SelectedOptionName, RangeIndex); } float UCustomizableObjectInstance::GetFloatParameterSelectedOption(const FString& FloatParamName, const int32 RangeIndex) const { return Descriptor.GetFloatParameterSelectedOption(FloatParamName, RangeIndex); } void UCustomizableObjectInstance::SetFloatParameterSelectedOption(const FString& FloatParamName, const float FloatValue, const int32 RangeIndex) { return Descriptor.SetFloatParameterSelectedOption(FloatParamName, FloatValue, RangeIndex); } UTexture* UCustomizableObjectInstance::GetTextureParameterSelectedOption(const FString& TextureParamName, const int32 RangeIndex) const { return Descriptor.GetTextureParameterSelectedOption(TextureParamName, RangeIndex); } void UCustomizableObjectInstance::SetTextureParameterSelectedOption(const FString& TextureParamName, UTexture* TextureValue, const int32 RangeIndex) { Descriptor.SetTextureParameterSelectedOption(TextureParamName, TextureValue, RangeIndex); } USkeletalMesh* UCustomizableObjectInstance::GetSkeletalMeshParameterSelectedOption(const FString& SkeletalMeshParamName, int32 RangeIndex) { return Descriptor.GetSkeletalMeshParameterSelectedOption(SkeletalMeshParamName, RangeIndex); } void UCustomizableObjectInstance::SetSkeletalMeshParameterSelectedOption(const FString& SkeletalMeshParamName, USkeletalMesh* SkeletalMeshValue, int32 RangeIndex) { Descriptor.SetSkeletalMeshParameterSelectedOption(SkeletalMeshParamName, SkeletalMeshValue, RangeIndex); } FLinearColor UCustomizableObjectInstance::GetColorParameterSelectedOption(const FString& ColorParamName) const { return Descriptor.GetColorParameterSelectedOption(ColorParamName); } void UCustomizableObjectInstance::SetColorParameterSelectedOption(const FString & ColorParamName, const FLinearColor& ColorValue) { Descriptor.SetColorParameterSelectedOption(ColorParamName, ColorValue); } bool UCustomizableObjectInstance::GetBoolParameterSelectedOption(const FString& BoolParamName) const { return Descriptor.GetBoolParameterSelectedOption(BoolParamName); } void UCustomizableObjectInstance::SetBoolParameterSelectedOption(const FString& BoolParamName, const bool BoolValue) { Descriptor.SetBoolParameterSelectedOption(BoolParamName, BoolValue); } void UCustomizableObjectInstance::SetVectorParameterSelectedOption(const FString& VectorParamName, const FLinearColor& VectorValue) { Descriptor.SetVectorParameterSelectedOption(VectorParamName, VectorValue); } FTransform UCustomizableObjectInstance::GetTransformParameterSelectedOption(const FString& TransformParamName) const { return Descriptor.GetTransformParameterSelectedOption(TransformParamName); } void UCustomizableObjectInstance::SetTransformParameterSelectedOption(const FString& TransformParamName, const FTransform& TransformValue) { Descriptor.SetTransformParameterSelectedOption(TransformParamName, TransformValue); } void UCustomizableObjectInstance::SetProjectorValue(const FString& ProjectorParamName, const FVector& Pos, const FVector& Direction, const FVector& Up, const FVector& Scale, const float Angle, const int32 RangeIndex) { Descriptor.SetProjectorValue(ProjectorParamName, Pos, Direction, Up, Scale, Angle, RangeIndex); } void UCustomizableObjectInstance::SetProjectorPosition(const FString& ProjectorParamName, const FVector& Pos, const int32 RangeIndex) { Descriptor.SetProjectorPosition(ProjectorParamName, Pos, RangeIndex); } void UCustomizableObjectInstance::SetProjectorDirection(const FString& ProjectorParamName, const FVector& Direction, int32 RangeIndex) { Descriptor.SetProjectorDirection(ProjectorParamName, Direction, RangeIndex); } void UCustomizableObjectInstance::SetProjectorUp(const FString& ProjectorParamName, const FVector& Up, int32 RangeIndex) { Descriptor.SetProjectorUp(ProjectorParamName, Up, RangeIndex); } void UCustomizableObjectInstance::SetProjectorScale(const FString& ProjectorParamName, const FVector& Scale, int32 RangeIndex) { Descriptor.SetProjectorScale(ProjectorParamName, Scale, RangeIndex); } void UCustomizableObjectInstance::SetProjectorAngle(const FString& ProjectorParamName, float Angle, int32 RangeIndex) { Descriptor.SetProjectorAngle(ProjectorParamName, Angle, RangeIndex); } void UCustomizableObjectInstance::GetProjectorValue(const FString& ProjectorParamName, FVector& OutPos, FVector& OutDir, FVector& OutUp, FVector& OutScale, float& OutAngle, ECustomizableObjectProjectorType& OutType, const int32 RangeIndex) const { Descriptor.GetProjectorValue(ProjectorParamName, OutPos, OutDir, OutUp, OutScale, OutAngle, OutType, RangeIndex); } void UCustomizableObjectInstance::GetProjectorValueF(const FString& ProjectorParamName, FVector3f& OutPos, FVector3f& OutDir, FVector3f& OutUp, FVector3f& OutScale, float& OutAngle, ECustomizableObjectProjectorType& OutType, int32 RangeIndex) const { Descriptor.GetProjectorValueF(ProjectorParamName, OutPos, OutDir, OutUp, OutScale, OutAngle, OutType, RangeIndex); } FVector UCustomizableObjectInstance::GetProjectorPosition(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetProjectorPosition(ParamName, RangeIndex); } FVector UCustomizableObjectInstance::GetProjectorDirection(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetProjectorDirection(ParamName, RangeIndex); } FVector UCustomizableObjectInstance::GetProjectorUp(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetProjectorUp(ParamName, RangeIndex); } FVector UCustomizableObjectInstance::GetProjectorScale(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetProjectorScale(ParamName, RangeIndex); } float UCustomizableObjectInstance::GetProjectorAngle(const FString& ParamName, int32 RangeIndex) const { return Descriptor.GetProjectorAngle(ParamName, RangeIndex); } ECustomizableObjectProjectorType UCustomizableObjectInstance::GetProjectorParameterType(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetProjectorParameterType(ParamName, RangeIndex); } FCustomizableObjectProjector UCustomizableObjectInstance::GetProjector(const FString& ParamName, const int32 RangeIndex) const { return Descriptor.GetProjector(ParamName, RangeIndex); } bool UCustomizableObjectInstance::ContainsIntParameter(const FString& ParameterName) const { return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Int) != INDEX_NONE; } bool UCustomizableObjectInstance::ContainsFloatParameter(const FString& ParameterName) const { return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Float) != INDEX_NONE; } bool UCustomizableObjectInstance::ContainsBoolParameter(const FString& ParameterName) const { return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Bool) != INDEX_NONE; } bool UCustomizableObjectInstance::ContainsVectorParameter(const FString& ParameterName) const { return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Color) != INDEX_NONE; } bool UCustomizableObjectInstance::ContainsProjectorParameter(const FString& ParameterName) const { return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Projector) != INDEX_NONE; } bool UCustomizableObjectInstance::ContainsTransformParameter(const FString& ParameterName) const { return Descriptor.FindTypedParameterIndex(ParameterName, EMutableParameterType::Transform) != INDEX_NONE; } int32 UCustomizableObjectInstance::FindIntParameterNameIndex(const FString& ParamName) const { return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Int); } int32 UCustomizableObjectInstance::FindFloatParameterNameIndex(const FString& ParamName) const { return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Float); } int32 UCustomizableObjectInstance::FindBoolParameterNameIndex(const FString& ParamName) const { return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Bool); } int32 UCustomizableObjectInstance::FindVectorParameterNameIndex(const FString& ParamName) const { return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Color); } int32 UCustomizableObjectInstance::FindProjectorParameterNameIndex(const FString& ParamName) const { return Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Projector); } int32 UCustomizableInstancePrivate::FindIntParameterNameIndex(const FString& ParamName) const { return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Int); } int32 UCustomizableInstancePrivate::FindFloatParameterNameIndex(const FString& ParamName) const { return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Float); } int32 UCustomizableInstancePrivate::FindBoolParameterNameIndex(const FString& ParamName) const { return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Bool); } int32 UCustomizableInstancePrivate::FindVectorParameterNameIndex(const FString& ParamName) const { return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Color); } int32 UCustomizableInstancePrivate::FindProjectorParameterNameIndex(const FString& ParamName) const { return GetPublic()->Descriptor.FindTypedParameterIndex(ParamName, EMutableParameterType::Projector); } #if WITH_EDITOR void UCustomizableInstancePrivate::UpdateSkeletalMeshAsyncResult(FInstanceUpdateNativeDelegate Callback, bool bIgnoreCloseDist, bool bForceHighPriority, TSharedPtr MutableSystemSettingsOverride) { UCustomizableObjectSystemPrivate* SystemPrivate = UCustomizableObjectSystem::GetInstance()->GetPrivate(); const TSharedRef Context = MakeShared(*GetPublic()); Context->bIgnoreCloseDist = bIgnoreCloseDist; Context->bForceHighPriority = bForceHighPriority; Context->UpdateNativeCallback = Callback; Context->UpdateSettingsOverride = MutableSystemSettingsOverride; SystemPrivate->EnqueueUpdateSkeletalMesh(Context); } #endif void UCustomizableObjectInstance::SetRandomValues() { Descriptor.SetRandomValues(); } void UCustomizableObjectInstance::SetRandomValuesFromStream(const FRandomStream& InStream) { Descriptor.SetRandomValuesFromStream(InStream); } void UCustomizableObjectInstance::SetDefaultValue(const FString& ParamName) { UCustomizableObject* CustomizableObject = GetCustomizableObject(); if (!CustomizableObject) { return; } Descriptor.SetDefaultValue(CustomizableObject->GetPrivate()->FindParameter(ParamName)); } void UCustomizableObjectInstance::SetDefaultValues() { Descriptor.SetDefaultValues(); } bool UCustomizableInstancePrivate::LoadParametersFromProfile(int32 ProfileIndex) { UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject(); if (!CustomizableObject) { return false; } #if WITH_EDITOR if (ProfileIndex < 0 || ProfileIndex >= CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles().Num() ) { return false; } // This could be done only when the instance changes. MigrateProfileParametersToCurrentInstance(ProfileIndex); const FProfileParameterDat& Profile = CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles()[ProfileIndex]; GetPublic()->Descriptor.BoolParameters = Profile.BoolParameters; GetPublic()->Descriptor.IntParameters = Profile.IntParameters; GetPublic()->Descriptor.FloatParameters = Profile.FloatParameters; GetPublic()->Descriptor.TextureParameters = Profile.TextureParameters; GetPublic()->Descriptor.ProjectorParameters = Profile.ProjectorParameters; GetPublic()->Descriptor.VectorParameters = Profile.VectorParameters; GetPublic()->Descriptor.TransformParameters = Profile.TransformParameters; #endif return true; } bool UCustomizableInstancePrivate::SaveParametersToProfile(int32 ProfileIndex) { UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject(); if (!CustomizableObject) { return false; } #if WITH_EDITOR bSelectedProfileDirty = ProfileIndex != SelectedProfileIndex; if (ProfileIndex < 0 || ProfileIndex >= CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles().Num()) { return false; } FProfileParameterDat& Profile = CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles()[ProfileIndex]; Profile.BoolParameters = GetPublic()->Descriptor.BoolParameters; Profile.IntParameters = GetPublic()->Descriptor.IntParameters; Profile.FloatParameters = GetPublic()->Descriptor.FloatParameters; Profile.TextureParameters = GetPublic()->Descriptor.TextureParameters; Profile.ProjectorParameters = GetPublic()->Descriptor.ProjectorParameters; Profile.VectorParameters = GetPublic()->Descriptor.VectorParameters; Profile.TransformParameters = GetPublic()->Descriptor.TransformParameters; #endif return true; } bool UCustomizableInstancePrivate::MigrateProfileParametersToCurrentInstance(int32 ProfileIndex) { UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject(); if (!CustomizableObject) { return false; } #if WITH_EDITOR if (ProfileIndex < 0 || ProfileIndex >= CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles().Num()) { return false; } FProfileParameterDat& Profile = CustomizableObject->GetPrivate()->GetInstancePropertiesProfiles()[ProfileIndex]; FProfileParameterDat TempProfile; TempProfile.ProfileName = Profile.ProfileName; TempProfile.BoolParameters = GetPublic()->Descriptor.BoolParameters; TempProfile.FloatParameters = GetPublic()->Descriptor.FloatParameters; TempProfile.IntParameters = GetPublic()->Descriptor.IntParameters; TempProfile.ProjectorParameters = GetPublic()->Descriptor.ProjectorParameters; TempProfile.TextureParameters = GetPublic()->Descriptor.TextureParameters; TempProfile.VectorParameters = GetPublic()->Descriptor.VectorParameters; TempProfile.TransformParameters = GetPublic()->Descriptor.TransformParameters; // Populate TempProfile with the parameters found in the profile. // Any profile parameter missing will be discarded. for (FCustomizableObjectBoolParameterValue& Parameter : TempProfile.BoolParameters) { using ParamValType = FCustomizableObjectBoolParameterValue; ParamValType* Found = Profile.BoolParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.ParameterValue = Found->ParameterValue; } } for (FCustomizableObjectIntParameterValue& Parameter : TempProfile.IntParameters) { using ParamValType = FCustomizableObjectIntParameterValue; ParamValType* Found = Profile.IntParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.ParameterValueName = Found->ParameterValueName; } } for (FCustomizableObjectFloatParameterValue& Parameter : TempProfile.FloatParameters) { using ParamValType = FCustomizableObjectFloatParameterValue; ParamValType* Found = Profile.FloatParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.ParameterValue = Found->ParameterValue; Parameter.ParameterRangeValues = Found->ParameterRangeValues; } } for (FCustomizableObjectTextureParameterValue& Parameter : TempProfile.TextureParameters) { using ParamValType = FCustomizableObjectTextureParameterValue; ParamValType* Found = Profile.TextureParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.ParameterValue = Found->ParameterValue; } } for (FCustomizableObjectSkeletalMeshParameterValue& Parameter : TempProfile.SkeletalMeshParameters) { using ParamValType = FCustomizableObjectSkeletalMeshParameterValue; ParamValType* Found = Profile.SkeletalMeshParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.ParameterValue = Found->ParameterValue; } } for (FCustomizableObjectVectorParameterValue& Parameter : TempProfile.VectorParameters) { using ParamValType = FCustomizableObjectVectorParameterValue; ParamValType* Found = Profile.VectorParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.ParameterValue = Found->ParameterValue; } } for (FCustomizableObjectProjectorParameterValue& Parameter : TempProfile.ProjectorParameters) { using ParamValType = FCustomizableObjectProjectorParameterValue; ParamValType* Found = Profile.ProjectorParameters.FindByPredicate( [&Parameter](const ParamValType& P) { return P.ParameterName == Parameter.ParameterName; }); if (Found) { Parameter.RangeValues = Found->RangeValues; Parameter.Value = Found->Value; } } Profile = TempProfile; //CustomizableObject->Modify(); #endif return true; } UCustomizableObjectInstance* UCustomizableInstancePrivate::GetPublic() const { UCustomizableObjectInstance* Public = StaticCast(GetOuter()); check(Public); return Public; } void UCustomizableInstancePrivate::SetSelectedParameterProfileDirty() { UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject(); if (!CustomizableObject) { return; } #if WITH_EDITOR bSelectedProfileDirty = SelectedProfileIndex != INDEX_NONE; if (bSelectedProfileDirty) { CustomizableObject->Modify(); } #endif } bool UCustomizableInstancePrivate::IsSelectedParameterProfileDirty() const { #if WITH_EDITOR return bSelectedProfileDirty && SelectedProfileIndex != INDEX_NONE; #else return false; #endif } void UCustomizableInstancePrivate::DiscardResources() { check(IsInGameThread()); UCustomizableObjectInstance* Instance = Cast(GetOuter()); if (!Instance) { return; } if (SkeletalMeshStatus == ESkeletalMeshStatus::Success) { if (CVarEnableReleaseMeshResources.GetValueOnGameThread()) { for (const TTuple>& Tuple : SkeletalMeshes) { USkeletalMesh* SkeletalMesh = Tuple.Get<1>(); if (SkeletalMesh->IsValidLowLevel() && !SkeletalMesh->HasPendingInitOrStreaming()) { SkeletalMesh->ReleaseResources(); } } } SkeletalMeshes.Empty(); ReleaseMutableResources(false, *Instance); } InvalidateGeneratedData(); } void UCustomizableInstancePrivate::SetReferenceSkeletalMesh() const { UCustomizableObjectInstance* Instance = Cast(GetOuter()); if (!Instance) { return; } UCustomizableObject* CustomizableObject = Instance->GetCustomizableObject(); if (!CustomizableObject) { return; } const UModelResources* ModelResources = CustomizableObject->GetPrivate()->GetModelResources(); if (!ModelResources) { return; } for (TObjectIterator It; It; ++It) { UCustomizableObjectInstanceUsage* CustomizableObjectInstanceUsage = *It; if (!IsValid(CustomizableObjectInstanceUsage) || CustomizableObjectInstanceUsage->GetCustomizableObjectInstance() != Instance) { continue; } #if WITH_EDITOR if (CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer)) { continue; } #endif const FName& ComponentName = CustomizableObjectInstanceUsage->GetComponentName(); const int32 ObjectComponentIndex = ModelResources->ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName); if (!ModelResources->ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex)) { continue; } if (USkeletalMeshComponent* Parent = CustomizableObjectInstanceUsage->GetAttachParent()) { Parent->EmptyOverrideMaterials(); TSoftObjectPtr SoftObjectPtr = ModelResources->ReferenceSkeletalMeshesData[ObjectComponentIndex].SoftSkeletalMesh; USkeletalMesh* SkeletalMesh = MutablePrivate::LoadObject(SoftObjectPtr); Parent->SetSkeletalMesh(SkeletalMesh); } } } namespace { inline float unpack_uint8(uint8 i) { float res = i; res -= 127.5f; res /= 127.5f; return res; } } bool MutableTextureUsesOfflineProcessedData() { #if PLATFORM_DESKTOP || PLATFORM_ANDROID || PLATFORM_IOS return true; #else return false; #endif } void SetTexturePropertiesFromMutableImageProps(UTexture2D* Texture, const FMutableModelImageProperties& Props, bool bNeverStream) { #if !PLATFORM_DESKTOP if (UCustomizableObjectSystem::GetInstance()->GetPrivate()->EnableMutableProgressiveMipStreaming <= 0) { Texture->NeverStream = true; } else { Texture->NeverStream = bNeverStream; } #else Texture->NeverStream = bNeverStream; #endif Texture->bNotOfflineProcessed = !MutableTextureUsesOfflineProcessedData(); Texture->SRGB = Props.SRGB; Texture->Filter = Props.Filter; Texture->LODBias = Props.LODBias; if (Props.MipGenSettings == TextureMipGenSettings::TMGS_NoMipmaps) { Texture->NeverStream = true; } #if WITH_EDITORONLY_DATA Texture->MipGenSettings = Props.MipGenSettings; Texture->bFlipGreenChannel = Props.FlipGreenChannel; #endif Texture->LODGroup = Props.LODGroup; Texture->AddressX = Props.AddressX; Texture->AddressY = Props.AddressY; } UCustomizableInstancePrivate* UCustomizableObjectInstance::GetPrivate() const { check(PrivateData); // Currently this is initialized in the constructor so we expect it always to exist. return PrivateData; } FMutableUpdateCandidate::FMutableUpdateCandidate(UCustomizableObjectInstance* InCustomizableObjectInstance): CustomizableObjectInstance(InCustomizableObjectInstance) { const FCustomizableObjectInstanceDescriptor& Descriptor = InCustomizableObjectInstance->GetPrivate()->GetDescriptor(); MinLOD = Descriptor.MinLOD; QualitySettingMinLODs = Descriptor.QualitySettingMinLODs; FirstRequestedLOD = Descriptor.GetFirstRequestedLOD(); } bool FMutableUpdateCandidate::HasBeenIssued() const { return bHasBeenIssued; } void FMutableUpdateCandidate::Issue() { bHasBeenIssued = true; } void FMutableUpdateCandidate::ApplyLODUpdateParamsToInstance(FUpdateContextPrivate& Context) { CustomizableObjectInstance->Descriptor.MinLOD = MinLOD; CustomizableObjectInstance->Descriptor.QualitySettingMinLODs = QualitySettingMinLODs; CustomizableObjectInstance->Descriptor.FirstRequestedLOD = FirstRequestedLOD; Context.SetMinLOD(MinLOD); Context.SetQualitySettingMinLODs(QualitySettingMinLODs); Context.SetFirstRequestedLOD(FirstRequestedLOD); } // The memory allocated in the function and pointed by the returned pointer is owned by the caller and must be freed. // If assigned to a UTexture2D, it will be freed by that UTexture2D FTexturePlatformData* MutableCreateImagePlatformData(TSharedPtr MutableImage, int32 OnlyLOD, uint16 FullSizeX, uint16 FullSizeY) { int32 SizeX = FMath::Max(MutableImage->GetSize()[0], FullSizeX); int32 SizeY = FMath::Max(MutableImage->GetSize()[1], FullSizeY); if (SizeX <= 0 || SizeY <= 0) { UE_LOG(LogMutable, Warning, TEXT("Invalid parameters specified for UCustomizableInstancePrivate::MutableCreateImagePlatformData()")); return nullptr; } int32 FirstLOD = 0; for (int32 l = 0; l < OnlyLOD; ++l) { if (SizeX <= 4 || SizeY <= 4) { break; } SizeX = FMath::Max(SizeX / 2, 1); SizeY = FMath::Max(SizeY / 2, 1); ++FirstLOD; } int32 MaxSize = FMath::Max(SizeX, SizeY); int32 FullLODCount = 1; int32 MipsToSkip = 0; if (OnlyLOD < 0) { FullLODCount = FMath::CeilLogTwo(MaxSize) + 1; MipsToSkip = FullLODCount - MutableImage->GetLODCount(); check(MipsToSkip >= 0); } // Reduce final texture size if we surpass the max size we can generate. UCustomizableObjectSystem* System = UCustomizableObjectSystem::GetInstance(); UCustomizableObjectSystemPrivate* SystemPrivate = System ? System->GetPrivate() : nullptr; int32 MaxTextureSizeToGenerate = SystemPrivate ? SystemPrivate->MaxTextureSizeToGenerate : 0; if (MaxTextureSizeToGenerate > 0) { // Skip mips only if texture streaming is disabled const bool bIsStreamingEnabled = MipsToSkip > 0; // Skip mips if the texture surpasses a certain size if (MaxSize > MaxTextureSizeToGenerate && !bIsStreamingEnabled && OnlyLOD < 0) { // Skip mips until MaxSize is equal or less than MaxTextureSizeToGenerate or there aren't more mips to skip while (MaxSize > MaxTextureSizeToGenerate && FirstLOD < (FullLODCount - 1)) { MaxSize = MaxSize >> 1; FirstLOD++; } // Update SizeX and SizeY SizeX = SizeX >> FirstLOD; SizeY = SizeY >> FirstLOD; } } if (MutableImage->GetLODCount() == 1) { MipsToSkip = 0; FullLODCount = 1; FirstLOD = 0; } int32 EndLOD = OnlyLOD < 0 ? FullLODCount : FirstLOD + 1; mu::EImageFormat MutableFormat = MutableImage->GetFormat(); int32 MaxPossibleSize = 0; if (MaxTextureSizeToGenerate > 0) { MaxPossibleSize = int32(FMath::Pow(2.f, float(FullLODCount - FirstLOD - 1))); } else { MaxPossibleSize = int32(FMath::Pow(2.f, float(FullLODCount - 1))); } // This could happen with non-power-of-two images. //check(SizeX == MaxPossibleSize || SizeY == MaxPossibleSize || FullLODCount == 1); if (!(SizeX == MaxPossibleSize || SizeY == MaxPossibleSize || FullLODCount == 1)) { UE_LOG(LogMutable, Warning, TEXT("Building instance: unsupported texture size %d x %d."), SizeX, SizeY); //return nullptr; } mu::FImageOperator ImOp = mu::FImageOperator::GetDefault(mu::FImageOperator::FImagePixelFormatFunc()); EPixelFormat PlatformFormat = PF_Unknown; switch (MutableFormat) { case mu::EImageFormat::RGB_UByte: // performance penalty. can happen in states that remove compression. PlatformFormat = PF_R8G8B8A8; UE_LOG(LogMutable, Display, TEXT("Building instance: a texture was generated in a format not supported by the hardware (RGB), this results in an additional conversion, so a performance penalty.")); break; case mu::EImageFormat::BGRA_UByte: // performance penalty. can happen with texture parameter images. PlatformFormat = PF_R8G8B8A8; UE_LOG(LogMutable, Display, TEXT("Building instance: a texture was generated in a format not supported by the hardware (BGRA), this results in an additional conversion, so a performance penalty.")); break; // Good cases: case mu::EImageFormat::RGBA_UByte: PlatformFormat = PF_R8G8B8A8; break; case mu::EImageFormat::BC1: PlatformFormat = PF_DXT1; break; case mu::EImageFormat::BC2: PlatformFormat = PF_DXT3; break; case mu::EImageFormat::BC3: PlatformFormat = PF_DXT5; break; case mu::EImageFormat::BC4: PlatformFormat = PF_BC4; break; case mu::EImageFormat::BC5: PlatformFormat = PF_BC5; break; case mu::EImageFormat::L_UByte: PlatformFormat = PF_G8; break; case mu::EImageFormat::ASTC_4x4_RGB_LDR: PlatformFormat = PF_ASTC_4x4; break; case mu::EImageFormat::ASTC_4x4_RGBA_LDR:PlatformFormat = PF_ASTC_4x4; break; case mu::EImageFormat::ASTC_4x4_RG_LDR: PlatformFormat = PF_ASTC_4x4; break; default: // Cannot prepare texture if it's not in the right format, this can happen if mutable is in debug mode or in case of bugs UE_LOG(LogMutable, Warning, TEXT("Building instance: a texture was generated in an unsupported format, it will be converted to Unreal with a performance penalty.")); switch (mu::GetImageFormatData(MutableFormat).Channels) { case 1: PlatformFormat = PF_R8; MutableImage = ImOp.ImagePixelFormat(0, MutableImage.Get(), mu::EImageFormat::L_UByte); break; case 2: case 3: case 4: PlatformFormat = PF_R8G8B8A8; MutableImage = ImOp.ImagePixelFormat(0, MutableImage.Get(), mu::EImageFormat::RGBA_UByte); break; default: // Absolutely worst case return nullptr; } } FTexturePlatformData* PlatformData = new FTexturePlatformData(); PlatformData->SizeX = SizeX; PlatformData->SizeY = SizeY; PlatformData->PixelFormat = PlatformFormat; // Allocate mipmaps. if (!FMath::IsPowerOfTwo(SizeX) || !FMath::IsPowerOfTwo(SizeY)) { EndLOD = FirstLOD + 1; MipsToSkip = 0; FullLODCount = 1; } for (int32 MipLevelUE = FirstLOD; MipLevelUE < EndLOD; ++MipLevelUE) { int32 MipLevelMutable = MipLevelUE - MipsToSkip; // Unlike Mutable, UE expects MIPs sizes to be at least the size of the compression block. // For example, a 8x8 PF_DXT1 texture will have the following MIPs: // Mutable Unreal Engine // 8x8 8x8 // 4x4 4x4 // 2x2 4x4 // 1x1 4x4 // // Notice that even though Mutable reports MIP smaller than the block size, the actual data contains at least a block. FTexture2DMipMap* Mip = new FTexture2DMipMap( FMath::Max(SizeX, GPixelFormats[PlatformFormat].BlockSizeX) , FMath::Max(SizeY, GPixelFormats[PlatformFormat].BlockSizeY)); PlatformData->Mips.Add(Mip); if(MipLevelUE >= MipsToSkip || OnlyLOD>=0) { check(MipLevelMutable >= 0); check(MipLevelMutable < MutableImage->GetLODCount()); Mip->BulkData.Lock(LOCK_READ_WRITE); Mip->BulkData.ClearBulkDataFlags(BULKDATA_SingleUse); const uint8* MutableData = MutableImage->GetLODData(MipLevelMutable); const uint32 SourceDataSize = MutableImage->GetLODDataSize(MipLevelMutable); uint32 DestDataSize = (MutableFormat == mu::EImageFormat::RGB_UByte) ? (SourceDataSize/3) * 4 : SourceDataSize; void* pData = Mip->BulkData.Realloc(DestDataSize); // Special inefficient cases if (MutableFormat== mu::EImageFormat::BGRA_UByte) { check(SourceDataSize==DestDataSize); MUTABLE_CPUPROFILER_SCOPE(Innefficent_BGRA_Format_Conversion); uint8_t* pDest = reinterpret_cast(pData); for (size_t p = 0; p < SourceDataSize / 4; ++p) { pDest[p * 4 + 0] = MutableData[p * 4 + 2]; pDest[p * 4 + 1] = MutableData[p * 4 + 1]; pDest[p * 4 + 2] = MutableData[p * 4 + 0]; pDest[p * 4 + 3] = MutableData[p * 4 + 3]; } } else if (MutableFormat == mu::EImageFormat::RGB_UByte) { MUTABLE_CPUPROFILER_SCOPE(Innefficent_RGB_Format_Conversion); uint8_t* pDest = reinterpret_cast(pData); for (size_t p = 0; p < SourceDataSize / 3; ++p) { pDest[p * 4 + 0] = MutableData[p * 3 + 0]; pDest[p * 4 + 1] = MutableData[p * 3 + 1]; pDest[p * 4 + 2] = MutableData[p * 3 + 2]; pDest[p * 4 + 3] = 255; } } // Normal case else { check(SourceDataSize == DestDataSize); FMemory::Memcpy(pData, MutableData, SourceDataSize); } Mip->BulkData.Unlock(); } else { Mip->BulkData.SetBulkDataFlags(BULKDATA_PayloadInSeperateFile); Mip->BulkData.ClearBulkDataFlags(BULKDATA_PayloadAtEndOfFile); } SizeX /= 2; SizeY /= 2; SizeX = SizeX > 0 ? SizeX : 1; SizeY = SizeY > 0 ? SizeY : 1; } #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Some consistency checks for dev builds int32 BulkDataCount = 0; for (int32 i = 0; i < PlatformData->Mips.Num(); ++i) { if (i > 0) { check(PlatformData->Mips[i].SizeX == PlatformData->Mips[i - 1].SizeX / 2 || PlatformData->Mips[i].SizeX == GPixelFormats[PlatformFormat].BlockSizeX); check(PlatformData->Mips[i].SizeY == PlatformData->Mips[i - 1].SizeY / 2 || PlatformData->Mips[i].SizeY == GPixelFormats[PlatformFormat].BlockSizeY); } if (PlatformData->Mips[i].BulkData.GetBulkDataSize() > 0) { BulkDataCount++; } } if (MaxTextureSizeToGenerate > 0) { check(FullLODCount == 1 || OnlyLOD >= 0 || (BulkDataCount == (MutableImage->GetLODCount() - FirstLOD))); } else { check(FullLODCount == 1 || OnlyLOD >= 0 || (BulkDataCount == MutableImage->GetLODCount())); } #endif return PlatformData; } void ConvertImage(UTexture2D* Texture, TSharedPtr MutableImage, const FMutableModelImageProperties& Props, int OnlyLOD, int32 ExtractChannel) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::ConvertImage); SetTexturePropertiesFromMutableImageProps(Texture, Props, false); mu::EImageFormat MutableFormat = MutableImage->GetFormat(); // Extract a single channel, if requested. if (ExtractChannel >= 0) { mu::FImageOperator ImOp = mu::FImageOperator::GetDefault(mu::FImageOperator::FImagePixelFormatFunc()); MutableImage = ImOp.ImagePixelFormat( 4, MutableImage.Get(), mu::EImageFormat::RGBA_UByte ); uint8_t Channel = uint8_t( FMath::Clamp(ExtractChannel,0,3) ); MutableImage = ImOp.ImageSwizzle( mu::EImageFormat::L_UByte, &MutableImage, &Channel ); MutableFormat = mu::EImageFormat::L_UByte; } // Hack: This format is unsupported in UE, but it shouldn't happen in production. if (MutableFormat == mu::EImageFormat::RGB_UByte) { UE_LOG(LogMutable, Warning, TEXT("Building instance: a texture was generated in RGB format, which is slow to convert to Unreal.")); // Expand the image. TSharedPtr Converted = MakeShared(MutableImage->GetSizeX(), MutableImage->GetSizeY(), MutableImage->GetLODCount(), mu::EImageFormat::RGBA_UByte, mu::EInitializationType::NotInitialized); for (int32 LODIndex = 0; LODIndex < Converted->GetLODCount(); ++LODIndex) { int32 PixelCount = MutableImage->GetLODDataSize(LODIndex)/3; const uint8* pSource = MutableImage->GetMipData(LODIndex); uint8* pTarget = Converted->GetMipData(LODIndex); for (int32 p = 0; p < PixelCount; ++p) { pTarget[4 * p + 0] = pSource[3 * p + 0]; pTarget[4 * p + 1] = pSource[3 * p + 1]; pTarget[4 * p + 2] = pSource[3 * p + 2]; pTarget[4 * p + 3] = 255; } } MutableImage = Converted; MutableFormat = mu::EImageFormat::RGBA_UByte; } else if (MutableFormat == mu::EImageFormat::BGRA_UByte) { UE_LOG(LogMutable, Warning, TEXT("Building instance: a texture was generated in BGRA format, which is slow to convert to Unreal.")); MUTABLE_CPUPROFILER_SCOPE(Swizzle); // Swizzle the image. // \TODO: Raise a warning? TSharedPtr Converted = MakeShared(MutableImage->GetSizeX(), MutableImage->GetSizeY(), 1, mu::EImageFormat::RGBA_UByte, mu::EInitializationType::NotInitialized); int32 PixelCount = MutableImage->GetSizeX() * MutableImage->GetSizeY(); const uint8* pSource = MutableImage->GetLODData(0); uint8* pTarget = Converted->GetLODData(0); for (int32 p = 0; p < PixelCount; ++p) { pTarget[4 * p + 0] = pSource[4 * p + 2]; pTarget[4 * p + 1] = pSource[4 * p + 1]; pTarget[4 * p + 2] = pSource[4 * p + 0]; pTarget[4 * p + 3] = pSource[4 * p + 3]; } MutableImage = Converted; MutableFormat = mu::EImageFormat::RGBA_UByte; } if (OnlyLOD >= 0) { OnlyLOD = FMath::Min( OnlyLOD, MutableImage->GetLODCount()-1 ); } Texture->SetPlatformData(MutableCreateImagePlatformData(MutableImage,OnlyLOD,0,0) ); } static int32 EnableRayTracingFix = 0; FAutoConsoleVariableRef CVarMutableEnableRayTracingFix( TEXT("mutable.EnableRayTracingFix"), EnableRayTracingFix, TEXT("If 0, Disabled. Generated meshes will have ray tracing enabled.") TEXT("If 1, Enable fix for meshes with mesh LOD streaming. Meshes will have ray tracing disabled.") TEXT("If 2, Enable fix for all generated meshes. Meshes will have ray tracing disabled.") ); void UCustomizableInstancePrivate::InitSkeletalMeshData(const TSharedRef& OperationData, USkeletalMesh* SkeletalMesh, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, const UCustomizableObject& CustomizableObject, FCustomizableObjectComponentIndex ObjectComponentIndex) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::InitSkeletalMesh); check(SkeletalMesh); const FName ComponentName = OperationData->ComponentNames[ObjectComponentIndex.GetValue()]; SkeletalMesh->NeverStream = !OperationData->bStreamMeshLODs; SkeletalMesh->SetImportedBounds(RefSkeletalMeshData.Bounds); SkeletalMesh->SetPostProcessAnimBlueprint(RefSkeletalMeshData.PostProcessAnimInst.Get()); SkeletalMesh->SetShadowPhysicsAsset(RefSkeletalMeshData.ShadowPhysicsAsset.Get()); const bool bEnableRayTracingFix = EnableRayTracingFix == 2 || (EnableRayTracingFix == 1 && OperationData->bStreamMeshLODs); if (bEnableRayTracingFix) { SkeletalMesh->SetSupportRayTracing(false); } SkeletalMesh->SetHasVertexColors(false); // Set the default Physics Assets SkeletalMesh->SetPhysicsAsset(RefSkeletalMeshData.PhysicsAsset.Get()); SkeletalMesh->SetEnablePerPolyCollision(RefSkeletalMeshData.Settings.bEnablePerPolyCollision); // Asset User Data { const FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); check(ComponentData); for (TObjectPtr AssetUserData : ComponentData->AssetUserDataArray) { SkeletalMesh->AddAssetUserData(AssetUserData); } //Custom Asset User Data if (OperationData->Instance->GetAnimationGameplayTags().Num() || ComponentData->AnimSlotToBP.Num()) { UCustomizableObjectInstanceUserData* InstanceData = NewObject(SkeletalMesh, NAME_None, RF_Public | RF_Transactional); InstanceData->AnimationGameplayTag = OperationData->Instance->GetAnimationGameplayTags(); for (const TTuple>& AnimSlot : ComponentData->AnimSlotToBP) { FCustomizableObjectAnimationSlot AnimationSlot; AnimationSlot.Name = AnimSlot.Key; AnimationSlot.AnimInstance = AnimSlot.Value; InstanceData->AnimationSlots.Add(AnimationSlot); } SkeletalMesh->AddAssetUserData(InstanceData); } } // Allocate resources for rendering and add LOD Info { MUTABLE_CPUPROFILER_SCOPE(InitSkeletalMesh_AddLODData); SkeletalMesh->AllocateResourceForRendering(); FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering(); int32 NumLODsAvailablePerComponent = OperationData->NumLODsAvailable[ComponentName]; RenderData->NumInlinedLODs = NumLODsAvailablePerComponent - OperationData->FirstResidentLOD[ComponentName]; RenderData->NumNonOptionalLODs = NumLODsAvailablePerComponent - OperationData->FirstLODAvailable[ComponentName]; RenderData->CurrentFirstLODIdx = OperationData->FirstResidentLOD[ComponentName]; RenderData->PendingFirstLODIdx = RenderData->CurrentFirstLODIdx; RenderData->LODBiasModifier = OperationData->FirstLODAvailable[ComponentName]; if (bEnableRayTracingFix) { RenderData->bSupportRayTracing = false; } for (int32 LODIndex = 0; LODIndex < NumLODsAvailablePerComponent; ++LODIndex) { RenderData->LODRenderData.Add(new FSkeletalMeshLODRenderData()); FSkeletalMeshLODRenderData& LODRenderData = RenderData->LODRenderData[LODIndex]; LODRenderData.bIsLODOptional = LODIndex < OperationData->FirstLODAvailable[ComponentName]; LODRenderData.bStreamedDataInlined = LODIndex >= OperationData->FirstResidentLOD[ComponentName]; const FMutableRefLODData& LODData = RefSkeletalMeshData.LODData[LODIndex]; FSkeletalMeshLODInfo& LODInfo = SkeletalMesh->AddLODInfo(); LODInfo.ScreenSize = LODData.LODInfo.ScreenSize; LODInfo.LODHysteresis = LODData.LODInfo.LODHysteresis; LODInfo.bSupportUniformlyDistributedSampling = LODData.LODInfo.bSupportUniformlyDistributedSampling; LODInfo.bAllowCPUAccess = LODData.LODInfo.bAllowCPUAccess; if (bEnableRayTracingFix) { LODInfo.SkinCacheUsage = ESkinCacheUsage::Disabled; } // Disable LOD simplification when baking instances LODInfo.ReductionSettings.NumOfTrianglesPercentage = 1.f; LODInfo.ReductionSettings.NumOfVertPercentage = 1.f; LODInfo.ReductionSettings.MaxNumOfTriangles = TNumericLimits::Max(); LODInfo.ReductionSettings.MaxNumOfVerts = TNumericLimits::Max(); LODInfo.ReductionSettings.bRecalcNormals = 0; LODInfo.ReductionSettings.WeldingThreshold = TNumericLimits::Min(); LODInfo.ReductionSettings.bMergeCoincidentVertBones = 0; LODInfo.ReductionSettings.bImproveTrianglesForCloth = 0; #if WITH_EDITORONLY_DATA LODInfo.ReductionSettings.MaxNumOfTrianglesPercentage = TNumericLimits::Max(); LODInfo.ReductionSettings.MaxNumOfVertsPercentage = TNumericLimits::Max(); LODInfo.BuildSettings.bRecomputeNormals = false; LODInfo.BuildSettings.bRecomputeTangents = false; LODInfo.BuildSettings.bUseMikkTSpace = false; LODInfo.BuildSettings.bComputeWeightedNormals = false; LODInfo.BuildSettings.bRemoveDegenerates = false; LODInfo.BuildSettings.bUseHighPrecisionTangentBasis = false; LODInfo.BuildSettings.bUseHighPrecisionSkinWeights = false; LODInfo.BuildSettings.bUseFullPrecisionUVs = true; LODInfo.BuildSettings.bUseBackwardsCompatibleF16TruncUVs = false; LODInfo.BuildSettings.ThresholdPosition = TNumericLimits::Min(); LODInfo.BuildSettings.ThresholdTangentNormal = TNumericLimits::Min(); LODInfo.BuildSettings.ThresholdUV = TNumericLimits::Min(); LODInfo.BuildSettings.MorphThresholdPosition = TNumericLimits::Min(); LODInfo.BuildSettings.BoneInfluenceLimit = 0; #endif LODInfo.LODMaterialMap.SetNumZeroed(1); } } if (RefSkeletalMeshData.SkeletalMeshLODSettings) { #if WITH_EDITORONLY_DATA SkeletalMesh->SetLODSettings(RefSkeletalMeshData.SkeletalMeshLODSettings); #else // This is the part from the above SkeletalMesh->SetLODSettings that's available in-game RefSkeletalMeshData.SkeletalMeshLODSettings->SetLODSettingsToMesh(SkeletalMesh); #endif } // Set Min LOD (Override the Reference Skeletal Mesh LOD Settings) const UModelResources& ModelResources = CustomizableObject.GetPrivate()->GetModelResourcesChecked(); SkeletalMesh->SetMinLod(FMath::Max(ModelResources.MinLODPerComponent.FindChecked(ComponentName).GetDefault(), static_cast(OperationData->FirstLODAvailable[ComponentName]))); SkeletalMesh->SetQualityLevelMinLod(ModelResources.MinQualityLevelLODPerComponent.FindChecked(ComponentName)); // Set up unreal's default material, will be replaced when building materials { MUTABLE_CPUPROFILER_SCOPE(InitSkeletalMesh_AddDefaultMaterial); UMaterialInterface* UnrealMaterial = UMaterial::GetDefaultMaterial(MD_Surface); SkeletalMesh->GetMaterials().SetNum(1); SkeletalMesh->GetMaterials()[0] = UnrealMaterial; // Default density SetMeshUVChannelDensity(SkeletalMesh->GetMaterials()[0].UVChannelData); } } bool UCustomizableInstancePrivate::BuildSkeletonData(const TSharedRef& OperationData, USkeletalMesh& SkeletalMesh, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, UCustomizableObject& CustomizableObject, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildSkeletonData); FCustomizableObjectComponentIndex ObjectComponentIndex = OperationData->GetObjectComponentIndex(InstanceComponentIndex); bool bCreatedNewSkeleton = false; const TObjectPtr Skeleton = MergeSkeletons(CustomizableObject, RefSkeletalMeshData, ObjectComponentIndex, bCreatedNewSkeleton); if (!Skeleton) { return false; } SkeletalMesh.SetSkeleton(Skeleton); SkeletalMesh.SetRefSkeleton(Skeleton->GetReferenceSkeleton()); FReferenceSkeleton& ReferenceSkeleton = SkeletalMesh.GetRefSkeleton(); const TArray& RawRefBoneInfo = ReferenceSkeleton.GetRawRefBoneInfo(); const int32 RawRefBoneCount = ReferenceSkeleton.GetRawBoneNum(); const TArray& BonePose = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BonePose; TMap>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap; { MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_BuildBoneInfoMap); BoneInfoMap.Reserve(RawRefBoneCount); const UModelResources& ModelResources = CustomizableObject.GetPrivate()->GetModelResourcesChecked(); for (int32 Index = 0; Index < RawRefBoneCount; ++Index) { const FName BoneName = RawRefBoneInfo[Index].Name; const FString BoneNameString = BoneName.ToString().ToLower(); if(const uint32* Hash = ModelResources.BoneNamesMap.Find(BoneNameString)) { const mu::FBoneName Bone(*Hash); TPair& BoneInfo = BoneInfoMap.Add(Bone); BoneInfo.Key = BoneName; BoneInfo.Value = Index; } } } { MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_EnsureBonesExist); // Ensure all required bones are present in the skeleton for (const FInstanceUpdateData::FBone& Bone : BonePose) { if (!BoneInfoMap.Find(Bone.Name)) { UE_LOG(LogMutable, Warning, TEXT("The skeleton of skeletal mesh [%s] is missing a bone with ID [%d], which the mesh requires."), *SkeletalMesh.GetName(), Bone.Name.Id); return false; } } } { MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_ApplyPose); TArray& RefBasesInvMatrix = SkeletalMesh.GetRefBasesInvMatrix(); RefBasesInvMatrix.Empty(RawRefBoneCount); // Calculate the InvRefMatrices to ensure all transforms are there for the second step SkeletalMesh.CalculateInvRefMatrices(); // First step is to update the RefBasesInvMatrix for the bones. for (const FInstanceUpdateData::FBone& Bone : BonePose) { const int32 BoneIndex = BoneInfoMap[Bone.Name].Value; RefBasesInvMatrix[BoneIndex] = Bone.MatrixWithScale; } // The second step is to update the pose transforms in the ref skeleton from the BasesInvMatrix FReferenceSkeletonModifier SkeletonModifier(ReferenceSkeleton, Skeleton); for (int32 RefSkelBoneIndex = 0; RefSkelBoneIndex < RawRefBoneCount; ++RefSkelBoneIndex) { int32 ParentBoneIndex = ReferenceSkeleton.GetParentIndex(RefSkelBoneIndex); if (ParentBoneIndex >= 0) { const FTransform3f BonePoseTransform( RefBasesInvMatrix[RefSkelBoneIndex].Inverse() * RefBasesInvMatrix[ParentBoneIndex]); SkeletonModifier.UpdateRefPoseTransform(RefSkelBoneIndex, (FTransform)BonePoseTransform); } } // Force a CalculateInvRefMatrices RefBasesInvMatrix.Empty(RawRefBoneCount); } { MUTABLE_CPUPROFILER_SCOPE(BuildSkeletonData_CalcInvRefMatrices); SkeletalMesh.CalculateInvRefMatrices(); } USkeleton* GeneratedSkeleton = SkeletalMesh.GetSkeleton(); if(GeneratedSkeleton && bCreatedNewSkeleton) { // If the skeleton is new, it means it has just been merged and the retargeting modes need merging too as the // MergeSkeletons function doesn't do it. Only do it for newly generated ones, not for cached or non-transient ones. GeneratedSkeleton->RecreateBoneTree(&SkeletalMesh); FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); check(ComponentData); TArray>& SkeletonsToMerge = ComponentData->Skeletons.SkeletonsToMerge; check(SkeletonsToMerge.Num() > 1); TMap BoneNamesToRetargetingMode; const int32 NumberOfSkeletons = SkeletonsToMerge.Num(); for (int32 SkeletonIndex = 0; SkeletonIndex < NumberOfSkeletons; ++SkeletonIndex) { const USkeleton* ToMergeSkeleton = SkeletonsToMerge[SkeletonIndex]; const FReferenceSkeleton& ToMergeReferenceSkeleton = ToMergeSkeleton->GetReferenceSkeleton(); const TArray& Bones = ToMergeReferenceSkeleton.GetRawRefBoneInfo(); const int32 NumBones = Bones.Num(); for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { const FMeshBoneInfo& Bone = Bones[BoneIndex]; EBoneTranslationRetargetingMode::Type RetargetingMode = ToMergeSkeleton->GetBoneTranslationRetargetingMode(BoneIndex, false); BoneNamesToRetargetingMode.Add(Bone.Name, RetargetingMode); } } for (const auto& Pair : BoneNamesToRetargetingMode) { const FName& BoneName = Pair.Key; const EBoneTranslationRetargetingMode::Type& RetargetingMode = Pair.Value; const int32 BoneIndex = GeneratedSkeleton->GetReferenceSkeleton().FindRawBoneIndex(BoneName); if (BoneIndex >= 0) { GeneratedSkeleton->SetBoneTranslationRetargetingMode(BoneIndex, RetargetingMode); } } } return true; } void UCustomizableInstancePrivate::BuildMeshSockets(const TSharedRef& OperationData, USkeletalMesh* SkeletalMesh, const UModelResources& ModelResources, const FMutableRefSkeletalMeshData& RefSkeletalMeshData, TSharedPtr MutableMesh) { // Build mesh sockets. MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildMeshSockets); check(SkeletalMesh); const uint32 SocketCount = RefSkeletalMeshData.Sockets.Num(); TArray>& Sockets = SkeletalMesh->GetMeshOnlySocketList(); Sockets.Empty(SocketCount); TMap> SocketMap; // Maps Socket name to Sockets Array index and priority // Add sockets used by the SkeletalMesh of reference. { MUTABLE_CPUPROFILER_SCOPE(BuildMeshSockets_RefMeshSockets); for (uint32 SocketIndex = 0; SocketIndex < SocketCount; ++SocketIndex) { const FMutableRefSocket& RefSocket = RefSkeletalMeshData.Sockets[SocketIndex]; USkeletalMeshSocket* Socket = NewObject(SkeletalMesh, RefSocket.SocketName); Socket->SocketName = RefSocket.SocketName; Socket->BoneName = RefSocket.BoneName; Socket->RelativeLocation = RefSocket.RelativeLocation; Socket->RelativeRotation = RefSocket.RelativeRotation; Socket->RelativeScale = RefSocket.RelativeScale; Socket->bForceAlwaysAnimated = RefSocket.bForceAlwaysAnimated; const int32 LastIndex = Sockets.Add(Socket); SocketMap.Add(Socket->SocketName, TTuple(LastIndex, RefSocket.Priority)); } } // Add or update sockets modified by Mutable. if (MutableMesh) { MUTABLE_CPUPROFILER_SCOPE(BuildMeshSockets_MutableSockets); for (int32 TagIndex = 0; TagIndex < MutableMesh->GetTagCount(); ++TagIndex) { FString Tag = MutableMesh->GetTag(TagIndex); if (Tag.RemoveFromStart("__Socket:")) { check(Tag.IsNumeric()); const int32 MutableSocketIndex = FCString::Atoi(*Tag); if (ModelResources.SocketArray.IsValidIndex(MutableSocketIndex)) { const FMutableRefSocket& MutableSocket = ModelResources.SocketArray[MutableSocketIndex]; int32 IndexToWriteSocket = -1; if (TTuple* FoundSocket = SocketMap.Find(MutableSocket.SocketName)) { if (FoundSocket->Value < MutableSocket.Priority) { // Overwrite the existing socket because the new mesh part one is higher priority IndexToWriteSocket = FoundSocket->Key; FoundSocket->Value = MutableSocket.Priority; } } else { // New Socket USkeletalMeshSocket* Socket = NewObject(SkeletalMesh, MutableSocket.SocketName); IndexToWriteSocket = Sockets.Add(Socket); SocketMap.Add(MutableSocket.SocketName, TTuple(IndexToWriteSocket, MutableSocket.Priority)); } if (IndexToWriteSocket >= 0) { check(Sockets.IsValidIndex(IndexToWriteSocket)); USkeletalMeshSocket* SocketToWrite = Sockets[IndexToWriteSocket]; SocketToWrite->SocketName = MutableSocket.SocketName; SocketToWrite->BoneName = MutableSocket.BoneName; SocketToWrite->RelativeLocation = MutableSocket.RelativeLocation; SocketToWrite->RelativeRotation = MutableSocket.RelativeRotation; SocketToWrite->RelativeScale = MutableSocket.RelativeScale; SocketToWrite->bForceAlwaysAnimated = MutableSocket.bForceAlwaysAnimated; } } } } } #if !WITH_EDITOR SkeletalMesh->RebuildSocketMap(); #endif // !WITH_EDITOR } void UCustomizableInstancePrivate::BuildOrCopyElementData(const TSharedRef& OperationData, USkeletalMesh* SkeletalMesh, UCustomizableObject& CustomizableObject, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyElementData); const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex); if (!Component) { return; } const FName ComponentName = OperationData->ComponentNames[Component->Id.GetValue()]; for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component->LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex]; if (!LOD.SurfaceCount) { continue; } for (int32 SurfaceIndex = 0; SurfaceIndex < LOD.SurfaceCount; ++SurfaceIndex) { new(SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections) FSkelMeshRenderSection(); } } } void UCustomizableInstancePrivate::BuildOrCopyMorphTargetsData(const TSharedRef& OperationData, USkeletalMesh* SkeletalMesh, const USkeletalMesh* LastUpdateSkeletalMesh, UCustomizableObject& CustomizableObject, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyMorphTargetsData); // This is a bit redundant as ComponentMorphTargets should not be generated. if (!CVarEnableRealTimeMorphTargets.GetValueOnAnyThread()) { return; } if (!SkeletalMesh) { return; } FCustomizableObjectComponentIndex ObjectComponentIndex = OperationData->GetObjectComponentIndex(InstanceComponentIndex); const FName ComponentName = OperationData->ComponentNames[ObjectComponentIndex.GetValue()]; FSkeletalMeshMorphTargets* ComponentMorphTargets = OperationData->InstanceUpdateData.RealTimeMorphTargets.Find(ComponentName); if (!ComponentMorphTargets) { return; } const int32 NumMorphTargets = ComponentMorphTargets->RealTimeMorphTargetNames.Num(); TArray>& MorphTargets = SkeletalMesh->GetMorphTargets(); MorphTargets.Empty(NumMorphTargets); for (int32 MorphTargetIndex = 0; MorphTargetIndex < NumMorphTargets; ++MorphTargetIndex) { TArray& MorphTargetData = ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex]; if (MorphTargetData.IsEmpty()) { continue; } const FName& MorphTargetName = ComponentMorphTargets->RealTimeMorphTargetNames[MorphTargetIndex]; UMorphTarget* NewMorphTarget = NewObject(SkeletalMesh, MorphTargetName); NewMorphTarget->BaseSkelMesh = SkeletalMesh; TArray& MorphLODModels = NewMorphTarget->GetMorphLODModels(); if (OperationData->bStreamMeshLODs) { MorphLODModels.SetNum(ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex].Num()); // Streamed LODs const int32 FirstLODAvailable = OperationData->FirstLODAvailable[ComponentName]; for (int32 LODIndex = FirstLODAvailable; LODIndex < OperationData->FirstResidentLOD[ComponentName]; ++LODIndex) { // Copy data required for streaming MorphLODModels[LODIndex].NumVertices = 1; // Trick the engine MorphLODModels[LODIndex].SectionIndices = MoveTemp(MorphTargetData[LODIndex].SectionIndices); } // Residents LODs for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < OperationData->NumLODsAvailable[ComponentName]; ++LODIndex) { MorphLODModels[LODIndex] = ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex][LODIndex]; } } else { MorphLODModels = MoveTemp(ComponentMorphTargets->RealTimeMorphsLODData[MorphTargetIndex]); } MorphTargets.Add(NewMorphTarget); } // Mutable hacky LOD Streaming if (!OperationData->bStreamMeshLODs) { // Copy MorphTargets from the FirstGeneratedLOD to the LODs below const int32 FirstRequestedLOD = OperationData->GetFirstRequestedLOD()[ComponentName]; for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstRequestedLOD; ++LODIndex) { MUTABLE_CPUPROFILER_SCOPE(CopyMorphTargetsData); for (int32 MorphTargetIndex = 0; MorphTargetIndex < MorphTargets.Num(); ++MorphTargetIndex) { MorphTargets[MorphTargetIndex]->GetMorphLODModels()[LODIndex] = MorphTargets[MorphTargetIndex]->GetMorphLODModels()[FirstRequestedLOD]; } } } const bool bInKeepEmptyMorphTargets = OperationData->bStreamMeshLODs; SkeletalMesh->InitMorphTargets(bInKeepEmptyMorphTargets); // True to avoid removing streamed Morph Targets. } namespace { // Only used to be able to create new clothing assets and assign a new guid to them without the factory. class UCustomizableObjectClothingAsset : public UClothingAssetCommon { public: void AssignNewGuid() { AssetGuid = FGuid::NewGuid(); } }; } void UCustomizableInstancePrivate::BuildOrCopyClothingData( const TSharedRef& OperationData, USkeletalMesh* SkeletalMesh, const UModelResources& ModelResources, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex, const TArray>& ClothingPhysicsAssets) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyClothingData); struct FSectionClothMetadata { int32 SectionIndex; int32 LODIndex; int32 ClothAssetIndex; int32 ClothAssetLodIndex; uint32 NumVertices; // Upper bound }; struct FPerClothAssetData { int32 MinLOD = 0; TArray>, TInlineAllocator<8>> AttachedSections; // Indices in SectionsWithCloth for render sections attached to this ClothAsset. FName Name; UPhysicsAsset* PhysicsAsset = nullptr; UClothingAssetCommon* ClothingAsset = nullptr; }; const TArray& ClothingAssetsData = ModelResources.ClothingAssetsData; const TArray& ClothSharedConfigsData = ModelResources.ClothSharedConfigsData; if (!(ClothingAssetsData.Num() && OperationData->InstanceUpdateData.ClothingMeshData.Num())) { return; } const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex); if (!Component) { return; } const FName& ComponentName = OperationData->ComponentNames[Component->Id.GetValue()]; TArray SectionClothMetadata; // Sections must be sorted ascending SectionClothMetadata.Reserve(32); TBitArray<> LODsWithClothing; LODsWithClothing.Init(false, Component->LODCount); // Keep in mind that clothing does not do the Hacky Mutable Streaming Copy. This is because LOD data can not be shared between LODs. // This means that LOD loops are a bit different form usual. With the hacky Mutable streaming, we must generate the requested and the hacky copied ones. // Metadata { int32 NumClothingDataNotFound = 0; for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component->LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex]; if (TSharedPtr MutableMesh = LOD.Mesh) { for (int32 SectionIndex = 0; SectionIndex < MutableMesh->GetSurfaceCount(); ++SectionIndex) { if (!MutableMesh->Surfaces[SectionIndex].SubMeshes.IsValidIndex(0)) { continue; } const mu::FSurfaceSubMesh& SubMesh = MutableMesh->Surfaces[SectionIndex].SubMeshes[0]; const uint32 ClothResourceId = ModelResources.MeshMetadata[SubMesh.ExternalId].ClothingMetadataId; if (ClothResourceId == 0) { continue; } if (MutableMesh->Surfaces[SectionIndex].SubMeshes.Num() > 1) { UE_LOG(LogMutable, Error, TEXT("Section %d has more than one submesh! Skipping section."), SectionIndex); continue; } FClothingMeshData* SectionClothingData = OperationData->InstanceUpdateData.ClothingMeshData.Find(ClothResourceId); if (!SectionClothingData) { ++NumClothingDataNotFound; continue; } check(SectionClothingData->ClothingAssetIndex != INDEX_NONE); check(SectionClothingData->ClothingAssetLOD != INDEX_NONE); const int32 ClothAssetIndex = SectionClothingData->ClothingAssetIndex; const int32 ClothAssetLodIndex = SectionClothingData->ClothingAssetLOD; check(SectionClothingData->ClothingAssetIndex == ClothAssetIndex); // Defensive check, this indicates the clothing data might be stale and needs to be recompiled. // Should never happen. if (!ensure(ClothAssetIndex >= 0 && ClothAssetIndex < ClothingAssetsData.Num() && ClothingAssetsData[ClothAssetIndex].LodData.Num())) { continue; } const uint32 NumVertices = SubMesh.VertexEnd - SubMesh.VertexBegin; SectionClothMetadata.Add(FSectionClothMetadata { SectionIndex, LODIndex, ClothAssetIndex, ClothAssetLodIndex, NumVertices }); LODsWithClothing[LODIndex] = true; } } } if (NumClothingDataNotFound > 0) { UE_LOG(LogMutable, Error, TEXT("Some clothing data could not be loaded properly, clothing assets may not behave as expected.")); } } // No clothing, early out. if (!SectionClothMetadata.Num()) { return; } TMap PerClothAssetData; PerClothAssetData.Reserve(32); // Per Cloth Asset data { // Gather attached sections clothing asset LOD. for (int32 MetadataIndex = 0; MetadataIndex < SectionClothMetadata.Num(); ++MetadataIndex) { const FSectionClothMetadata& SectionClothing = SectionClothMetadata[MetadataIndex]; FPerClothAssetData& AssetData = PerClothAssetData.FindOrAdd(SectionClothing.ClothAssetIndex); AssetData.MinLOD = FMath::Min(AssetData.MinLOD, SectionClothing.ClothAssetLodIndex); int32 MaxLOD = FMath::Max(AssetData.AttachedSections.Num() - 1, SectionClothing.ClothAssetLodIndex); AssetData.AttachedSections.SetNum(MaxLOD + 1); AssetData.AttachedSections[SectionClothing.ClothAssetLodIndex].Add(MetadataIndex); } for (TPair& Data : PerClothAssetData) { int32 ClothAssetIndex = Data.Key; FPerClothAssetData& ClothAssetData = Data.Value; ClothAssetData.Name = ClothingAssetsData[ClothAssetIndex].Name; ClothAssetData.PhysicsAsset = ClothingPhysicsAssets[ClothAssetIndex]; } } TArray SectionsClothData; // Sorted by LOD, Section SectionsClothData.Reserve(32); // Data { MUTABLE_CPUPROFILER_SCOPE(DiscoverSectionsWithCloth); int32 NumClothingDataNotFound = 0; for (int32 LODIndex = OperationData->FirstResidentLOD[ComponentName]; LODIndex < Component->LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex]; if (TSharedPtr MutableMesh = LOD.Mesh) { GetSectionClothData(*MutableMesh, LODIndex, OperationData->InstanceUpdateData.ClothingMeshData, SectionsClothData, NumClothingDataNotFound); } } if (NumClothingDataNotFound > 0) { UE_LOG(LogMutable, Error, TEXT("Some clothing data could not be loaded properly, clothing assets may not behave as expected.")); } CopyMeshToMeshClothData(SectionsClothData); } // Create Clothing Assets { MUTABLE_CPUPROFILER_SCOPE(CreateClothingAssets) auto CreateNewClothConfigFromData = [](UObject* Outer, const FCustomizableObjectClothConfigData& ConfigData) -> UClothConfigCommon* { UClass* ClothConfigClass = FindObject(nullptr, *ConfigData.ClassPath); if (ClothConfigClass) { UClothConfigCommon* ClothConfig = NewObject(Outer, ClothConfigClass); if (ClothConfig) { FMemoryReaderView MemoryReader(ConfigData.ConfigBytes); ClothConfig->Serialize(MemoryReader); return ClothConfig; } } return nullptr; }; TArray> SharedConfigs; SharedConfigs.Reserve(ClothSharedConfigsData.Num()); for (const FCustomizableObjectClothConfigData& ConfigData : ClothSharedConfigsData) { UClothConfigCommon* ClothConfig = CreateNewClothConfigFromData(SkeletalMesh, ConfigData); if (ClothConfig) { SharedConfigs.Emplace(ConfigData.ConfigName, ClothConfig); } } bool bAllNamesUnique = true; TArray> UniqueAssetNames; for (TPair& AssetData : PerClothAssetData) { const int32 PrevNumUniqueElems = UniqueAssetNames.Num(); const int32 ElemIndex = UniqueAssetNames.AddUnique(AssetData.Value.Name); if (ElemIndex < PrevNumUniqueElems) { bAllNamesUnique = false; break; } } for (TPair& AssetData : PerClothAssetData) { int32 AssetIndex = AssetData.Key; FPerClothAssetData& ClothAssetData = AssetData.Value; FName ClothingAssetObjectName = bAllNamesUnique ? ClothAssetData.Name : FName(FString::Printf(TEXT("%s_%d"), *ClothAssetData.Name.ToString(), AssetIndex)); UCustomizableObjectClothingAsset* NewClothingAsset = NewObject(SkeletalMesh, ClothingAssetObjectName); NewClothingAsset->AssignNewGuid(); const int32 NumClothLODs = ClothingAssetsData[AssetIndex].LodData.Num() - ClothAssetData.MinLOD; NewClothingAsset->LodData.SetNum(NumClothLODs); for (int32 LODIndex = 0; LODIndex < NumClothLODs; ++LODIndex) { NewClothingAsset->LodData[LODIndex] = ClothingAssetsData[AssetIndex].LodData[LODIndex + ClothAssetData.MinLOD]; } // Reconstruct clothing asset lod map. NewClothingAsset->LodMap.Init(INDEX_NONE, Component->LODCount); for (int32 LODIndex = 0; LODIndex < NumClothLODs; ++LODIndex) { for (int32 SectionWithClothIndex : ClothAssetData.AttachedSections[LODIndex]) { NewClothingAsset->LodMap[SectionClothMetadata[SectionWithClothIndex].LODIndex] = LODIndex; } } NewClothingAsset->UsedBoneIndices = ClothingAssetsData[AssetIndex].UsedBoneIndices; NewClothingAsset->UsedBoneNames = ClothingAssetsData[AssetIndex].UsedBoneNames; NewClothingAsset->ReferenceBoneIndex = ClothingAssetsData[AssetIndex].ReferenceBoneIndex; NewClothingAsset->RefreshBoneMapping(SkeletalMesh); NewClothingAsset->CalculateReferenceBoneIndex(); NewClothingAsset->PhysicsAsset = ClothAssetData.PhysicsAsset; for (const FCustomizableObjectClothConfigData& ConfigData : ClothingAssetsData[AssetIndex].ConfigsData) { UClothConfigCommon* ClothConfig = CreateNewClothConfigFromData(NewClothingAsset, ConfigData); if (ClothConfig) { NewClothingAsset->ClothConfigs.Add(ConfigData.ConfigName, ClothConfig); } } for (const TTuple& SharedConfig : SharedConfigs) { NewClothingAsset->ClothConfigs.Add(SharedConfig); } ClothAssetData.ClothingAsset = NewClothingAsset; SkeletalMesh->GetMeshClothingAssets().AddUnique(NewClothingAsset); } } const bool bAllowClothingPhysicsEdits = !bDisableClothingPhysicsEditsPropagation && ModelResources.bAllowClothingPhysicsEditsPropagation && !OperationData->bStreamMeshLODs; if (bAllowClothingPhysicsEdits) { if (IMutableClothingModule* MutableClothingModule = FModuleManager::GetModulePtr(MUTABLE_CLOTHING_MODULE_NAME)) { for (TPair& Data : PerClothAssetData) { FPerClothAssetData& ClothAssetData = Data.Value; UClothingAssetCommon* ClothingAsset = ClothAssetData.ClothingAsset; if (!ClothingAsset) { continue; } bool bNeedsLodTransitionUpdate = false; for (int32 LODIndex = 0; LODIndex < ClothingAsset->LodData.Num(); ++LODIndex) { TArray, TInlineAllocator<8>> MeshToMeshDataViews; for (int32 AttachedSectionIndex : ClothAssetData.AttachedSections[LODIndex]) { MeshToMeshDataViews.Add(MakeArrayView(SectionsClothData[AttachedSectionIndex].MappingData)); } bool bModified = MutableClothingModule->UpdateClothSimulationLOD( LODIndex, *ClothingAsset, MakeConstArrayView(MeshToMeshDataViews)); bNeedsLodTransitionUpdate = bNeedsLodTransitionUpdate || bModified; } if (bNeedsLodTransitionUpdate) { // This needs to happen after all LODs have been processed. for (int32 LODIndex = 0; LODIndex < ClothingAsset->LodData.Num(); ++LODIndex) { MutableClothingModule->FixLODTransitionMappings(LODIndex, *ClothingAsset); } } } } else { UE_LOG(LogMutable, Warning, TEXT("MutableClothing plugin could not be found. Make sure the plugin is enabled if you want to use advanced clothing features.")); } } TArray> ResidentLODMappingData; ResidentLODMappingData.SetNum(Component->LODCount); TArray> ResidentLODClothIndexMapping; ResidentLODClothIndexMapping.SetNum(Component->LODCount); // Zero all LODs (even those which do not use cloth). for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component->LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD + LODIndex]; TSharedPtr MutableMesh = LOD.Mesh; if (!MutableMesh) { continue; } ResidentLODClothIndexMapping[LODIndex].SetNumZeroed(MutableMesh->GetSurfaceCount()); } // Create the mapping of cloth LODs. for (const UnrealConversionUtils::FSectionClothData& Data : SectionsClothData) { CreateClothMapping(Data, ResidentLODMappingData[Data.LODIndex], ResidentLODClothIndexMapping[Data.LODIndex]); } FSkeletalMeshRenderData* RenderResource = SkeletalMesh->GetResourceForRendering(); { MUTABLE_CPUPROFILER_SCOPE(InitClothRenderData) // Streamed for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < OperationData->FirstResidentLOD[ComponentName]; ++LODIndex) { FSkeletalMeshLODRenderData& LODModel = RenderResource->LODRenderData[LODIndex]; if (LODsWithClothing[LODIndex]) { TArray ClothIndexMapping; ClothIndexMapping.SetNumZeroed(LODModel.RenderSections.Num()); int32 Stride = sizeof(FMeshToMeshVertData); int32 NumVertices = 0; // Upper bound for (const FSectionClothMetadata& Metadata : SectionClothMetadata) { if (Metadata.LODIndex == LODIndex) { // Based on FSkeletalMeshLODModel::GetClothMappingData(). FSkelMeshRenderSection& RenderSection = LODModel.RenderSections[Metadata.SectionIndex]; check(Metadata.NumVertices == RenderSection.NumVertices); // Both values are upper bounds since we can not know the exact number of vertices without executing the code. ClothIndexMapping[Metadata.SectionIndex].BaseVertexIndex = RenderSection.BaseVertexIndex; ClothIndexMapping[Metadata.SectionIndex].MappingOffset = NumVertices; ClothIndexMapping[Metadata.SectionIndex].LODBiasStride = Metadata.NumVertices; NumVertices += Metadata.NumVertices; } } LODModel.ClothVertexBuffer.SetMetadata(ClothIndexMapping, Stride, NumVertices); } } // Resident for (int32 LODIndex = OperationData->FirstResidentLOD[ComponentName]; LODIndex < OperationData->NumLODsAvailable[ComponentName]; ++LODIndex) { FSkeletalMeshLODRenderData& LODModel = RenderResource->LODRenderData[LODIndex]; if (LODsWithClothing[LODIndex]) { LODModel.ClothVertexBuffer.Init(ResidentLODMappingData[LODIndex], ResidentLODClothIndexMapping[LODIndex]); } } } for (const FSectionClothMetadata& Metadata : SectionClothMetadata) { FSkeletalMeshLODRenderData& LODModel = RenderResource->LODRenderData[Metadata.LODIndex]; FSkelMeshRenderSection& SectionData = LODModel.RenderSections[Metadata.SectionIndex]; // Ideally we would copy the data of all LODs, but we do not have this information in the initial generation. In any case, // ClothMappingDataLODs is only used for CPU Skinning, and some engine checks (they only check the array size). // The size must be a multiple of SectionData.NumVertices. Currently Mutable only supports one influence per vertex (NumVertices * 1). SectionData.ClothMappingDataLODs.AddDefaulted(1); SectionData.ClothMappingDataLODs[0].SetNum(SectionData.NumVertices); // = MoveTemp(SectionWithCloth.MappingData); FPerClothAssetData& AssetData = PerClothAssetData.FindChecked(Metadata.ClothAssetIndex); SectionData.CorrespondClothAssetIndex = SkeletalMesh->GetClothingAssetIndex(AssetData.ClothingAsset); SectionData.ClothingData.AssetGuid = AssetData.ClothingAsset->GetAssetGuid(); SectionData.ClothingData.AssetLodIndex = AssetData.ClothingAsset->LodMap[Metadata.LODIndex]; } SkeletalMesh->SetHasActiveClothingAssets(!SectionClothMetadata.IsEmpty()); } bool UCustomizableInstancePrivate::BuildOrCopyRenderData(const TSharedRef& OperationData, USkeletalMesh* SkeletalMesh, const USkeletalMesh* LastUpdateSkeletalMesh, UCustomizableObjectInstance* Public, FCustomizableObjectInstanceComponentIndex InstanceComponentIndex) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildOrCopyRenderData); FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering(); check(RenderData); UCustomizableObject* CustomizableObject = Public->GetCustomizableObject(); // It must be not null as it's checked in the calling function check(CustomizableObject); const FInstanceUpdateData::FComponent* Component = OperationData->GetComponentUpdateData(InstanceComponentIndex); if (!Component) { return false; } const UModelResources& ModelResources = CustomizableObject->GetPrivate()->GetModelResourcesChecked(); const FName ComponentName = ModelResources.ComponentNamesPerObjectComponent[Component->Id.GetValue()]; for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < Component->LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex]; if (!LOD.Mesh || LOD.SurfaceCount == 0) { continue; } SetLastMeshId(Component->Id, LODIndex, LOD.MeshID); } const int32 FirstLOD = OperationData->bStreamMeshLODs ? OperationData->FirstLODAvailable[ComponentName] : OperationData->GetFirstRequestedLOD()[ComponentName]; for (int32 LODIndex = FirstLOD; LODIndex < Component->LODCount; ++LODIndex) { MUTABLE_CPUPROFILER_SCOPE(BuildRenderData); const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component->FirstLOD+LODIndex]; // There could be components without a mesh in LODs if (!LOD.Mesh || LOD.SurfaceCount == 0) { UE_LOG(LogMutable, Warning, TEXT("Building instance: generated mesh [%s] has LOD [%d] of object component index [%d] with no mesh.") , *SkeletalMesh->GetName() , LODIndex , Component->Id.GetValue()); // End with failure return false; } TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("BuildRenderData: Component index %d, LOD %d"), Component->Id.GetValue(), LODIndex)); FSkeletalMeshLODRenderData& LODResource = RenderData->LODRenderData[LODIndex]; const TMap>& BoneInfoMap = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex.GetValue()].BoneInfoMap; // Set active and required bones { const TArray& ActiveBones = OperationData->InstanceUpdateData.ActiveBones; LODResource.ActiveBoneIndices.Reserve(LOD.ActiveBoneCount); for (uint32 Index = 0; Index < LOD.ActiveBoneCount; ++Index) { const uint16 ActiveBoneIndex = BoneInfoMap[ActiveBones[LOD.FirstActiveBone + Index]].Value; LODResource.ActiveBoneIndices.Add(ActiveBoneIndex); } LODResource.RequiredBones = LODResource.ActiveBoneIndices; LODResource.RequiredBones.Sort(); } // Find referenced surface metadata. const int32 MeshNumSurfaces = LOD.Mesh->Surfaces.Num(); TArray MeshSurfacesMetadata; MeshSurfacesMetadata.Init(nullptr, MeshNumSurfaces); for (int32 MeshSectionIndex = 0; MeshSectionIndex < MeshNumSurfaces; ++MeshSectionIndex) { uint32 MeshSurfaceId = LOD.Mesh->GetSurfaceId(MeshSectionIndex); int32 InstanceSurfaceIndex = OperationData->MutableInstance->FindSurfaceById(InstanceComponentIndex.GetValue(), LODIndex, MeshSurfaceId); if (InstanceSurfaceIndex < 0) { continue; } uint32 SurfaceMetadataId = OperationData->MutableInstance->GetSurfaceCustomId(InstanceComponentIndex.GetValue(), LODIndex, InstanceSurfaceIndex); uint32 UsedSurfaceMetadataId = 0; if (SurfaceMetadataId != 0) { UsedSurfaceMetadataId = SurfaceMetadataId; } else { // In case the surface does not have metadata, check if any submesh has surface metadata. for (const mu::FSurfaceSubMesh& SubMesh : LOD.Mesh->Surfaces[MeshSectionIndex].SubMeshes) { const FMutableMeshMetadata* FoundMeshMetadata = ModelResources.MeshMetadata.Find(SubMesh.ExternalId); if (!FoundMeshMetadata) { continue; } UsedSurfaceMetadataId = FoundMeshMetadata->SurfaceMetadataId; if (UsedSurfaceMetadataId != 0) { break; } } } MeshSurfacesMetadata[MeshSectionIndex] = ModelResources.SurfaceMetadata.Find(UsedSurfaceMetadataId); } // Set RenderSections UnrealConversionUtils::SetupRenderSections( LODResource, LOD.Mesh.Get(), OperationData->InstanceUpdateData.BoneMaps, BoneInfoMap, LOD.FirstBoneMap, MeshSurfacesMetadata); // Set SkinWeightProfiles LODResource.SkinWeightProfilesData.Init(&LODResource.SkinWeightVertexBuffer); // Active SkinWeightProfiles ID and Name TArray> ActiveSkinWeightProfiles; const mu::FMeshBufferSet& MutableMeshVertexBuffers = LOD.Mesh->GetVertexBuffers(); const int32 NumBuffers = MutableMeshVertexBuffers.GetBufferCount(); for (int32 BufferIndex = 0; BufferIndex < NumBuffers; ++BufferIndex) { if (MutableMeshVertexBuffers.Buffers[BufferIndex].Channels.IsEmpty()) { continue; } mu::EMeshBufferSemantic Semantic; int32 SemanticIndex; MutableMeshVertexBuffers.GetChannel(BufferIndex, 0, &Semantic, &SemanticIndex, nullptr, nullptr, nullptr); if (Semantic != mu::EMeshBufferSemantic::AltSkinWeight) { continue; } const FMutableSkinWeightProfileInfo* ProfileInfo = ModelResources.SkinWeightProfilesInfo.FindByPredicate( [&SemanticIndex](const FMutableSkinWeightProfileInfo& P) { return P.NameId == SemanticIndex; }); if (ensure(ProfileInfo)) { const FSkinWeightProfileInfo* ExistingProfile = SkeletalMesh->GetSkinWeightProfiles().FindByPredicate( [&ProfileInfo](const FSkinWeightProfileInfo& P) { return P.Name == ProfileInfo->Name; }); if (!ExistingProfile) { SkeletalMesh->AddSkinWeightProfile({ ProfileInfo->Name, ProfileInfo->DefaultProfile, ProfileInfo->DefaultProfileFromLODIndex }); } ActiveSkinWeightProfiles.Add({ ProfileInfo->NameId, ProfileInfo->Name }); LODResource.SkinWeightProfilesData.AddOverrideData(ProfileInfo->Name); } } if (LODResource.bStreamedDataInlined) // Non-streamable LOD { // Copy Vertices UnrealConversionUtils::CopyMutableVertexBuffers( LODResource, LOD.Mesh.Get(), SkeletalMesh->GetLODInfo(LODIndex)->bAllowCPUAccess); // SurfaceIDs. Required to copy index buffers with padding TArray SurfaceIDs; SurfaceIDs.SetNum(LOD.SurfaceCount); for (int32 SurfaceIndex = 0; SurfaceIndex < LOD.SurfaceCount; ++SurfaceIndex) { SurfaceIDs[SurfaceIndex] = LOD.Mesh->GetSurfaceId(SurfaceIndex); } // Copy indices. bool bMarkRenderStateDirty = false; if (!UnrealConversionUtils::CopyMutableIndexBuffers(LODResource, LOD.Mesh.Get(), SurfaceIDs, bMarkRenderStateDirty)) { // End with failure return false; } // Copy SkinWeightProfiles UnrealConversionUtils::CopyMutableSkinWeightProfilesBuffers( LODResource, *SkeletalMesh, LODIndex, LOD.Mesh.Get(), ActiveSkinWeightProfiles); } else // Streamable LOD. { // Init VertexBuffers for streaming UnrealConversionUtils::InitVertexBuffersWithDummyData( LODResource, LOD.Mesh.Get(), SkeletalMesh->GetLODInfo(LODIndex)->bAllowCPUAccess); // Init IndexBuffers for streaming UnrealConversionUtils::InitIndexBuffersWithDummyData(LODResource, LOD.Mesh.Get()); } if (LODResource.StaticVertexBuffers.ColorVertexBuffer.GetNumVertices()) { SkeletalMesh->SetHasVertexColors(true); } if (LODResource.DoesVertexBufferUse16BitBoneIndex() && !UCustomizableObjectSystem::GetInstance()->IsSupport16BitBoneIndexEnabled()) { OperationData->UpdateResult = EUpdateResult::Error16BitBoneIndex; const FString Msg = FString::Printf(TEXT("Customizable Object [%s] requires of Skinning - 'Support 16 Bit Bone Index' to be enabled. Please, update the Project Settings."), *CustomizableObject->GetName()); UE_LOG(LogMutable, Error, TEXT("%s"), *Msg); #if WITH_EDITOR FNotificationInfo Info(FText::FromString(Msg)); Info.bFireAndForget = true; Info.FadeOutDuration = 1.0f; Info.ExpireDuration = 10.0f; FSlateNotificationManager::Get().AddNotification(Info); #endif } } // Mutable hacky LOD Streaming if (!OperationData->bStreamMeshLODs) { // Copy LODRenderData from the FirstRequestedLOD to the LODs below const int32 FirstRequestedLOD = OperationData->GetFirstRequestedLOD()[ComponentName]; for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstRequestedLOD; ++LODIndex) { TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("CopyRenderData: From LOD %d to LOD %d"), FirstRequestedLOD, LODIndex)); // Render Data will be reused from the previously generated component FSkeletalMeshLODRenderData& SourceLODResource = RenderData->LODRenderData[FirstRequestedLOD]; FSkeletalMeshLODRenderData& LODResource = RenderData->LODRenderData[LODIndex]; UnrealConversionUtils::CopySkeletalMeshLODRenderData( LODResource, SourceLODResource, *SkeletalMesh, LODIndex, SkeletalMesh->GetLODInfo(LODIndex)->bAllowCPUAccess ); } } return true; } UE::Tasks::FTask UCustomizableInstancePrivate::LoadAdditionalAssetsAndData(const TSharedRef& OperationData) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::LoadAdditionalAssetsAndDataAsync); UCustomizableObject* CustomizableObject = GetPublic()->GetCustomizableObject(); const UModelResources& ModelResources = CustomizableObject->GetPrivate()->GetModelResourcesChecked(); const TSharedPtr& ModelStreamableBulkData = CustomizableObject->GetPrivate()->GetModelStreamableBulkData(); FMutableStreamRequest StreamRequest(ModelStreamableBulkData); TArray AssetsToStream; TArray& Components = OperationData->InstanceUpdateData.Components; ObjectToInstanceIndexMap.Empty(); ReferencedMaterials.Empty(); const int32 NumClothingAssets = ModelResources.ClothingAssetsData.Num(); ClothingPhysicsAssets.Reset(NumClothingAssets); ClothingPhysicsAssets.SetNum(NumClothingAssets); GatheredAnimBPs.Empty(); AnimBPGameplayTags.Reset(); AnimBpPhysicsAssets.Reset(); for (const FInstanceUpdateData::FSurface& Surface : OperationData->InstanceUpdateData.Surfaces) { const int32 MaterialIndex = Surface.MaterialIndex; if (MaterialIndex<0 || ObjectToInstanceIndexMap.Contains(MaterialIndex)) { continue; } TSoftObjectPtr AssetPtr = ModelResources.Materials.IsValidIndex(MaterialIndex) ? ModelResources.Materials[MaterialIndex] : nullptr; UMaterialInterface* LoadedMaterial = AssetPtr.Get(); const int32 ReferencedMaterialsIndex = ReferencedMaterials.Add(LoadedMaterial); ObjectToInstanceIndexMap.Add(MaterialIndex, ReferencedMaterialsIndex); if (!LoadedMaterial && !AssetPtr.IsNull()) { AssetsToStream.Add(AssetPtr.ToSoftObjectPath()); } } for (const FInstanceUpdateData::FComponent& Component : OperationData->InstanceUpdateData.Components) { if (Component.OverlayMaterial == INDEX_NONE || ObjectToInstanceIndexMap.Contains(Component.OverlayMaterial)) { continue; } TSoftObjectPtr AssetPtr = ModelResources.Materials.IsValidIndex(Component.OverlayMaterial) ? ModelResources.Materials[Component.OverlayMaterial] : nullptr; UMaterialInterface* LoadedMaterial = AssetPtr.Get(); const int32 ReferencedMaterialsIndex = ReferencedMaterials.Add(LoadedMaterial); ObjectToInstanceIndexMap.Add(Component.OverlayMaterial, ReferencedMaterialsIndex); if (!LoadedMaterial && !AssetPtr.IsNull()) { AssetsToStream.Add(AssetPtr.ToSoftObjectPath()); } } // Load Skeletons required by the SubMeshes of the newly generated Mesh, will be merged later for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < OperationData->NumInstanceComponents; ++InstanceComponentIndex) { FCustomizableObjectComponentIndex ObjectComponentIndex = OperationData->GetObjectComponentIndex(FCustomizableObjectInstanceComponentIndex(InstanceComponentIndex)); if (!ObjectComponentIndex.IsValid()) { continue; } const FInstanceUpdateData::FSkeletonData& SkeletonData = OperationData->InstanceUpdateData.SkeletonsPerInstanceComponent[InstanceComponentIndex]; FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); if (!ComponentData) { continue; } // Reuse merged Skeleton if cached ComponentData->Skeletons.Skeleton = CustomizableObject->GetPrivate()->SkeletonCache.Get(SkeletonData.SkeletonIds); if (ComponentData->Skeletons.Skeleton) { ComponentData->Skeletons.SkeletonIds.Empty(); ComponentData->Skeletons.SkeletonsToMerge.Empty(); continue; } // Add Skeletons to merge for (const uint32 SkeletonId : SkeletonData.SkeletonIds) { TSoftObjectPtr AssetPtr = ModelResources.Skeletons.IsValidIndex(SkeletonId) ? ModelResources.Skeletons[SkeletonId] : nullptr; if (AssetPtr.IsNull()) { continue; } // Add referenced skeletons to the assets to stream ComponentData->Skeletons.SkeletonIds.Add(SkeletonId); if (USkeleton* Skeleton = AssetPtr.Get()) { ComponentData->Skeletons.SkeletonsToMerge.Add(Skeleton); } else { AssetsToStream.Add(AssetPtr.ToSoftObjectPath()); } } } bool bHasInvalidMesh = false; const bool bUpdateMeshes = DoComponentsNeedUpdate(GetPublic(), OperationData, bHasInvalidMesh); // Load assets coming from SubMeshes of the newly generated Mesh if (OperationData->InstanceUpdateData.LODs.Num()) { for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < OperationData->InstanceUpdateData.Components.Num(); ++InstanceComponentIndex) { const FInstanceUpdateData::FComponent& Component = Components[InstanceComponentIndex]; FCustomizableInstanceComponentData* ComponentData = GetComponentData(Component.Id); TSharedPtr FirstComponentMesh = OperationData->InstanceUpdateData.LODs.IsValidIndex(Component.FirstLOD) ? OperationData->InstanceUpdateData.LODs[Component.FirstLOD].Mesh : nullptr; if (FirstComponentMesh && FirstComponentMesh->IsReference()) { // Pass-through components don't have a Reference Mesh so don't access it continue; } const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id; const FMutableRefSkeletalMeshData& RefSkeletalMeshData = ModelResources.ReferenceSkeletalMeshesData[ObjectComponentIndex.GetValue()]; for (int32 AssetUserDataIndex : RefSkeletalMeshData.AssetUserDataIndices) { #if !WITH_EDITOR OperationData->StreamedResourceIndex.AddUnique(AssetUserDataIndex); // Used to hold/release streamed resources in non-editor builds. #endif ComponentData->StreamedResourceIndex.AddUnique(AssetUserDataIndex); } const FName ComponentName = OperationData->ComponentNames[Component.Id.GetValue()]; if (bUpdateMeshes) { // Morphs { // Data for (int32 LODIndex = OperationData->GetFirstRequestedLOD()[ComponentName]; LODIndex < Component.LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex]; TSharedPtr MutableMesh = LOD.Mesh; if (!MutableMesh) { continue; } LoadMorphTargetsData(StreamRequest, MutableMesh.ToSharedRef(), OperationData->InstanceUpdateData.RealTimeMorphTargetMeshData); } // Metadata const int32 FirstLOD = OperationData->bStreamMeshLODs ? OperationData->FirstResidentLOD[ComponentName] : OperationData->GetFirstRequestedLOD()[ComponentName]; for (int32 LODIndex = FirstLOD; LODIndex < Component.LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex]; TSharedPtr MutableMesh = LOD.Mesh; if (!MutableMesh) { continue; } LoadMorphTargetsMetadata(StreamRequest, MutableMesh.ToSharedRef(), OperationData->InstanceUpdateData.RealTimeMorphTargetMeshData); } } // Cloth { // Data // From FirstResidentLOD instead of FirstRequestedLOD since clothing we generate all LODs, even the hacky streaming copied ones. for (int32 LODIndex = OperationData->FirstResidentLOD[ComponentName]; LODIndex < Component.LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex]; TSharedPtr MutableMesh = LOD.Mesh; if (!MutableMesh) { continue; } if (bUpdateMeshes) { LoadClothing(StreamRequest, MutableMesh.ToSharedRef(), OperationData->InstanceUpdateData.ClothingMeshData); } } // Metadata for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component.LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex]; TSharedPtr MutableMesh = LOD.Mesh; if (!MutableMesh) { continue; } const TArray& StreamedResources = MutableMesh->GetStreamedResources(); for (uint64 ResourceId : StreamedResources) { FCustomizableObjectStreameableResourceId TypedResourceId = BitCast(ResourceId); if (TypedResourceId.Type == (uint8)FCustomizableObjectStreameableResourceId::EType::Clothing) { check(TypedResourceId.Id != 0 && TypedResourceId.Id <= TNumericLimits::Max()); const TMap& ClothingStreamables = ModelStreamableBulkData->ClothingStreamables; if (const FClothingStreamable* ClothingStreamable = ClothingStreamables.Find(TypedResourceId.Id)) { FClothingMeshData& ReadDestData = OperationData->InstanceUpdateData.ClothingMeshData.FindOrAdd(TypedResourceId.Id); ReadDestData.ClothingAssetIndex = ClothingStreamable->ClothingAssetIndex; ReadDestData.ClothingAssetLOD = ClothingStreamable->ClothingAssetLOD; // TODO: Add async loading of ClothingAsset Data. This could be loaded as an streamead resource similar to and the asset user data. int32 ClothingAssetIndex = ClothingStreamable->ClothingAssetIndex; int32 PhysicsAssetIndex = ClothingStreamable->PhysicsAssetIndex; const TSoftObjectPtr& PhysicsAsset = ModelResources.PhysicsAssets.IsValidIndex(PhysicsAssetIndex) ? ModelResources.PhysicsAssets[PhysicsAssetIndex] : nullptr; // The entry should always be in the map if (!PhysicsAsset.IsNull()) { if (PhysicsAsset.Get()) { if (ClothingPhysicsAssets.IsValidIndex(ClothingAssetIndex)) { ClothingPhysicsAssets[ClothingAssetIndex] = PhysicsAsset.Get(); } } else { ComponentData->ClothingPhysicsAssetsToStream.Emplace(ClothingAssetIndex, PhysicsAssetIndex); AssetsToStream.AddUnique(PhysicsAsset.ToSoftObjectPath()); } } } else { UE_LOG(LogMutable, Error, TEXT("Invalid streamed clothing data block [%d] found."), TypedResourceId.Id); } } } } } } for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < Component.LODCount; ++LODIndex) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD + LODIndex]; TSharedPtr MutableMesh = LOD.Mesh; if (!MutableMesh) { continue; } const TArray& StreamedResources = MutableMesh->GetStreamedResources(); for (uint64 ResourceId : StreamedResources) { FCustomizableObjectStreameableResourceId TypedResourceId = BitCast(ResourceId); if (TypedResourceId.Type == (uint8)FCustomizableObjectStreameableResourceId::EType::AssetUserData) { const uint32 ResourceIndex = TypedResourceId.Id; #if !WITH_EDITOR OperationData->StreamedResourceIndex.AddUnique(ResourceIndex); // Used to hold/release streamed resources in non-editor builds. #endif ComponentData->StreamedResourceIndex.AddUnique(ResourceIndex); } } for (int32 TagIndex = 0; TagIndex < MutableMesh->GetTagCount(); ++TagIndex) { FString Tag = MutableMesh->GetTag(TagIndex); if (Tag.RemoveFromStart("__PA:")) { const int32 AssetIndex = FCString::Atoi(*Tag); const TSoftObjectPtr& PhysicsAsset = ModelResources.PhysicsAssets.IsValidIndex(AssetIndex) ? ModelResources.PhysicsAssets[AssetIndex] : nullptr; if (!PhysicsAsset.IsNull()) { if (PhysicsAsset.Get()) { ComponentData->PhysicsAssets.PhysicsAssetsToMerge.Add(PhysicsAsset.Get()); } else { ComponentData->PhysicsAssets.PhysicsAssetToLoad.Add(AssetIndex); AssetsToStream.AddUnique(PhysicsAsset.ToSoftObjectPath()); } } } if (Tag.RemoveFromStart("__AnimBP:")) { FString SlotIndexString, AnimBpIndexString; if (Tag.Split(TEXT("_Slot_"), &SlotIndexString, &AnimBpIndexString)) { if (SlotIndexString.IsEmpty() || AnimBpIndexString.IsEmpty()) { continue; } const int32 AnimBpIndex = FCString::Atoi(*AnimBpIndexString); if (!ModelResources.AnimBPs.IsValidIndex(AnimBpIndex)) { continue; } FName SlotIndex = *SlotIndexString; const TSoftClassPtr& AnimBPAsset = ModelResources.AnimBPs[AnimBpIndex]; if (!AnimBPAsset.IsNull()) { const TSoftClassPtr* FoundAnimBpSlot = ComponentData->AnimSlotToBP.Find(SlotIndex); bool bIsSameAnimBp = FoundAnimBpSlot && AnimBPAsset == *FoundAnimBpSlot; if (!FoundAnimBpSlot) { ComponentData->AnimSlotToBP.Add(SlotIndex, AnimBPAsset); if (AnimBPAsset.Get()) { GatheredAnimBPs.Add(AnimBPAsset.Get()); } else { AssetsToStream.AddUnique(AnimBPAsset.ToSoftObjectPath()); } } else if (!bIsSameAnimBp) { // Two submeshes should not have the same animation slot index OperationData->UpdateResult = EUpdateResult::Warning; FString WarningMessage = FString::Printf(TEXT("Two submeshes have the same anim slot index [%s] in a Mutable Instance."), *SlotIndex.ToString()); UE_LOG(LogMutable, Warning, TEXT("%s"), *WarningMessage); #if WITH_EDITOR FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(WarningMessage), EMessageSeverity::Warning, true); #endif } } } } else if (Tag.RemoveFromStart("__AnimBPTag:")) { AnimBPGameplayTags.AddTag(FGameplayTag::RequestGameplayTag(*Tag)); } #if WITH_EDITORONLY_DATA else if (Tag.RemoveFromStart("__MeshPath:")) { ComponentData->MeshPartPaths.Add(Tag); } #endif } const int32 AdditionalPhysicsNum = MutableMesh->AdditionalPhysicsBodies.Num(); for (int32 I = 0; I < AdditionalPhysicsNum; ++I) { const int32 ExternalId = MutableMesh->AdditionalPhysicsBodies[I]->CustomId; ComponentData->PhysicsAssets.AdditionalPhysicsAssetsToLoad.Add(ExternalId); AssetsToStream.Add(ModelResources.AnimBpOverridePhysiscAssetsInfo[ExternalId].SourceAsset.ToSoftObjectPath()); } } for (int32 ResourceIndex : ComponentData->StreamedResourceIndex) { #if WITH_EDITOR if (!ModelResources.StreamedResourceDataEditor.IsValidIndex(ResourceIndex)) { UE_LOG(LogMutable, Error, TEXT("Invalid streamed resource index. Max Index [%d]. Resource Index [%d]."), ModelResources.StreamedResourceDataEditor.Num(), ResourceIndex); continue; } if (const FCustomizableObjectAssetUserData* AUDResource = ModelResources.StreamedResourceDataEditor[ResourceIndex].Data.GetPtr()) { AssetsToStream.AddUnique(AUDResource->AssetUserDataEditor.ToSoftObjectPath()); } #else if (!ModelResources.StreamedResourceData.IsValidIndex(ResourceIndex)) { UE_LOG(LogMutable, Error, TEXT("Invalid streamed resource index. Max Index [%d]. Resource Index [%d]."), ModelResources.StreamedResourceData.Num(), ResourceIndex); continue; } const FCustomizableObjectStreamedResourceData& StreamedResource = ModelResources.StreamedResourceData[ResourceIndex]; if (!StreamedResource.IsLoaded()) { AssetsToStream.AddUnique(StreamedResource.GetPath().ToSoftObjectPath()); } #endif } } } for (TSoftObjectPtr& TextureRef : PassThroughTexturesToLoad) { AssetsToStream.Add(TextureRef.ToSoftObjectPath()); } for (TSoftObjectPtr& MeshRef : PassThroughMeshesToLoad) { AssetsToStream.Add(MeshRef.ToSoftObjectPath()); } // Copy FExtensionData Object node input from the Instance to the InstanceUpdateData for (int32 ExtensionDataIndex = 0; ExtensionDataIndex < OperationData->MutableInstance->GetExtensionDataCount(); ExtensionDataIndex++) { TSharedPtr ExtensionData; FName Name; OperationData->MutableInstance->GetExtensionData(ExtensionDataIndex, ExtensionData, Name); check(ExtensionData); FInstanceUpdateData::FNamedExtensionData& NewEntry = OperationData->InstanceUpdateData.ExtendedInputPins.AddDefaulted_GetRef(); NewEntry.Data = ExtensionData; NewEntry.Name = Name; check(NewEntry.Name != NAME_None); #if WITH_EDITOR if (!ModelResources.StreamedExtensionDataEditor.IsValidIndex(ExtensionData->Index)) { // The compiled data appears to be out of sync with the CO's properties UE_LOG(LogMutable, Error, TEXT("Couldn't find streamed Extension Data with index %d in %s. Compiled data may be stale."), ExtensionData->Index, *CustomizableObject->GetFullName()); } #else if (!ModelResources.StreamedExtensionData.IsValidIndex(ExtensionData->Index)) { // The compiled data appears to be out of sync with the CO's properties UE_LOG(LogMutable, Error, TEXT("Couldn't find streamed Extension Data with index %d in %s. Compiled data may be stale."), ExtensionData->Index, *CustomizableObject->GetFullName()); continue; } const FCustomizableObjectStreamedResourceData& StreamedData = ModelResources.StreamedExtensionData[ExtensionData->Index]; if (StreamedData.IsLoaded()) { continue; } // Note that this just checks if the path is non-null, NOT if the object is loaded check(!StreamedData.GetPath().IsNull()); OperationData->ExtensionStreamedResourceIndex.Add(ExtensionData->Index); AssetsToStream.Add(StreamedData.GetPath().ToSoftObjectPath()); #endif } TArray> Prerequisites; if (AssetsToStream.Num() > 0) { #if WITH_EDITOR // TODO: Remove with UE-217665 when the underlying bug in the ColorPicker is solved // Disable the Slate throttling, otherwise the AsyncLoad may not complete until the editor window is clicked on due to a bug in // some widgets such as the ColorPicker's throttling handling FSlateThrottleManager::Get().DisableThrottle(true); #endif UE::Tasks::FTaskEvent Event(TEXT("AssetsStreamed")); Prerequisites.Add(Event); UCustomizableObjectSystemPrivate* PrivateSystem = UCustomizableObjectSystem::GetInstance()->GetPrivate(); PrivateSystem->StreamableManager->RequestAsyncLoad( AssetsToStream, FStreamableDelegate::CreateUObject(this, &UCustomizableInstancePrivate::AdditionalAssetsAsyncLoaded, OperationData, Event), CVarMutableHighPriorityLoading.GetValueOnAnyThread() ? FStreamableManager::AsyncLoadHighPriority : FStreamableManager::DefaultAsyncLoadPriority); } // Stream files UE::Tasks::FTask StreamingTask = StreamRequest.Stream(); Prerequisites.Add(StreamingTask); return UE::Tasks::Launch(TEXT("CaptureOperationData"), [OperationData]() {}, // Keep a reference to make sure allocated memory is always alive. Prerequisites, UE::Tasks::ETaskPriority::Inherit); } FCustomizableObjectInstanceDescriptor& UCustomizableInstancePrivate::GetDescriptor() const { return GetPublic()->Descriptor; } const TArray>* UCustomizableObjectInstance::GetOverrideMaterials(FCustomizableObjectComponentIndex ComponentIndex) const { UCustomizableObject* Object = GetCustomizableObject(); if (!Object) { return nullptr; } const UModelResources* ModelResources = Object->GetPrivate()->GetModelResources(); if (!ModelResources) { return nullptr; } FName ComponentName = ModelResources->ComponentNamesPerObjectComponent[ComponentIndex.GetValue()]; FCustomizableInstanceComponentData* ComponentData = PrivateData->GetComponentData(ComponentName); return ComponentData ? &ComponentData->OverrideMaterials : nullptr; } void UCustomizableInstancePrivate::AdditionalAssetsAsyncLoaded(const TSharedRef OperationData, UE::Tasks::FTaskEvent Event) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::AdditionalAssetsAsyncLoaded); check(IsInGameThread()) Event.Trigger(); UCustomizableObjectPrivate* CustomizableObjectPrivate = GetPublic()->GetCustomizableObject()->GetPrivate(); UModelResources& ModelResources = *CustomizableObjectPrivate->GetModelResources(); for (int32 ResourceIndex : OperationData->StreamedResourceIndex) { ModelResources.StreamedResourceData[ResourceIndex].Hold(); } for (int32 ResourceIndex : OperationData->ExtensionStreamedResourceIndex) { ModelResources.StreamedExtensionData[ResourceIndex].Hold(); } // Loaded Materials check(ObjectToInstanceIndexMap.Num() == ReferencedMaterials.Num()); for (TPair Pair : ObjectToInstanceIndexMap) { const TSoftObjectPtr& AssetPtr = ModelResources.Materials.IsValidIndex(Pair.Key) ? ModelResources.Materials[Pair.Key] : nullptr; ReferencedMaterials[Pair.Value] = AssetPtr.Get(); #if WITH_EDITOR if (!ReferencedMaterials[Pair.Value]) { if (!AssetPtr.IsNull()) { FString ErrorMsg = FString::Printf(TEXT("Mutable couldn't load the material [%s] and won't be rendered. If it has been deleted or renamed, please recompile all the mutable objects that use it."), *AssetPtr.GetAssetName()); UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg); FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true); } else { ensure(false); // Couldn't load the material, and we don't know which material } } #endif } for (FCustomizableInstanceComponentData& ComponentData : ComponentsData) { for (int32 ResourceIndex : ComponentData.StreamedResourceIndex) { #if WITH_EDITOR if (ModelResources.StreamedResourceDataEditor.IsValidIndex(ResourceIndex)) { if (const FCustomizableObjectAssetUserData* AUDResource = ModelResources.StreamedResourceDataEditor[ResourceIndex].Data.GetPtr()) { ComponentData.AssetUserDataArray.Add(MutablePrivate::LoadObject(AUDResource->AssetUserDataEditor)); // Already loaded } } #else if (ModelResources.StreamedResourceData.IsValidIndex(ResourceIndex) && ModelResources.StreamedResourceData[ResourceIndex].IsLoaded()) { const FCustomizableObjectResourceData& ResourceData = ModelResources.StreamedResourceData[ResourceIndex].GetLoadedData(); if (const FCustomizableObjectAssetUserData* AUDResource = ResourceData.Data.GetPtr()) { ComponentData.AssetUserDataArray.Add(AUDResource->AssetUserData); } } #endif } // Loaded Skeletons FReferencedSkeletons& Skeletons = ComponentData.Skeletons; for (int32 SkeletonIndex : Skeletons.SkeletonIds) { const TSoftObjectPtr& AssetPtr = ModelResources.Skeletons.IsValidIndex(SkeletonIndex) ? ModelResources.Skeletons[SkeletonIndex] : nullptr; Skeletons.SkeletonsToMerge.AddUnique(AssetPtr.Get()); } // Loaded PhysicsAssets FReferencedPhysicsAssets& PhysicsAssets = ComponentData.PhysicsAssets; for(const int32 PhysicsAssetIndex : PhysicsAssets.PhysicsAssetToLoad) { check(ModelResources.PhysicsAssets.IsValidIndex(PhysicsAssetIndex)); const TSoftObjectPtr& PhysicsAsset = ModelResources.PhysicsAssets[PhysicsAssetIndex]; PhysicsAssets.PhysicsAssetsToMerge.Add(PhysicsAsset.Get()); #if WITH_EDITOR if (!PhysicsAsset.Get()) { if (!PhysicsAsset.IsNull()) { FString ErrorMsg = FString::Printf(TEXT("Mutable couldn't load the PhysicsAsset [%s] and won't be merged. If it has been deleted or renamed, please recompile all the mutable objects that use it."), *PhysicsAsset.GetAssetName()); UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg); FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true); } else { ensure(false); // Couldn't load the PhysicsAsset, and we don't know which PhysicsAsset } } #endif } PhysicsAssets.PhysicsAssetToLoad.Empty(); // Loaded Clothing PhysicsAssets for ( TPair& AssetToStream : ComponentData.ClothingPhysicsAssetsToStream ) { const int32 AssetIndex = AssetToStream.Key; if (ClothingPhysicsAssets.IsValidIndex(AssetIndex) && ModelResources.PhysicsAssets.IsValidIndex(AssetToStream.Value)) { const TSoftObjectPtr& PhysicsAssetPtr = ModelResources.PhysicsAssets[AssetToStream.Value]; ClothingPhysicsAssets[AssetIndex] = PhysicsAssetPtr.Get(); } } ComponentData.ClothingPhysicsAssetsToStream.Empty(); // Loaded anim BPs for (TPair>& SlotAnimBP : ComponentData.AnimSlotToBP) { if (TSubclassOf AnimBP = SlotAnimBP.Value.Get()) { if (!GatheredAnimBPs.Contains(AnimBP)) { GatheredAnimBPs.Add(AnimBP); } } #if WITH_EDITOR else { FString ErrorMsg = FString::Printf(TEXT("Mutable couldn't load the AnimBlueprint [%s]. If it has been deleted or renamed, please recompile all the mutable objects that use it."), *SlotAnimBP.Value.GetAssetName()); UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg); FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true); } #endif } const int32 AdditionalPhysicsNum = ComponentData.PhysicsAssets.AdditionalPhysicsAssetsToLoad.Num(); ComponentData.PhysicsAssets.AdditionalPhysicsAssets.Reserve(AdditionalPhysicsNum); for (int32 I = 0; I < AdditionalPhysicsNum; ++I) { // Make the loaded assets references strong. const int32 AnimBpPhysicsOverrideIndex = ComponentData.PhysicsAssets.AdditionalPhysicsAssetsToLoad[I]; ComponentData.PhysicsAssets.AdditionalPhysicsAssets.Add( ModelResources.AnimBpOverridePhysiscAssetsInfo[AnimBpPhysicsOverrideIndex].SourceAsset.Get()); } ComponentData.PhysicsAssets.AdditionalPhysicsAssetsToLoad.Empty(); } LoadedPassThroughTexturesPendingSetMaterial.Empty(PassThroughTexturesToLoad.Num()); for (TSoftObjectPtr& TextureRef : PassThroughTexturesToLoad) { ensure(TextureRef.IsValid()); LoadedPassThroughTexturesPendingSetMaterial.Add(TextureRef.Get()); } PassThroughTexturesToLoad.Empty(); LoadedPassThroughMeshesPendingSetMaterial.Empty(PassThroughMeshesToLoad.Num()); for (TSoftObjectPtr& MeshRef : PassThroughMeshesToLoad) { ensure(MeshRef.IsValid()); LoadedPassThroughMeshesPendingSetMaterial.Add(MeshRef.Get()); } PassThroughMeshesToLoad.Empty(); #if WITH_EDITOR // TODO: Remove with UE-217665 when the underlying bug in the ColorPicker is solved // Reenable the throttling which disabled when launching the Async Load FSlateThrottleManager::Get().DisableThrottle(false); #endif } void UpdateTextureRegionsMutable(UTexture2D* Texture, int32 MipIndex, uint32 NumMips, const FUpdateTextureRegion2D& Region, uint32 SrcPitch, const FByteBulkData* BulkData, TSharedRef& PlatformData) { if (Texture->GetResource()) { struct FUpdateTextureRegionsData { FTexture2DResource* Texture2DResource; int32 MipIndex; FUpdateTextureRegion2D Region; uint32 SrcPitch; uint32 NumMips; // The Platform Data mips will be automatically deleted when all FUpdateTextureRegionsData that reference it are deleted // in the render thread after being used to update the texture TSharedRef PlatformData; FUpdateTextureRegionsData(TSharedRef& InPlatformData) : PlatformData(InPlatformData) {} }; FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData(PlatformData); RegionData->Texture2DResource = (FTexture2DResource*)Texture->GetResource(); RegionData->MipIndex = MipIndex; RegionData->Region = Region; RegionData->SrcPitch = SrcPitch; RegionData->NumMips = NumMips; ENQUEUE_RENDER_COMMAND(UpdateTextureRegionsMutable)( [RegionData, BulkData](FRHICommandList& CmdList) { check(int32(RegionData->NumMips) >= RegionData->Texture2DResource->GetCurrentMipCount()); int32 MipDifference = RegionData->NumMips - RegionData->Texture2DResource->GetCurrentMipCount(); check(MipDifference >= 0); int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip(); uint8* SrcData = (uint8*)BulkData->LockReadOnly(); //uint32 Size = RegionData->SrcPitch / (sizeof(uint8) * 4); //UE_LOG(LogMutable, Warning, TEXT("UpdateTextureRegionsMutable MipIndex = %d, FirstMip = %d, size = %d"), // RegionData->MipIndex, CurrentFirstMip, Size); //checkf(Size <= RegionData->Texture2DResource->GetSizeX(), // TEXT("UpdateTextureRegionsMutable incorrect size. %d, %d. NumMips=%d"), // Size, RegionData->Texture2DResource->GetSizeX(), RegionData->Texture2DResource->GetCurrentMipCount()); if (RegionData->MipIndex >= CurrentFirstMip + MipDifference) { RHIUpdateTexture2D( RegionData->Texture2DResource->GetTexture2DRHI(), RegionData->MipIndex - CurrentFirstMip - MipDifference, RegionData->Region, RegionData->SrcPitch, SrcData); } BulkData->Unlock(); delete RegionData; // This will implicitly delete the Platform Data if this is the last RegionData referencing it }); } } void UCustomizableInstancePrivate::ReuseTexture(UTexture2D* Texture, TSharedRef& PlatformData) { uint32 NumMips = PlatformData->Mips.Num(); for (uint32 i = 0; i < NumMips; i++) { FTexture2DMipMap& Mip = PlatformData->Mips[i]; if (Mip.BulkData.GetElementCount() > 0) { FUpdateTextureRegion2D Region; Region.DestX = 0; Region.DestY = 0; Region.SrcX = 0; Region.SrcY = 0; Region.Width = Mip.SizeX; Region.Height = Mip.SizeY; check(int32(Region.Width) <= Texture->GetSizeX()); check(int32(Region.Height) <= Texture->GetSizeY()); UpdateTextureRegionsMutable(Texture, i, NumMips, Region, Mip.SizeX * sizeof(uint8) * 4, &Mip.BulkData, PlatformData); } } } void UCustomizableInstancePrivate::BuildMaterials(const TSharedRef& OperationData, UCustomizableObjectInstance* Public) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableInstancePrivate::BuildMaterials) UCustomizableObject* CustomizableObject = Public->GetCustomizableObject(); const UModelResources& ModelResources = *CustomizableObject->GetPrivate()->GetModelResources(); TArray NewGeneratedTextures; // Temp copy to allow reuse of MaterialInstances TArray OldGeneratedMaterials; Exchange(OldGeneratedMaterials, GeneratedMaterials); GeneratedMaterials.Reset(); // Prepare the data to store in order to regenerate resources for this instance (usually texture mips). TSharedPtr UpdateContext = MakeShared( CustomizableObject->GetPathName(), Public->GetPathName(), UCustomizableObjectSystem::GetInstance()->GetPrivate()->MutableSystem, OperationData->Model, CustomizableObject->GetPrivate()->GetModelStreamableBulkData(), OperationData->Parameters, OperationData->GetCapturedDescriptor().GetState()); // Cache the descriptor as a string if we want to later report it using our benchmark utility. if (FLogBenchmarkUtil::IsBenchmarkingReportingEnabled()) { UpdateContext->CapturedDescriptor = OperationData->GetCapturedDescriptor().ToString(); if (GWorld) { UpdateContext->bLevelBegunPlay = GWorld->GetBegunPlay(); } } const bool bReuseTextures = OperationData->bReuseInstanceTextures; TArray RecreateRenderStateOnInstanceComponent; RecreateRenderStateOnInstanceComponent.Init(false, OperationData->NumInstanceComponents); TArray NotifyUpdateOnInstanceComponent; NotifyUpdateOnInstanceComponent.Init(false, OperationData->NumInstanceComponents); for (int32 InstanceComponentIndex = 0; InstanceComponentIndex < OperationData->NumInstanceComponents; ++InstanceComponentIndex) { const FInstanceUpdateData::FComponent& Component = OperationData->InstanceUpdateData.Components[InstanceComponentIndex]; const FCustomizableObjectComponentIndex ObjectComponentIndex = Component.Id; if (!ModelResources.ComponentNamesPerObjectComponent.IsValidIndex(ObjectComponentIndex.GetValue())) { continue; } const FName& ComponentName = ModelResources.ComponentNamesPerObjectComponent[ObjectComponentIndex.GetValue()]; TObjectPtr* Result = SkeletalMeshes.Find(ComponentName); TObjectPtr SkeletalMesh = Result ? *Result : nullptr; if (!SkeletalMesh) { continue; } const bool bReuseMaterials = !OperationData->MeshChangedPerInstanceComponent[InstanceComponentIndex]; // If the mesh is not transient, it means it's pass-through so it should use material overrides and not be modified in any way const bool bIsTransientMesh = static_cast(SkeletalMesh->HasAllFlags(EObjectFlags::RF_Transient)); // It is not safe to replace the materials of a SkeletalMesh whose resources are initialized. Use overrides instead. const bool bUseOverrideMaterialsOnly = !bIsTransientMesh || (OperationData->bUseMeshCache && SkeletalMesh->GetResourceForRendering()->IsInitialized()); UMaterialInterface* OverlayMaterial = nullptr; FCustomizableInstanceComponentData* ComponentData = GetComponentData(ObjectComponentIndex); if (ComponentData) { ComponentData->OverrideMaterials.Reset(); ComponentData->OverlayMaterial = nullptr; if (const uint32* ReferencedMaterialIndex = ObjectToInstanceIndexMap.Find(Component.OverlayMaterial)) { if (ReferencedMaterials.IsValidIndex(*ReferencedMaterialIndex)) { ComponentData->OverlayMaterial = ReferencedMaterials[*ReferencedMaterialIndex]; OverlayMaterial = ComponentData->OverlayMaterial; } } } if (!bUseOverrideMaterialsOnly) { RecreateRenderStateOnInstanceComponent[InstanceComponentIndex] |= SkeletalMesh->GetOverlayMaterial() != OverlayMaterial; SkeletalMesh->SetOverlayMaterial(OverlayMaterial); } TArray Materials; // Maps serializations of FMutableMaterialPlaceholder to Created Dynamic Material instances, used to reuse materials across LODs TSet ReuseMaterialCache; // SurfaceId per MaterialSlotIndex TArray SurfaceIdToMaterialIndex; MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_LODLoop); const int32 FirstLOD = OperationData->bStreamMeshLODs ? OperationData->FirstLODAvailable[ComponentName] : OperationData->GetFirstRequestedLOD()[ComponentName]; for (int32 LODIndex = FirstLOD; LODIndex < Component.LODCount; LODIndex++) { const FInstanceUpdateData::FLOD& LOD = OperationData->InstanceUpdateData.LODs[Component.FirstLOD+LODIndex]; if (!bUseOverrideMaterialsOnly && LODIndex < SkeletalMesh->GetLODNum()) { SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Reset(); } // Pass-through components will not have a reference mesh. const FMutableRefSkeletalMeshData* RefSkeletalMeshData = nullptr; if (ModelResources.ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex.GetValue())) { RefSkeletalMeshData = &ModelResources.ReferenceSkeletalMeshesData[ObjectComponentIndex.GetValue()]; } for (int32 SurfaceIndex = 0; SurfaceIndex < LOD.SurfaceCount; ++SurfaceIndex) { const FInstanceUpdateData::FSurface& Surface = OperationData->InstanceUpdateData.Surfaces[LOD.FirstSurface + SurfaceIndex]; // Is this a material in a passthrough mesh that we don't modify? if (Surface.MaterialIndex<0) { Materials.Emplace(); #if WITH_EDITOR // Without this, a change of a referenced material and recompilation doesn't show up in the preview. RecreateRenderStateOnInstanceComponent[InstanceComponentIndex] = true; #endif continue; } // Reuse MaterialSlot from the previous LOD. if (const int32 MaterialIndex = SurfaceIdToMaterialIndex.Find(Surface.SurfaceId); MaterialIndex != INDEX_NONE) { if (!bUseOverrideMaterialsOnly) { const int32 LODMaterialIndex = SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Add(MaterialIndex); SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections[SurfaceIndex].MaterialIndex = LODMaterialIndex; } continue; } const uint32 ReferencedMaterialIndex = ObjectToInstanceIndexMap[Surface.MaterialIndex]; UMaterialInterface* MaterialTemplate = ReferencedMaterials[ReferencedMaterialIndex]; if (!MaterialTemplate) { // Missing MaterialTemplate. Use DefaultMaterial instead. MaterialTemplate = UMaterial::GetDefaultMaterial(MD_Surface); check(MaterialTemplate); UE_LOG(LogMutable, Error, TEXT("Build Materials: Missing referenced template to use as parent material on CustomizableObject [%s]."), *CustomizableObject->GetName()); } // This section will require a new slot SurfaceIdToMaterialIndex.Add(Surface.SurfaceId); // Add and set up the material data for this slot const int32 MaterialSlotIndex = Materials.Num(); FSkeletalMaterial& MaterialSlot = Materials.AddDefaulted_GetRef(); MaterialSlot.MaterialInterface = MaterialTemplate; uint32 UsedSurfaceMetadataId = Surface.SurfaceMetadataId; // If the surface metadata is invalid, check if any of the mesh fragments has metadata. // For now use the fisrt found, an aggregate may be needed. if (Surface.SurfaceMetadataId == 0 && LOD.Mesh) { int32 MeshSurfaceIndex = LOD.Mesh->Surfaces.IndexOfByPredicate([SurfaceId = Surface.SurfaceId](const mu::FMeshSurface& Surface) { return SurfaceId == Surface.Id; }); if (MeshSurfaceIndex != INDEX_NONE) { for (const mu::FSurfaceSubMesh& SubMesh : LOD.Mesh->Surfaces[SurfaceIndex].SubMeshes) { const FMutableMeshMetadata* FoundMeshMetadata = ModelResources.MeshMetadata.Find(SubMesh.ExternalId); if (!FoundMeshMetadata) { continue; } UsedSurfaceMetadataId = FoundMeshMetadata->SurfaceMetadataId; if (UsedSurfaceMetadataId != 0) { break; } } } } const FMutableSurfaceMetadata* FoundSurfaceMetadata = ModelResources.SurfaceMetadata.Find(UsedSurfaceMetadataId); if (FoundSurfaceMetadata) { MaterialSlot.MaterialSlotName = FoundSurfaceMetadata->MaterialSlotName; } if (RefSkeletalMeshData) { SetMeshUVChannelDensity(MaterialSlot.UVChannelData, RefSkeletalMeshData->Settings.DefaultUVChannelDensity); } if (!bUseOverrideMaterialsOnly) { if (SkeletalMesh->GetResourceForRendering()->LODRenderData.IsValidIndex(LODIndex) && SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections.IsValidIndex(SurfaceIndex)) { const int32 LODMaterialIndex = SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap.Add(MaterialSlotIndex); SkeletalMesh->GetResourceForRendering()->LODRenderData[LODIndex].RenderSections[SurfaceIndex].MaterialIndex = LODMaterialIndex; } else { ensure(false); } } FMutableMaterialPlaceholder MutableMaterialPlaceholder; MutableMaterialPlaceholder.ParentMaterialID = MaterialTemplate->GetUniqueID(); MutableMaterialPlaceholder.MatIndex = MaterialSlotIndex; { MUTABLE_CPUPROFILER_SCOPE(ParamLoop); for (int32 VectorIndex = 0; VectorIndex < Surface.VectorCount; ++VectorIndex) { const FInstanceUpdateData::FVector& Vector = OperationData->InstanceUpdateData.Vectors[Surface.FirstVector + VectorIndex]; // Decoding Material Layer from Mutable parameter name FString EncodingString = "-MutableLayerParam:"; FString VectorName = Vector.Name.ToString(); int32 EncodingPosition = VectorName.Find(EncodingString); int32 LayerIndex = -1; if (EncodingPosition == INDEX_NONE) { MutableMaterialPlaceholder.AddParam( FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(Vector.Name), -1, Vector.Vector)); } else { //Getting layer index int32 LayerPosition = VectorName.Len() - (EncodingPosition + EncodingString.Len()); FString IndexString = VectorName.RightChop(VectorName.Len() - LayerPosition); LayerIndex = FCString::Atof(*IndexString); //Getting parameter name FString Sufix = EncodingString + FString::FromInt(LayerIndex); VectorName.RemoveFromEnd(Sufix); MutableMaterialPlaceholder.AddParam( FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(VectorName), LayerIndex, Vector.Vector)); } } for (int32 ScalarIndex = 0; ScalarIndex < Surface.ScalarCount; ++ScalarIndex) { const FInstanceUpdateData::FScalar& Scalar = OperationData->InstanceUpdateData.Scalars[Surface.FirstScalar + ScalarIndex]; // Decoding Material Layer from Mutable parameter name FString EncodingString = "-MutableLayerParam:"; FString ScalarName = Scalar.Name.ToString(); int32 EncodingPosition = ScalarName.Find(EncodingString); int32 LayerIndex = -1; if (EncodingPosition == INDEX_NONE) { MutableMaterialPlaceholder.AddParam( FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(Scalar.Name), -1, Scalar.Scalar)); } else { //Getting layer index int32 LayerPosition = ScalarName.Len() - (EncodingPosition + EncodingString.Len()); FString IndexString = ScalarName.RightChop(ScalarName.Len() - LayerPosition); LayerIndex = FCString::Atof(*IndexString); //Getting parameter name FString Sufix = EncodingString + FString::FromInt(LayerIndex); ScalarName.RemoveFromEnd(Sufix); MutableMaterialPlaceholder.AddParam( FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(FName(ScalarName), LayerIndex, Scalar.Scalar)); } } } { MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_ImageLoop); // Get the cache of resources of all live instances of this object FMutableResourceCache& Cache = UCustomizableObjectSystem::GetInstance()->GetPrivate()->GetObjectCache(CustomizableObject); FString CurrentState = Public->GetCurrentState(); bool bNeverStream = OperationData->bNeverStream; check((bNeverStream && OperationData->MipsToSkip == 0) || (!bNeverStream && OperationData->MipsToSkip >= 0)); for (int32 ImageIndex = 0; ImageIndex < Surface.ImageCount; ++ImageIndex) { const FInstanceUpdateData::FImage& Image = OperationData->InstanceUpdateData.Images[Surface.FirstImage + ImageIndex]; FString KeyName = Image.Name.ToString(); TSharedPtr MutableImage = Image.Image; UTexture2D* MutableTexture = nullptr; // Texture generated by mutable UTexture* PassThroughTexture = nullptr; // Texture not generated by mutable // \TODO: Change this key to a struct. FString TextureReuseCacheRef = bReuseTextures ? FString::Printf(TEXT("%d-%d-%d-%d"), Image.BaseLOD, ObjectComponentIndex.GetValue(), Surface.SurfaceId, ImageIndex) : FString(); // If the mutable image is null, it must be in the cache FMutableImageCacheKey ImageCacheKey = { Image.ImageID, OperationData->MipsToSkip }; if (!MutableImage) { TWeakObjectPtr* CachedPointerPtr = Cache.Images.Find(ImageCacheKey); if (CachedPointerPtr) { ensure(!CachedPointerPtr->IsStale()); MutableTexture = CachedPointerPtr->Get(); } check(MutableTexture); } // Check if the image is a reference to an engine texture if (MutableImage && Image.bIsPassThrough) { check(MutableImage->IsReference()); uint32 ReferenceID = MutableImage->GetReferencedTexture(); if (ModelResources.PassThroughTextures.IsValidIndex(ReferenceID)) { TSoftObjectPtr Ref = ModelResources.PassThroughTextures[ReferenceID]; // The texture should have been loaded by now by LoadAdditionalAssetsAsync() PassThroughTexture = Ref.Get(); if (!PassThroughTexture) { // The texture should be loaded, something went wrong, possibly a bug in LoadAdditionalAssetsAsync() UE_LOG(LogMutable, Error, TEXT("Pass-through texture with name %s hasn't been loaded yet in BuildMaterials(). Forcing sync load."), *Ref.ToSoftObjectPath().ToString()); ensure(false); PassThroughTexture = MutablePrivate::LoadObject(Ref); } } if (!PassThroughTexture) { // Internal error. UE_LOG(LogMutable, Error, TEXT("Missing referenced image [%d]."), ReferenceID); continue; } } // Find the additional information for this image int32 ImageKey = FCString::Atoi(*KeyName); if (ImageKey >= 0 && ImageKey < ModelResources.ImageProperties.Num()) { const FMutableModelImageProperties& Props = ModelResources.ImageProperties[ImageKey]; if (!MutableTexture && !PassThroughTexture && MutableImage) { TWeakObjectPtr* ReusedTexture = bReuseTextures ? TextureReuseCache.Find(TextureReuseCacheRef) : nullptr; // This shared ptr will hold the reused texture platform data (mips) until the reused texture is updated // and delete it automatically TSharedPtr ReusedTexturePlatformData; // Ensure the name of the texture is unique (prevents black textures in editor previews) FString MutableTextureName = FString::Printf(TEXT("T_%s_%llu"), *Props.TextureParameterName, Image.ImageID); MutableTextureName.ReplaceInline(TEXT(" "), TEXT("_")); MutableTextureName = MakeUniqueObjectName(GetTransientPackage(), UTexture2D::StaticClass(), *MutableTextureName, EUniqueObjectNameOptions::GloballyUnique).ToString(); if (ReusedTexture && (*ReusedTexture).IsValid() && !(*ReusedTexture)->HasAnyFlags(RF_BeginDestroyed)) { // Only uncompressed textures can be reused. This also fixes an issue in the editor where textures supposedly // uncompressed by their state, are still compressed because the CO has not been compiled at maximum settings // and the uncompressed setting cannot be applied to them. EPixelFormat PixelFormat = (*ReusedTexture)->GetPixelFormat(); if (PixelFormat == EPixelFormat::PF_R8G8B8A8) { MutableTexture = (*ReusedTexture).Get(); check(MutableTexture != nullptr); } else { ReusedTexture = nullptr; MutableTexture = CreateTexture(MutableTextureName); #if WITH_EDITOR UE_LOG(LogMutable, Warning, TEXT("Tried to reuse an uncompressed texture with name %s. Make sure the selected Mutable state disables texture compression/streaming, that one of the state's runtime parameters affects the texture and that the CO is compiled with max. optimization settings."), *MutableTexture->GetName()); #endif } } else { ReusedTexture = nullptr; MutableTexture = CreateTexture(MutableTextureName); } if (MutableTexture) { if (OperationData->ImageToPlatformDataMap.Contains(Image.ImageID)) { SetTexturePropertiesFromMutableImageProps(MutableTexture, Props, bNeverStream); FTexturePlatformData* PlatformData = OperationData->ImageToPlatformDataMap[Image.ImageID]; if (ReusedTexture) { check(PlatformData->Mips.Num() == MutableTexture->GetPlatformData()->Mips.Num()); check(PlatformData->Mips[0].SizeX == MutableTexture->GetPlatformData()->Mips[0].SizeX); check(PlatformData->Mips[0].SizeY == MutableTexture->GetPlatformData()->Mips[0].SizeY); // Now the ReusedTexturePlatformData shared ptr owns the platform data ReusedTexturePlatformData = TSharedPtr(PlatformData); } else { // Now the MutableTexture owns the platform data MutableTexture->SetPlatformData(PlatformData); } OperationData->ImageToPlatformDataMap.Remove(Image.ImageID); } else { UE_LOG(LogMutable, Error, TEXT("Required image [%s] with ID [%lld] was not generated in the mutable thread, and it is not cached. LOD [%d]. Object Component [%d]"), *Props.TextureParameterName, Image.ImageID, LODIndex, ObjectComponentIndex.GetValue()); continue; } if (bNeverStream) { // To prevent LogTexture Error "Loading non-streamed mips from an external bulk file." for (int32 i = 0; i < MutableTexture->GetPlatformData()->Mips.Num(); ++i) { MutableTexture->GetPlatformData()->Mips[i].BulkData.ClearBulkDataFlags(BULKDATA_PayloadInSeperateFile); } } { MUTABLE_CPUPROFILER_SCOPE(UpdateResource); #if REQUIRES_SINGLEUSE_FLAG_FOR_RUNTIME_TEXTURES for (int32 i = 0; i < MutableTexture->GetPlatformData()->Mips.Num(); ++i) { uint32 DataFlags = MutableTexture->GetPlatformData()->Mips[i].BulkData.GetBulkDataFlags(); MutableTexture->GetPlatformData()->Mips[i].BulkData.SetBulkDataFlags(DataFlags | BULKDATA_SingleUse); } #endif if (ReusedTexture) { // Must remove texture from cache since it will be reused with a different ImageID for (TPair>& CachedTexture : Cache.Images) { if (CachedTexture.Value == MutableTexture) { Cache.Images.Remove(CachedTexture.Key); break; } } check(ReusedTexturePlatformData.IsValid()); if (ReusedTexturePlatformData.IsValid()) { TSharedRef PlatformDataRef = ReusedTexturePlatformData.ToSharedRef(); ReuseTexture(MutableTexture, PlatformDataRef); } } else if (MutableTexture) { //if (!bNeverStream) // No need to check bNeverStream. In that case, the texture won't use // the MutableMipDataProviderFactory anyway and it's needed for detecting Mutable textures elsewhere { UMutableTextureMipDataProviderFactory* MutableMipDataProviderFactory = Cast(MutableTexture->GetAssetUserDataOfClass(UMutableTextureMipDataProviderFactory::StaticClass())); if (!MutableMipDataProviderFactory) { MutableMipDataProviderFactory = NewObject(); if (MutableMipDataProviderFactory) { MutableMipDataProviderFactory->CustomizableObjectInstance = Public; check(LODIndex < 256 && InstanceComponentIndex < 256 && ImageIndex < 256); MutableMipDataProviderFactory->ImageRef.ImageID = Image.ImageID; MutableMipDataProviderFactory->ImageRef.SurfaceId = Surface.SurfaceId; MutableMipDataProviderFactory->ImageRef.LOD = uint8(Image.BaseLOD); MutableMipDataProviderFactory->ImageRef.Component = uint8(InstanceComponentIndex); MutableMipDataProviderFactory->ImageRef.Image = uint8(ImageIndex); MutableMipDataProviderFactory->ImageRef.BaseMip = uint8(Image.BaseMip); MutableMipDataProviderFactory->ImageRef.ConstantImagesNeededToGenerate = Image.ConstantImagesNeededToGenerate; MutableMipDataProviderFactory->UpdateContext = UpdateContext; MutableTexture->AddAssetUserData(MutableMipDataProviderFactory); } } } MutableTexture->UpdateResource(); } } Cache.Images.Add(ImageCacheKey, MutableTexture); } else { UE_LOG(LogMutable, Error, TEXT("Texture creation failed.")); } } FGeneratedTexture TextureData; TextureData.Key = ImageCacheKey; TextureData.Name = Props.TextureParameterName; TextureData.Texture = MutableTexture ? MutableTexture : PassThroughTexture; // Only add textures generated by mutable to the cache if (MutableTexture) { NewGeneratedTextures.Add(TextureData); } // Decoding Material Layer from Mutable parameter name FString ImageName = Image.Name.ToString(); FString EncodingString = "-MutableLayerParam:"; int32 EncodingPosition = ImageName.Find(EncodingString); int32 LayerIndex = -1; if (EncodingPosition == INDEX_NONE) { MutableMaterialPlaceholder.AddParam( FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(*Props.TextureParameterName, -1, TextureData)); } else { //Getting layer index int32 LayerPosition = ImageName.Len() - (EncodingPosition + EncodingString.Len()); FString IndexString = ImageName.RightChop(ImageName.Len() - LayerPosition); LayerIndex = FCString::Atof(*IndexString); MutableMaterialPlaceholder.AddParam( FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam(*Props.TextureParameterName, LayerIndex, TextureData)); } } else { // This means the compiled model (maybe coming from derived data) has images that the asset doesn't know about. UE_LOG(LogMutable, Error, TEXT("CustomizableObject derived data out of sync with asset for [%s]. Try recompiling it."), *CustomizableObject->GetName()); } if (bReuseTextures) { if (MutableTexture) { TextureReuseCache.Add(TextureReuseCacheRef, MutableTexture); } else { TextureReuseCache.Remove(TextureReuseCacheRef); } } } } // Find or create the material for this slot UMaterialInterface* MaterialInterface = MaterialSlot.MaterialInterface; if (FMutableMaterialPlaceholder* FoundMaterialPlaceholder = ReuseMaterialCache.Find(MutableMaterialPlaceholder)) { MaterialInterface = Materials[FoundMaterialPlaceholder->MatIndex].MaterialInterface; } else // Material not cached, create a new one { MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_CreateMaterial); ReuseMaterialCache.Add(MutableMaterialPlaceholder); FGeneratedMaterial& Material = GeneratedMaterials.AddDefaulted_GetRef(); Material.SurfaceId = Surface.SurfaceId; Material.MaterialIndex = Surface.MaterialIndex; Material.MaterialInterface = MaterialInterface; #if WITH_EDITORONLY_DATA Material.ComponentName = ComponentName; #endif UMaterialInstanceDynamic* MaterialInstance = nullptr; if (const int32 OldMaterialIndex = OldGeneratedMaterials.Find(Material); bReuseMaterials && OldMaterialIndex != INDEX_NONE) { const FGeneratedMaterial& OldMaterial = OldGeneratedMaterials[OldMaterialIndex]; MaterialInstance = Cast(OldMaterial.MaterialInterface); Material.MaterialInterface = OldMaterial.MaterialInterface; } if (!MaterialInstance && MutableMaterialPlaceholder.GetParams().Num() != 0) { #if WITH_EDITOR // Remove the MI_ or M_ prefixes from the material string so we use it as the name of the MID FString MIDName = MaterialTemplate->GetName(); { const FString MaterialPrefix = TEXT("M_"); // Material const FString MaterialInstancePrefix = TEXT("MI_"); // Material Instance const FString MaterialInstanceConstantPrefix = TEXT("MIC_"); // Material Instance Constant if (MIDName.Find(MaterialInstancePrefix, ESearchCase::CaseSensitive) == 0) { MIDName = MIDName.RightChop(MaterialInstancePrefix.Len()); } else if (MIDName.Find(MaterialPrefix, ESearchCase::CaseSensitive) == 0) { MIDName = MIDName.RightChop(MaterialPrefix.Len()); } else if (MIDName.Find(MaterialInstanceConstantPrefix, ESearchCase::CaseSensitive) == 0) { MIDName = MIDName.RightChop(MaterialInstanceConstantPrefix.Len()); } const uint32 MaterialPlaceHolderHash = GetTypeHash(MutableMaterialPlaceholder); MIDName = FString::Printf(TEXT("%s%s_%u"),TEXT("MID_"),*MIDName, MaterialPlaceHolderHash); MIDName = MakeUniqueObjectName(GetTransientPackage(), UMaterialInstanceDynamic::StaticClass(), *MIDName, EUniqueObjectNameOptions::GloballyUnique).ToString(); } MaterialInstance = UMaterialInstanceDynamic::Create(MaterialTemplate, GetTransientPackage(), FName(MIDName)); #else MaterialInstance = UMaterialInstanceDynamic::Create(MaterialTemplate, GetTransientPackage()); #endif Material.MaterialInterface = MaterialInstance; } if (MaterialInstance) { for (const FMutableMaterialPlaceholder::FMutableMaterialPlaceHolderParam& Param : MutableMaterialPlaceholder.GetParams()) { switch (Param.Type) { case FMutableMaterialPlaceholder::EPlaceHolderParamType::Vector: if (Param.LayerIndex < 0) { FLinearColor Color = Param.Vector; // HACK: We encode an invalid value (Nan) for table option "None. // Decoding "None" color parameters that use the material color if (FMath::IsNaN(Color.R)) { FMaterialParameterInfo ParameterInfo(Param.ParamName); MaterialTemplate->GetVectorParameterValue(ParameterInfo, Color); } MaterialInstance->SetVectorParameterValue(Param.ParamName, Color); } else { FMaterialParameterInfo ParameterInfo = FMaterialParameterInfo(Param.ParamName, EMaterialParameterAssociation::LayerParameter, Param.LayerIndex); MaterialInstance->SetVectorParameterValueByInfo(ParameterInfo, Param.Vector); } break; case FMutableMaterialPlaceholder::EPlaceHolderParamType::Scalar: if (Param.LayerIndex < 0) { MaterialInstance->SetScalarParameterValue(FName(Param.ParamName), Param.Scalar); } else { FMaterialParameterInfo ParameterInfo = FMaterialParameterInfo(Param.ParamName, EMaterialParameterAssociation::LayerParameter, Param.LayerIndex); MaterialInstance->SetScalarParameterValueByInfo(ParameterInfo, Param.Scalar); } break; case FMutableMaterialPlaceholder::EPlaceHolderParamType::Texture: if (Param.LayerIndex < 0) { MaterialInstance->SetTextureParameterValue(Param.ParamName, Param.Texture.Texture); } else { FMaterialParameterInfo ParameterInfo = FMaterialParameterInfo(Param.ParamName, EMaterialParameterAssociation::LayerParameter, Param.LayerIndex); MaterialInstance->SetTextureParameterValueByInfo(ParameterInfo, Param.Texture.Texture); } if (!bDisableNotifyComponentsOfTextureUpdates) { NotifyUpdateOnInstanceComponent[InstanceComponentIndex] = true; } Material.Textures.Add(Param.Texture); break; } } } MaterialInterface = Material.MaterialInterface; } // Assign the material to the slot, and add it to the OverrideMaterials MaterialSlot.MaterialInterface = MaterialInterface; if (ComponentData) { ComponentData->OverrideMaterials.Add(MaterialInterface); } } } if (!bUseOverrideMaterialsOnly) { // Mutable hacky LOD Streaming if (!OperationData->bStreamMeshLODs) { // Copy data from the FirstLODAvailable into the LODs below. for (int32 LODIndex = OperationData->FirstLODAvailable[ComponentName]; LODIndex < FirstLOD; ++LODIndex) { SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap = SkeletalMesh->GetLODInfo(FirstLOD)->LODMaterialMap; TIndirectArray& LODRenderData = SkeletalMesh->GetResourceForRendering()->LODRenderData; const int32 NumRenderSections = LODRenderData[LODIndex].RenderSections.Num(); check(NumRenderSections == LODRenderData[FirstLOD].RenderSections.Num()); if (NumRenderSections == LODRenderData[FirstLOD].RenderSections.Num()) { for (int32 RenderSectionIndex = 0; RenderSectionIndex < NumRenderSections; ++RenderSectionIndex) { const int32 MaterialIndex = LODRenderData[FirstLOD].RenderSections[RenderSectionIndex].MaterialIndex; LODRenderData[LODIndex].RenderSections[RenderSectionIndex].MaterialIndex = MaterialIndex; } } } } // Force recreate render state after replacing the materials to avoid a crash in the render pipeline if the old materials are GCed while in use. RecreateRenderStateOnInstanceComponent[InstanceComponentIndex] |= SkeletalMesh->GetResourceForRendering()->IsInitialized() && SkeletalMesh->GetMaterials() != Materials; SkeletalMesh->SetMaterials(Materials); #if WITH_EDITOR if (RecreateRenderStateOnInstanceComponent[InstanceComponentIndex]) { // Close all open editors for this mesh to invalidate viewports. GEditor->GetEditorSubsystem()->CloseAllEditorsForAsset(SkeletalMesh); } #endif } // Ensure the number of materials is the same on both sides when using overrides. //check(SkeletalMesh->GetMaterials().Num() == Materials.Num()); } // Force recreate render state if the mesh is reused and the materials have changed. // TODO: MTBL-1697 Remove after merging ConvertResources and Callbacks. if (RecreateRenderStateOnInstanceComponent.Find(true) != INDEX_NONE || NotifyUpdateOnInstanceComponent.Find(true) != INDEX_NONE) { MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_RecreateRenderState); for (TObjectIterator It; It; ++It) { UCustomizableObjectInstanceUsage* CustomizableObjectInstanceUsage = *It; if (!IsValid(CustomizableObjectInstanceUsage) || CustomizableObjectInstanceUsage->GetCustomizableObjectInstance() != Public) { continue; } #if WITH_EDITOR if (CustomizableObjectInstanceUsage->GetPrivate()->IsNetMode(NM_DedicatedServer)) { continue; } #endif const FName& ComponentName = CustomizableObjectInstanceUsage->GetComponentName(); const int32 ObjectComponentIndex = ModelResources.ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName); int32 InstanceComponentIndex = -1; for (int32 CurrentInstanceIndex=0; CurrentInstanceIndexInstanceUpdateData.Components.Num(); ++CurrentInstanceIndex) { if (OperationData->InstanceUpdateData.Components[CurrentInstanceIndex].Id.GetValue() == ObjectComponentIndex) { InstanceComponentIndex = CurrentInstanceIndex; break; } } bool bDoRecreateRenderStateOnComponent = RecreateRenderStateOnInstanceComponent.IsValidIndex(InstanceComponentIndex) && RecreateRenderStateOnInstanceComponent[InstanceComponentIndex]; bool bDoNotifyUpdateOnComponent = NotifyUpdateOnInstanceComponent.IsValidIndex(InstanceComponentIndex) && NotifyUpdateOnInstanceComponent[InstanceComponentIndex]; if (!bDoRecreateRenderStateOnComponent && !bDoNotifyUpdateOnComponent) { continue; } USkeletalMeshComponent* AttachedParent = CustomizableObjectInstanceUsage->GetAttachParent(); TObjectPtr* SkeletalMesh = SkeletalMeshes.Find(ComponentName); if (!AttachedParent || (SkeletalMesh && AttachedParent->GetSkeletalMeshAsset() != *SkeletalMesh)) { continue; } if (bDoRecreateRenderStateOnComponent) { AttachedParent->RecreateRenderState_Concurrent(); } else if (bDoNotifyUpdateOnComponent) { IStreamingManager::Get().NotifyPrimitiveUpdated(AttachedParent); } } } { MUTABLE_CPUPROFILER_SCOPE(BuildMaterials_Exchange); UCustomizableObjectSystemPrivate* CustomizableObjectSystem = UCustomizableObjectSystem::GetInstance()->GetPrivate(); TexturesToRelease.Empty(); for (const FGeneratedTexture& Texture : NewGeneratedTextures) { CustomizableObjectSystem->AddTextureReference(Texture.Key); } for (const FGeneratedTexture& Texture : GeneratedTextures) { if (CustomizableObjectSystem->RemoveTextureReference(Texture.Key)) { if (CustomizableObjectSystem->bReleaseTexturesImmediately) { TexturesToRelease.Add(Texture); // Texture count is zero, so prepare to release it } } } Exchange(GeneratedTextures, NewGeneratedTextures); // All pass-through textures and meshes have been set, no need to keep referencing them from the instance LoadedPassThroughTexturesPendingSetMaterial.Empty(); LoadedPassThroughMeshesPendingSetMaterial.Empty(); } } void UCustomizableObjectInstance::SetReplacePhysicsAssets(bool bReplaceEnabled) { bReplaceEnabled ? GetPrivate()->SetCOInstanceFlags(ReplacePhysicsAssets) : GetPrivate()->ClearCOInstanceFlags(ReplacePhysicsAssets); } void UCustomizableObjectInstance::SetReuseInstanceTextures(bool bTextureReuseEnabled) { bTextureReuseEnabled ? GetPrivate()->SetCOInstanceFlags(ReuseTextures) : GetPrivate()->ClearCOInstanceFlags(ReuseTextures); } void UCustomizableObjectInstance::SetForceGenerateResidentMips(bool bForceGenerateResidentMips) { bForceGenerateResidentMips ? GetPrivate()->SetCOInstanceFlags(ForceGenerateMipTail) : GetPrivate()->ClearCOInstanceFlags(ForceGenerateMipTail); } void UCustomizableObjectInstance::SetIsBeingUsedByComponentInPlay(bool bIsUsedByComponentInPlay) { bIsUsedByComponentInPlay ? GetPrivate()->SetCOInstanceFlags(UsedByComponentInPlay) : GetPrivate()->ClearCOInstanceFlags(UsedByComponentInPlay); } bool UCustomizableObjectInstance::GetIsBeingUsedByComponentInPlay() const { return GetPrivate()->HasCOInstanceFlags(UsedByComponentInPlay); } void UCustomizableObjectInstance::SetIsDiscardedBecauseOfTooManyInstances(bool bIsDiscarded) { bIsDiscarded ? GetPrivate()->SetCOInstanceFlags(DiscardedByNumInstancesLimit) : GetPrivate()->ClearCOInstanceFlags(DiscardedByNumInstancesLimit); } bool UCustomizableObjectInstance::GetIsDiscardedBecauseOfTooManyInstances() const { return GetPrivate()->HasCOInstanceFlags(DiscardedByNumInstancesLimit); } void UCustomizableObjectInstance::SetIsPlayerOrNearIt(bool bIsPlayerorNearIt) { bIsPlayerorNearIt ? GetPrivate()->SetCOInstanceFlags(UsedByPlayerOrNearIt) : GetPrivate()->ClearCOInstanceFlags(UsedByPlayerOrNearIt); } float UCustomizableObjectInstance::GetMinSquareDistToPlayer() const { return GetPrivate()->MinSquareDistFromComponentToPlayer; } void UCustomizableObjectInstance::SetMinSquareDistToPlayer(float NewValue) { GetPrivate()->MinSquareDistFromComponentToPlayer = NewValue; } int32 UCustomizableObjectInstance::GetNumComponents() const { return GetCustomizableObject() ? GetCustomizableObject()->GetComponentCount() : 0; } void UCustomizableObjectInstance::SetRequestedLODs(const TMap& InMinLODs, const TMap& InFirstRequestedLOD, FMutableInstanceUpdateMap& InOutRequestedUpdates) { check(PrivateData); if (!GetPrivate()->CanUpdateInstance()) { return; } if (GetPrivate()->SkeletalMeshStatus == ESkeletalMeshStatus::Error) { return; } UCustomizableObject* CustomizableObject = GetCustomizableObject(); if (!CustomizableObject) { return; } if (IsStreamingEnabled(*CustomizableObject)) { return; } if (CVarPreserveUserLODsOnFirstGeneration.GetValueOnGameThread() && CustomizableObject->bPreserveUserLODsOnFirstGeneration && GetPrivate()->SkeletalMeshStatus != ESkeletalMeshStatus::Success) { return; } FMutableUpdateCandidate MutableUpdateCandidate(this); // Clamp Min LOD const UModelResources* ModelResources = CustomizableObject->GetPrivate()->GetModelResources(); if (!ModelResources) { return; } bool bMinLODChanged = false; // Save the new LODs MutableUpdateCandidate.MinLOD = InMinLODs; MutableUpdateCandidate.FirstRequestedLOD = Descriptor.GetFirstRequestedLOD(); const TMap& FirstRequestedLOD = GetPrivate()->CommittedDescriptorHash.FirstRequestedLOD; for (const FName& ComponentName : ModelResources->ComponentNamesPerObjectComponent) { uint8& InMinLOD = MutableUpdateCandidate.MinLOD.FindOrAdd(ComponentName); if (const uint8* Result = InMinLODs.Find(ComponentName)) { InMinLOD = *Result; } const uint8 MinLODIdx = CustomizableObject->GetPrivate()->GetMinLODIndex(ComponentName); MutableUpdateCandidate.QualitySettingMinLODs.Add(ComponentName, MinLODIdx); int32 MaxLODIdx = 0; if (const uint8* Found = ModelResources->NumLODsAvailable.Find(ComponentName)) { MaxLODIdx = *Found - 1; } InMinLOD = FMath::Clamp(InMinLOD, MinLODIdx, MaxLODIdx); uint8 DescriptorMinLOD = 0; if (const uint8* Result = Descriptor.MinLOD.Find(ComponentName)) { DescriptorMinLOD = *Result; } bMinLODChanged |= DescriptorMinLOD != InMinLOD; if (UCustomizableObjectSystem::GetInstance()->IsOnlyGenerateRequestedLODsEnabled()) { uint8 CurrentMinLOD = 0; if (const uint8* Result = GetPrivate()->CommittedDescriptor.MinLOD.Find(ComponentName)) { CurrentMinLOD = *Result; } PrivateData->SetCOInstanceFlags(InMinLOD > CurrentMinLOD ? PendingLODsDowngrade : ECONone); uint8 FirstNonStreamedLODIndex = 0; if (const uint8* Found = ModelResources->NumLODsToStream.Find(ComponentName)) { FirstNonStreamedLODIndex = *Found; } MutableUpdateCandidate.FirstRequestedLOD.Add(ComponentName, FirstNonStreamedLODIndex); uint8 PredictedLOD = FirstNonStreamedLODIndex; if (const uint8* Result = InFirstRequestedLOD.Find(ComponentName)) { PredictedLOD = FMath::Min(*Result, PredictedLOD); } if (const uint8* Result = FirstRequestedLOD.Find(ComponentName)) { PredictedLOD = FMath::Min(*Result, PredictedLOD); } PredictedLOD = FMath::Clamp(PredictedLOD, MinLODIdx, MaxLODIdx); // Save new RequestedLODs MutableUpdateCandidate.FirstRequestedLOD[ComponentName] = PredictedLOD; } } if (bMinLODChanged || FirstRequestedLOD != MutableUpdateCandidate.FirstRequestedLOD) { // TODO: Remove this flag as it will become redundant with the new InOutRequestedUpdates system PrivateData->SetCOInstanceFlags(PendingLODsUpdate); InOutRequestedUpdates.Add(this, MutableUpdateCandidate); } } #if WITH_EDITOR void UCustomizableObjectInstance::Bake(const FBakingConfiguration& InBakingConfiguration) { if (ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { Module->BakeCustomizableObjectInstance(this, InBakingConfiguration); } else { // Notify of the error UE_LOG(LogMutable, Error, TEXT("The module \" ICustomizableObjectEditorModule \" could not be loaded and therefore the baking operation could not be started.")); if (InBakingConfiguration.OnBakeOperationCompletedCallback.IsBound()) { FCustomizableObjectInstanceBakeOutput Output; Output.bWasBakeSuccessful = false; Output.SavedPackages.Empty(); InBakingConfiguration.OnBakeOperationCompletedCallback.Execute(Output); } } } #endif USkeletalMesh* UCustomizableObjectInstance::GetSkeletalMesh(int32 ObjectComponentIndex) const { return GetComponentMeshSkeletalMesh(FName(FString::FromInt(ObjectComponentIndex))); } USkeletalMesh* UCustomizableObjectInstance::GetComponentMeshSkeletalMesh(const FName& ComponentName) const { TObjectPtr* Result = GetPrivate()->SkeletalMeshes.Find(ComponentName); return Result ? *Result : nullptr; } bool UCustomizableObjectInstance::HasAnySkeletalMesh() const { return !GetPrivate()->SkeletalMeshes.IsEmpty(); } bool UCustomizableObjectInstance::HasAnyParameters() const { return Descriptor.HasAnyParameters(); } TArray UCustomizableObjectInstance::GetComponentNames() const { TArray GeneratedComponents; // For now, the instances don't really hold a direct array of generated components FNames. // They can be identified with the ones having a valid SkeletalMesh in the SkeletalMeshes array, but this will // not longer work when we have components that don't have a SkeletalMesh, like grooms, or panel clothing. (TODO) for ( const TPair>& Entry : GetPrivate()->SkeletalMeshes ) { if (Entry.Value) { GeneratedComponents.Add(Entry.Key); } } return GeneratedComponents; } TSubclassOf UCustomizableObjectInstance::GetAnimBP(FName ComponentName, const FName& SlotName) const { FCustomizableInstanceComponentData* ComponentData = GetPrivate()->GetComponentData(ComponentName); if (!ComponentData) { FString ErrorMsg = FString::Printf(TEXT("Tried to access an invalid component index [%s] in a Mutable Instance."), *ComponentName.ToString()); UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg); #if WITH_EDITOR FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); MessageLogModule.RegisterLogListing(FName("Mutable"), FText::FromString(FString("Mutable"))); FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true); #endif return nullptr; } TSoftClassPtr* Result = ComponentData->AnimSlotToBP.Find(SlotName); return Result ? Result->Get() : nullptr; } const FGameplayTagContainer& UCustomizableObjectInstance::GetAnimationGameplayTags() const { return GetPrivate()->AnimBPGameplayTags; } namespace MutablePrivate { template void InternalForEachAnimInstance(UCustomizableInstancePrivate* Private, FName ComponentName, DELEGATE Delegate) { // allow us to log out both bad states with one pass bool bAnyErrors = false; if (!Delegate.IsBound()) { FString ErrorMsg = FString::Printf(TEXT("Attempting to iterate over AnimInstances with an unbound delegate for component [%s]."), *ComponentName.ToString()); UE_LOG(LogMutable, Warning, TEXT("%s"), *ErrorMsg); #if WITH_EDITOR FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); MessageLogModule.RegisterLogListing(FName("Mutable"), FText::FromString(FString("Mutable"))); FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Warning, true); #endif bAnyErrors = true; } const FCustomizableInstanceComponentData* ComponentData = Private->GetComponentData(ComponentName); if (!ComponentData) { FString ErrorMsg = FString::Printf(TEXT("Tried to access an invalid component [%s] in a Mutable Instance."), *ComponentName.ToString()); UE_LOG(LogMutable, Error, TEXT("%s"), *ErrorMsg); #if WITH_EDITOR FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); MessageLogModule.RegisterLogListing(FName("Mutable"), FText::FromString(FString("Mutable"))); FMessageLog MessageLog("Mutable"); MessageLog.Notify(FText::FromString(ErrorMsg), EMessageSeverity::Error, true); #endif bAnyErrors = true; } if (bAnyErrors) { return; } for (const TPair>& MapElem : ComponentData->AnimSlotToBP) { const FName& Index = MapElem.Key; const TSoftClassPtr& AnimBP = MapElem.Value; // if this _can_ resolve to a real AnimBP if (!AnimBP.IsNull()) { // force a load right now - we don't know whether we would have loaded already - this could be called in editor const TSubclassOf LiveAnimBP = MutablePrivate::LoadClass(AnimBP); if (LiveAnimBP) { Delegate.Execute(Index, LiveAnimBP); } } } } } void UCustomizableObjectInstance::ForEachComponentAnimInstance(FName ComponentName, FEachComponentAnimInstanceClassDelegate Delegate) const { MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate); } void UCustomizableObjectInstance::ForEachComponentAnimInstance(FName ComponentName, FEachComponentAnimInstanceClassNativeDelegate Delegate) const { MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate); } // Deprecated void UCustomizableObjectInstance::ForEachAnimInstance(int32 ObjectComponentIndex, FEachComponentAnimInstanceClassDelegate Delegate) const { UCustomizableObject* CO = GetCustomizableObject(); if (CO) { FName ComponentName = CO->GetPrivate()->GetComponentName(FCustomizableObjectComponentIndex(ObjectComponentIndex)); MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate); } } // Deprecated void UCustomizableObjectInstance::ForEachAnimInstance(int32 ObjectComponentIndex, FEachComponentAnimInstanceClassNativeDelegate Delegate) const { UCustomizableObject* CO = GetCustomizableObject(); if (CO) { FName ComponentName = CO->GetPrivate()->GetComponentName(FCustomizableObjectComponentIndex(ObjectComponentIndex)); MutablePrivate::InternalForEachAnimInstance<>(GetPrivate(), ComponentName, Delegate); } } bool UCustomizableObjectInstance::AnimInstanceNeedsFixup(TSubclassOf AnimInstanceClass) const { return PrivateData->AnimBpPhysicsAssets.Contains(AnimInstanceClass); } void UCustomizableObjectInstance::AnimInstanceFixup(UAnimInstance* InAnimInstance) const { if (!InAnimInstance) { return; } TSubclassOf AnimInstanceClass = InAnimInstance->GetClass(); const TArray* AnimInstanceOverridePhysicsAssets = PrivateData->GetGeneratedPhysicsAssetsForAnimInstance(AnimInstanceClass); if (!AnimInstanceOverridePhysicsAssets) { return; } // Swap RigidBody anim nodes override physics assets with mutable generated ones. if (UAnimBlueprintGeneratedClass* AnimClass = Cast(AnimInstanceClass)) { bool bPropertyMismatchFound = false; const int32 AnimNodePropertiesNum = AnimClass->AnimNodeProperties.Num(); for (const FAnimInstanceOverridePhysicsAsset& PropIndexAndAsset : *AnimInstanceOverridePhysicsAssets) { check(PropIndexAndAsset.PropertyIndex >= 0); if (PropIndexAndAsset.PropertyIndex >= AnimNodePropertiesNum) { bPropertyMismatchFound = true; continue; } const int32 AnimNodePropIndex = PropIndexAndAsset.PropertyIndex; FStructProperty* StructProperty = AnimClass->AnimNodeProperties[AnimNodePropIndex]; if (!ensure(StructProperty)) { bPropertyMismatchFound = true; continue; } const bool bIsRigidBodyNode = StructProperty->Struct->IsChildOf(FAnimNode_RigidBody::StaticStruct()); if (!bIsRigidBodyNode) { bPropertyMismatchFound = true; continue; } FAnimNode_RigidBody* RbanNode = StructProperty->ContainerPtrToValuePtr(InAnimInstance); if (!ensure(RbanNode)) { bPropertyMismatchFound = true; continue; } RbanNode->OverridePhysicsAsset = PropIndexAndAsset.PhysicsAsset; } #if WITH_EDITOR if (bPropertyMismatchFound) { UE_LOG(LogMutable, Warning, TEXT("AnimBp %s is not in sync with the data stored in the CO %s. A CO recompilation may be needed."), *AnimInstanceClass.Get()->GetName(), *GetCustomizableObject()->GetName()); } #endif } } const TArray* UCustomizableInstancePrivate::GetGeneratedPhysicsAssetsForAnimInstance(TSubclassOf AnimInstanceClass) const { const FAnimBpGeneratedPhysicsAssets* Found = AnimBpPhysicsAssets.Find(AnimInstanceClass); if (!Found) { return nullptr; } return &Found->AnimInstancePropertyIndexAndPhysicsAssets; } FInstancedStruct UCustomizableObjectInstance::GetExtensionInstanceData(const UCustomizableObjectExtension* Extension) const { const FExtensionInstanceData* FoundData = Algo::FindBy(PrivateData->ExtensionInstanceData, Extension, &FExtensionInstanceData::Extension); if (FoundData) { return FoundData->Data; } // Data not found. Return an empty instance. return FInstancedStruct(); } TSet UCustomizableObjectInstance::GetMergedAssetUserData(int32 ComponentIndex) const { UCustomizableInstancePrivate* PrivateInstanceData = GetPrivate(); if (PrivateInstanceData && PrivateInstanceData->ComponentsData.IsValidIndex(ComponentIndex)) { TSet Set; // Have to convert to UAssetUserData* because BP functions don't support TObjectPtr for (const TObjectPtr& Elem : PrivateInstanceData->ComponentsData[ComponentIndex].AssetUserDataArray) { Set.Add(Elem); } return Set; } else { return TSet(); } } bool UCustomizableObjectInstance::CanUpdateInstance() const { return GetPrivate()->CanUpdateInstance(); } #if WITH_EDITORONLY_DATA void CalculateBonesToRemove(const FSkeletalMeshLODRenderData& LODResource, const FReferenceSkeleton& RefSkeleton, TArray& OutBonesToRemove) { const int32 NumBones = RefSkeleton.GetNum(); OutBonesToRemove.Empty(NumBones); TArray RemovedBones; RemovedBones.Init(true, NumBones); for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex) { if (LODResource.RequiredBones.Find((uint16)BoneIndex) != INDEX_NONE) { RemovedBones[BoneIndex] = false; continue; } const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex); if (RemovedBones.IsValidIndex(ParentIndex) && !RemovedBones[ParentIndex]) { OutBonesToRemove.Add(RefSkeleton.GetBoneName(BoneIndex)); } } } void UCustomizableInstancePrivate::RegenerateImportedModels() { MUTABLE_CPUPROFILER_SCOPE(RegenerateImportedModels); using UVsVectorType = typename TDecay().UVs[0])>::Type; struct FMeshDataConvertJob { int32 NumIndices = 0; int32 IndicesOffset = 0; const FRawStaticIndexBuffer16or32Interface* IndexBuffer = nullptr; uint32* DestIndexBuffer = nullptr; int32 NumVertices = 0; int32 VerticesOffset = 0; const FStaticMeshVertexBuffers* StaticVertexBuffers = nullptr; const FSkinWeightVertexBuffer* SkinWeightVertexBuffer = nullptr; FSoftSkinVertex* DestVertexBuffer = nullptr; }; constexpr int32 MaxJobCost = 1 << 18; constexpr int32 MaxVerticesPerJob = FMath::Max(1, MaxJobCost / sizeof(FSoftSkinVertex)); constexpr int32 MaxIndicesPerJob = FMath::Max(1, MaxJobCost / sizeof(int32)); TArray> Jobs; TArray> JobRanges; JobRanges.Add(0); for (const TTuple>& Tuple : SkeletalMeshes) { USkeletalMesh* SkeletalMesh = Tuple.Get<1>(); if (!SkeletalMesh) { continue; } const bool bIsTransientMesh = static_cast(SkeletalMesh->HasAllFlags(EObjectFlags::RF_Transient)); if (!bIsTransientMesh) { // This must be a pass-through referenced mesh so don't do anything to it continue; } FSkeletalMeshRenderData* RenderData = SkeletalMesh->GetResourceForRendering(); if (!RenderData || RenderData->IsInitialized()) { continue; } for (UClothingAssetBase* ClothingAssetBase : SkeletalMesh->GetMeshClothingAssets()) { if (!ClothingAssetBase) { continue; } UClothingAssetCommon* ClothAsset = Cast(ClothingAssetBase); if (!ClothAsset) { continue; } if (!ClothAsset->LodData.Num()) { continue; } for (FClothLODDataCommon& ClothLodData : ClothAsset->LodData) { ClothLodData.PointWeightMaps.Empty(16); for (TPair& WeightMap : ClothLodData.PhysicalMeshData.WeightMaps) { if (WeightMap.Value.Num()) { FPointWeightMap& PointWeightMap = ClothLodData.PointWeightMaps.AddDefaulted_GetRef(); PointWeightMap.Initialize(WeightMap.Value, WeightMap.Key); } } } } FSkeletalMeshModel* ImportedModel = SkeletalMesh->GetImportedModel(); ImportedModel->bGuidIsHash = false; ImportedModel->SkeletalMeshModelGUID = FGuid::NewGuid(); ImportedModel->LODModels.Empty(); int32 OriginalIndex = 0; for (int32 LODIndex = 0; LODIndex < RenderData->LODRenderData.Num(); ++LODIndex) { ImportedModel->LODModels.Add(new FSkeletalMeshLODModel()); FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex]; FSkeletalMeshLODRenderData& LODRenderData = RenderData->LODRenderData[LODIndex]; LODModel.ActiveBoneIndices = LODRenderData.ActiveBoneIndices; LODModel.NumTexCoords = LODRenderData.GetNumTexCoords(); LODModel.RequiredBones = LODRenderData.RequiredBones; LODModel.NumVertices = LODRenderData.GetNumVertices(); // Indices if (LODRenderData.MultiSizeIndexContainer.IsIndexBufferValid()) { const FRawStaticIndexBuffer16or32Interface* IndexBuffer = LODRenderData.MultiSizeIndexContainer.GetIndexBuffer(); const int32 NumIndices = IndexBuffer->Num(); LODModel.IndexBuffer.SetNum(NumIndices); uint32* BaseDestIndexBuffer = LODModel.IndexBuffer.GetData(); const int32 NumIndicesJobs = FMath::DivideAndRoundUp(NumIndices, MaxIndicesPerJob); int32 CurrentJobIndex = Jobs.Num(); Jobs.SetNum(Jobs.Num() + NumIndicesJobs); for (int32 I = 0; I < NumIndicesJobs; ++I) { FMeshDataConvertJob Job; Job.NumIndices = FMath::Min(MaxIndicesPerJob, NumIndices - I*MaxIndicesPerJob); Job.IndexBuffer = IndexBuffer; Job.IndicesOffset = I*MaxIndicesPerJob; Job.DestIndexBuffer = BaseDestIndexBuffer + I*MaxIndicesPerJob; Jobs[I + CurrentJobIndex] = Job; } } LODModel.Sections.SetNum(LODRenderData.RenderSections.Num()); for (int32 SectionIndex = 0; SectionIndex < LODRenderData.RenderSections.Num(); ++SectionIndex) { check(!LODRenderData.StaticVertexBuffers.StaticMeshVertexBuffer.GetUseHighPrecisionTangentBasis()); const FSkelMeshRenderSection& RenderSection = LODRenderData.RenderSections[SectionIndex]; FSkelMeshSection& ImportedSection = ImportedModel->LODModels[LODIndex].Sections[SectionIndex]; ImportedSection.CorrespondClothAssetIndex = RenderSection.CorrespondClothAssetIndex; ImportedSection.ClothingData = RenderSection.ClothingData; if (RenderSection.ClothMappingDataLODs.Num()) { TArray& ImportedClothMappingData = ImportedSection.ClothMappingDataLODs.AddDefaulted_GetRef(); const int32 NumClothVerts = LODRenderData.ClothVertexBuffer.GetNumVertices(); ImportedClothMappingData.SetNumUninitialized(NumClothVerts); for (int32 ClothVertDataIndex = 0; ClothVertDataIndex < NumClothVerts; ++ClothVertDataIndex) { ImportedClothMappingData[ClothVertDataIndex] = LODRenderData.ClothVertexBuffer.MappingData(ClothVertDataIndex); } } // Vertices ImportedSection.NumVertices = RenderSection.NumVertices; ImportedSection.SoftVertices.Empty(RenderSection.NumVertices); ImportedSection.SoftVertices.AddUninitialized(RenderSection.NumVertices); ImportedSection.bUse16BitBoneIndex = LODRenderData.DoesVertexBufferUse16BitBoneIndex(); const int32 SectionNumVertices = RenderSection.NumVertices; const int32 SectionBaseVertexIndex = RenderSection.BaseVertexIndex; const FStaticMeshVertexBuffers* StaticVertexBuffers = &LODRenderData.StaticVertexBuffers; const FSkinWeightVertexBuffer* SkinWeightVertexBuffer = &LODRenderData.SkinWeightVertexBuffer; FSoftSkinVertex* BaseSectionSoftVertex = ImportedSection.SoftVertices.GetData(); int32 NumSectionJobs = FMath::DivideAndRoundUp(RenderSection.NumVertices, MaxVerticesPerJob); int32 FirstSectionJobIndex = Jobs.Num(); Jobs.SetNum(Jobs.Num() + NumSectionJobs); for (int32 I = 0; I < NumSectionJobs; ++I) { FMeshDataConvertJob Job; Job.NumVertices = FMath::Min(MaxVerticesPerJob, SectionNumVertices - I*MaxVerticesPerJob); Job.StaticVertexBuffers = StaticVertexBuffers; Job.SkinWeightVertexBuffer = SkinWeightVertexBuffer; Job.VerticesOffset = SectionBaseVertexIndex + I*MaxVerticesPerJob; Job.DestVertexBuffer = BaseSectionSoftVertex + I*MaxVerticesPerJob; Jobs[I + FirstSectionJobIndex] = Job; } // Triangles ImportedSection.NumTriangles = RenderSection.NumTriangles; ImportedSection.BaseIndex = RenderSection.BaseIndex; ImportedSection.BaseVertexIndex = RenderSection.BaseVertexIndex; ImportedSection.BoneMap = RenderSection.BoneMap; // Add bones to remove CalculateBonesToRemove(LODRenderData, SkeletalMesh->GetRefSkeleton(), SkeletalMesh->GetLODInfo(LODIndex)->BonesToRemove); const TArray& LODMaterialMap = SkeletalMesh->GetLODInfo(LODIndex)->LODMaterialMap; if (LODMaterialMap.IsValidIndex(RenderSection.MaterialIndex)) { ImportedSection.MaterialIndex = LODMaterialMap[RenderSection.MaterialIndex]; } else { // The material should have been in the LODMaterialMap ensureMsgf(false, TEXT("Unexpected material index in UCustomizableInstancePrivate::RegenerateImportedModel")); // Fallback index, may shift materials around sections if (SkeletalMesh->GetMaterials().IsValidIndex(RenderSection.MaterialIndex)) { ImportedSection.MaterialIndex = RenderSection.MaterialIndex; } else { ImportedSection.MaterialIndex = 0; } } ImportedSection.MaxBoneInfluences = RenderSection.MaxBoneInfluences; ImportedSection.OriginalDataSectionIndex = OriginalIndex++; FSkelMeshSourceSectionUserData& SectionUserData = LODModel.UserSectionsData.FindOrAdd(ImportedSection.OriginalDataSectionIndex); SectionUserData.bCastShadow = RenderSection.bCastShadow; SectionUserData.bDisabled = RenderSection.bDisabled; SectionUserData.CorrespondClothAssetIndex = RenderSection.CorrespondClothAssetIndex; SectionUserData.ClothingData.AssetGuid = RenderSection.ClothingData.AssetGuid; SectionUserData.ClothingData.AssetLodIndex = RenderSection.ClothingData.AssetLodIndex; LODModel.SyncronizeUserSectionsDataArray(); // DDC keys const USkeletalMeshLODSettings* LODSettings = SkeletalMesh->GetLODSettings(); const bool bValidLODSettings = LODSettings && LODSettings->GetNumberOfSettings() > LODIndex; const FSkeletalMeshLODGroupSettings* SkeletalMeshLODGroupSettings = bValidLODSettings ? &LODSettings->GetSettingsForLODLevel(LODIndex) : nullptr; FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(LODIndex); LODInfo->BuildGUID = LODInfo->ComputeDeriveDataCacheKey(SkeletalMeshLODGroupSettings); LODModel.BuildStringID = LODModel.GetLODModelDeriveDataKey(); } } // Try to bundle Jobs so all cost roughly the same. Large Jobs are already split so they cost about MaxJobCost. // It uses a gready approach and assumes in general Jobs are sorted by cost. const int32 NumJobs = Jobs.Num(); for (int32 JobIndex = 0; JobIndex < NumJobs;) { int32 RangeJobCost = 0; for (; JobIndex < NumJobs;) { int32 CurrentJobCost = Jobs[JobIndex].NumVertices*sizeof(FSoftSkinVertex) + Jobs[JobIndex].NumIndices*sizeof(int32); RangeJobCost += CurrentJobCost; if (RangeJobCost >= MaxJobCost) { // Go to the next Job if the current job alone cost is larger than MaxJobCost // and no other job has been processed for the range. JobIndex += static_cast(CurrentJobCost == RangeJobCost); break; } ++JobIndex; } JobRanges.Add(JobIndex); } } { MUTABLE_CPUPROFILER_SCOPE(DoImportedModelMeshDataConversion) ParallelFor(JobRanges.Num() - 1, [&JobRanges, &Jobs](int32 JobId) { int32 JobRangeBegin = JobRanges[JobId]; int32 JobRangeEnd = JobRanges[JobId + 1]; for (int32 J = JobRangeBegin; J < JobRangeEnd; ++J) { FMeshDataConvertJob Job = Jobs[J]; if (Job.NumIndices > 0) { MUTABLE_CPUPROFILER_SCOPE(DoImportedModelMeshDataConversion_Indices) for (int32 Index = 0; Index < Job.NumIndices; ++Index) { Job.DestIndexBuffer[Index] = Job.IndexBuffer->Get(Job.IndicesOffset + Index); } } if (Job.NumVertices > 0) { MUTABLE_CPUPROFILER_SCOPE(DoImportedModelMeshDataConversion_Vertices) check(Job.StaticVertexBuffers); check(Job.SkinWeightVertexBuffer); check(Job.DestVertexBuffer); const FPositionVertex* PositionBuffer = static_cast(Job.StaticVertexBuffers->PositionVertexBuffer.GetVertexData()) + Job.VerticesOffset; const FPackedNormal* TangentBuffer = static_cast(Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetTangentData()) + Job.VerticesOffset*2; const int32 NumTexCoords = Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetNumTexCoords(); const int32 UVSize = Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetUseFullPrecisionUVs() ? 2 * sizeof(float) : 2 * sizeof(FFloat16); const uint8* TexCoordBuffer = static_cast(Job.StaticVertexBuffers->StaticMeshVertexBuffer.GetTexCoordData()) + Job.VerticesOffset*NumTexCoords*UVSize; const FColor* ColorBuffer = static_cast(Job.StaticVertexBuffers->ColorVertexBuffer.GetVertexData()); const bool bHasColor = !!ColorBuffer; ColorBuffer += Job.VerticesOffset; const FSkinWeightVertexBuffer* SkinWeightBuffer = Job.SkinWeightVertexBuffer; const int32 MaxBoneInfluences = Job.SkinWeightVertexBuffer->GetMaxBoneInfluences(); for (int32 JobVertexIndex = 0; JobVertexIndex < Job.NumVertices; ++JobVertexIndex) { FSoftSkinVertex* Vertex = Job.DestVertexBuffer + JobVertexIndex; FMemory::Memzero(Vertex, sizeof(FSoftSkinVertex)); Vertex->Position = PositionBuffer[JobVertexIndex].Position; const FPackedNormal* Tangent = TangentBuffer + JobVertexIndex*2; Vertex->TangentX = Tangent[0].ToFVector3f(); Vertex->TangentZ = Tangent[1].ToFVector3f(); float TangentSign = Tangent[1].Vector.W < 0 ? -1.0f : 1.0f; Vertex->TangentY = FVector3f::CrossProduct(Vertex->TangentZ, Vertex->TangentX) * TangentSign; const void* TexCoord = TexCoordBuffer + JobVertexIndex*NumTexCoords*UVSize; // Switch based jumptable. if (UVSize == 4) { const FFloat16* TypedSource = reinterpret_cast(TexCoord); switch (NumTexCoords) { case 4: Vertex->UVs[3] = { TypedSource[6], TypedSource[7] }; // Fall through case 3: Vertex->UVs[2] = { TypedSource[4], TypedSource[5] }; // Fall through case 2: Vertex->UVs[1] = { TypedSource[2], TypedSource[3] }; // Fall through case 1: Vertex->UVs[0] = { TypedSource[0], TypedSource[1] }; // Fall through default: break; } } else { const FVector2f* TypedSource = reinterpret_cast(TexCoord); switch (NumTexCoords) { case 4: Vertex->UVs[3] = TypedSource[3]; // Fall through case 3: Vertex->UVs[2] = TypedSource[2]; // Fall through case 2: Vertex->UVs[1] = TypedSource[1]; // Fall through case 1: Vertex->UVs[0] = TypedSource[0]; // Fall through default: break; } } Vertex->Color = bHasColor ? ColorBuffer[JobVertexIndex] : FColor::White; const int32 SourceVertexIndex = (JobVertexIndex + Job.VerticesOffset); for (int32 InfluenceIndex = 0; InfluenceIndex < MaxBoneInfluences; ++InfluenceIndex) { Vertex->InfluenceBones[InfluenceIndex] = SkinWeightBuffer->GetBoneIndex(SourceVertexIndex, InfluenceIndex); Vertex->InfluenceWeights[InfluenceIndex] = SkinWeightBuffer->GetBoneWeight(SourceVertexIndex, InfluenceIndex); } } } } }); } } #endif