// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MuR/Model.h" #include "MuR/System.h" #include "MuR/SerialisationPrivate.h" #include "MuR/Operations.h" #include "MuR/ExtensionData.h" #include "MuR/Image.h" #include "MuR/Mesh.h" #include "MuR/ParametersPrivate.h" #include "MuR/MutableRuntimeModule.h" #define UE_API MUTABLERUNTIME_API #define MUTABLE_MAX_RUNTIME_PARAMETERS_PER_STATE 65 #define MUTABLE_GROW_BORDER_VALUE 2 namespace UE::Mutable::Private { /** Used to debug and log. */ constexpr bool DebugRom = false; constexpr bool DebugRomAll = false; constexpr int32 DebugRomIndex = 44; constexpr int32 DebugImageIndex = 9; struct FConstantResourceIndex { uint32 Index : 31; /** This may mean that the resource needs to be looked up on a different array. */ uint32 Streamable : 1; }; static_assert(sizeof(FConstantResourceIndex) == 4); MUTABLE_DEFINE_POD_SERIALISABLE(FConstantResourceIndex); //MUTABLE_DEFINE_POD_VECTOR_SERIALISABLE(FConstantResourceIndex); /** This is encoded with minimal bits. Make sure to review all uses if extended. */ enum class ERomDataType : uint32 { Image = 0, Mesh = 1 }; /** Data stored for a rom even if it is not loaded. * This struct is size-sensitive since there may be many roms and it is always loaded in memory when a CO is. */ struct FRomDataRuntime { /** Size of the rom */ uint32 Size : 30; /** Index of the resource in its type-specific array. See ERomDataType. */ uint32 ResourceType : 1; /** Properties of the rom data. */ uint32 IsHighRes : 1; // TODO: Store the offset here and delete the FModelStreamableBlock map? /** Offset in file */ // uint32 Offset = 0; }; /** Not critical to keep this size, but it is memory-usage sensitive. */ static_assert(sizeof(FRomDataRuntime) == 4); MUTABLE_DEFINE_POD_SERIALISABLE(FRomDataRuntime); struct FRomDataCompile { /** ID used to identify the origin of this data and used for grouping. */ uint32 SourceId; }; MUTABLE_DEFINE_POD_SERIALISABLE(FRomDataCompile); //! template inline void AppendCode(TArray& Code, const DATA& Data ) { int32 Pos = Code.Num(); Code.SetNum( Pos+sizeof(DATA), EAllowShrinking::No); FMemory::Memcpy (&Code[Pos], &Data, sizeof(DATA)); } //! struct FImageLODRange { int32 FirstIndex = 0; uint16 ImageSizeX = 0; uint16 ImageSizeY = 0; uint8 LODCount = 0; uint8 NumLODsInTail = 0; uint8 Flags = 0; EImageFormat ImageFormat = EImageFormat::None; }; MUTABLE_DEFINE_POD_SERIALISABLE(FImageLODRange); struct FMeshContentRange { static constexpr uint32 FirstIndexMaxBits = 24; static constexpr uint32 ContentFlagsMaxBits = 32 - FirstIndexMaxBits; static constexpr uint32 FirstIndexBitMask = (1 << FirstIndexMaxBits) - 1; static_assert(FirstIndexMaxBits < 32); static_assert(ContentFlagsMaxBits >= sizeof(EMeshContentFlags)*8); // NOTE: Bitfields layout can be implementation defined, here we want consistency across all // compilers so that the struct can be POD serializable. uint32 FirstIndex_ContentFlags = 0; // Low bits are FirstIndex, high bits are ContentFlags. uint32 MeshIDPrefix = 0; FORCEINLINE EMeshContentFlags GetContentFlags() const { return static_cast( (FirstIndex_ContentFlags >> FirstIndexMaxBits) & ((1 << ContentFlagsMaxBits) - 1)); } FORCEINLINE uint32 GetFirstIndex() const { return FirstIndex_ContentFlags & FirstIndexBitMask; } FORCEINLINE void SetContentFlags(EMeshContentFlags ContentFlags) { check(uint32(ContentFlags) < (1 << ContentFlagsMaxBits)); FirstIndex_ContentFlags = (FirstIndex_ContentFlags & FirstIndexBitMask) | ((uint32(ContentFlags) << FirstIndexMaxBits)); } FORCEINLINE void SetFirstIndex(uint32 FirstIndex) { check(FirstIndex < ((1 << FirstIndexMaxBits))); FirstIndex_ContentFlags = (FirstIndex_ContentFlags & ~FirstIndexBitMask) | (FirstIndex & FirstIndexBitMask); } }; static_assert(sizeof(FMeshContentRange) == sizeof(uint32)*2); MUTABLE_DEFINE_POD_SERIALISABLE(FMeshContentRange); struct FExtensionDataConstant { // This should always be valid, but if the state is Unloaded it won't be usable. // // Avoid storing references to this Data in Memory while the state is Unloaded. TSharedPtr Data; enum class ELoadState : uint8 { Invalid, Unloaded, FailedToLoad, CurrentlyLoaded, AlwaysLoaded }; void Serialise(FOutputArchive& Arch) const { Arch << Data; } void Unserialise(FInputArchive& Arch) { Arch >> Data; check(Data); check(Data->Origin == FExtensionData::EOrigin::ConstantAlwaysLoaded || Data->Origin == FExtensionData::EOrigin::ConstantStreamed); } }; //! struct FProgram { FProgram() { // Add the null instruction at address 0. // TODO: Will do it in the linker AppendCode( ByteCode, EOpType::NONE ); OpAddress.Add(0); } struct FState { /** Name of the state */ FString Name; /** First instruction of the full build of an instance in this state */ OP::ADDRESS Root = 0; /** * List of parameters index (to FProgram::Parameters) of the runtime parameters of * this state. */ TArray m_runtimeParameters; /** List of instructions that need to be cached to efficiently update this state */ TArray m_updateCache; /** * List of root instructions for the dynamic resources that depend on the runtime * parameters of this state, with a mask of relevant runtime parameters. * The mask has a bit on for every runtime parameter in the m_runtimeParameters array. * The uint64 is linked to MUTABLE_MAX_RUNTIME_PARAMETERS_PER_STATE */ TArray< TPair > m_dynamicResources; /** */ inline void Serialise( FOutputArchive& arch ) const { arch << Name; arch << Root; arch << m_runtimeParameters; arch << m_updateCache; arch << m_dynamicResources; } /** */ inline void Unserialise( FInputArchive& arch ) { arch >> Name; arch >> Root; arch >> m_runtimeParameters; arch >> m_updateCache; arch >> m_dynamicResources; } /** Returns the mask of parameters (from the runtime parameter list of this state) including the parameters that * are relevant for the dynamic resource at the given address. */ uint64 IsDynamic( OP::ADDRESS at ) const { uint64 res = 0; for ( int32 i=0; i OpAddress; /** Byte-coded representation of the program, using variable-sized op data. */ TArray ByteCode; /** */ TArray States; /** Data for every rom required in-game. */ TArray Roms; /** Data for every rom required at compile-time. It is empty in cooked data. */ TArray RomsCompileData; /** Loaded roms worth tracking (only images and meshes for now). Stores the rom's data type.*/ TSparseArray LoadedMemTrackedRoms; /** Constant image mip data is split in 2 sets: ConstantImageLODsPermanent constains data that is always loaded. * Index with FConstantResourceIndex::Index, when Streamable is 0. */ TArray> ConstantImageLODsPermanent; /** Constant image mip data is split in 2 sets: ConstantImageLODsStreamed constains data that is streamed in and out. * Index with FConstantResourceIndex::Index, when Streamable is 1. * This part is empty for an unused Model, and shouldn't be serialised. */ TMap> ConstantImageLODsStreamed; /** Constant image mip chain indices: ranges in this array are defined in FImageLODRange and the indices here refer to ConstantImageLODs. */ TArray ConstantImageLODIndices; /** Constant image data. */ TArray ConstantImages; /** Constant mesh content indices: ranges in this array are defined in FMeshContentRange and the indices here refer to ConstantMeshes. */ TArray ConstantMeshContentIndices; /** Constant mesh data */ TArray ConstantMeshes; /** Constant mesh data is split in 2 sets: ConstantMeshesPermanent constains data that is always loaded. * Index with FConstantResourceIndex::Index, when Streamable is 0. */ TArray> ConstantMeshesPermanent; /** Constant mesh data is split in 2 sets: ConstantMeshesStreamed constains data that is streamed in and out. * Index with FConstantResourceIndex::Index, when Streamable is 1. * This part is empty for an unused Model, and shouldn't be serialised. */ TMap> ConstantMeshesStreamed; /** Constant FExtensionData */ TArray ConstantExtensionData; /** Constant string data */ TArray ConstantStrings; /** */ TArray> ConstantUInt32Lists; /** */ TArray> ConstantUInt64Lists; /** Constant layout data */ TArray> ConstantLayouts; /** Constant projectors */ TArray ConstantProjectors; /** Constant matrices, usually used for transforms */ TArray ConstantMatrices; /** Constant shapes */ TArray ConstantShapes; /** Constant curves */ TArray ConstantCurves; /** Constant skeletons */ TArray> ConstantSkeletons; /** Constant Physics Bodies */ TArray> ConstantPhysicsBodies; /** FParameters of the model. */ /** The value stored here is the default value. */ TArray Parameters; /** Ranges for iteration of the model operations. */ TArray Ranges; /** * List of parameter lists. These are used in several places, like storing the * pregenerated list of parameters influencing a resource. * The parameter lists are sorted. */ TArray> ParameterLists; /** Constant Material Data */ TArray> ConstantMaterials; #if WITH_EDITOR /** * State of the program. True unless the streamed resources were destroyed, * which could happen in the editor after recompiling the CO. */ bool bIsValid = true; #endif /** */ void Serialise(FOutputArchive& Arch) const { Arch << OpAddress; Arch << ByteCode; Arch << States; Arch << Roms; Arch << RomsCompileData; Arch << ConstantImageLODsPermanent; Arch << ConstantImageLODIndices; Arch << ConstantImages; Arch << ConstantMeshesPermanent; Arch << ConstantMeshContentIndices; Arch << ConstantMeshes; Arch << ConstantExtensionData; Arch << ConstantStrings; Arch << ConstantUInt32Lists; Arch << ConstantUInt64Lists; Arch << ConstantLayouts; Arch << ConstantProjectors; Arch << ConstantMatrices; Arch << ConstantShapes; Arch << ConstantCurves; Arch << ConstantSkeletons; Arch << Parameters; Arch << Ranges; Arch << ParameterLists; Arch << ConstantMaterials; } /** */ void Unserialise(FInputArchive& Arch) { Arch >> OpAddress; Arch >> ByteCode; Arch >> States; Arch >> Roms; Arch >> RomsCompileData; Arch >> ConstantImageLODsPermanent; Arch >> ConstantImageLODIndices; Arch >> ConstantImages; Arch >> ConstantMeshesPermanent; Arch >> ConstantMeshContentIndices; Arch >> ConstantMeshes; Arch >> ConstantExtensionData; Arch >> ConstantStrings; Arch >> ConstantUInt32Lists; Arch >> ConstantUInt64Lists; Arch >> ConstantLayouts; Arch >> ConstantProjectors; Arch >> ConstantMatrices; Arch >> ConstantShapes; Arch >> ConstantCurves; Arch >> ConstantSkeletons; Arch >> Parameters; Arch >> Ranges; Arch >> ParameterLists; Arch >> ConstantMaterials; } /** Debug method that sanity-checks the program with a variety of tests. */ void Check(); /** Debug method that logs the top used instruction types. */ void LogHistogram() const; /** Return true if the given ROM is loaded. */ FORCEINLINE bool IsRomLoaded(int32 RomIndex) const { switch (Roms[RomIndex].ResourceType) { case uint32(ERomDataType::Image): return ConstantImageLODsStreamed.Contains(RomIndex); case uint32(ERomDataType::Mesh): return ConstantMeshesStreamed.Contains(RomIndex); //TODO(Max): Make materials loadable and unloadable from a rom default: check(false); break; } return false; } /** Unload a rom resource. * If a pointer to a size is passed in OutDataSize, the value will be set with the size of the unloaded rom. */ FORCEINLINE void UnloadRom(int32 RomIndex, int32* OutDataSize=nullptr) { if (DebugRom && (DebugRomAll||RomIndex == DebugRomIndex)) UE_LOG(LogMutableCore, Log, TEXT("Unloading rom %d."), RomIndex); if (LoadedMemTrackedRoms.IsValidIndex(RomIndex)) { LoadedMemTrackedRoms.RemoveAt(RomIndex); } switch (Roms[RomIndex].ResourceType) { case uint32(ERomDataType::Image): { TSharedPtr Data; ConstantImageLODsStreamed.RemoveAndCopyValue(RomIndex, Data); if (OutDataSize && Data) { *OutDataSize = Data->GetDataSize(); } break; } case uint32(ERomDataType::Mesh): { TSharedPtr Data; ConstantMeshesStreamed.RemoveAndCopyValue(RomIndex, Data); if (OutDataSize && Data) { *OutDataSize = Data->GetDataSize(); } break; } //TODO(Max): Make materials loadable and unloadable from a rom default: check(false); break; } } FORCEINLINE void SetMeshRomValue(uint32 RomIndex, const TSharedPtr& Value) { check(Roms[RomIndex].ResourceType == uint32(ERomDataType::Mesh)); check(!ConstantMeshesStreamed.Contains(RomIndex)); LoadedMemTrackedRoms.EmplaceAt(RomIndex, (uint8)Roms[RomIndex].ResourceType); ConstantMeshesStreamed.Add(RomIndex, Value); } FORCEINLINE void SetImageRomValue(uint32 RomIndex, const TSharedPtr& Value) { check(Roms[RomIndex].ResourceType == uint32(ERomDataType::Image)); check(!ConstantImageLODsStreamed.Contains(RomIndex)); LoadedMemTrackedRoms.EmplaceAt(RomIndex, (uint8)Roms[RomIndex].ResourceType); ConstantImageLODsStreamed.Add(RomIndex, Value); } OP::ADDRESS AddConstant(TSharedPtr Data) { // Ensure unique for (int32 Index = 0; Index < ConstantExtensionData.Num(); Index++) { const FExtensionData* Candidate = ConstantExtensionData[Index].Data.Get(); if (*Candidate == *Data) { return Index; } } FExtensionDataConstant& NewConstant = ConstantExtensionData.AddDefaulted_GetRef(); NewConstant.Data = Data; return ConstantExtensionData.Num() - 1; } OP::ADDRESS AddConstant( TSharedPtr pLayout ) { // Ensure unique for (int32 i=0; i Skeleton ) { // Ensure unique for ( int32 i=0; i pPhysicsBody ) { // Ensure unique for (int32 i=0; i& UInt32List) { return (OP::ADDRESS)ConstantUInt32Lists.AddUnique(UInt32List); } OP::ADDRESS AddConstant(const TArray& UInt64List) { return (OP::ADDRESS)ConstantUInt64Lists.AddUnique(UInt64List); } OP::ADDRESS AddConstant(const TArray& StringList) { const int32 NumStrings = StringList.Num(); TArray StringAddrs; StringAddrs.SetNum(NumStrings); for (int32 StringIndex = 0; StringIndex < NumStrings; ++StringIndex) { StringAddrs[StringIndex] = AddConstant(StringList[StringIndex]); } return (OP::ADDRESS)AddConstant(StringAddrs); } OP::ADDRESS AddConstant( const FMatrix44f& m ) { // Ensure unique for (int32 i=0; i Material) { OP::ADDRESS index = OP::ADDRESS(ConstantMaterials.AddUnique(Material)); return index; } inline TSharedPtr GetImageLOD(FConstantResourceIndex Index) const { TSharedPtr Mip; if (!Index.Streamable) { if (ConstantImageLODsPermanent.IsValidIndex(Index.Index)) { Mip = ConstantImageLODsPermanent[Index.Index]; } } else { const TSharedPtr* Found = ConstantImageLODsStreamed.Find(Index.Index); if (Found) { Mip = *Found; } } return Mip; } /** Get a constant image, assuming at least some mips are loaded. The image constant will be composed with lodaded mips if necessary. */ template void GetConstant(uint32 ConstantIndex, TSharedPtr& Result, int32 MipsToSkip, const CreateImageFunc& CreateImage) const { const int32 NumLODs = ConstantImages[ConstantIndex].LODCount; check(NumLODs > 0); const int32 ReallySkippedLODs = FMath::Min(NumLODs - 1, MipsToSkip); const int32 FirstLODIndexIndex = ConstantImages[ConstantIndex].FirstIndex; const int32 FirstTailLOD = ConstantImages[ConstantIndex].LODCount - ConstantImages[ConstantIndex].NumLODsInTail; // Compose the result image { MUTABLE_CPUPROFILER_SCOPE(ComposeConstantImage); TSharedPtr ResultImage = nullptr; int32 LOD = ReallySkippedLODs; // Find first availabe LOD. for (; LOD <= FirstTailLOD; ++LOD) { MUTABLE_CPUPROFILER_SCOPE(Search_FisrtValid); TSharedPtr FirstValidLODImage = GetImageLOD(ConstantImageLODIndices[FirstLODIndexIndex + LOD]); if (FirstValidLODImage) { break; } } if (LOD < NumLODs) { int32 SizeX = ConstantImages[ConstantIndex].ImageSizeX; int32 SizeY = ConstantImages[ConstantIndex].ImageSizeY; for (int32 I = 0; I < LOD; ++I) { SizeX = FMath::DivideAndRoundUp(SizeX, 2); SizeY = FMath::DivideAndRoundUp(SizeY, 2); } EImageFormat Format = ConstantImages[ConstantIndex].ImageFormat; ResultImage = CreateImage(SizeX, SizeY, NumLODs - LOD, Format, EInitializationType::NotInitialized); ResultImage->Flags = ConstantImages[ConstantIndex].Flags; } check(ResultImage); // Some non-block pixel formats require manual resize. const bool bFormatNeedsDataResize = ResultImage->DataStorage.IsEmpty(); int32 DestLOD = 0; // Process hi-res separeted mips. for (; LOD < FirstTailLOD; ++LOD, ++DestLOD) { MUTABLE_CPUPROFILER_SCOPE(NonTailLODs); TSharedPtr Image = GetImageLOD(ConstantImageLODIndices[FirstLODIndexIndex + LOD]); if (bFormatNeedsDataResize) { ResultImage->DataStorage.ResizeLOD(DestLOD, Image->DataStorage.GetLOD(0).Num()); } TArrayView ResultLODView = ResultImage->DataStorage.GetLOD(DestLOD); TArrayView ImageLODView = Image->DataStorage.GetLOD(0); check(ResultLODView.Num() == ImageLODView.Num()) FMemory::Memcpy(ResultLODView.GetData(), ImageLODView.GetData(), ResultLODView.Num()); } // Process compacted tail. // NOTE: The rom tail data storage can have multiple image buffers. if (LOD < NumLODs) { MUTABLE_CPUPROFILER_SCOPE(TailLODs); TSharedPtr TailLODsImage = GetImageLOD(ConstantImageLODIndices[FirstLODIndexIndex + FirstTailLOD]); if (!ensureAlwaysMsgf(TailLODsImage, TEXT("Missing tail image rom, setting image to black."))) { ResultImage->InitToBlack(); Result = ResultImage; return; } check(TailLODsImage->DataStorage.GetNumLODs() >= NumLODs - LOD); int32 FirstTailImageTailLOD = FMath::Min( TailLODsImage->DataStorage.ComputeFirstCompactedTailLOD(), TailLODsImage->DataStorage.GetNumLODs()); int32 TailImageLOD = FMath::Max(0, ReallySkippedLODs - FirstTailLOD); for (; TailImageLOD < FirstTailImageTailLOD; ++TailImageLOD, ++LOD, ++DestLOD) { MUTABLE_CPUPROFILER_SCOPE(TailLODCopy); if (bFormatNeedsDataResize) { ResultImage->DataStorage.ResizeLOD(DestLOD, TailLODsImage->DataStorage.GetLOD(TailImageLOD).Num()); } TArrayView ResultLODView = ResultImage->DataStorage.GetLOD(DestLOD); TArrayView ImageLODView = TailLODsImage->DataStorage.GetLOD(TailImageLOD); check(ResultLODView.Num() == ImageLODView.Num()); FMemory::Memcpy(ResultLODView.GetData(), ImageLODView.GetData(), ResultLODView.Num()); } if (LOD < NumLODs) { MUTABLE_CPUPROFILER_SCOPE(TailTailCopy); check(DestLOD >= ResultImage->DataStorage.ComputeFirstCompactedTailLOD()); check(TailImageLOD >= TailLODsImage->DataStorage.ComputeFirstCompactedTailLOD()); TArrayView FirstTailLODView = TailLODsImage->DataStorage.GetLOD(TailImageLOD); TArrayView LastTailLODView = TailLODsImage->DataStorage.GetLOD(TailLODsImage->DataStorage.GetNumLODs() - 1); // The tail is stored compacted in memory. int64 NumBytesInTail = LastTailLODView.GetData() + LastTailLODView.Num() - FirstTailLODView.GetData(); check(NumBytesInTail >= 0); if (bFormatNeedsDataResize) { const int32 NumLODsToResize = ResultImage->DataStorage.GetNumLODs() - DestLOD; check(NumLODsToResize == TailLODsImage->DataStorage.GetNumLODs() - TailImageLOD); ResultImage->DataStorage.GetInternalArray(DestLOD).Reserve(NumBytesInTail); for (int32 LODOffset = 0; LODOffset < NumLODsToResize; ++LODOffset) { int32 TailLODSizeInBytes = TailLODsImage->DataStorage.GetLOD(LODOffset + TailImageLOD).Num(); ResultImage->DataStorage.ResizeLOD(LODOffset + DestLOD, TailLODSizeInBytes); } } TArrayView ResultLODView = ResultImage->DataStorage.GetLOD(DestLOD); check((int64)ResultImage->DataStorage.GetInternalArray(DestLOD).Num() >= NumBytesInTail); FMemory::Memcpy(ResultLODView.GetData(), FirstTailLODView.GetData(), NumBytesInTail); } } Result = ResultImage; } } template void GetConstant( uint32 MeshConstantIndex, int32 SkeletonConstantIndex, TSharedPtr& OutMesh, EMeshContentFlags FilterContentFlags, CreateMeshFunc&& CreateMesh) const { MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh) FMeshContentRange MeshContentRange = ConstantMeshes[MeshConstantIndex]; TSharedPtr EmptyMesh = nullptr; auto GetMeshAtResourceIndex = [&](FConstantResourceIndex ResourceIndex) -> TSharedPtr { MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh_GetMesh) if (!ResourceIndex.Streamable) { if (ConstantMeshesPermanent.IsValidIndex(ResourceIndex.Index)) { return ConstantMeshesPermanent[ResourceIndex.Index]; } if (!EmptyMesh) { EmptyMesh = MakeShared(); } return EmptyMesh; } else { const TSharedPtr* Found = ConstantMeshesStreamed.Find(ResourceIndex.Index); if (Found) { return *Found; } if (!EmptyMesh) { EmptyMesh = MakeShared(); } return EmptyMesh; } }; TSharedPtr GeometryMesh = nullptr; TSharedPtr PoseMesh = nullptr; TSharedPtr PhysicsMesh = nullptr; TSharedPtr MetaDataMesh = nullptr; int32 MeshRomCurrentIndex = MeshContentRange.GetFirstIndex(); if (EnumHasAnyFlags(FilterContentFlags, EMeshContentFlags::GeometryData) && EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::GeometryData)) { const FConstantResourceIndex ResourceIndex = ConstantMeshContentIndices[MeshRomCurrentIndex]; GeometryMesh = GetMeshAtResourceIndex(ResourceIndex); } MeshRomCurrentIndex += (int32)EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::GeometryData); if (EnumHasAnyFlags(FilterContentFlags, EMeshContentFlags::PoseData) && EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::PoseData)) { const FConstantResourceIndex ResourceIndex = ConstantMeshContentIndices[MeshRomCurrentIndex]; PoseMesh = GetMeshAtResourceIndex(ResourceIndex); } MeshRomCurrentIndex += (int32)EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::PoseData); if (EnumHasAnyFlags(FilterContentFlags, EMeshContentFlags::PhysicsData) && EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::PhysicsData)) { const FConstantResourceIndex ResourceIndex = ConstantMeshContentIndices[MeshRomCurrentIndex]; PhysicsMesh = GetMeshAtResourceIndex(ResourceIndex); } MeshRomCurrentIndex += (int32)EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::PhysicsData); if (EnumHasAnyFlags(FilterContentFlags, EMeshContentFlags::MetaData) && EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::MetaData)) { const FConstantResourceIndex ResourceIndex = ConstantMeshContentIndices[MeshRomCurrentIndex]; MetaDataMesh = GetMeshAtResourceIndex(ResourceIndex); } MeshRomCurrentIndex += (int32)EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::MetaData); check(MeshRomCurrentIndex - MeshContentRange.GetFirstIndex() == FMath::CountBits((uint64)MeshContentRange.GetContentFlags())); uint32 MeshBudgetReserve = 0; MeshBudgetReserve += GeometryMesh ? GeometryMesh->GetDataSize() : 0; MeshBudgetReserve += PoseMesh ? PoseMesh->GetDataSize() : 0; MeshBudgetReserve += PhysicsMesh ? PhysicsMesh->GetDataSize() : 0; MeshBudgetReserve += MetaDataMesh ? MetaDataMesh->GetDataSize() : 0; TSharedPtr Result = nullptr; if (GeometryMesh) { MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh_Geoemtry) Result = CreateMesh(MeshBudgetReserve); Result->CopyFrom(*GeometryMesh); } if (PoseMesh) { MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh_Pose) if (!Result) { Result = CreateMesh(MeshBudgetReserve); Result->CopyFrom(*PoseMesh); } else { Result->BonePoses = PoseMesh->BonePoses; Result->BoneMap = PoseMesh->BoneMap; for (const TPair& AdditionalBuffer : PoseMesh->AdditionalBuffers) { const bool bIsPoseBufferType = AdditionalBuffer.Key == EMeshBufferType::SkeletonDeformBinding; if (bIsPoseBufferType) { Result->AdditionalBuffers.Emplace(AdditionalBuffer); } } } } if (PhysicsMesh) { MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh_Physics) if (!Result) { Result = CreateMesh(MeshBudgetReserve); Result->CopyFrom(*PhysicsMesh); } else { Result->PhysicsBody = PhysicsMesh->PhysicsBody; for (const TPair& AdditionalBuffer : PhysicsMesh->AdditionalBuffers) { const bool bIsPhysicsBufferType = AdditionalBuffer.Key == EMeshBufferType::PhysicsBodyDeformBinding || AdditionalBuffer.Key == EMeshBufferType::PhysicsBodyDeformSelection || AdditionalBuffer.Key == EMeshBufferType::PhysicsBodyDeformOffsets; if (bIsPhysicsBufferType) { Result->AdditionalBuffers.Emplace(AdditionalBuffer); } } } } if (MetaDataMesh) { MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh_Metadata) if (!Result) { Result = CreateMesh(MeshBudgetReserve); Result->CopyFrom(*MetaDataMesh); } else { // Only in case the geoemtry has been filtered, add the meta data descriptors. if (EnumHasAnyFlags(MeshContentRange.GetContentFlags(), EMeshContentFlags::GeometryData) && !EnumHasAnyFlags(FilterContentFlags, EMeshContentFlags::GeometryData)) { check(MetaDataMesh->VertexBuffers.IsDescriptor()); check(MetaDataMesh->IndexBuffers.IsDescriptor()); Result->VertexBuffers = MetaDataMesh->VertexBuffers; Result->IndexBuffers = MetaDataMesh->IndexBuffers; Result->Surfaces = MetaDataMesh->Surfaces; } Result->Tags = MetaDataMesh->Tags; Result->SkeletonIDs = MetaDataMesh->SkeletonIDs; Result->StreamedResources = MetaDataMesh->StreamedResources; } } Result->MeshIDPrefix = MeshContentRange.MeshIDPrefix; check(ConstantSkeletons.Num() > SkeletonConstantIndex); if (ConstantSkeletons.IsValidIndex(SkeletonConstantIndex)) { Result->Skeleton = ConstantSkeletons[SkeletonConstantIndex]; } OutMesh = Result; } void GetExtensionDataConstant(int32 ConstantIndex, TSharedPtr& Result) const { const FExtensionDataConstant& Constant = ConstantExtensionData[ConstantIndex]; check(Constant.Data); Result = Constant.Data; } FORCEINLINE EOpType GetOpType( OP::ADDRESS at ) const { if (at>=OP::ADDRESS(OpAddress.Num())) return EOpType::NONE; EOpType result; uint64 byteCodeAddress = OpAddress[at]; FMemory::Memcpy( &result, &ByteCode[byteCodeAddress], sizeof(EOpType) ); check( result inline const ARGS GetOpArgs(OP::ADDRESS at) const { ARGS result; uint64 byteCodeAddress = OpAddress[at]; byteCodeAddress += sizeof(EOpType); FMemory::Memcpy(&result, &ByteCode[byteCodeAddress], sizeof(ARGS)); return result; } template inline void SetOpArgs(OP::ADDRESS at, const ARGS& Args) { uint64 byteCodeAddress = OpAddress[at]; byteCodeAddress += sizeof(EOpType); FMemory::Memcpy(&ByteCode[byteCodeAddress], &Args, sizeof(ARGS)); } inline const uint8* GetOpArgsPointer( OP::ADDRESS at ) const { uint64 byteCodeAddress = OpAddress[at]; byteCodeAddress += sizeof(EOpType); const uint8* pData = (const uint8*)&ByteCode[byteCodeAddress]; return pData; } inline uint8* GetOpArgsPointer( OP::ADDRESS at ) { uint64 byteCodeAddress = OpAddress[at]; byteCodeAddress += sizeof(EOpType); uint8* pData = (uint8*)&ByteCode[byteCodeAddress]; return pData; } }; //! class FModel::Private { public: UE::Mutable::Private::FProgram Program; UE_API void UnloadRoms(); void Serialise( FOutputArchive& arch ) const { arch << Program; } void Unserialise( FInputArchive& arch ) { arch >> Program; } }; } #undef UE_API