1075 lines
32 KiB
C++
1075 lines
32 KiB
C++
// 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 mu
|
|
{
|
|
|
|
/** 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<typename DATA>
|
|
inline void AppendCode(TArray<uint8>& 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;
|
|
uint16 _Padding = 0;
|
|
uint8 LODCount = 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<EMeshContentFlags>(
|
|
(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<const FExtensionData> 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<int> m_runtimeParameters;
|
|
|
|
/** List of instructions that need to be cached to efficiently update this state */
|
|
TArray<OP::ADDRESS> 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<OP::ADDRESS,uint64> > 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<m_dynamicResources.Num(); ++i )
|
|
{
|
|
if ( m_dynamicResources[i].Key==at )
|
|
{
|
|
res = m_dynamicResources[i].Value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/** */
|
|
bool IsUpdateCache( OP::ADDRESS at ) const
|
|
{
|
|
bool res = false;
|
|
|
|
for (int32 i=0; !res && i<m_updateCache.Num(); ++i )
|
|
{
|
|
if ( m_updateCache[i]==at )
|
|
{
|
|
res = true;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/** */
|
|
void AddUpdateCache( OP::ADDRESS at )
|
|
{
|
|
if ( !IsUpdateCache(at) )
|
|
{
|
|
m_updateCache.Add( at );
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Location in the ByteCode of the beginning of each operation */
|
|
TArray<uint32> OpAddress;
|
|
|
|
/** Byte-coded representation of the program, using variable-sized op data. */
|
|
TArray<uint8> ByteCode;
|
|
|
|
/** */
|
|
TArray<FState> States;
|
|
|
|
/** Data for every rom required in-game. */
|
|
TArray<FRomDataRuntime> Roms;
|
|
|
|
/** Data for every rom required at compile-time. It is empty in cooked data. */
|
|
TArray<FRomDataCompile> RomsCompileData;
|
|
|
|
/** Loaded roms worth tracking (only images and meshes for now). Stores the rom's data type.*/
|
|
TSparseArray<uint8> 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<TSharedPtr<const FImage>> 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<uint32, TSharedPtr<const FImage>> ConstantImageLODsStreamed;
|
|
|
|
/** Constant image mip chain indices: ranges in this array are defined in FImageLODRange and the indices here refer to ConstantImageLODs. */
|
|
TArray<FConstantResourceIndex> ConstantImageLODIndices;
|
|
|
|
/** Constant image data. */
|
|
TArray<FImageLODRange> ConstantImages;
|
|
|
|
/** Constant mesh content indices: ranges in this array are defined in FMeshContentRange and the indices here refer to ConstantMeshes. */
|
|
TArray<FConstantResourceIndex> ConstantMeshContentIndices;
|
|
|
|
/** Constant mesh data */
|
|
TArray<FMeshContentRange> 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<TSharedPtr<const FMesh>> 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<uint32, TSharedPtr<const FMesh>> ConstantMeshesStreamed;
|
|
|
|
/** Constant FExtensionData */
|
|
TArray<FExtensionDataConstant> ConstantExtensionData;
|
|
|
|
/** Constant string data */
|
|
TArray<FString> ConstantStrings;
|
|
|
|
/** */
|
|
TArray<TArray<uint32>> ConstantUInt32Lists;
|
|
|
|
/** */
|
|
TArray<TArray<uint64>> ConstantUInt64Lists;
|
|
|
|
/** Constant layout data */
|
|
TArray<TSharedPtr<const FLayout>> ConstantLayouts;
|
|
|
|
/** Constant projectors */
|
|
TArray<FProjector> ConstantProjectors;
|
|
|
|
/** Constant matrices, usually used for transforms */
|
|
TArray<FMatrix44f> ConstantMatrices;
|
|
|
|
/** Constant shapes */
|
|
TArray<FShape> ConstantShapes;
|
|
|
|
/** Constant curves */
|
|
TArray<FRichCurve> ConstantCurves;
|
|
|
|
/** Constant skeletons */
|
|
TArray<TSharedPtr<const FSkeleton>> ConstantSkeletons;
|
|
|
|
/** Constant Physics Bodies */
|
|
TArray<TSharedPtr<const FPhysicsBody>> ConstantPhysicsBodies;
|
|
|
|
/** FParameters of the model. */
|
|
/** The value stored here is the default value. */
|
|
TArray<FParameterDesc> Parameters;
|
|
|
|
/** Ranges for iteration of the model operations. */
|
|
TArray<FRangeDesc> 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<TArray<uint16>> ParameterLists;
|
|
|
|
#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;
|
|
}
|
|
|
|
/** */
|
|
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;
|
|
}
|
|
|
|
/** 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);
|
|
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<const FImage> Data;
|
|
ConstantImageLODsStreamed.RemoveAndCopyValue(RomIndex, Data);
|
|
if (OutDataSize && Data)
|
|
{
|
|
*OutDataSize = Data->GetDataSize();
|
|
}
|
|
break;
|
|
}
|
|
case uint32(ERomDataType::Mesh):
|
|
{
|
|
TSharedPtr<const FMesh> Data;
|
|
ConstantMeshesStreamed.RemoveAndCopyValue(RomIndex, Data);
|
|
if (OutDataSize && Data)
|
|
{
|
|
*OutDataSize = Data->GetDataSize();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
FORCEINLINE void SetMeshRomValue(uint32 RomIndex, const TSharedPtr<FMesh>& 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<FImage>& 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<const FExtensionData> 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<const FLayout> pLayout )
|
|
{
|
|
// Ensure unique
|
|
for (int32 i=0; i<ConstantLayouts.Num(); ++i)
|
|
{
|
|
if (ConstantLayouts[i]==pLayout)
|
|
{
|
|
return (OP::ADDRESS)i;
|
|
}
|
|
}
|
|
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantLayouts.Num() );
|
|
ConstantLayouts.Add( pLayout );
|
|
return index;
|
|
}
|
|
|
|
OP::ADDRESS AddConstant( TSharedPtr<const FSkeleton> Skeleton )
|
|
{
|
|
// Ensure unique
|
|
for ( int32 i=0; i<ConstantSkeletons.Num(); ++i)
|
|
{
|
|
if ( ConstantSkeletons[i]== Skeleton
|
|
||
|
|
*ConstantSkeletons[i]==*Skeleton
|
|
)
|
|
{
|
|
return (OP::ADDRESS)i;
|
|
}
|
|
}
|
|
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantSkeletons.Num() );
|
|
ConstantSkeletons.Add(Skeleton);
|
|
return index;
|
|
}
|
|
|
|
OP::ADDRESS AddConstant(TSharedPtr<const FPhysicsBody> pPhysicsBody )
|
|
{
|
|
// Ensure unique
|
|
for (int32 i=0; i<ConstantPhysicsBodies.Num(); ++i)
|
|
{
|
|
if ( ConstantPhysicsBodies[i]==pPhysicsBody
|
|
||
|
|
*ConstantPhysicsBodies[i]==*pPhysicsBody
|
|
)
|
|
{
|
|
return (OP::ADDRESS)i;
|
|
}
|
|
}
|
|
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantPhysicsBodies.Num() );
|
|
ConstantPhysicsBodies.Add( pPhysicsBody );
|
|
return index;
|
|
}
|
|
|
|
OP::ADDRESS AddConstant(const FString& Str)
|
|
{
|
|
return (OP::ADDRESS)ConstantStrings.AddUnique(Str);
|
|
}
|
|
|
|
OP::ADDRESS AddConstant(const TArray<uint32>& UInt32List)
|
|
{
|
|
return (OP::ADDRESS)ConstantUInt32Lists.AddUnique(UInt32List);
|
|
}
|
|
|
|
OP::ADDRESS AddConstant(const TArray<uint64>& UInt64List)
|
|
{
|
|
return (OP::ADDRESS)ConstantUInt64Lists.AddUnique(UInt64List);
|
|
}
|
|
|
|
OP::ADDRESS AddConstant(const TArray<FString>& StringList)
|
|
{
|
|
const int32 NumStrings = StringList.Num();
|
|
|
|
TArray<uint32> 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<ConstantMatrices.Num(); ++i)
|
|
{
|
|
if (ConstantMatrices[i]==m)
|
|
{
|
|
return (OP::ADDRESS)i;
|
|
}
|
|
}
|
|
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantMatrices.Num() );
|
|
ConstantMatrices.Add( m );
|
|
return index;
|
|
}
|
|
|
|
OP::ADDRESS AddConstant( const FShape& m )
|
|
{
|
|
// Ensure unique
|
|
for (int32 i=0; i<ConstantShapes.Num(); ++i)
|
|
{
|
|
if (ConstantShapes[i]==m)
|
|
{
|
|
return (OP::ADDRESS)i;
|
|
}
|
|
}
|
|
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantShapes.Num() );
|
|
ConstantShapes.Add( m );
|
|
return index;
|
|
}
|
|
|
|
OP::ADDRESS AddConstant( const FProjector& m )
|
|
{
|
|
// Ensure unique
|
|
for (int32 i=0; i<ConstantProjectors.Num(); ++i)
|
|
{
|
|
if (ConstantProjectors[i]==m)
|
|
{
|
|
return (OP::ADDRESS)i;
|
|
}
|
|
}
|
|
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantProjectors.Num() );
|
|
ConstantProjectors.Add( m );
|
|
return index;
|
|
}
|
|
|
|
OP::ADDRESS AddConstant( const FRichCurve& m )
|
|
{
|
|
OP::ADDRESS index = OP::ADDRESS( ConstantCurves.AddUnique( m ) );
|
|
return index;
|
|
}
|
|
|
|
|
|
inline TSharedPtr<const FImage> GetImageLOD(FConstantResourceIndex Index) const
|
|
{
|
|
TSharedPtr<const FImage> Mip;
|
|
if (!Index.Streamable)
|
|
{
|
|
if (ConstantImageLODsPermanent.IsValidIndex(Index.Index))
|
|
{
|
|
Mip = ConstantImageLODsPermanent[Index.Index];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const TSharedPtr<const FImage>* 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 <typename CreateImageFunc>
|
|
void GetConstant( uint32 ConstantIndex, TSharedPtr<const FImage>& res, int32 MipsToSkip, const CreateImageFunc& CreateImage) const
|
|
{
|
|
int32 ReallySkippedLODs = FMath::Min(ConstantImages[ConstantIndex].LODCount - 1, MipsToSkip);
|
|
int32 FirstLODIndexIndex = ConstantImages[ConstantIndex].FirstIndex;
|
|
|
|
// Get the first mip
|
|
int32 ResultLODIndexIndex = FirstLODIndexIndex + ReallySkippedLODs;
|
|
FConstantResourceIndex ResultLODIndex = ConstantImageLODIndices[ResultLODIndexIndex];
|
|
TSharedPtr<const FImage> CurrentMip = GetImageLOD(ResultLODIndex);
|
|
|
|
// We may need to skip more LODs if they are not loaded
|
|
while (!CurrentMip)
|
|
{
|
|
++ReallySkippedLODs;
|
|
|
|
if (ReallySkippedLODs >= ConstantImages[ConstantIndex].LODCount)
|
|
{
|
|
// We don't have a single mip loaded for the image that was requested
|
|
ensure(false);
|
|
break;
|
|
}
|
|
|
|
++ResultLODIndexIndex;
|
|
ResultLODIndex = ConstantImageLODIndices[ResultLODIndexIndex];
|
|
CurrentMip = GetImageLOD(ResultLODIndex);
|
|
}
|
|
|
|
int32 FinalLODs = ConstantImages[ConstantIndex].LODCount - ReallySkippedLODs;
|
|
check(FinalLODs > 0);
|
|
|
|
// Shortcut if we only want one mip
|
|
if (FinalLODs == 1)
|
|
{
|
|
res = CurrentMip;
|
|
return;
|
|
}
|
|
|
|
// Compose the result image
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ComposeConstantImage);
|
|
|
|
TSharedPtr<FImage> Result = CreateImage(CurrentMip->GetSizeX(), CurrentMip->GetSizeY(), FinalLODs, CurrentMip->GetFormat(), EInitializationType::NotInitialized);
|
|
Result->Flags = CurrentMip->Flags;
|
|
|
|
// Some non-block pixel formats require separate memory size calculation
|
|
if (Result->DataStorage.IsEmpty())
|
|
{
|
|
for (int32 LOD = 0; LOD < FinalLODs; ++LOD)
|
|
{
|
|
FConstantResourceIndex LODIndex = ConstantImageLODIndices[ResultLODIndexIndex + LOD];
|
|
|
|
TSharedPtr<const FImage> Image = GetImageLOD(LODIndex);
|
|
|
|
// This could happen in the case of missing data files
|
|
if (!Image)
|
|
{
|
|
break;
|
|
}
|
|
|
|
int32 MipSizeBytes = Image->GetLODDataSize(0);
|
|
Result->DataStorage.ResizeLOD(LOD, MipSizeBytes);
|
|
}
|
|
}
|
|
|
|
for (int32 LOD = 0; LOD < FinalLODs; ++LOD)
|
|
{
|
|
check(CurrentMip->GetLODCount() == 1);
|
|
check(CurrentMip->GetFormat() == Result->GetFormat());
|
|
|
|
TArrayView<uint8> ResultLODView = Result->DataStorage.GetLOD(LOD);
|
|
TArrayView<const uint8> CurrentMipView = CurrentMip->DataStorage.GetLOD(0);
|
|
|
|
check(CurrentMipView.Num() == ResultLODView.Num());
|
|
|
|
FMemory::Memcpy(ResultLODView.GetData(), CurrentMipView.GetData(), ResultLODView.Num());
|
|
|
|
if (LOD + 1 < FinalLODs)
|
|
{
|
|
ResultLODIndex = ConstantImageLODIndices[ResultLODIndexIndex + LOD + 1];
|
|
CurrentMip = GetImageLOD(ResultLODIndex);
|
|
|
|
// This could only happen if missing or corrupted data.
|
|
if (!CurrentMip)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
res = Result;
|
|
}
|
|
}
|
|
|
|
template<class CreateMeshFunc>
|
|
void GetConstant(
|
|
uint32 MeshConstantIndex,
|
|
int32 SkeletonConstantIndex,
|
|
TSharedPtr<const FMesh>& OutMesh,
|
|
EMeshContentFlags FilterContentFlags,
|
|
CreateMeshFunc&& CreateMesh) const
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh)
|
|
|
|
FMeshContentRange MeshContentRange = ConstantMeshes[MeshConstantIndex];
|
|
|
|
TSharedPtr<const FMesh> EmptyMesh = nullptr;
|
|
auto GetMeshAtResourceIndex = [&](FConstantResourceIndex ResourceIndex) -> TSharedPtr<const FMesh>
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(GetConstant_Mesh_GetMesh)
|
|
|
|
if (!ResourceIndex.Streamable)
|
|
{
|
|
if (ConstantMeshesPermanent.IsValidIndex(ResourceIndex.Index))
|
|
{
|
|
return ConstantMeshesPermanent[ResourceIndex.Index];
|
|
}
|
|
|
|
if (!EmptyMesh)
|
|
{
|
|
EmptyMesh = MakeShared<FMesh>();
|
|
}
|
|
|
|
return EmptyMesh;
|
|
}
|
|
else
|
|
{
|
|
const TSharedPtr<const FMesh>* Found = ConstantMeshesStreamed.Find(ResourceIndex.Index);
|
|
if (Found)
|
|
{
|
|
return *Found;
|
|
}
|
|
|
|
if (!EmptyMesh)
|
|
{
|
|
EmptyMesh = MakeShared<FMesh>();
|
|
}
|
|
|
|
return EmptyMesh;
|
|
}
|
|
};
|
|
|
|
TSharedPtr<const FMesh> GeometryMesh = nullptr;
|
|
TSharedPtr<const FMesh> PoseMesh = nullptr;
|
|
TSharedPtr<const FMesh> PhysicsMesh = nullptr;
|
|
TSharedPtr<const FMesh> 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<FMesh> 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<EMeshBufferType, FMeshBufferSet>& 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<EMeshBufferType, FMeshBufferSet>& 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<const FExtensionData>& Result) const
|
|
{
|
|
const FExtensionDataConstant& Constant = ConstantExtensionData[ConstantIndex];
|
|
|
|
check(Constant.Data);
|
|
|
|
Result = Constant.Data;
|
|
}
|
|
|
|
inline 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<EOpType::COUNT);
|
|
return result;
|
|
}
|
|
|
|
template<typename ARGS>
|
|
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<typename ARGS>
|
|
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:
|
|
|
|
mu::FProgram Program;
|
|
|
|
UE_API void UnloadRoms();
|
|
|
|
void Serialise( FOutputArchive& arch ) const
|
|
{
|
|
arch << Program;
|
|
}
|
|
|
|
void Unserialise( FInputArchive& arch )
|
|
{
|
|
arch >> Program;
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#undef UE_API
|