// Copyright Epic Games, Inc. All Rights Reserved. #include "MuCO/CustomizableObject.h" #include "Algo/Copy.h" #include "Async/AsyncFileHandle.h" #include "EdGraph/EdGraph.h" #include "Engine/Engine.h" #include "Engine/SkeletalMesh.h" #include "Animation/AnimInstance.h" #include "Animation/Skeleton.h" #include "Engine/AssetUserData.h" #include "HAL/FileManager.h" #include "HAL/PlatformFileManager.h" #include "Input/Reply.h" #include "Interfaces/ITargetPlatform.h" #include "Materials/MaterialInterface.h" #include "Misc/PackageName.h" #include "Misc/Paths.h" #include "MuCO/CustomizableObjectInstance.h" #include "MuCO/CustomizableObjectInstancePrivate.h" #include "MuCO/CustomizableObjectPrivate.h" #include "MuCO/CustomizableObjectSystem.h" #include "MuCO/CustomizableObjectSystemPrivate.h" #include "MuCO/CustomizableObjectUIData.h" #include "MuCO/ICustomizableObjectModule.h" #include "MuCO/MutableProjectorTypeUtils.h" #include "MuCO/UnrealMutableModelDiskStreamer.h" #include "MuR/Model.h" #include "MuR/ModelPrivate.h" #include "MuR/Operations.h" #include "PhysicsEngine/PhysicsAsset.h" #include "Serialization/MemoryReader.h" #include "Serialization/MemoryWriter.h" #include "Serialization/ObjectAndNameAsStringProxyArchive.h" #include "UObject/AssetRegistryTagsContext.h" #include "UObject/ObjectSaveContext.h" #include "MuCO/ICustomizableObjectEditorModule.h" #include "Misc/MessageDialog.h" #if WITH_EDITOR #include "Editor.h" #include "Engine/TextureLODSettings.h" #include "DerivedDataCache.h" #include "DerivedDataCacheKey.h" #include "DerivedDataRequestOwner.h" #include "MuCO/LoadUtils.h" #endif #include "MuCO/CustomizableObjectCustomVersion.h" #include "MuCO/CustomizableObjectResourceDataTypes.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(CustomizableObject) #define LOCTEXT_NAMESPACE "CustomizableObject" DEFINE_LOG_CATEGORY(LogMutable); #if WITH_EDITOR TAutoConsoleVariable CVarPackagedDataBytesLimitOverride( TEXT("mutable.PackagedDataBytesLimitOverride"), -1, TEXT("Defines the value to be used as 'PackagedDataBytesLimitOverride' for the compilation of all COs.\n") TEXT(" <0 : Use value defined in the CO\n") TEXT(" >=0 : Use this value instead\n")); TAutoConsoleVariable CVarMutableUseBulkData( TEXT("Mutable.UseBulkData"), true, TEXT("Switch between .utoc/.ucas (FBulkData) and .mut files (CookAdditionalFiles).\n") TEXT("True - Use FBulkData to store streamable data.\n") TEXT("False - Use Mut files to store streamable data\n")); TAutoConsoleVariable CVarMutableAsyncCook( TEXT("Mutable.CookAsync"), true, TEXT("True - Customizable Objects will be compiled asynchronously during cook.\n") TEXT("False - Sync compilation.\n")); #endif static const FString s_EmptyString; #if WITH_EDITORONLY_DATA namespace UE::Mutable::Private { template T* MoveOldObjectAndCreateNew(UClass* Class, UObject* InOuter) { FName ObjectFName = Class->GetFName(); FString ObjectNameStr = ObjectFName.ToString(); UObject* Existing = FindObject(InOuter, *ObjectNameStr); if (Existing) { // Move the old object out of the way Existing->Rename(nullptr /* Rename will pick a free name*/, GetTransientPackage(), REN_DontCreateRedirectors); } return NewObject(InOuter, Class, *ObjectNameStr); } } #endif //------------------------------------------------------------------------------------------------- UCustomizableObject::UCustomizableObject() : UObject() { Private = CreateDefaultSubobject(FName("Private")); #if WITH_EDITORONLY_DATA const FString CVarName = TEXT("r.SkeletalMesh.MinLodQualityLevel"); const FString ScalabilitySectionName = TEXT("ViewDistanceQuality"); PRAGMA_DISABLE_DEPRECATION_WARNINGS LODSettings.MinQualityLevelLOD.SetQualityLevelCVarForCooking(*CVarName, *ScalabilitySectionName); PRAGMA_ENABLE_DEPRECATION_WARNINGS #endif } #if WITH_EDITOR bool UCustomizableObject::IsEditorOnly() const { return bIsChildObject; } void UCustomizableObjectPrivate::UpdateVersionId() { GetPublic()->VersionId = FGuid::NewGuid(); } FGuid UCustomizableObjectPrivate::GetVersionId() const { return GetPublic()->VersionId; } void UCustomizableObject::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const { int32 isRoot = 0; if (const ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { isRoot = Module->IsRootObject(*this) ? 1 : 0; } Context.AddTag(FAssetRegistryTag("IsRoot", FString::FromInt(isRoot), FAssetRegistryTag::TT_Numerical)); Super::GetAssetRegistryTags(Context); } void UCustomizableObject::PreSave(FObjectPreSaveContext ObjectSaveContext) { Super::PreSave(ObjectSaveContext); // Update the derived child object flag if (GetPrivate()->TryUpdateIsChildObject()) { if (bIsChildObject) { GetPackage()->SetPackageFlags(PKG_EditorOnly); } else { GetPackage()->ClearPackageFlags(PKG_EditorOnly); } } if (ObjectSaveContext.IsCooking() && !bIsChildObject) { const ITargetPlatform* TargetPlatform = ObjectSaveContext.GetTargetPlatform(); // Load cached data before saving if (GetPrivate()->TryLoadCompiledCookDataForPlatform(TargetPlatform)) { // Change current platform ModelResources' outer from TransientPkg to CO before save. // PostSaveRoot will set the outer to TransientPkg again. This is done to avoid serializing multiple ModelResources when cooking more than one platform at once. TObjectPtr ModelResources = GetPrivate()->GetModelResources(true); ModelResources->Rename(nullptr, this, REN_DontCreateRedirectors); const bool bUseBulkData = CVarMutableUseBulkData.GetValueOnAnyThread(); if (bUseBulkData) { MutablePrivate::FMutableCachedPlatformData& CachedPlatformData = *GetPrivate()->CachedPlatformsData.Find(TargetPlatform->PlatformName()); TSharedPtr ModelStreamableBulkData = GetPrivate()->GetModelStreamableBulkData(true); const int32 NumBulkDataFiles = CachedPlatformData.BulkDataFiles.Num(); ModelStreamableBulkData->StreamableBulkData.SetNum(NumBulkDataFiles); const auto WriteBulkData = [ModelStreamableBulkData](MutablePrivate::FFile& File, TArray64& FileBulkData, uint32 FileIndex) { MUTABLE_CPUPROFILER_SCOPE(WriteBulkData); FByteBulkData& ByteBulkData = ModelStreamableBulkData->StreamableBulkData[FileIndex]; // BulkData file to store the file to. CookedIndex 0 is used as a default for backwards compatibility, +1 to skip it. ByteBulkData.SetCookedIndex(FBulkDataCookedIndex((File.Id % MAX_uint8) + 1)); ByteBulkData.Lock(LOCK_READ_WRITE); uint8* Ptr = (uint8*)ByteBulkData.Realloc(FileBulkData.Num()); FMemory::Memcpy(Ptr, FileBulkData.GetData(), FileBulkData.Num()); ByteBulkData.Unlock(); uint32 BulkDataFlags = BULKDATA_PayloadInSeperateFile | BULKDATA_Force_NOT_InlinePayload; if (File.Flags == uint16(EMutableFileFlags::HighRes)) { BulkDataFlags |= BULKDATA_OptionalPayload; } ByteBulkData.SetBulkDataFlags(BulkDataFlags); }; bool bDropData = true; MutablePrivate::SerializeBulkDataFiles(CachedPlatformData, CachedPlatformData.BulkDataFiles, WriteBulkData, bDropData); } else { // Create an export object to manage the streamable data if (!BulkData) { BulkData = UE::Mutable::Private::MoveOldObjectAndCreateNew(UCustomizableObjectBulk::StaticClass(), this); } BulkData->Mark(OBJECTMARK_TagExp); } } else { UE_LOG(LogMutable, Warning, TEXT("Cook: Customizable Object [%s] is missing [%s] platform data."), *GetName(), *ObjectSaveContext.GetTargetPlatform()->PlatformName()); // Clear model resources GetPrivate()->SetModel(nullptr, FGuid()); GetPrivate()->SetModelResources(nullptr, true /* bIsCooking */); GetPrivate()->GetModelStreamableBulkData(true).Reset(); } } } void UCustomizableObject::PostSaveRoot(FObjectPostSaveRootContext ObjectSaveContext) { Super::PostSaveRoot(ObjectSaveContext); if (ObjectSaveContext.IsCooking() && !bIsChildObject) { const ITargetPlatform* TargetPlatform = ObjectSaveContext.GetTargetPlatform(); const MutablePrivate::FMutableCachedPlatformData* PlatformData = GetPrivate()->CachedPlatformsData.Find(TargetPlatform->PlatformName()); if (PlatformData && PlatformData->ModelResources) { // Set the outer to TransientPkg again. This is done to avoid serializing multiple ModelResources when cooking more than one platform at once. PlatformData->ModelResources->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors); } } } bool UCustomizableObjectPrivate::TryUpdateIsChildObject() { if (const ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { GetPublic()->bIsChildObject = !Module->IsRootObject(*GetPublic()); return true; } else { return false; } } bool UCustomizableObject::IsChildObject() const { return bIsChildObject; } void UCustomizableObjectPrivate::SetIsChildObject(bool bIsChildObject) { GetPublic()->bIsChildObject = bIsChildObject; } bool UCustomizableObjectPrivate::TryLoadCompiledCookDataForPlatform(const ITargetPlatform* TargetPlatform) { const MutablePrivate::FMutableCachedPlatformData* PlatformData = CachedPlatformsData.Find(TargetPlatform->PlatformName()); if (!PlatformData) { return false; } SetModel(PlatformData->Model, GenerateIdentifier(*GetPublic())); SetModelResources(PlatformData->ModelResources.Get(), true); SetModelStreamableBulkData(PlatformData->ModelStreamableBulkData, true); return GetModel() != nullptr; } #endif // End WITH_EDITOR bool bShowOldCustomizableObjectWarning = true; void UCustomizableObject::PostLoad() { Super::PostLoad(); const int32 CustomVersion = GetLinkerCustomVersion(FCustomizableObjectCustomVersion::GUID); if (CustomVersion < FCustomizableObjectCustomVersion::UseUVRects) // A bit older than 5.5 { if (bShowOldCustomizableObjectWarning) { bShowOldCustomizableObjectWarning = false; FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("OldCustomizableObject", "Old Customizable Objects found. Please resave them to avoid future incompatibilities. See output log for more information.")); } UE_LOG(LogMutable, Warning, TEXT("Unsupported old Customizable Object. Please resave it: %s"), *GetPackage()->GetPathName()); } #if WITH_EDITOR if (Source) { Source->ConditionalPostLoad(); } for (int32 Version = CustomVersion + 1; Version <= FCustomizableObjectCustomVersion::LatestVersion; ++Version) { GetPrivate()->BackwardsCompatibleFixup(Version); if (Source) { if (ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { // Execute backwards compatible code for all nodes. It requires all nodes to be loaded. Module->BackwardsCompatibleFixup(*Source, Version); } } } if (Source) { if (ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { Module->PostBackwardsCompatibleFixup(*Source); } } // Register to dirty delegate so we update derived data version ID each time that the package is marked as dirty. if (UPackage* Package = GetOutermost()) { Package->PackageMarkedDirtyEvent.AddWeakLambda(this, [this](UPackage* Pkg, bool bWasDirty) { if (GetPackage() == Pkg) { GetPrivate()->UpdateVersionId(); } }); } GetPrivate()->Status.NextState(FCustomizableObjectStatusTypes::EState::Loading); UCustomizableObjectSystem::GetInstance()->GetPrivate()->AddPendingLoad(this); #endif } void UCustomizableObjectPrivate::BackwardsCompatibleFixup(int32 CustomizableObjectCustomVersion) { #if WITH_EDITOR if (GetPublic()->ReferenceSkeletalMesh_DEPRECATED) { GetPublic()->ReferenceSkeletalMeshes_DEPRECATED.Add(GetPublic()->ReferenceSkeletalMesh_DEPRECATED); GetPublic()->ReferenceSkeletalMesh_DEPRECATED = nullptr; } #if WITH_EDITORONLY_DATA if (CustomizableObjectCustomVersion == FCustomizableObjectCustomVersion::CompilationOptions) { OptimizationLevel = GetPublic()->CompileOptions_DEPRECATED.OptimizationLevel; TextureCompression = GetPublic()->CompileOptions_DEPRECATED.TextureCompression; bUseDiskCompilation = GetPublic()->CompileOptions_DEPRECATED.bUseDiskCompilation; EmbeddedDataBytesLimit = GetPublic()->CompileOptions_DEPRECATED.EmbeddedDataBytesLimit; PackagedDataBytesLimit = GetPublic()->CompileOptions_DEPRECATED.PackagedDataBytesLimit; } if (CustomizableObjectCustomVersion == FCustomizableObjectCustomVersion::NewComponentOptions) { if (MutableMeshComponents_DEPRECATED.IsEmpty()) { for (int32 SkeletalMeshIndex = 0; SkeletalMeshIndex < GetPublic()->ReferenceSkeletalMeshes_DEPRECATED.Num(); ++SkeletalMeshIndex) { FMutableMeshComponentData NewComponent; NewComponent.Name = FName(FString::FromInt(SkeletalMeshIndex)); NewComponent.ReferenceSkeletalMesh = GetPublic()->ReferenceSkeletalMeshes_DEPRECATED[SkeletalMeshIndex]; MutableMeshComponents_DEPRECATED.Add(NewComponent); } GetPublic()->ReferenceSkeletalMeshes_DEPRECATED.Empty(); } } #endif #endif } bool UCustomizableObjectPrivate::IsLocked() const { return bLocked; } void UCustomizableObject::Serialize(FArchive& Ar_Asset) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableObject::Serialize) Super::Serialize(Ar_Asset); Ar_Asset.UsingCustomVersion(FCustomizableObjectCustomVersion::GUID); #if WITH_EDITOR if (Ar_Asset.IsCooking()) { if (Ar_Asset.IsSaving()) { UE_LOG(LogMutable, Verbose, TEXT("Serializing cooked data for Customizable Object [%s]."), *GetName()); GetPrivate()->SaveEmbeddedData(Ar_Asset); } } else { // Can't remove this or saved customizable objects will fail to load int64 InternalVersion = 0; Ar_Asset << InternalVersion; } #else if (Ar_Asset.IsLoading()) { GetPrivate()->LoadEmbeddedData(Ar_Asset); } #endif } #if WITH_EDITOR void UCustomizableObject::PostRename(UObject * OldOuter, const FName OldName) { Super::PostRename(OldOuter, OldName); if (Source) { Source->PostRename(OldOuter, OldName); } } void UCustomizableObject::BeginCacheForCookedPlatformData(const ITargetPlatform* TargetPlatform) { ICustomizableObjectEditorModule& Module = ICustomizableObjectEditorModule::GetChecked(); Module.BeginCacheForCookedPlatformData(*this, TargetPlatform); } bool UCustomizableObject::IsCachedCookedPlatformDataLoaded(const ITargetPlatform* TargetPlatform) { ICustomizableObjectEditorModule& Module = ICustomizableObjectEditorModule::GetChecked(); return Module.IsCachedCookedPlatformDataLoaded(*this, TargetPlatform); } FGuid GenerateIdentifier(const UCustomizableObject& CustomizableObject) { // Generate the Identifier using the path and name of the asset uint32 FullPathHash = GetTypeHash(CustomizableObject.GetFullName()); uint32 OutermostHash = GetTypeHash(GetNameSafe(CustomizableObject.GetOutermost())); uint32 OuterHash = GetTypeHash(CustomizableObject.GetName()); return FGuid(0, FullPathHash, OutermostHash, OuterHash); } FString GetModelResourcesNameForPlatform(const UCustomizableObject& CustomizableObject, const ITargetPlatform& InTargetPlatform) { return GenerateIdentifier(CustomizableObject).ToString() + InTargetPlatform.PlatformName(); } bool UCustomizableObjectPrivate::LoadModelResources(FArchive& MemoryReader, const ITargetPlatform* InTargetPlatform, bool bIsCooking) { TObjectPtr LocalModelResources = LoadModelResources_Internal(MemoryReader, GetPublic(), InTargetPlatform, bIsCooking); SetModelResources(LocalModelResources, bIsCooking); return LocalModelResources != nullptr; } void UCustomizableObjectPrivate::LoadModelStreamableBulk(FArchive& MemoryReader, bool bIsCooking) { SetModelStreamableBulkData(LoadModelStreamableBulk_Internal(MemoryReader), bIsCooking); } void UCustomizableObjectPrivate::LoadModel(FArchive& MemoryReader) { SetModel(LoadModel_Internal(MemoryReader), GenerateIdentifier(*GetPublic())); } TObjectPtr LoadModelResources_Internal(FArchive& MemoryReader, const UCustomizableObject* Outer, const ITargetPlatform* InTargetPlatform, bool bIsCooking) { // Make sure mutable has been initialised. UCustomizableObjectSystem::GetInstance(); const FString ModelResourcesName = GetModelResourcesNameForPlatform(*Outer, *InTargetPlatform); TObjectPtr LocalModelResources = NewObject((UObject*)GetTransientPackage(), FName(*ModelResourcesName), RF_Public); FObjectAndNameAsStringProxyArchive ObjectReader(MemoryReader, true); LocalModelResources->Serialize(ObjectReader); const bool bLoadedSuccessfully = LocalModelResources->CodeVersion == GetECustomizableObjectVersionEnumHash(); return bLoadedSuccessfully ? LocalModelResources : nullptr; } const TSharedPtr LoadModelStreamableBulk_Internal(FArchive& MemoryReader) { TSharedPtr LocalModelStreamablesPtr = MakeShared(); FModelStreamableBulkData& LocalModelStreamables = *LocalModelStreamablesPtr.Get(); MemoryReader << LocalModelStreamables; return LocalModelStreamablesPtr; } const TSharedPtr LoadModel_Internal(FArchive& MemoryReader) { FUnrealMutableInputStream Stream(MemoryReader); mu::FInputArchive Arch(&Stream); return mu::FModel::StaticUnserialise(Arch); } void UModelResources::InitCookData(UObject& InCustomizableObject) { const FString ObjectName = InCustomizableObject.GetName(); const int32 NumStreamedResources = StreamedResourceDataEditor.Num(); for (int32 Index = 0; Index < NumStreamedResources; ++Index) { FCustomizableObjectResourceData& ResourceData = StreamedResourceDataEditor[Index]; const FString& ContainerName = ObjectName + FString::Printf(TEXT("_SR_%d"), Index); UCustomizableObjectResourceDataContainer* Container = FindObject(&InCustomizableObject, *ContainerName); if (!Container) { Container = NewObject( &InCustomizableObject, FName(*ContainerName), RF_Public); Container->Data = ResourceData; if (FCustomizableObjectAssetUserData* AUDResource = Container->Data.Data.GetMutablePtr()) { // Find or duplicate the AUD replacing the outer UAssetUserData* SourceAssetUserData = MutablePrivate::LoadObject(AUDResource->AssetUserDataEditor); const FString AssetName = GetNameSafe(SourceAssetUserData); check(SourceAssetUserData != nullptr); UAssetUserData* AssetUserData = FindObject(Container, *AssetName); if (!AssetUserData) { // AUD may be private objects within meshes. Duplicate changing the outer to avoid including meshes into the builds. AssetUserData = DuplicateObject(SourceAssetUserData, Container, FName(*AssetName)); } AUDResource->AssetUserData = AssetUserData; } } StreamedResourceData.Emplace(Container); } const TArrayView AllExtensions = ICustomizableObjectModule::Get().GetRegisteredExtensions(); const int32 NumStreamedExtensionResources = StreamedExtensionDataEditor.Num(); for (int32 Index = 0; Index < NumStreamedExtensionResources; ++Index) { const FString& ContainerName = ObjectName + FString::Printf(TEXT("_SE_%d"), Index); FCustomizableObjectResourceData& ResourceData = StreamedExtensionDataEditor[Index]; UCustomizableObjectResourceDataContainer* Container = FindObject(&InCustomizableObject, *ContainerName); if (!Container) { Container = NewObject( &InCustomizableObject, FName(*ContainerName), RF_Public); for (const UCustomizableObjectExtension* Extension : AllExtensions) { Extension->MovePrivateReferencesToContainer(ResourceData.Data, Container); } Container->Data = MoveTemp(ResourceData); } StreamedExtensionData.Emplace(Container); } } void UCustomizableObjectPrivate::LoadCompiledDataFromDisk() { MUTABLE_CPUPROFILER_SCOPE(UCustomizableObjectPrivate::LoadCompiledDataFromDisk) // Skip data loading from disk if (IsRunningCookCommandlet()) { Status.NextState(FCustomizableObjectStatusTypes::EState::NoModel); return; } ITargetPlatformManagerModule& TargetPlatformManager = GetTargetPlatformManagerRef(); const ITargetPlatform* RunningPlatform = TargetPlatformManager.GetRunningTargetPlatform(); check(RunningPlatform); IFileManager& FileManager = IFileManager::Get(); TArray> FileHandles; // Compose Folder Name const FString FullFileName = GetCompiledDataFileName(RunningPlatform); FGuid CompiledVersionId; bool bHasCompiledData = true; for (int32 DataType = 0; DataType < static_cast(MutablePrivate::EStreamableDataType::DataTypeCount); ++DataType) { const FString FilePath = FullFileName + GetDataTypeExtension(static_cast(DataType)); if (IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FilePath)) { FileHandles.Emplace(FileHandle); TArray HeaderBytes; constexpr int32 HeaderSize = sizeof(MutableCompiledDataStreamHeader); HeaderBytes.SetNum(HeaderSize); FileHandle->Read(HeaderBytes.GetData(), HeaderSize); FMemoryReader AuxMemoryReader(HeaderBytes); MutableCompiledDataStreamHeader DataTypeHeader; AuxMemoryReader << DataTypeHeader; if(DataTypeHeader.InternalVersion != GetECustomizableObjectVersionEnumHash() || (!IsRunningGame() && DataTypeHeader.VersionId != GetVersionId()) || (CompiledVersionId.IsValid() && CompiledVersionId != DataTypeHeader.VersionId)) { bHasCompiledData = false; break; } CompiledVersionId = DataTypeHeader.VersionId; } else { bHasCompiledData = false; break; } } if (bHasCompiledData) { TArray64 CompiledDataBytes; const int64 CompiledDataSize = FileHandles[0]->Size() - FileHandles[0]->Tell(); CompiledDataBytes.SetNumUninitialized(CompiledDataSize); FileHandles[0]->Read(CompiledDataBytes.GetData(), CompiledDataSize); FMemoryReaderView MemoryReader(CompiledDataBytes); if (LoadModelResources(MemoryReader, RunningPlatform)) { TArray OutOfDatePackages; TArray AddedPackages; TArray RemovedPackages; bool bReleaseVersion; const bool bOutOfDate = IsCompilationOutOfDate(false, OutOfDatePackages, AddedPackages, RemovedPackages, bReleaseVersion); if (!bOutOfDate) { LoadModelStreamableBulk(MemoryReader, /* bIsCooking */false); LoadModel(MemoryReader); } else { if (OutOfDatePackages.Num()) { UE_LOG(LogMutable, Display, TEXT("Invalidating compiled data due to changes in %s."), *OutOfDatePackages[0].ToString()); } PrintParticipatingPackagesDiff(OutOfDatePackages, AddedPackages, RemovedPackages, bReleaseVersion); } } } if (!GetModel()) // Failed to load the model { Status.NextState(FCustomizableObjectStatusTypes::EState::NoModel); } } bool UCustomizableObject::ConditionalAutoCompile() { check(IsInGameThread()); // Don't compile objects being compiled if (GetPrivate()->IsLocked()) { return false; } // Don't compile compiled objects if (IsCompiled()) { return true; } UCustomizableObjectSystem* System = UCustomizableObjectSystem::GetInstance(); if (!System || !System->IsValidLowLevel() || System->HasAnyFlags(RF_BeginDestroyed)) { return false; } // Don't re-compile objects if they failed to compile. if (GetPrivate()->CompilationResult == ECompilationResultPrivate::Errors) { return false; } // By default, don't compile in a commandlet. // Notice that the cook is also a commandlet. Do not add a warning/error, otherwise we could end up invalidating the cook for no reason. if (IsRunningCookCommandlet() || (IsRunningCommandlet() && !System->IsAutoCompileCommandletEnabled())) { return false; } // Don't compile if Mutable or AutoCompile is disabled. if (!System->IsActive() || !System->IsAutoCompileEnabled()) { return false; } // Get the EditorModule : Will be null if IsRunningGame() is true if (ICustomizableObjectEditorModule* EditorModule = ICustomizableObjectEditorModule::Get()) { if (System->IsAutoCompilationSync() && GetPrivate()->Status.Get() == FCustomizableObjectStatusTypes::EState::Loading) { return false; } else { EditorModule->CompileCustomizableObject(*this, nullptr, true, false); } } return IsCompiled(); } FReply UCustomizableObjectPrivate::AddNewParameterProfile(FString Name, UCustomizableObjectInstance& CustomInstance) { if (Name.IsEmpty()) { Name = "Unnamed_Profile"; } FString ProfileName = Name; int32 Suffix = 0; bool bUniqueNameFound = false; while (!bUniqueNameFound) { FProfileParameterDat* Found = GetPublic()->InstancePropertiesProfiles.FindByPredicate( [&ProfileName](const FProfileParameterDat& Profile) { return Profile.ProfileName == ProfileName; }); bUniqueNameFound = static_cast(!Found); if (Found) { ProfileName = Name + FString::FromInt(Suffix); ++Suffix; } } int32 ProfileIndex = GetPublic()->InstancePropertiesProfiles.Emplace(); GetPublic()->InstancePropertiesProfiles[ProfileIndex].ProfileName = ProfileName; CustomInstance.GetPrivate()->SaveParametersToProfile(ProfileIndex); Modify(); return FReply::Handled(); } uint32 GetECustomizableObjectVersionEnumHash() { static const uint32 VersionsHash = []() -> uint32 { UEnum* Enum = StaticEnum(); check(Enum); const int32 StartIndex = Enum->GetIndexByValue(static_cast(ECustomizableObjectVersions::FirstEnumeratedVersion)); const int32 EndIndex = Enum->GetIndexByValue(static_cast(ECustomizableObjectVersions::LastCustomizableObjectVersion)); check(StartIndex < EndIndex); uint32 CombinedHash = 0; for (int32 Index = StartIndex; Index <= EndIndex; Index++) { const FString VersionString = Enum->GetNameStringByIndex(Index); CombinedHash = HashCombine(GetTypeHash(VersionString), CombinedHash); } return CombinedHash; }(); return VersionsHash; } FString GetCompiledDataFolderPath() { return FPaths::ConvertRelativePathToFull(FPaths::ProjectSavedDir() + TEXT("MutableStreamedDataEditor/")); } FString UCustomizableObjectPrivate::GetCompiledDataFileName(const ITargetPlatform* InTargetPlatform, bool bIsDiskStreamer) const { const FString FilePath = GetCompiledDataFolderPath(); const FString PlatformName = InTargetPlatform ? InTargetPlatform->PlatformName() : FPlatformProperties::PlatformName(); const FString FileIdentifier = bIsDiskStreamer ? Identifier.ToString() : GenerateIdentifier(*GetPublic()).ToString(); return FilePath + PlatformName + FileIdentifier; } FString GetDataTypeExtension(MutablePrivate::EStreamableDataType DataType) { switch (DataType) { case MutablePrivate::EStreamableDataType::None: return TEXT(".mut"); case MutablePrivate::EStreamableDataType::Model: return TEXT("_M.mut"); case MutablePrivate::EStreamableDataType::RealTimeMorph: return TEXT("_RTM.mut"); case MutablePrivate::EStreamableDataType::Clothing: return TEXT("_C.mut"); default: { unimplemented(); return s_EmptyString; } } } FString UCustomizableObject::GetDesc() { int32 States = GetStateCount(); int32 Params = GetParameterCount(); return FString::Printf(TEXT("%d States, %d Parameters"), States, Params); } void UCustomizableObjectPrivate::SaveEmbeddedData(FArchive& Ar) { UE_LOG(LogMutable, Verbose, TEXT("Saving embedded data for Customizable Object [%s] now at position %d."), *GetName(), int(Ar.Tell())); TSharedPtr Model = GetModel(); int64 InternalVersion = Model ? (int64)GetECustomizableObjectVersionEnumHash() : INDEX_NONE; Ar << InternalVersion; if (Model) { if (Ar.IsCooking()) { Model->GetPrivate()->Program.RomsCompileData.Empty(); } UnrealMutableOutputStream Stream(Ar); mu::FOutputArchive Arch(&Stream); mu::FModel::Serialise(Model.Get(), Arch); UE_LOG(LogMutable, Verbose, TEXT("Saved embedded data for Customizable Object [%s] now at position %d."), *GetName(), int(Ar.Tell())); } } #endif // End WITH_EDITOR void UCustomizableObjectPrivate::LoadEmbeddedData(FArchive& Ar) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableObject::LoadEmbeddedData) int64 InternalVersion; Ar << InternalVersion; // If this fails, something went wrong with the packaging: we have data that belongs // to a different version than the code. if (ensure(InternalVersion != INDEX_NONE)) { // Load model FUnrealMutableInputStream Stream(Ar); mu::FInputArchive Arch(&Stream); TSharedPtr Model = mu::FModel::StaticUnserialise(Arch); SetModel(Model, FGuid()); } } const UCustomizableObjectPrivate* UCustomizableObject::GetPrivate() const { check(Private); return Private; } UCustomizableObjectPrivate* UCustomizableObject::GetPrivate() { check(Private); return Private; } bool UCustomizableObject::IsCompiled() const { #if WITH_EDITOR const bool bIsCompiled = GetPrivate()->GetModel() != nullptr && GetPrivate()->GetModel()->IsValid(); #else const bool bIsCompiled = GetPrivate()->GetModel() != nullptr; #endif return bIsCompiled; } bool UCustomizableObject::IsLoading() const { return GetPrivate()->Status.Get() == FCustomizableObjectStatusTypes::EState::Loading; } void UCustomizableObjectPrivate::AddUncompiledCOWarning(const FString& AdditionalLoggingInfo) { // Send a warning (on-screen notification, log error, and in-editor notification) UCustomizableObjectSystem* System = UCustomizableObjectSystem::GetInstance(); if (!System || !System->IsValidLowLevel() || System->HasAnyFlags(RF_BeginDestroyed)) { return; } System->AddUncompiledCOWarning(*GetPublic(), &AdditionalLoggingInfo); } USkeletalMesh* UCustomizableObject::GetComponentMeshReferenceSkeletalMesh(const FName& ComponentName) const { // Try to get the data from the CO graph. #if WITH_EDITORONLY_DATA if (const ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { return Module->GetReferenceSkeletalMesh(*this, ComponentName); } #endif // If WITH_EDITORONLY_DATA is false Look instead for the ReferenceSkeletalMesh in the compiled resources // TODO UE-282479 Make this a separated option so we can access the compiled data even in the editor execution as // currently the graph search will be always performed if the editor only data is accessible. if (const UModelResources* ModelResources = Private->GetModelResources()) { int32 ObjectComponentIndex = ModelResources->ComponentNamesPerObjectComponent.IndexOfByKey(ComponentName); if (ModelResources->ReferenceSkeletalMeshesData.IsValidIndex(ObjectComponentIndex)) { // Can be nullptr if RefSkeletalMeshes are not loaded yet. return ModelResources->ReferenceSkeletalMeshesData[ObjectComponentIndex].SkeletalMesh; } } return nullptr; } int32 UCustomizableObject::FindState( const FString& Name ) const { return GetPrivate()->FindState(Name); } int32 UCustomizableObject::GetStateCount() const { int32 Result = 0; if (Private->GetModel()) { Result = Private->GetModel()->GetStateCount(); } return Result; } FString UCustomizableObject::GetStateName(int32 StateIndex) const { return GetPrivate()->GetStateName(StateIndex); } FString UCustomizableObjectPrivate::GetStateName(int32 StateIndex) const { FString Result; if (GetModel()) { Result = GetModel()->GetStateName(StateIndex); } return Result; } int32 UCustomizableObject::GetStateParameterCount( int32 StateIndex ) const { int32 Result = 0; if (Private->GetModel()) { Result = Private->GetModel()->GetStateParameterCount(StateIndex); } return Result; } int32 UCustomizableObject::GetStateParameterIndex(int32 StateIndex, int32 ParameterIndex) const { return GetPrivate()->GetStateParameterIndex(StateIndex, ParameterIndex); } int32 UCustomizableObject::GetStateParameterCount(const FString& StateName) const { int32 StateIndex = GetPrivate()->FindState(StateName); PRAGMA_DISABLE_DEPRECATION_WARNINGS return GetStateParameterCount(StateIndex); PRAGMA_ENABLE_DEPRECATION_WARNINGS } FString UCustomizableObject::GetStateParameterName(const FString& StateName, int32 ParameterIndex) const { int32 StateIndex = GetPrivate()->FindState(StateName); PRAGMA_DISABLE_DEPRECATION_WARNINGS return GetStateParameterName(StateIndex, ParameterIndex); PRAGMA_ENABLE_DEPRECATION_WARNINGS } FString UCustomizableObject::GetStateParameterName(int32 StateIndex, int32 ParameterIndex) const { return GetParameterName(GetPrivate()->GetStateParameterIndex(StateIndex, ParameterIndex)); } #if WITH_EDITORONLY_DATA void UCustomizableObjectPrivate::PostCompile() { for (TObjectIterator It; It; ++It) { if (It->GetCustomizableObject() == this->GetPublic()) { // This cannot be bound to the PostCompileDelegate below because the CO Editor binds to it too and the order of broadcast is indeterminate. // The Instance's OnPostCompile() must happen before all the other bindings. It->GetPrivate()->OnPostCompile(); } } PostCompileDelegate.Broadcast(); } #endif const UCustomizableObjectBulk* UCustomizableObjectPrivate::GetStreamableBulkData() const { return GetPublic()->BulkData; } UCustomizableObject* UCustomizableObjectPrivate::GetPublic() const { UCustomizableObject* Public = StaticCast(GetOuter()); check(Public); return Public; } #if WITH_EDITORONLY_DATA FPostCompileDelegate& UCustomizableObject::GetPostCompileDelegate() { return GetPrivate()->PostCompileDelegate; } #endif UCustomizableObjectInstance* UCustomizableObject::CreateInstance() { MUTABLE_CPUPROFILER_SCOPE(UCustomizableObject::CreateInstance) UCustomizableObjectInstance* PreviewInstance = NewObject(GetTransientPackage(), NAME_None, RF_Transient); PreviewInstance->SetObject(this); PreviewInstance->GetPrivate()->bShowOnlyRuntimeParameters = false; UE_LOG(LogMutable, Verbose, TEXT("Created Customizable Object Instance.")); return PreviewInstance; } int32 UCustomizableObject::GetComponentCount() const { if (const UModelResources* ModelResources = GetPrivate()->GetModelResources()) { return ModelResources->ComponentNamesPerObjectComponent.Num(); } return 0; } FName UCustomizableObjectPrivate::GetComponentName(FCustomizableObjectComponentIndex ObjectComponentIndex) const { if (const UModelResources* LocalModelResources = GetModelResources()) { const TArray& ComponentNames = LocalModelResources->ComponentNamesPerObjectComponent; if (ComponentNames.IsValidIndex(ObjectComponentIndex.GetValue())) { return ComponentNames[ObjectComponentIndex.GetValue()]; } } return NAME_None; } #if WITH_EDITORONLY_DATA EMutableCompileMeshType UCustomizableObjectPrivate::GetMeshCompileType() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->MeshCompileType; PRAGMA_ENABLE_DEPRECATION_WARNINGS } const TArray>& UCustomizableObjectPrivate::GetWorkingSet() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->WorkingSet; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsAssetUserDataMergeEnabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnableAssetUserDataMerge; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsTableMaterialsParentCheckDisabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bDisableTableMaterialsParentCheck; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsRealTimeMorphTargetsEnabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnableRealTimeMorphTargets; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsClothingEnabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnableClothing; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::Is16BitBoneWeightsEnabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnable16BitBoneWeights; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsAltSkinWeightProfilesEnabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnableAltSkinWeightProfiles; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsPhysicsAssetMergeEnabled() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnablePhysicsAssetMerge; PRAGMA_ENABLE_DEPRECATION_WARNINGS } bool UCustomizableObjectPrivate::IsEnabledAnimBpPhysicsAssetsManipulation() const { PRAGMA_DISABLE_DEPRECATION_WARNINGS // TODO Remove >5.6 return GetPublic()->bEnableAnimBpPhysicsAssetsManipualtion; PRAGMA_ENABLE_DEPRECATION_WARNINGS } #endif const FString& UCustomizableObjectPrivate::GetIntParameterAvailableOption(int32 ParamIndex, int32 K) const { if (ParamIndex >= 0 && ParamIndex < ParameterProperties.Num()) { if (K >= 0 && K < GetEnumParameterNumValues(ParamIndex)) { return ParameterProperties[ParamIndex].PossibleValues[K].Name; } else { UE_LOG(LogMutable, Warning, TEXT("Index [%d] out of IntParameterNumOptions bounds at GetIntParameterAvailableOption at CO %s."), K, *GetName()); } } else { UE_LOG(LogMutable, Warning, TEXT("Index [%d] out of ParameterProperties bounds at GetIntParameterAvailableOption at CO %s."), ParamIndex, *GetName()); } return s_EmptyString; } int32 UCustomizableObjectPrivate::GetEnumParameterNumValues(int32 ParamIndex) const { if (ParamIndex >= 0 && ParamIndex < ParameterProperties.Num()) { return ParameterProperties[ParamIndex].PossibleValues.Num(); } else { UE_LOG(LogMutable, Warning, TEXT("Index [%d] out of ParameterProperties bounds at GetIntParameterNumOptions at CO %s."), ParamIndex, *GetName()); } return 0; } FString UCustomizableObjectPrivate::FindIntParameterValueName(int32 ParamIndex, int32 ParamValue) const { if (ParamIndex >= 0 && ParamIndex < ParameterProperties.Num()) { const TArray & PossibleValues = ParameterProperties[ParamIndex].PossibleValues; const int32 MinValueIndex = !PossibleValues.IsEmpty() ? PossibleValues[0].Value : 0; ParamValue = ParamValue - MinValueIndex; if (PossibleValues.IsValidIndex(ParamValue)) { return PossibleValues[ParamValue].Name; } } else { UE_LOG(LogMutable, Warning, TEXT("Index [%d] out of ParameterProperties bounds at FindIntParameterValueName at CO %s."), ParamIndex, *GetName()); } return FString(); } int32 UCustomizableObjectPrivate::FindState(const FString& Name) const { int32 Result = -1; if (GetModel()) { Result = GetModel()->FindState(Name); } return Result; } int32 UCustomizableObjectPrivate::GetStateParameterIndex(int32 StateIndex, int32 ParameterIndex) const { int32 Result = 0; if (GetModel()) { Result = GetModel()->GetStateParameterIndex(StateIndex, ParameterIndex); } return Result; } FName UCustomizableObject::GetComponentName(int32 ComponentIndex) const { return GetPrivate()->GetComponentName(FCustomizableObjectComponentIndex(ComponentIndex)); } int32 UCustomizableObject::GetParameterCount() const { return GetPrivate()->ParameterProperties.Num(); } EMutableParameterType UCustomizableObject::GetParameterType(int32 ParamIndex) const { return GetPrivate()->GetParameterType(ParamIndex); } EMutableParameterType UCustomizableObjectPrivate::GetParameterType(int32 ParamIndex) const { if (ParamIndex >= 0 && ParamIndex < ParameterProperties.Num()) { return ParameterProperties[ParamIndex].Type; } else { UE_LOG(LogMutable, Error, TEXT("Index [%d] out of ParameterProperties bounds at GetParameterType."), ParamIndex); } return EMutableParameterType::None; } EMutableParameterType UCustomizableObject::GetParameterTypeByName(const FString& Name) const { const int32 Index = GetPrivate()->FindParameter(Name); if (GetPrivate()->ParameterProperties.IsValidIndex(Index)) { return GetPrivate()->ParameterProperties[Index].Type; } UE_LOG(LogMutable, Warning, TEXT("Name '%s' does not exist in ParameterProperties lookup table at GetParameterTypeByName at CO %s."), *Name, *GetName()); for (int32 ParamIndex = 0; ParamIndex < GetPrivate()->ParameterProperties.Num(); ++ParamIndex) { if (GetPrivate()->ParameterProperties[ParamIndex].Name == Name) { return GetPrivate()->ParameterProperties[ParamIndex].Type; } } UE_LOG(LogMutable, Warning, TEXT("Name '%s' does not exist in ParameterProperties at GetParameterTypeByName at CO %s."), *Name, *GetName()); return EMutableParameterType::None; } const FString& UCustomizableObject::GetParameterName(int32 ParamIndex) const { if (ParamIndex >= 0 && ParamIndex < GetPrivate()->ParameterProperties.Num()) { return GetPrivate()->ParameterProperties[ParamIndex].Name; } else { UE_LOG(LogMutable, Warning, TEXT("Index [%d] out of ParameterProperties bounds at GetParameterName at CO %s."), ParamIndex, *GetName()); } return s_EmptyString; } void UCustomizableObjectPrivate::UpdateParameterPropertiesFromModel(const TSharedPtr& Model) { if (Model) { TSharedPtr MutableParameters = mu::FModel::NewParameters(Model); const int32 NumParameters = MutableParameters->GetCount(); TArray TypedParametersCount; TypedParametersCount.SetNum(static_cast(mu::EParameterType::Count)); ParameterProperties.Reset(NumParameters); ParameterPropertiesLookupTable.Empty(NumParameters); for (int32 Index = 0; Index < NumParameters; ++Index) { FMutableModelParameterProperties Data; Data.Name = MutableParameters->GetName(Index); Data.Type = EMutableParameterType::None; mu::EParameterType ParameterType = MutableParameters->GetType(Index); switch (ParameterType) { case mu::EParameterType::Bool: { Data.Type = EMutableParameterType::Bool; break; } case mu::EParameterType::Int: { Data.Type = EMutableParameterType::Int; const int32 ValueCount = MutableParameters->GetIntPossibleValueCount(Index); Data.PossibleValues.Reserve(ValueCount); for (int32 ValueIndex = 0; ValueIndex < ValueCount; ++ValueIndex) { FMutableModelParameterValue& ValueData = Data.PossibleValues.AddDefaulted_GetRef(); ValueData.Name = MutableParameters->GetIntPossibleValueName(Index, ValueIndex); ValueData.Value = MutableParameters->GetIntPossibleValue(Index, ValueIndex); } break; } case mu::EParameterType::Float: { Data.Type = EMutableParameterType::Float; break; } case mu::EParameterType::Color: { Data.Type = EMutableParameterType::Color; break; } case mu::EParameterType::Projector: { Data.Type = EMutableParameterType::Projector; break; } case mu::EParameterType::Matrix: { Data.Type = EMutableParameterType::Transform; break; } case mu::EParameterType::Image: { Data.Type = EMutableParameterType::Texture; break; } case mu::EParameterType::Mesh: { Data.Type = EMutableParameterType::SkeletalMesh; break; } default: // Unhandled type? check(false); break; } ParameterProperties.Add(Data); ParameterPropertiesLookupTable.Add(Data.Name, FMutableParameterIndex(Index, TypedParametersCount[static_cast(ParameterType)]++)); } } else { ParameterProperties.Empty(); ParameterPropertiesLookupTable.Empty(); } } int32 UCustomizableObject::GetIntParameterNumOptions(int32 ParamIndex) const { return GetPrivate()->GetEnumParameterNumValues(ParamIndex); } int32 UCustomizableObject::GetEnumParameterNumValues(const FString& ParamName) const { if (int32 ParamIndex = GetPrivate()->FindParameter(ParamName)) { return GetPrivate()->GetEnumParameterNumValues(ParamIndex); } else { return 0; } } const FString& UCustomizableObject::GetEnumParameterValue(const FString& ParamName, int32 ValueIndex) const { if (int32 ParamIndex = GetPrivate()->FindParameter(ParamName)) { return GetPrivate()->GetIntParameterAvailableOption(ParamIndex, ValueIndex); } else { return s_EmptyString; } } bool UCustomizableObject::ContainsEnumParameterValue(const FString& ParameterName, const FString Value) const { int32 ParamIndex = GetPrivate()->FindParameter(ParameterName); if (ParamIndex == INDEX_NONE) { return false; } return GetPrivate()->FindIntParameterValue(ParamIndex, Value) != INDEX_NONE; } const FString& UCustomizableObject::GetIntParameterAvailableOption(int32 ParamIndex, int32 K) const { return GetPrivate()->GetIntParameterAvailableOption(ParamIndex, K); } int32 UCustomizableObject::FindParameter(const FString& Name) const { return GetPrivate()->FindParameter(Name); } bool UCustomizableObject::ContainsParameter(const FString& ParameterName) const { return GetPrivate()->FindParameter(ParameterName) != INDEX_NONE; } int32 UCustomizableObjectPrivate::FindParameter(const FString& Name) const { if (const FMutableParameterIndex* Found = ParameterPropertiesLookupTable.Find(Name)) { return Found->Index; } return INDEX_NONE; } int32 UCustomizableObjectPrivate::FindParameterTyped(const FString& Name, EMutableParameterType Type) const { if (const FMutableParameterIndex* Found = ParameterPropertiesLookupTable.Find(Name)) { if (ParameterProperties[Found->Index].Type == Type) { return Found->TypedIndex; } } return INDEX_NONE; } int32 UCustomizableObject::FindIntParameterValue(int32 ParamIndex, const FString& Value) const { return GetPrivate()->FindIntParameterValue(ParamIndex, Value); } int32 UCustomizableObjectPrivate::FindIntParameterValue(int32 ParamIndex, const FString& Value) const { int32 MinValueIndex = INDEX_NONE; if (ParamIndex >= 0 && ParamIndex < ParameterProperties.Num()) { const TArray& PossibleValues = ParameterProperties[ParamIndex].PossibleValues; if (PossibleValues.Num()) { MinValueIndex = PossibleValues[0].Value; for (int32 OrderValue = 0; OrderValue < PossibleValues.Num(); ++OrderValue) { const FString& Name = PossibleValues[OrderValue].Name; if (Name == Value) { int32 CorrectedValue = OrderValue + MinValueIndex; check(PossibleValues[OrderValue].Value == CorrectedValue); return CorrectedValue; } } } } return MinValueIndex; } FString UCustomizableObject::FindIntParameterValueName(int32 ParamIndex, int32 ParamValue) const { return GetPrivate()->FindIntParameterValueName(ParamIndex, ParamValue); } USkeletalMesh* UCustomizableObject::GetRefSkeletalMesh(int32 ObjectComponentIndex) const { return GetComponentMeshReferenceSkeletalMesh(FName(FString::FromInt(ObjectComponentIndex))); } FMutableParamUIMetadata UCustomizableObject::GetParameterUIMetadata(const FString& ParamName) const { if (const UModelResources* ModelResources = Private->GetModelResources()) { const FMutableParameterData* ParameterData = ModelResources->ParameterUIDataMap.Find(ParamName); return ParameterData ? ParameterData->ParamUIMetadata : FMutableParamUIMetadata(); } return FMutableParamUIMetadata(); } FMutableParamUIMetadata UCustomizableObject::GetEnumParameterValueUIMetadata(const FString& ParamName, const FString& OptionName) const { const UModelResources* ModelResources = Private->GetModelResources(); if (!ModelResources) { return FMutableParamUIMetadata(); } const int32 ParameterIndex = GetPrivate()->FindParameter(ParamName); if (ParameterIndex == INDEX_NONE) { return FMutableParamUIMetadata(); } if (const FMutableParameterData* ParameterData = ModelResources->ParameterUIDataMap.Find(ParamName)) { if (const FIntegerParameterUIData* IntegerParameterUIData = ParameterData->ArrayIntegerParameterOption.Find(OptionName)) { return IntegerParameterUIData->ParamUIMetadata; } } return FMutableParamUIMetadata(); } FMutableParamUIMetadata UCustomizableObject::GetIntParameterOptionUIMetadata(const FString& ParamName, const FString& OptionName) const { return GetEnumParameterValueUIMetadata(ParamName, OptionName); } ECustomizableObjectGroupType UCustomizableObject::GetEnumParameterGroupType(const FString& ParamName) const { const UModelResources* ModelResources = Private->GetModelResources(); if (!ModelResources) { return ECustomizableObjectGroupType::COGT_TOGGLE; } const int32 ParameterIndex = GetPrivate()->FindParameter(ParamName); if (ParameterIndex == INDEX_NONE) { return ECustomizableObjectGroupType::COGT_TOGGLE; } if (const FMutableParameterData* ParameterData = ModelResources->ParameterUIDataMap.Find(ParamName)) { return ParameterData->IntegerParameterGroupType; } return ECustomizableObjectGroupType::COGT_TOGGLE; } ECustomizableObjectGroupType UCustomizableObject::GetIntParameterGroupType(const FString& ParamName) const { return GetEnumParameterGroupType(ParamName); } FMutableStateUIMetadata UCustomizableObject::GetStateUIMetadata(const FString& StateName) const { if (const UModelResources* ModelResources = Private->GetModelResources()) { if (const FMutableStateData* StateData = ModelResources->StateUIDataMap.Find(StateName)) { return StateData->StateUIMetadata; } } return FMutableStateUIMetadata(); } #if WITH_EDITOR uint32 GetTypeHash(const FIntegerParameterOptionKey& Key) { uint32 Hash = GetTypeHash(Key.ParameterName); Hash = HashCombine(Hash, GetTypeHash(Key.ParameterOption)); return Hash; } TArray> UCustomizableObject::GetEnumParameterValueDataTable(const FString& ParamName, const FString& ValueName) { if(const UModelResources* LocalModelResources = GetPrivate()->GetModelResources()) { if (const FIntegerParameterOptionDataTable* Result = LocalModelResources->IntParameterOptionDataTable.Find({ ParamName, ValueName })) { return Result->DataTables.Array(); } } return {}; } TArray> UCustomizableObject::GetIntParameterOptionDataTable(const FString& ParamName, const FString& OptionName) { return GetEnumParameterValueDataTable(ParamName, OptionName); } #endif float UCustomizableObject::GetFloatParameterDefaultValue(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent float parameter [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return FCustomizableObjectFloatParameterValue::DEFAULT_PARAMETER_VALUE; } const TSharedPtr& Model = GetPrivate()->GetModel(); if (!Model) { checkNoEntry(); return FCustomizableObjectFloatParameterValue::DEFAULT_PARAMETER_VALUE; } return Model->GetFloatDefaultValue(ParameterIndex); } int32 UCustomizableObject::GetEnumParameterDefaultValue(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent integer parameter [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return FCustomizableObjectIntParameterValue::DEFAULT_PARAMETER_VALUE; } const TSharedPtr& Model = GetPrivate()->GetModel(); if (!Model) { checkNoEntry(); return FCustomizableObjectIntParameterValue::DEFAULT_PARAMETER_VALUE; } return Model->GetIntDefaultValue(ParameterIndex); } int32 UCustomizableObject::GetIntParameterDefaultValue(const FString& InParameterName) const { return GetEnumParameterDefaultValue(InParameterName); } bool UCustomizableObject::GetBoolParameterDefaultValue(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent boolean parameter [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return FCustomizableObjectBoolParameterValue::DEFAULT_PARAMETER_VALUE; } const TSharedPtr& Model = GetPrivate()->GetModel(); if (!Model) { checkNoEntry(); return FCustomizableObjectBoolParameterValue::DEFAULT_PARAMETER_VALUE; } return Model->GetBoolDefaultValue(ParameterIndex); } FLinearColor UCustomizableObject::GetColorParameterDefaultValue(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent color parameter [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return FCustomizableObjectVectorParameterValue::DEFAULT_PARAMETER_VALUE; } const TSharedPtr& Model = GetPrivate()->GetModel(); if (!Model) { checkNoEntry(); return FCustomizableObjectVectorParameterValue::DEFAULT_PARAMETER_VALUE; } FVector4f Value; Model->GetColourDefaultValue(ParameterIndex, Value); return Value; } FTransform UCustomizableObject::GetTransformParameterDefaultValue(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent color parameter [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return FCustomizableObjectTransformParameterValue::DEFAULT_PARAMETER_VALUE; } const TSharedPtr& Model = GetPrivate()->GetModel(); if (!Model) { checkNoEntry(); return FCustomizableObjectTransformParameterValue::DEFAULT_PARAMETER_VALUE; } const FMatrix44f Matrix = Model->GetMatrixDefaultValue(ParameterIndex); return FTransform(FMatrix(Matrix)); } void UCustomizableObject::GetProjectorParameterDefaultValue(const FString& InParameterName, FVector3f& OutPos, FVector3f& OutDirection, FVector3f& OutUp, FVector3f& OutScale, float& OutAngle, ECustomizableObjectProjectorType& OutType) const { const FCustomizableObjectProjector Projector = GetProjectorParameterDefaultValue(InParameterName); OutType = Projector.ProjectionType; OutPos = Projector.Position; OutDirection = Projector.Direction; OutUp = Projector.Up; OutScale = Projector.Scale; OutAngle = Projector.Angle; } FCustomizableObjectProjector UCustomizableObject::GetProjectorParameterDefaultValue(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent projector [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return FCustomizableObjectProjectorParameterValue::DEFAULT_PARAMETER_VALUE; } const TSharedPtr& Model = GetPrivate()->GetModel(); if (!Model) { checkNoEntry(); return FCustomizableObjectProjectorParameterValue::DEFAULT_PARAMETER_VALUE; } FCustomizableObjectProjector Value; mu::EProjectorType Type; Model->GetProjectorDefaultValue(ParameterIndex, &Type, &Value.Position, &Value.Direction, &Value.Up, &Value.Scale, &Value.Angle); Value.ProjectionType = ProjectorUtils::GetEquivalentProjectorType(Type); return Value; } UTexture* UCustomizableObject::GetTextureParameterDefaultValue(const FString& ParameterName) const { const TObjectPtr* Result = GetPrivate()->GetModelResources()->TextureParameterDefaultValues.Find(FName(ParameterName)); return Result ? *Result : nullptr; } USkeletalMesh* UCustomizableObject::GetSkeletalMeshParameterDefaultValue(const FString& ParameterName) const { const TObjectPtr* Result = GetPrivate()->GetModelResources()->SkeletalMeshParameterDefaultValues.Find(FName(ParameterName)); return Result ? *Result : nullptr; } bool UCustomizableObject::IsParameterMultidimensional(const FString& InParameterName) const { const int32 ParameterIndex = GetPrivate()->FindParameter(InParameterName); if (ParameterIndex == INDEX_NONE) { UE_LOG(LogMutable, Error, TEXT("Tried to access the default value of the nonexistent parameter [%s] in the CustomizableObject [%s]."), *InParameterName, *GetName()); return false; } return GetPrivate()->IsParameterMultidimensional(ParameterIndex); } void UCustomizableObject::Compile(const FCompileParams& Params) { if (ICustomizableObjectEditorModule* EditorModule = ICustomizableObjectEditorModule::Get()) { EditorModule->CompileCustomizableObject(*this, &Params, false, true); } else { FCompileCallbackParams CallbackParams; CallbackParams.bRequestFailed = true; CallbackParams.bCompiled = IsCompiled(); Params.Callback.ExecuteIfBound(CallbackParams); Params.CallbackNative.ExecuteIfBound(CallbackParams); } } bool UCustomizableObject::IsParameterMultidimensional(const int32& InParamIndex) const { return GetPrivate()->IsParameterMultidimensional(InParamIndex); } bool UCustomizableObjectPrivate::IsParameterMultidimensional(const int32& InParamIndex) const { check(InParamIndex != INDEX_NONE); if (const TSharedPtr& Model = GetModel()) { return Model->IsParameterMultidimensional(InParamIndex); } return false; } void UCustomizableObjectPrivate::ApplyStateForcedValuesToParameters(FCustomizableObjectInstanceDescriptor& Descriptor) { const UModelResources* LocalModelResources = GetModelResources(); if (!LocalModelResources) { return; } const FString& StateName = Descriptor.GetCurrentState(); const FMutableStateData* StateData = LocalModelResources->StateUIDataMap.Find(StateName); if (!StateData) { return; } for (FCustomizableObjectIntParameterValue& IntParameter : Descriptor.IntParameters) { if (const FString* Result = StateData->ForcedParameterValues.Find(IntParameter.ParameterName)) { IntParameter.ParameterValueName = *Result; for (FString& Range : IntParameter.ParameterRangeValueNames) { Range = *Result; } } } for (FCustomizableObjectBoolParameterValue& BoolParameter : Descriptor.BoolParameters) { if (const FString* Result = StateData->ForcedParameterValues.Find(BoolParameter.ParameterName)) { BoolParameter.ParameterValue = Result->ToBool(); } } } void UCustomizableObjectPrivate::GetLowPriorityTextureNames(TArray& OutTextureNames) { OutTextureNames.Reset(GetPublic()->LowPriorityTextures.Num()); const UModelResources* LocalModelResources = GetModelResources(); if (LocalModelResources && !GetPublic()->LowPriorityTextures.IsEmpty()) { const int32 ImageCount = LocalModelResources->ImageProperties.Num(); for (int32 ImageIndex = 0; ImageIndex < ImageCount; ++ImageIndex) { if (GetPublic()->LowPriorityTextures.Find(FName(LocalModelResources->ImageProperties[ImageIndex].TextureParameterName)) != INDEX_NONE) { OutTextureNames.Add(FString::FromInt(ImageIndex)); } } } } uint8 UCustomizableObjectPrivate::GetMinLODIndex(const FName& ComponentName) const { uint8 MinLODIdx = 0; const UModelResources* LocalModelResources = GetModelResources(); if (LocalModelResources) { // Use the Scalability quality settings to determine what the MinLOD will be used if (GEngine && GEngine->UseSkeletalMeshMinLODPerQualityLevels) { if (UCustomizableObjectSystem::GetInstance() != nullptr) { // Get the quality level for the skeletal meshes for the current scalability setting. The bigger the better. // Value extracted from the Scalability.ini of the current platform (if found). const int32 QualityLevel = UCustomizableObjectSystem::GetInstance()->GetSkeletalMeshMinLODQualityLevel(); const FPerQualityLevelInt* QualityLevelInt = LocalModelResources->MinQualityLevelLODPerComponent.Find(ComponentName); if (ensure(QualityLevelInt)) { MinLODIdx = QualityLevelInt->GetValue(QualityLevel); } } } else { const FPerPlatformInt* PlatformInt = LocalModelResources->MinLODPerComponent.Find(ComponentName); if (ensure(PlatformInt)) { MinLODIdx = PlatformInt->GetValue(); } } } // Get the first lod the current platform can get generated. It represents the Min LOD mutable can generate for the current platform. uint8 FirstLODAvailable = 0; if (LocalModelResources) { if (const uint8* Result = LocalModelResources->FirstLODAvailable.Find(ComponentName)) { FirstLODAvailable = *Result; } } return FMath::Max(MinLODIdx, FirstLODAvailable); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- USkeletalMesh* FMeshCache::Get(const TArray& Key) { const TWeakObjectPtr* Result = GeneratedMeshes.Find(Key); return Result ? Result->Get() : nullptr; } void FMeshCache::Add(const TArray& Key, USkeletalMesh* Value) { if (!Value) { return; } GeneratedMeshes.Add(Key, Value); // Remove invalid SkeletalMeshes from the cache. for (auto MeshIterator = GeneratedMeshes.CreateIterator(); MeshIterator; ++MeshIterator) { if (MeshIterator.Value().IsStale()) { MeshIterator.RemoveCurrent(); } } } USkeleton* FSkeletonCache::Get(const TArray& Key) { const TWeakObjectPtr* Result = MergedSkeletons.Find(Key); return Result ? Result->Get() : nullptr; } void FSkeletonCache::Add(const TArray& Key, USkeleton* Value) { if (!Value) { return; } MergedSkeletons.Add(Key, Value); // Remove invalid SkeletalMeshes from the cache. for (auto SkeletonIterator = MergedSkeletons.CreateIterator(); SkeletonIterator; ++SkeletonIterator) { if (SkeletonIterator.Value().IsStale()) { SkeletonIterator.RemoveCurrent(); } } } FArchive& operator<<(FArchive& Ar, FIntegerParameterUIData& Struct) { Ar << Struct.ParamUIMetadata; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableParameterData& Struct) { Ar << Struct.ParamUIMetadata; Ar << Struct.Type; Ar << Struct.ArrayIntegerParameterOption; Ar << Struct.IntegerParameterGroupType; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableStateData& Struct) { Ar << Struct.StateUIMetadata; Ar << Struct.bLiveUpdateMode; Ar << Struct.bDisableTextureStreaming; Ar << Struct.bReuseInstanceTextures; Ar << Struct.ForcedParameterValues; return Ar; } void FModelStreamableBulkData::Serialize(FArchive& Ar, UObject* Owner, bool bCooked) { MUTABLE_CPUPROFILER_SCOPE(FModelStreamableBulkData::Serialize); Ar << ModelStreamables; Ar << ClothingStreamables; Ar << RealTimeMorphStreamables; if (bCooked) { int32 NumBulkDatas = StreamableBulkData.Num(); Ar << NumBulkDatas; StreamableBulkData.SetNum(NumBulkDatas); for (FByteBulkData& BulkData : StreamableBulkData) { BulkData.Serialize(Ar, Owner); } } } UModelStreamableData::UModelStreamableData() { StreamingData = MakeShared(); } void UModelStreamableData::Serialize(FArchive& Ar) { Super::Serialize(Ar); bool bCooked = Ar.IsCooking(); Ar << bCooked; if (bCooked && !IsTemplate() && !Ar.IsCountingMemory()) { check(StreamingData); StreamingData->Serialize(Ar, GetOutermostObject(), bCooked); } } void UModelStreamableData::PostLoad() { Super::PostLoad(); if (StreamingData) { const FString OutermostName = GetOutermost()->GetName(); FString PackageFilename = FPackageName::LongPackageNameToFilename(OutermostName); FPaths::MakeStandardFilename(PackageFilename); StreamingData->FullFilePath = PackageFilename; } } void UCustomizableObjectPrivate::SetModel(const TSharedPtr& Model, const FGuid Id) { if (MutableModel == Model #if WITH_EDITOR && Identifier == Id #endif ) { return; } #if WITH_EDITOR if (MutableModel) { MutableModel->Invalidate(); } Identifier = Id; #endif MutableModel = Model; // Create parameter properties UpdateParameterPropertiesFromModel(Model); using EState = FCustomizableObjectStatus::EState; Status.NextState(Model ? EState::ModelLoaded : EState::NoModel); } const TSharedPtr& UCustomizableObjectPrivate::GetModel() { return MutableModel; } const TSharedPtr UCustomizableObjectPrivate::GetModel() const { return MutableModel; } #if WITH_EDITOR void UCustomizableObjectPrivate::SetModelStreamableBulkData(const TSharedPtr& StreamableData, bool bIsCooking) { if (bIsCooking) { if (!ModelStreamableData) { ModelStreamableData = NewObject(GetOuter()); } ModelStreamableData->StreamingData = StreamableData; } else { ModelStreamableDataEditor = StreamableData; } } #endif TSharedPtr UCustomizableObjectPrivate::GetModelStreamableBulkData(bool bIsCooking) const { #if WITH_EDITOR if (bIsCooking) { return ModelStreamableData ? ModelStreamableData->StreamingData : nullptr; } return ModelStreamableDataEditor; #else return ModelStreamableData ? ModelStreamableData->StreamingData : nullptr; #endif } UModelResources* UCustomizableObjectPrivate::GetModelResources() { const UCustomizableObjectPrivate* ConstThis = this; return const_cast(ConstThis->GetModelResources()); } const UModelResources* UCustomizableObjectPrivate::GetModelResources() const { #if WITH_EDITORONLY_DATA return ModelResourcesEditor; #else return ModelResources; #endif } const UModelResources& UCustomizableObjectPrivate::GetModelResourcesChecked() const { const UModelResources* LocalModelResources = GetModelResources(); check(LocalModelResources); return *LocalModelResources; } #if WITH_EDITORONLY_DATA UModelResources* UCustomizableObjectPrivate::GetModelResources(bool bIsCooking) { const UCustomizableObjectPrivate* ConstThis = this; return const_cast(ConstThis->GetModelResources(bIsCooking)); } const UModelResources* UCustomizableObjectPrivate::GetModelResources(bool bIsCooking) const { return bIsCooking ? ModelResources : ModelResourcesEditor; } void UCustomizableObjectPrivate::SetModelResources(UModelResources* InModelResources, bool bIsCooking) { if (bIsCooking) { ModelResources = InModelResources; } else { ModelResourcesEditor = InModelResources; } } #endif #if WITH_EDITOR bool UCustomizableObjectPrivate::IsCompilationOutOfDate(bool bSkipIndirectReferences, TArray& OutOfDatePackages, TArray& AddedPackages, TArray& RemovedPackages, bool& bReleaseVersionDiff) const { if (const ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { return Module->IsCompilationOutOfDate(*GetPublic(), bSkipIndirectReferences, OutOfDatePackages, AddedPackages, RemovedPackages, bReleaseVersionDiff); } return false; } #endif TArray& UCustomizableObjectPrivate::GetCustomizableObjectClassTags() { return GetPublic()->CustomizableObjectClassTags; } TArray& UCustomizableObjectPrivate::GetPopulationClassTags() { return GetPublic()->PopulationClassTags; } TMap& UCustomizableObjectPrivate::GetCustomizableObjectParametersTags() { return GetPublic()->CustomizableObjectParametersTags; } #if WITH_EDITORONLY_DATA TArray& UCustomizableObjectPrivate::GetInstancePropertiesProfiles() { return GetPublic()->InstancePropertiesProfiles; } #endif #if WITH_EDITORONLY_DATA TObjectPtr& UCustomizableObjectPrivate::GetSource() const { return GetPublic()->Source; } FCompilationOptions UCustomizableObjectPrivate::GetCompileOptions() const { FCompilationOptions Options; Options.TextureCompression = TextureCompression; Options.OptimizationLevel = OptimizationLevel; Options.bUseDiskCompilation = bUseDiskCompilation; Options.TargetPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); const int32 TargetBulkDataFileBytesOverride = CVarPackagedDataBytesLimitOverride.GetValueOnAnyThread(); if ( TargetBulkDataFileBytesOverride >= 0) { Options.PackagedDataBytesLimit = TargetBulkDataFileBytesOverride; UE_LOG(LogMutable,Display, TEXT("Ignoring CO PackagedDataBytesLimit value in favour of overriding CVar value : mutable.PackagedDataBytesLimitOverride %llu"), Options.PackagedDataBytesLimit); } else { Options.PackagedDataBytesLimit = PackagedDataBytesLimit; } Options.EmbeddedDataBytesLimit = EmbeddedDataBytesLimit; Options.CustomizableObjectNumBoneInfluences = ICustomizableObjectModule::Get().GetNumBoneInfluences(); Options.bRealTimeMorphTargetsEnabled = IsRealTimeMorphTargetsEnabled(); Options.bClothingEnabled = IsClothingEnabled(); Options.b16BitBoneWeightsEnabled = Is16BitBoneWeightsEnabled(); Options.bSkinWeightProfilesEnabled = IsAltSkinWeightProfilesEnabled(); Options.bPhysicsAssetMergeEnabled = IsPhysicsAssetMergeEnabled(); Options.bAnimBpPhysicsManipulationEnabled = IsEnabledAnimBpPhysicsAssetsManipulation(); Options.ImageTiling = ImageTiling; return Options; } #endif #if WITH_EDITOR namespace MutablePrivate { uint64 FFile::GetSize() const { uint64 FileSize = 0; for (const FBlock& Block : Blocks) { FileSize += Block.Size; } return FileSize; } void FFile::GetFileData(FMutableCachedPlatformData* PlatformData, TArray64& DestData, bool bDropData) { check(PlatformData); const uint64 DestSize = DestData.Num(); const int32 NumBlocks = Blocks.Num(); const uint8* SourceData = nullptr; if (DataType == EStreamableDataType::Model) { for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex) { const FBlock& Block = Blocks[BlockIndex]; check(Block.Offset + Block.Size <= DestSize); PlatformData->ModelStreamableData.Get(Block.Id, TArrayView64(DestData.GetData() + Block.Offset, Block.Size), bDropData); } return; } else if (DataType == EStreamableDataType::RealTimeMorph) { for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex) { const FBlock& Block = Blocks[BlockIndex]; check(Block.Offset + Block.Size <= DestSize); PlatformData->MorphStreamableData.Get(Block.Id, TArrayView64(DestData.GetData() + Block.Offset, Block.Size), bDropData); } } else if (DataType == EStreamableDataType::Clothing) { for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex) { const FBlock& Block = Blocks[BlockIndex]; check(Block.Offset + Block.Size <= DestSize); PlatformData->ClothingStreamableData.Get(Block.Id, TArrayView64(DestData.GetData() + Block.Offset, Block.Size), bDropData); } } else { checkf(false, TEXT("Unknown file DataType found.")); } } FFileCategoryID::FFileCategoryID(EStreamableDataType InDataType, uint16 InResourceType, uint16 InFlags) { DataType = InDataType; ResourceType = InResourceType; Flags = InFlags; } uint32 GetTypeHash(const FFileCategoryID& Key) { uint32 Hash = (uint32)Key.DataType; Hash = HashCombine(Hash, Key.ResourceType); Hash = HashCombine(Hash, Key.Flags); return Hash; } TPair FindOrAddCategory(TArray& Buckets, FFileBucket& DefaultBucket, const FFileCategoryID CategoryID) { // Find the category for (FFileBucket& Bucket : Buckets) { for (FFileCategory& Category : Bucket.Categories) { if (Category.Id == CategoryID) { return TPair(Bucket, Category); } } } // Category not found, add to default bucket FFileCategory& Category = DefaultBucket.Categories.AddDefaulted_GetRef(); Category.Id = CategoryID; return TPair(DefaultBucket, Category); } struct FClassifyNode { TArray Blocks; }; void AddNode(TMap& Nodes, int32 Slack, const FFileCategoryID& CategoryID, const FBlock& Block) { FClassifyNode& Root = Nodes.FindOrAdd(CategoryID); if (Root.Blocks.IsEmpty()) { Root.Blocks.Reserve(Slack); } Root.Blocks.Add(Block); } void GenerateBulkDataFilesListWithFileLimit( TSharedPtr Model, FModelStreamableBulkData& ModelStreamableBulkData, uint32 NumFilesPerBucket, TArray& OutBulkDataFiles) { MUTABLE_CPUPROFILER_SCOPE(GenerateBulkDataFilesListWithFileLimit); if (!Model) { return; } /* Overview. * 1. Add categories to the different buckets and accumulate the size of its resources * to know the total size of each category and the size of the buckets. * 2. Use the accumulated sizes to distribute the NumFilesPerBucket between the bucket's categories. * 3. Generate the list of BulkData files based on the number of files per category. */ // Two buckets. One for non-optional data and one for optional data. TArray FileBuckets; // DefaultBucket is for non-optional BulkData FFileBucket& DefaultBucket = FileBuckets.AddDefaulted_GetRef(); FFileBucket& OptionalBucket = FileBuckets.AddDefaulted_GetRef(); // Model Roms. Iterate all Model roms to distribute them in categories. { // Add meshes and low-res textures to the Default bucket DefaultBucket.Categories.Add({ FFileCategoryID(EStreamableDataType::Model, uint16(mu::EDataType::Mesh), 0), 0, 0, 0 }); DefaultBucket.Categories.Add({ FFileCategoryID(EStreamableDataType::Model, uint16(mu::EDataType::Image), 0), 0, 0, 0 }); // Add High-res textures to the Optional bucket OptionalBucket.Categories.Add({ FFileCategoryID(EStreamableDataType::Model, uint16(mu::EDataType::Image), uint16(EMutableFileFlags::HighRes)), 0, 0, 0}); const int32 NumRoms = Model->GetRomCount(); for (int32 RomIndex = 0; RomIndex < NumRoms; ++RomIndex) { const uint32 BlockSize = Model->GetRomSize(RomIndex); const uint16 BlockResourceType = Model->IsMeshData(RomIndex) ? uint16(mu::EDataType::Mesh) : uint16(mu::EDataType::Image); const EMutableFileFlags BlockFlags = Model->IsRomHighRes(RomIndex) ? EMutableFileFlags::HighRes : EMutableFileFlags::None; FFileCategoryID CategoryID = { EStreamableDataType::Model, BlockResourceType, uint16(BlockFlags) }; TPair It = FindOrAddCategory(FileBuckets, DefaultBucket, CategoryID); // Add block to an existing or new category It.Key.DataSize += BlockSize; It.Value.DataSize += BlockSize; } } // RealTime Morphs. Iterate RealTimeMorph streamables to accumulate their sizes. { // Add RealTimeMorphs to the Default bucket FFileCategory& RealTimeMorphCategory = DefaultBucket.Categories.AddDefaulted_GetRef(); RealTimeMorphCategory.Id.DataType = EStreamableDataType::RealTimeMorph; const TMap& RealTimeMorphStreamables = ModelStreamableBulkData.RealTimeMorphStreamables; for (const TPair& MorphStreamable : RealTimeMorphStreamables) { RealTimeMorphCategory.DataSize += MorphStreamable.Value.Size; } DefaultBucket.DataSize += RealTimeMorphCategory.DataSize; } // Clothing. Iterate clothing streamables to accumulate their sizes. { // Add Clothing to the Default bucket FFileCategory& ClothingCategory = DefaultBucket.Categories.AddDefaulted_GetRef(); ClothingCategory.Id.DataType = EStreamableDataType::Clothing; const TMap& ClothingStreamables = ModelStreamableBulkData.ClothingStreamables; for (const TPair& ClothStreamable : ClothingStreamables) { ClothingCategory.DataSize += ClothStreamable.Value.Size; } DefaultBucket.DataSize += ClothingCategory.DataSize; } // Limited number of files in each bucket. Find the ideal file distribution between categories based on the accumulated size of their resources. TArray Categories; for (FFileBucket& Bucket : FileBuckets) { uint32 NumFiles = 0; for (FFileCategory& Category : Bucket.Categories) { if (Category.DataSize > 0) { double DataDistribution = (double)Category.DataSize / Bucket.DataSize; Category.NumFiles = FMath::Max(DataDistribution * NumFilesPerBucket, 1); // At least one file if size > 0 Category.FirstFile = NumFiles; NumFiles += Category.NumFiles; } } Categories.Append(Bucket.Categories); } // Function to create the list of bulk data files. Blocks will be grouped by source Id. const auto CreateFileList = [Categories](const FFileCategoryID& CategoryID, const FClassifyNode& Node, TArray& OutBulkDataFiles) { const FFileCategory* Category = Categories.FindByPredicate( [CategoryID](const FFileCategory& C) { return C.Id == CategoryID; }); check(Category); int32 NumBulkDataFiles = OutBulkDataFiles.Num(); OutBulkDataFiles.Reserve(NumBulkDataFiles + Category->NumFiles); // FileID (File Index) to BulkData file index. TArray BulkDataFileIndex; BulkDataFileIndex.Init(INDEX_NONE, Category->NumFiles); const int32 NumBlocks = Node.Blocks.Num(); for (const FBlock& Block : Node.Blocks) { // Use the module of the source id to determine the file id (FileIndex) const uint32 FileID = Block.SourceId % Category->NumFiles; int64& FileIndex = BulkDataFileIndex[FileID]; // Add new file if (FileIndex == INDEX_NONE) { FFile& NewFile = OutBulkDataFiles.AddDefaulted_GetRef(); NewFile.DataType = CategoryID.DataType; NewFile.ResourceType = CategoryID.ResourceType; NewFile.Flags = CategoryID.Flags; NewFile.Id = FileID + Category->FirstFile; FileIndex = NumBulkDataFiles; ++NumBulkDataFiles; } // Add block to the file OutBulkDataFiles[FileIndex].Blocks.Add(Block); } }; // Generate the list of BulkData files. GenerateBulkDataFilesList(Model, ModelStreamableBulkData, true /* bUseRomTypeAndFlagsToFilter */, CreateFileList, OutBulkDataFiles); } void GenerateBulkDataFilesListWithSizeLimit( TSharedPtr Model, FModelStreamableBulkData& ModelStreamableBulkData, const ITargetPlatform* TargetPlatform, uint64 TargetBulkDataFileBytes, TArray& OutBulkDataFiles) { MUTABLE_CPUPROFILER_SCOPE(GenerateBulkDataFilesListWithSizeLimit); if (!Model) { return; } const uint64 MaxChunkSize = UCustomizableObjectSystem::GetInstance()->GetMaxChunkSizeForPlatform(TargetPlatform); TargetBulkDataFileBytes = FMath::Min(TargetBulkDataFileBytes, MaxChunkSize); // Unlimited number of files, limited file size. Add blocks to the file if the size limit won't be surpassed. Add at least one block to each file. const auto CreateFileList = [TargetBulkDataFileBytes](const FFileCategoryID& CategoryID, const FClassifyNode& Node, TArray& OutBulkDataFiles) { // Temp: Group by order in the array for (int32 BlockIndex = 0; BlockIndex < Node.Blocks.Num(); ) { FFile File; File.DataType = CategoryID.DataType; File.ResourceType = CategoryID.ResourceType; File.Flags = CategoryID.Flags; uint64 FileSize = 0; uint32 FileId = uint32(CategoryID.DataType); while (BlockIndex < Node.Blocks.Num()) { const FBlock& CurrentBlock = Node.Blocks[BlockIndex]; if (FileSize > 0 && FileSize + CurrentBlock.Size > TargetBulkDataFileBytes && TargetBulkDataFileBytes > 0) { break; } // Block added to file. Set offset and increase file size. FileSize += CurrentBlock.Size; // Generate cumulative id for this file FileId = HashCombine(FileId, CurrentBlock.Id); // Add the block to the current file File.Blocks.Add(CurrentBlock); // Next block ++BlockIndex; } const int32 NumFiles = OutBulkDataFiles.Num(); // Ensure the FileId is unique bool bUnique = false; while (!bUnique) { bUnique = true; for (int32 PreviousFileIndex = 0; PreviousFileIndex < NumFiles; ++PreviousFileIndex) { if (OutBulkDataFiles[PreviousFileIndex].Id == FileId) { bUnique = false; ++FileId; break; } } } // Set it to the editor-only file descriptor File.Id = FileId; OutBulkDataFiles.Add(MoveTemp(File)); } }; // TODO: Temp. Remove after unifying generated output files code between editor an package. UE-222777 const bool bUseRomTypeAndFlagsToFilter = TargetPlatform->RequiresCookedData(); GenerateBulkDataFilesList(Model, ModelStreamableBulkData, bUseRomTypeAndFlagsToFilter, CreateFileList, OutBulkDataFiles); } void GenerateBulkDataFilesList( TSharedPtr Model, FModelStreamableBulkData& ModelStreamableBulkData, bool bUseRomTypeAndFlagsToFilter, TFunctionRef&)> CreateFileList, TArray& OutBulkDataFiles) { MUTABLE_CPUPROFILER_SCOPE(GenerateBulkDataFilesList); OutBulkDataFiles.Empty(); if (!Model) { return; } // Root nodes by flags. const uint32 NumRoms = Model->GetRomCount(); TMap RootNode; // Create blocks data. { for (uint32 RomIndex = 0; RomIndex < NumRoms; ++RomIndex) { uint32 SourceBlockId = Model->GetRomSourceId(RomIndex); const uint32 BlockSize = Model->GetRomSize(RomIndex); uint16 BlockResourceType = 0; EMutableFileFlags BlockFlags = EMutableFileFlags::None; // TODO: Temp. Remove after unifying generated output files code between editor an package. UE-222777 if (bUseRomTypeAndFlagsToFilter) { BlockResourceType = Model->IsMeshData(RomIndex) ? uint16(mu::EDataType::Mesh) : uint16(mu::EDataType::Image); BlockFlags = Model->IsRomHighRes(RomIndex) ? EMutableFileFlags::HighRes : EMutableFileFlags::None; } FFileCategoryID CurrentCategory = { EStreamableDataType::Model, BlockResourceType, uint16(BlockFlags) }; FBlock CurrentBlock = { .Id = RomIndex, .SourceId = SourceBlockId, .Size = BlockSize, .Offset = 0 }; AddNode(RootNode, NumRoms, CurrentCategory, CurrentBlock); } } { const TMap& RealTimeMorphStreamables = ModelStreamableBulkData.RealTimeMorphStreamables; const FFileCategoryID RealTimeMorphCategory = { EStreamableDataType::RealTimeMorph, uint16(mu::EDataType::None), uint16(EMutableFileFlags::None) }; for (const TPair& MorphStreamable : RealTimeMorphStreamables) { const uint32 BlockSize = MorphStreamable.Value.Size; FBlock CurrentBlock = { .Id = MorphStreamable.Key, .SourceId = MorphStreamable.Value.SourceId, .Size = BlockSize, .Offset = 0 }; AddNode(RootNode, NumRoms, RealTimeMorphCategory, CurrentBlock); } } { const TMap& ClothingStreamables = ModelStreamableBulkData.ClothingStreamables; const FFileCategoryID ClothingCategory = { EStreamableDataType::Clothing, (uint16)mu::EDataType::None, (uint16)EMutableFileFlags::None }; for (const TPair& ClothStreamable : ClothingStreamables) { const uint32 BlockSize = ClothStreamable.Value.Size; FBlock CurrentBlock = { .Id = ClothStreamable.Key, .SourceId = ClothStreamable.Value.SourceId, .Size = BlockSize, .Offset = 0 }; AddNode(RootNode, NumRoms, ClothingCategory, CurrentBlock); } } // Create Files list for (TPair& Node : RootNode) { CreateFileList(Node.Key, Node.Value, OutBulkDataFiles); } // Update streamable blocks data const int32 NumBulkDataFiles = OutBulkDataFiles.Num(); for (int32 FileIndex = 0; FileIndex < NumBulkDataFiles; ++FileIndex) { FFile& File = OutBulkDataFiles[FileIndex]; uint64 SourceOffset = 0; switch (File.DataType) { case EStreamableDataType::Model: { for (FBlock& Block : File.Blocks) { Block.Offset = SourceOffset; SourceOffset += Block.Size; FMutableStreamableBlock& StreamableBlock = ModelStreamableBulkData.ModelStreamables[Block.Id]; StreamableBlock.FileId = FileIndex; StreamableBlock.Offset = Block.Offset; } break; } case EStreamableDataType::RealTimeMorph: { for (FBlock& Block : File.Blocks) { Block.Offset = SourceOffset; SourceOffset += Block.Size; FMutableStreamableBlock& StreamableBlock = ModelStreamableBulkData.RealTimeMorphStreamables[Block.Id].Block; StreamableBlock.FileId = FileIndex; StreamableBlock.Offset = Block.Offset; } break; } case EStreamableDataType::Clothing: { for (FBlock& Block : File.Blocks) { Block.Offset = SourceOffset; SourceOffset += Block.Size; FMutableStreamableBlock& StreamableBlock = ModelStreamableBulkData.ClothingStreamables[Block.Id].Block; StreamableBlock.FileId = FileIndex; StreamableBlock.Offset = Block.Offset; } break; } default: UE_LOG(LogMutable, Error, TEXT("Unknown DataType found while fixing streaming block files ids.")); unimplemented(); break; } } } void CUSTOMIZABLEOBJECT_API SerializeBulkDataFiles( FMutableCachedPlatformData& CachedPlatformData, TArray& BulkDataFiles, TFunctionRef&, uint32)> WriteFile, bool bDropData) { MUTABLE_CPUPROFILER_SCOPE(SerializeBulkDataFiles); TArray64 FileBulkData; const uint32 NumBulkDataFiles = BulkDataFiles.Num(); for (uint32 FileIndex = 0; FileIndex < NumBulkDataFiles; ++FileIndex) { MutablePrivate::FFile& CurrentFile = BulkDataFiles[FileIndex]; const int64 FileSize = CurrentFile.GetSize(); FileBulkData.SetNumUninitialized(FileSize, EAllowShrinking::No); // Get the file data in memory CurrentFile.GetFileData(&CachedPlatformData, FileBulkData, bDropData); WriteFile(CurrentFile, FileBulkData, FileIndex); } } UE::DerivedData::FValueId GetDerivedDataModelId() { UE::DerivedData::FValueId::ByteArray ValueIdBytes = {}; FMemory::Memset(&ValueIdBytes[0], 1, sizeof(ValueIdBytes)); return UE::DerivedData::FValueId(ValueIdBytes); } UE::DerivedData::FValueId GetDerivedDataModelResourcesId() { UE::DerivedData::FValueId::ByteArray ValueIdBytes = {}; FMemory::Memset(&ValueIdBytes[0], 2, sizeof(ValueIdBytes)); return UE::DerivedData::FValueId(ValueIdBytes); } UE::DerivedData::FValueId GetDerivedDataModelStreamableBulkDataId() { UE::DerivedData::FValueId::ByteArray ValueIdBytes = {}; FMemory::Memset(&ValueIdBytes[0], 3, sizeof(ValueIdBytes)); return UE::DerivedData::FValueId(ValueIdBytes); } UE::DerivedData::FValueId GetDerivedDataBulkDataFilesId() { UE::DerivedData::FValueId::ByteArray ValueIdBytes = {}; FMemory::Memset(&ValueIdBytes[0], 4, sizeof(ValueIdBytes)); return UE::DerivedData::FValueId(ValueIdBytes); } } void SerializeCompilationOptionsForDDC(FArchive& Ar, FCompilationOptions& Options) { FString PlatformName = Options.TargetPlatform ? Options.TargetPlatform->PlatformName() : FString(); Ar << PlatformName; Ar << Options.TextureCompression; Ar << Options.OptimizationLevel; Ar << Options.CustomizableObjectNumBoneInfluences; Ar << Options.bRealTimeMorphTargetsEnabled; Ar << Options.bClothingEnabled; Ar << Options.b16BitBoneWeightsEnabled; Ar << Options.bSkinWeightProfilesEnabled; Ar << Options.bPhysicsAssetMergeEnabled; Ar << Options.bAnimBpPhysicsManipulationEnabled; Ar << Options.ImageTiling; Ar << Options.ParamNamesToSelectedOptions; } void SerializeTextureGroupSettingsForDDC(FArchive& Ar, const ITargetPlatform& Platform) { const UTextureLODSettings& LODSettings = Platform.GetTextureLODSettings(); for (FTextureLODGroup LODGroup : LODSettings.TextureLODGroups) { Ar << LODGroup.Filter; Ar << LODGroup.Group; Ar << LODGroup.LODBias; Ar << LODGroup.LODBias_Smaller; Ar << LODGroup.LODBias_Smallest; Ar << LODGroup.LossyCompressionAmount; Ar << LODGroup.MaxAniso; Ar << LODGroup.MaxLODMipCount; Ar << LODGroup.MaxLODSize; Ar << LODGroup.MaxLODSize_Smaller; Ar << LODGroup.MaxLODSize_Smallest; Ar << LODGroup.MinLODSize; FString MipFilter = LODGroup.MipFilter.ToString().ToLower(); Ar << MipFilter; Ar << LODGroup.MipGenSettings; Ar << LODGroup.MipLoadOptions; Ar << LODGroup.NumStreamedMips; Ar << LODGroup.OptionalLODBias; Ar << LODGroup.OptionalMaxLODSize; } } TArray UCustomizableObjectPrivate::BuildDerivedDataKey(FCompilationOptions Options) { MUTABLE_CPUPROFILER_SCOPE(UCustomizableObjectPrivate::BuildDerivedDataKey) check(IsInGameThread()); UCustomizableObject& CustomizableObject = *GetPublic(); TArray Bytes; FMemoryWriter Ar(Bytes, /*bIsPersistent=*/ true); { uint32 Version = DerivedDataVersion; Ar << Version; } { uint32 VersionsHash = GetECustomizableObjectVersionEnumHash(); Ar << VersionsHash; } // Custom Version { int32 CustomVersion = GetLinkerCustomVersion(FCustomizableObjectCustomVersion::GUID); Ar << CustomVersion; } // Customizable Object Ids { FGuid Id = GenerateIdentifier(CustomizableObject); Ar << Id; } { FGuid Version = CustomizableObject.VersionId; Ar << Version; } // Compile Options { SerializeCompilationOptionsForDDC(Ar, Options); } // Texture Settings { SerializeTextureGroupSettingsForDDC(Ar, *Options.TargetPlatform); } // Release Version if (const ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { FString Version = Module->GetCurrentReleaseVersionForObject(CustomizableObject); Ar << Version; } // Participating objects hash if (ICustomizableObjectEditorModule* Module = ICustomizableObjectEditorModule::Get()) { TArray> ParticipatingObjects = Module->GetParticipatingObjects(GetPublic(), &Options).Array(); ParticipatingObjects.Sort([](const TTuple& A, const TTuple& B) { return A.Get<0>().LexicalLess(B.Get<0>()) && A.Get<1>() < B.Get<1>(); }); for (const TTuple& Tuple : ParticipatingObjects) { FString Key = Tuple.Get<0>().ToString(); Key.ToLowerInline(); Ar << Key; FGuid Id = Tuple.Get<1>(); Ar << Id; } } // TODO List of plugins and their custom versions return Bytes; } UE::DerivedData::FCacheKey UCustomizableObjectPrivate::GetDerivedDataCacheKeyForOptions(FCompilationOptions InOptions) { using namespace UE::DerivedData; TArray DerivedDataKey = BuildDerivedDataKey(InOptions); FCacheKey CacheKey; CacheKey.Bucket = FCacheBucket(TEXT("CustomizableObject")); CacheKey.Hash = FIoHashBuilder::HashBuffer(MakeMemoryView(DerivedDataKey)); return CacheKey; } UE::DerivedData::FValueId GetDerivedDataValueIdForResource(MutablePrivate::EStreamableDataType StreamableDataType, uint32 FileId, uint16 ResourceType, uint16 Flags) { UE::DerivedData::FValueId::ByteArray ValueIdBytes = {}; static_assert(sizeof(UE::DerivedData::FValueId::ByteArray) >= sizeof(MutablePrivate::EStreamableDataType) + sizeof(uint32) + sizeof(uint16) + sizeof(uint16)); int8 ValueIdOffset = 0; FMemory::Memcpy(&ValueIdBytes[ValueIdOffset], &StreamableDataType, sizeof(StreamableDataType)); ValueIdOffset += sizeof(StreamableDataType); FMemory::Memcpy(&ValueIdBytes[ValueIdOffset], &FileId, sizeof(FileId)); ValueIdOffset += sizeof(FileId); uint16 DataType = (uint16)ResourceType; FMemory::Memcpy(&ValueIdBytes[ValueIdOffset], &DataType, sizeof(DataType)); ValueIdOffset += sizeof(DataType); FMemory::Memcpy(&ValueIdBytes[ValueIdOffset], &Flags, sizeof(Flags)); UE::DerivedData::FValueId ResourceId = UE::DerivedData::FValueId(ValueIdBytes); return ResourceId; } class FMutableMemoryCounterArchive final : public FArchive { public: FMutableMemoryCounterArchive() { SetIsSaving(true); SetIsPersistent(true); ArIsCountingMemory = true; } virtual void Serialize(void*, int64 Length) override { Size += Length; } virtual int64 TotalSize() override { return Size; } private: int64 Size = 0; }; void UCustomizableObjectPrivate::LogMemory() { // Log in-memory data for the compiled CustomizableObject UE_LOG(LogMutable, Log, TEXT("CustomizableObject [%s] memory report:"), *GetPublic()->GetName()); const mu::FModel* MuModel = GetModel().Get(); if (MuModel) { const mu::FProgram& Program = MuModel->GetPrivate()->Program; int32 ByteCode = Program.ByteCode.GetAllocatedSize(); int32 OpAddress = Program.OpAddress.GetAllocatedSize(); int32 Roms = Program.Roms.GetAllocatedSize(); int32 ConstantImages = Program.ConstantImages.GetAllocatedSize(); int32 ConstantImageLODIndices = Program.ConstantImageLODIndices.GetAllocatedSize(); int32 ConstantImageLODPermanent = Program.ConstantImageLODsPermanent.GetAllocatedSize(); int32 ConstantImageLODStreamed = Program.ConstantImageLODsStreamed.GetAllocatedSize(); int32 PermanentImages = 0; for (const TSharedPtr& Entry : Program.ConstantImageLODsPermanent) { if (Entry) { PermanentImages += Entry->GetDataSize(); } } int32 ImageTotal = ConstantImages + ConstantImageLODIndices + ConstantImageLODPermanent + PermanentImages + ConstantImageLODStreamed; int32 ConstantMeshes = Program.ConstantMeshes.GetAllocatedSize(); int32 ConstantMeshContentIndices = Program.ConstantMeshContentIndices.GetAllocatedSize(); int32 ConstantMeshesPermanent = Program.ConstantMeshesPermanent.GetAllocatedSize(); int32 ConstantMeshesStreamed = Program.ConstantMeshesStreamed.GetAllocatedSize(); int32 PermanentMeshes = 0; for (const TSharedPtr& Entry : Program.ConstantMeshesPermanent) { if (Entry) { PermanentMeshes += Entry->GetDataSize(); } } int32 MeshesTotal = ConstantMeshes + ConstantMeshContentIndices + ConstantMeshesPermanent + ConstantMeshesStreamed + PermanentMeshes; int32 StringsTotal = Program.ConstantStrings.GetAllocatedSize(); for (const FString& Entry : Program.ConstantStrings) { StringsTotal += Entry.GetAllocatedSize(); } int32 SkeletonsTotal = Program.ConstantSkeletons.GetAllocatedSize(); for (const TSharedPtr& Entry : Program.ConstantSkeletons) { SkeletonsTotal += sizeof(mu::FSkeleton) + Entry->DebugBoneNames.GetAllocatedSize() + Entry->BoneIds.GetAllocatedSize() + Entry->BoneParents.GetAllocatedSize(); } int32 PhysicsTotal = Program.ConstantPhysicsBodies.GetAllocatedSize(); for (const TSharedPtr& Entry : Program.ConstantPhysicsBodies) { PhysicsTotal += sizeof(mu::FPhysicsBody) + Entry->BoneIds.GetAllocatedSize() + Entry->Bodies.GetAllocatedSize() + Entry->BodiesCustomIds.GetAllocatedSize(); for (const mu::FPhysicsBodyAggregate& Body : Entry->Bodies) { PhysicsTotal += Body.Spheres.GetAllocatedSize() + Body.Boxes.GetAllocatedSize() + Body.Convex.GetAllocatedSize() + Body.Sphyls.GetAllocatedSize() + Body.TaperedCapsules.GetAllocatedSize(); } } int32 ParametersTotal = Program.Parameters.GetAllocatedSize(); for (const mu::FParameterDesc& Entry : Program.Parameters) { ParametersTotal += Entry.Ranges.GetAllocatedSize() + Entry.PossibleValues.GetAllocatedSize(); for (const mu::FParameterDesc::FIntValueDesc& ValueDesc : Entry.PossibleValues) { ParametersTotal += ValueDesc.Name.GetAllocatedSize(); } } int32 ModelTotal = ByteCode + OpAddress + Roms + ImageTotal + MeshesTotal + StringsTotal + SkeletonsTotal + PhysicsTotal + ParametersTotal; int32 StreamableData = 0; if (ModelStreamableData && ModelStreamableData->StreamingData) { const FModelStreamableBulkData& Bulk = *ModelStreamableData->StreamingData; StreamableData += Bulk.ModelStreamables.GetAllocatedSize(); StreamableData += Bulk.ClothingStreamables.GetAllocatedSize(); StreamableData += Bulk.RealTimeMorphStreamables.GetAllocatedSize(); StreamableData += Bulk.StreamableBulkData.GetAllocatedSize(); } int32 ModeleResData = 0; { ModeleResData += sizeof(UModelResources); FMutableMemoryCounterArchive Arch; if (ModelResources) { Arch << ModelResources->ReferenceSkeletalMeshesData; Arch << ModelResources->Skeletons; Arch << ModelResources->Materials; Arch << ModelResources->PassThroughTextures; Arch << ModelResources->PassThroughMeshes; Arch << ModelResources->PhysicsAssets; Arch << ModelResources->AnimBPs; Arch << ModelResources->AnimBpOverridePhysiscAssetsInfo; Arch << ModelResources->MaterialSlotNames; Arch << ModelResources->BoneNamesMap; Arch << ModelResources->SocketArray; Arch << ModelResources->SkinWeightProfilesInfo; Arch << ModelResources->MeshMetadata; Arch << ModelResources->SurfaceMetadata; Arch << ModelResources->ParameterUIDataMap; Arch << ModelResources->StateUIDataMap; Arch << ModelResources->ClothSharedConfigsData; Arch << ModelResources->ClothingAssetsData; Arch << ModelResources->ComponentNamesPerObjectComponent; Arch << ModelResources->MinLODPerComponent; Arch << ModelResources->MinQualityLevelLODPerComponent; ModeleResData += ModelResources->StreamedResourceData.GetAllocatedSize(); for (FCustomizableObjectStreamedResourceData& Data : ModelResources->StreamedResourceData) { TSoftObjectPtr PathCopy = Data.GetPath(); Arch << PathCopy; } } ModeleResData += Arch.TotalSize(); } UE_LOG(LogMutable, Log, TEXT("Total : %8d"), ModelTotal+StreamableData+ModeleResData); UE_LOG(LogMutable, Log, TEXT(" Streamable : %8d"), StreamableData); UE_LOG(LogMutable, Log, TEXT(" ModelResources : %8d"), ModeleResData); UE_LOG(LogMutable, Log, TEXT(" Model : %8d"), ModelTotal); UE_LOG(LogMutable, Log, TEXT(" ByteCode : %8d"), ByteCode); UE_LOG(LogMutable, Log, TEXT(" OpAddress : %8d"), OpAddress); UE_LOG(LogMutable, Log, TEXT(" Roms : %8d"), Roms); UE_LOG(LogMutable, Log, TEXT(" Strings : %8d"), StringsTotal); UE_LOG(LogMutable, Log, TEXT(" Images : %8d"), ImageTotal); UE_LOG(LogMutable, Log, TEXT(" Buffer : %8d"), ConstantImages); UE_LOG(LogMutable, Log, TEXT(" LODIndices : %8d"), ConstantImageLODIndices); UE_LOG(LogMutable, Log, TEXT(" PermanentBuf : %8d"), ConstantImageLODPermanent); UE_LOG(LogMutable, Log, TEXT(" Permanent : %8d"), PermanentImages); UE_LOG(LogMutable, Log, TEXT(" StreamedBuf : %8d"), ConstantImageLODStreamed); UE_LOG(LogMutable, Log, TEXT(" Meshes : %8d"), MeshesTotal); UE_LOG(LogMutable, Log, TEXT(" Buffer : %8d"), ConstantMeshes); UE_LOG(LogMutable, Log, TEXT(" ContentIndices : %8d"), ConstantMeshContentIndices); UE_LOG(LogMutable, Log, TEXT(" PermanentBuf : %8d"), ConstantMeshesPermanent); UE_LOG(LogMutable, Log, TEXT(" Permanent : %8d"), PermanentMeshes); UE_LOG(LogMutable, Log, TEXT(" StreamedBuf : %8d"), ConstantMeshesStreamed); UE_LOG(LogMutable, Log, TEXT(" Skeletons : %8d"), SkeletonsTotal); UE_LOG(LogMutable, Log, TEXT(" PhysicBodies : %8d"), PhysicsTotal); UE_LOG(LogMutable, Log, TEXT(" Parameters : %8d"), ParametersTotal); } } #endif // WITH_EDITOR //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void UCustomizableObjectBulk::PostLoad() { UObject::PostLoad(); const FString OutermostName = GetOutermost()->GetName(); FString PackageFilename = FPackageName::LongPackageNameToFilename(OutermostName); FPaths::MakeStandardFilename(PackageFilename); BulkFilePrefix = PackageFilename; } TUniquePtr UCustomizableObjectBulk::OpenFileAsyncRead(uint32 FileId, uint32 Flags) const { check(IsInGameThread()); FString FilePath = FString::Printf(TEXT("%s-%08x.mut"), *BulkFilePrefix, FileId); if (Flags == uint16(EMutableFileFlags::HighRes)) { FilePath += TEXT(".high"); } IAsyncReadFileHandle* Result = FPlatformFileManager::Get().GetPlatformFile().OpenAsyncRead(*FilePath); // Result being null does not mean the file does not exist. A request has to be made. Let the callee deal with it. //UE_CLOG(!Result, LogMutable, Warning, TEXT("CustomizableObjectBulkData: Failed to open file [%s]."), *FilePath); return TUniquePtr(Result); } #if WITH_EDITOR void UCustomizableObjectBulk::CookAdditionalFilesOverride(const TCHAR* PackageFilename, const ITargetPlatform* TargetPlatform, TFunctionRef WriteAdditionalFile) { // Don't save streamed data on server builds since it won't be used anyway. if (TargetPlatform->IsServerOnly()) { return; } UCustomizableObject* CustomizableObject = CastChecked(GetOutermostObject()); MutablePrivate::FMutableCachedPlatformData* PlatformData = CustomizableObject->GetPrivate()->CachedPlatformsData.Find(TargetPlatform->PlatformName()); if (!PlatformData) { UE_LOG(LogMutable, Warning, TEXT("CookAdditionalFilesOverride: Customizable Object [%s] is missing [%s] platform data."), *CustomizableObject->GetName(), *TargetPlatform->PlatformName()); return; } const FString CookedBulkFileName = FString::Printf(TEXT("%s/%s"), *FPaths::GetPath(PackageFilename), *CustomizableObject->GetName()); const auto WriteFile = [WriteAdditionalFile, CookedBulkFileName](MutablePrivate::FFile& File, TArray64& FileBulkData, uint32 FileIndex) { FString FileName = CookedBulkFileName + FString::Printf(TEXT("-%08x.mut"), File.Id); if (File.Flags == uint16(EMutableFileFlags::HighRes)) { // We can do something different here for high-res data. // For example: change the file name. We also need to detect it when generating the file name for loading. FileName += TEXT(".high"); } WriteAdditionalFile(*FileName, FileBulkData.GetData(), FileBulkData.Num()); }; bool bDropData = true; MutablePrivate::SerializeBulkDataFiles(*PlatformData, PlatformData->BulkDataFiles, WriteFile, bDropData); } #endif // WITH_EDITOR //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- bool FAnimBpOverridePhysicsAssetsInfo::operator==(const FAnimBpOverridePhysicsAssetsInfo& Rhs) const { return AnimInstanceClass == Rhs.AnimInstanceClass && SourceAsset == Rhs.SourceAsset && PropertyIndex == Rhs.PropertyIndex; } bool FMutableModelImageProperties::operator!=(const FMutableModelImageProperties& Other) const { return TextureParameterName != Other.TextureParameterName || Filter != Other.Filter || SRGB != Other.SRGB || FlipGreenChannel != Other.FlipGreenChannel || IsPassThrough != Other.IsPassThrough || LODBias != Other.LODBias || MipGenSettings != Other.MipGenSettings || LODGroup != Other.LODGroup || AddressX != Other.AddressX || AddressY != Other.AddressY; } bool FMutableRefSocket::operator==(const FMutableRefSocket& Other) const { if ( SocketName == Other.SocketName && BoneName == Other.BoneName && RelativeLocation == Other.RelativeLocation && RelativeRotation == Other.RelativeRotation && RelativeScale == Other.RelativeScale && bForceAlwaysAnimated == Other.bForceAlwaysAnimated && Priority == Other.Priority) { return true; } return false; } bool FMutableSkinWeightProfileInfo::operator==(const FMutableSkinWeightProfileInfo& Other) const { return Name == Other.Name; } FArchive& operator<<(FArchive& Ar, FMutableSkinWeightProfileInfo& Info) { Ar << Info.Name; Ar << Info.NameId; Ar << Info.DefaultProfile; Ar << Info.DefaultProfileFromLODIndex; return Ar; } FIntegerParameterUIData::FIntegerParameterUIData(const FMutableParamUIMetadata& InParamUIMetadata) { ParamUIMetadata = InParamUIMetadata; } FMutableParameterData::FMutableParameterData(const FMutableParamUIMetadata& InParamUIMetadata, EMutableParameterType InType) { ParamUIMetadata = InParamUIMetadata; Type = InType; } #if WITH_EDITORONLY_DATA FArchive& operator<<(FArchive& Ar, FAnimBpOverridePhysicsAssetsInfo& Info) { FString AnimInstanceClassPathString; FString PhysicsAssetPathString; if (Ar.IsLoading()) { Ar << AnimInstanceClassPathString; Ar << PhysicsAssetPathString; Ar << Info.PropertyIndex; Info.AnimInstanceClass = TSoftClassPtr(AnimInstanceClassPathString); Info.SourceAsset = TSoftObjectPtr(FSoftObjectPath(PhysicsAssetPathString)); } if (Ar.IsSaving()) { AnimInstanceClassPathString = Info.AnimInstanceClass.ToString(); PhysicsAssetPathString = Info.SourceAsset.ToString(); Ar << AnimInstanceClassPathString; Ar << PhysicsAssetPathString; Ar << Info.PropertyIndex; } return Ar; } FArchive& operator<<(FArchive& Ar, FMutableRefSocket& Data) { Ar << Data.SocketName; Ar << Data.BoneName; Ar << Data.RelativeLocation; Ar << Data.RelativeRotation; Ar << Data.RelativeScale; Ar << Data.bForceAlwaysAnimated; Ar << Data.Priority; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableRefLODRenderData& Data) { Ar << Data.bIsLODOptional; Ar << Data.bStreamedDataInlined; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableRefLODInfo& Data) { Ar << Data.ScreenSize; Ar << Data.LODHysteresis; Ar << Data.bSupportUniformlyDistributedSampling; Ar << Data.bAllowCPUAccess; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableRefLODData& Data) { Ar << Data.LODInfo; Ar << Data.RenderData; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableRefSkeletalMeshSettings& Data) { Ar << Data.bEnablePerPolyCollision; Ar << Data.DefaultUVChannelDensity; return Ar; } FArchive& operator<<(FArchive& Ar, FMutableRefSkeletalMeshData& Data) { Ar << Data.LODData; Ar << Data.Sockets; Ar << Data.Bounds; Ar << Data.Settings; Ar << Data.SkeletalMesh; Ar << Data.SkeletalMeshLODSettings; Ar << Data.Skeleton; Ar << Data.ShadowPhysicsAsset; Ar << Data.AssetUserDataIndices; return Ar; } #endif #undef LOCTEXT_NAMESPACE