Files
UnrealEngine/Engine/Plugins/Mutable/Source/CustomizableObject/Private/MuCO/UnrealConversionUtils.cpp
2025-05-18 13:04:45 +08:00

1501 lines
58 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuCO/UnrealConversionUtils.h"
#include "MuCO/CustomizableObjectInstance.h"
#include "MuCO/UnrealPortabilityHelpers.h"
#include "MuCO/CustomizableObject.h"
#include "MuCO/CustomizableObjectPrivate.h"
#include "MuCO/CustomizableObjectSystemPrivate.h"
#include "MuCO/MutableMeshBufferUtils.h"
#include "MuR/Skeleton.h"
#include "MuR/Mesh.h"
#include "MuR/MeshBufferSet.h"
#include "MuR/MutableTrace.h"
#include "Animation/Skeleton.h"
#include "Engine/SkeletalMesh.h"
#include "GPUSkinVertexFactory.h"
#include "Rendering/SkeletalMeshRenderData.h"
class USkeleton;
namespace UnrealConversionUtils
{
// Hidden functions only used internally to aid other functions
namespace
{
/**
* Initializes the static mesh vertex buffers object provided with the data found on the mutable buffers
* @param OutVertexBuffers - The Unreal's vertex buffers container to be updated with the mutable data.
* @param NumVertices - The amount of vertices on the buffer
* @param NumTexCoords - The amount of texture coordinates
* @param bUseFullPrecisionUVs - Determines if we want to use or not full precision UVs
* @param bNeedCPUAccess - Determines if CPU access is required
* @param InMutablePositionData - Mutable position data buffer
* @param InMutableTangentData - Mutable tangent data buffer
* @param InMutableTextureData - Mutable texture data buffer
*/
void FStaticMeshVertexBuffers_InitWithMutableData(
FStaticMeshVertexBuffers& OutVertexBuffers,
const int32 NumVertices,
const int32 NumTexCoords,
const bool bUseFullPrecisionUVs,
const bool bNeedCPUAccess,
const void* InMutablePositionData,
const void* InMutableTangentData,
const void* InMutableTextureData)
{
// positions
OutVertexBuffers.PositionVertexBuffer.Init(NumVertices, bNeedCPUAccess);
FMemory::Memcpy(OutVertexBuffers.PositionVertexBuffer.GetVertexData(), InMutablePositionData, NumVertices * OutVertexBuffers.PositionVertexBuffer.GetStride());
// tangent and texture coords
OutVertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(bUseFullPrecisionUVs);
OutVertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(false);
OutVertexBuffers.StaticMeshVertexBuffer.Init(NumVertices, NumTexCoords, bNeedCPUAccess);
FMemory::Memcpy(OutVertexBuffers.StaticMeshVertexBuffer.GetTangentData(), InMutableTangentData, OutVertexBuffers.StaticMeshVertexBuffer.GetTangentSize());
FMemory::Memcpy(OutVertexBuffers.StaticMeshVertexBuffer.GetTexCoordData(), InMutableTextureData, OutVertexBuffers.StaticMeshVertexBuffer.GetTexCoordSize());
}
/**
* Initializes the color vertex buffers object provided with the data found on the mutable buffers
* @param OutVertexBuffers - The Unreal's vertex buffers container to be updated with the mutable data.
* @param NumVertices - The amount of vertices on the buffer
* @param InMutableColorData - Mutable color data buffer
*/
void FColorVertexBuffers_InitWithMutableData(
FStaticMeshVertexBuffers& OutVertexBuffers,
const int32 NumVertices,
const void* InMutableColorData
)
{
// positions
OutVertexBuffers.ColorVertexBuffer.Init(NumVertices);
FMemory::Memcpy(OutVertexBuffers.ColorVertexBuffer.GetVertexData(), InMutableColorData, NumVertices * OutVertexBuffers.ColorVertexBuffer.GetStride());
}
/**
* Initializes the skin vertex buffers object provided with the data found on the mutable buffers
* @param OutVertexWeightBuffer - The Unreal's vertex buffers container to be updated with the mutable data.
* @param NumVertices - The amount of vertices on the buffer
* @param NumBones - The amount of bones to use to init the skin weights buffer
* @param NumBoneInfluences - The amount of bone influences on the buffer
* @param bNeedCPUAccess - Determines if CPU access is required
* @param InMutableData - Mutable data buffer
*/
void FSkinWeightVertexBuffer_InitWithMutableData(
FSkinWeightVertexBuffer& OutVertexWeightBuffer,
const int32 NumVertices,
const int32 NumBones,
const int32 NumBoneInfluences,
const bool bNeedCPUAccess,
const void* InMutableData,
const uint32 MutableDataSize)
{
OutVertexWeightBuffer.SetNeedsCPUAccess(bNeedCPUAccess);
FSkinWeightDataVertexBuffer* VertexBuffer = OutVertexWeightBuffer.GetDataVertexBuffer();
VertexBuffer->SetMaxBoneInfluences(NumBoneInfluences);
if (!NumVertices)
{
return;
}
int32 MaxBoneInfluences = VertexBuffer->GetMaxBoneInfluences();
if (MaxBoneInfluences == NumBoneInfluences)
{
VertexBuffer->Init(NumBones, NumVertices);
uint32 OutVertexWeightBufferSize = VertexBuffer->GetVertexDataSize();
ensure(MutableDataSize == OutVertexWeightBufferSize);
void* Data = VertexBuffer->GetWeightData();
FMemory::Memcpy(Data, InMutableData, OutVertexWeightBufferSize);
}
else if (NumBones>0)
{
// We need to expand it with blank data interleaved
uint32 MutableVertexDataSize = MutableDataSize / NumVertices;
uint32 FinalVertexDataSize = ( MutableDataSize / NumBones) * MaxBoneInfluences;
VertexBuffer->Init(NumVertices*MaxBoneInfluences, NumVertices);
uint32 OutVertexWeightBufferSize = VertexBuffer->GetVertexDataSize();
ensure(FinalVertexDataSize*NumVertices == OutVertexWeightBufferSize);
int32 BoneIndexSize = OutVertexWeightBuffer.GetBoneIndexByteSize();
int32 WeightSize = OutVertexWeightBuffer.GetBoneWeightByteSize();
const uint8* MutableData = reinterpret_cast<const uint8*>(InMutableData);
uint8* Data = VertexBuffer->GetWeightData();
FMemory::Memzero(Data, OutVertexWeightBufferSize);
for (int32 V=0; V<NumVertices; ++V)
{
// Bone indices
FMemory::Memcpy(Data, MutableData, NumBoneInfluences*BoneIndexSize);
MutableData += NumBoneInfluences * BoneIndexSize;
Data += MaxBoneInfluences * BoneIndexSize;
// Weights
FMemory::Memcpy(Data, MutableData, NumBoneInfluences * WeightSize);
MutableData += NumBoneInfluences * WeightSize;
Data += MaxBoneInfluences * WeightSize;
}
}
bool bIsVariableBonesPerVertex = VertexBuffer->GetVariableBonesPerVertex();
check(!FGPUBaseSkinVertexFactory::UseUnlimitedBoneInfluences(NumBoneInfluences) || bIsVariableBonesPerVertex);
if (bIsVariableBonesPerVertex)
{
OutVertexWeightBuffer.RebuildLookupVertexBuffer();
{
MUTABLE_CPUPROFILER_SCOPE(OptimizeVertexAndLookupBuffers);
// Everything in this scope is optional and makes extra copies, but will optimize the variable bone
// influences buffers. Without it, the vertices are assumed to have a constant NumBoneInfluences per vertex.
TArray<FSkinWeightInfo> TempVertices;
OutVertexWeightBuffer.GetSkinWeights(TempVertices);
// The assignment operator actually optimizes the DataVertexBuffer
OutVertexWeightBuffer = TempVertices;
}
}
}
}
void SetupRenderSections(
FSkeletalMeshLODRenderData& LODResource,
const mu::FMesh* InMutableMesh,
const TArray<mu::FBoneName>& InBoneMap,
const TMap<mu::FBoneName, TPair<FName, uint16>>& BoneInfoMap,
const int32 InFirstBoneMapIndex,
const TArray<const FMutableSurfaceMetadata*>& SurfacesMetadata)
{
check(InMutableMesh);
const mu::FMeshBufferSet& MutableMeshVertexBuffers = InMutableMesh->GetVertexBuffers();
// Find the number of influences from this mesh
int32 NumBoneInfluences = 0;
int32 boneIndexBuffer = -1;
int32 boneIndexChannel = -1;
MutableMeshVertexBuffers.FindChannel(mu::EMeshBufferSemantic::BoneIndices, 0, &boneIndexBuffer, &boneIndexChannel);
if (boneIndexBuffer >= 0 || boneIndexChannel >= 0)
{
MutableMeshVertexBuffers.GetChannel(boneIndexBuffer, boneIndexChannel,
nullptr, nullptr, nullptr, &NumBoneInfluences, nullptr);
}
const int32 SurfaceCount = InMutableMesh->GetSurfaceCount();
for (int32 SurfaceIndex = 0; SurfaceIndex < SurfaceCount; ++SurfaceIndex)
{
MUTABLE_CPUPROFILER_SCOPE(SetupRenderSections);
int32 FirstIndex;
int32 IndexCount;
int32 FirstVertex;
int32 VertexCount;
int32 FirstBone;
int32 BoneCount;
InMutableMesh->GetSurface(SurfaceIndex, FirstVertex, VertexCount, FirstIndex, IndexCount, FirstBone, BoneCount);
FSkelMeshRenderSection& Section = LODResource.RenderSections[SurfaceIndex];
Section.DuplicatedVerticesBuffer.Init(1, TMap<int, TArray<int32>>());
if (VertexCount == 0 || IndexCount == 0)
{
Section.bDisabled = true;
continue; // Unreal doesn't like empty meshes
}
Section.BaseIndex = FirstIndex;
Section.NumTriangles = IndexCount / 3;
Section.BaseVertexIndex = FirstVertex;
Section.MaxBoneInfluences = NumBoneInfluences;
Section.NumVertices = VertexCount;
check(SurfacesMetadata.Num() == InMutableMesh->Surfaces.Num());
if (SurfacesMetadata[SurfaceIndex])
{
Section.bCastShadow = SurfacesMetadata[SurfaceIndex]->bCastShadow;
}
// InBoneMaps may contain bonemaps from other sections. Copy the bones belonging to this mesh.
FirstBone += InFirstBoneMapIndex;
Section.BoneMap.Reserve(BoneCount);
for (int32 BoneMapIndex = 0; BoneMapIndex < BoneCount; ++BoneMapIndex, ++FirstBone)
{
if (InBoneMap.IsValidIndex(FirstBone))
{
mu::FBoneName FirstBoneName = InBoneMap[FirstBone];
if (const TPair<FName, uint16>* Found = BoneInfoMap.Find(FirstBoneName))
{
Section.BoneMap.Add(Found->Value);
}
}
}
}
}
void InitVertexBuffersWithDummyData(
FSkeletalMeshLODRenderData& LODResource,
const mu::FMesh* InMutableMesh,
const bool bAllowCPUAccess)
{
MUTABLE_CPUPROFILER_SCOPE(InitVertexBuffersWithDummyData);
const mu::FMeshBufferSet& MutableMeshVertexBuffers = InMutableMesh->GetVertexBuffers();
check(MutableMeshVertexBuffers.GetElementCount() > 0);
const bool bUseFullPrecisionUVs = true;
const bool bUseFullPrecisionTangentBasis = false;
const uint32 NumVertices = MutableMeshVertexBuffers.GetElementCount();
const uint32 NumTexCoords = MutableMeshVertexBuffers.GetBufferChannelCount(MUTABLE_VERTEXBUFFER_TEXCOORDS);
const uint32 DummyNumVertices = 1;
// Static Vertex buffers
{
LODResource.StaticVertexBuffers.PositionVertexBuffer.Init(DummyNumVertices, bAllowCPUAccess);
LODResource.StaticVertexBuffers.PositionVertexBuffer.SetMetaData(LODResource.StaticVertexBuffers.PositionVertexBuffer.GetStride(), NumVertices);
// tangent and texture coords
LODResource.StaticVertexBuffers.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(bUseFullPrecisionUVs);
LODResource.StaticVertexBuffers.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(bUseFullPrecisionTangentBasis);
LODResource.StaticVertexBuffers.StaticMeshVertexBuffer.Init(DummyNumVertices, NumTexCoords, bAllowCPUAccess);
LODResource.StaticVertexBuffers.StaticMeshVertexBuffer.SetMetaData(NumTexCoords, NumVertices, bUseFullPrecisionUVs, bUseFullPrecisionTangentBasis);
}
mu::EMeshBufferFormat BoneIndexFormat = mu::EMeshBufferFormat::None;
int32 NumBoneInfluences = 0;
int32 BoneIndexBuffer = -1;
int32 BoneIndexChannel = -1;
MutableMeshVertexBuffers.FindChannel(mu::EMeshBufferSemantic::BoneIndices, 0, &BoneIndexBuffer, &BoneIndexChannel);
if (BoneIndexBuffer >= 0 || BoneIndexChannel >= 0)
{
MutableMeshVertexBuffers.GetChannel(BoneIndexBuffer, BoneIndexChannel,
nullptr, nullptr, &BoneIndexFormat, &NumBoneInfluences, nullptr);
}
mu::EMeshBufferFormat BoneWeightFormat = mu::EMeshBufferFormat::None;
int32 BoneWeightBuffer = -1;
int32 BoneWeightChannel = -1;
MutableMeshVertexBuffers.FindChannel(mu::EMeshBufferSemantic::BoneWeights, 0, &BoneWeightBuffer, &BoneWeightChannel);
if (BoneWeightBuffer >= 0 || BoneWeightChannel >= 0)
{
MutableMeshVertexBuffers.GetChannel(BoneWeightBuffer, BoneWeightChannel,
nullptr, nullptr, &BoneWeightFormat, nullptr, nullptr);
}
// Skin Weights
{
const bool bUse16BitBoneIndex = BoneIndexFormat == mu::EMeshBufferFormat::UInt16;
const bool bUse16BitBoneWeights = BoneWeightFormat == mu::EMeshBufferFormat::NUInt16;
LODResource.SkinWeightVertexBuffer.SetUse16BitBoneIndex(bUse16BitBoneIndex);
LODResource.SkinWeightVertexBuffer.SetUse16BitBoneWeight(bUse16BitBoneWeights);
LODResource.SkinWeightVertexBuffer.SetNeedsCPUAccess(bAllowCPUAccess);
FSkinWeightDataVertexBuffer* VertexBuffer = LODResource.SkinWeightVertexBuffer.GetDataVertexBuffer();
// NumBoneInfluences must be equal to MaxBoneInfluences. Set and then GetMaxBoneInfluences to know the number of bone influences to use (at least MAX_INFLUENCES_PER_STREAM).
VertexBuffer->SetMaxBoneInfluences(NumBoneInfluences);
NumBoneInfluences = VertexBuffer->GetMaxBoneInfluences();
VertexBuffer->Init(NumBoneInfluences, DummyNumVertices);
VertexBuffer->SetMetaData(NumVertices, NumBoneInfluences, bUse16BitBoneIndex, bUse16BitBoneWeights);
if (VertexBuffer->GetVariableBonesPerVertex())
{
FSkinWeightLookupVertexBuffer* LookUpVertexBuffer = const_cast<FSkinWeightLookupVertexBuffer*>(LODResource.SkinWeightVertexBuffer.GetLookupVertexBuffer());
LookUpVertexBuffer->SetMetaData(NumVertices);
}
}
// Optional buffers
for (int32 Buffer = MUTABLE_VERTEXBUFFER_TEXCOORDS + 1; Buffer < MutableMeshVertexBuffers.GetBufferCount(); ++Buffer)
{
if (MutableMeshVertexBuffers.GetBufferChannelCount(Buffer) > 0)
{
mu::EMeshBufferSemantic Semantic;
mu::EMeshBufferFormat Format;
int32 SemanticIndex;
int32 ComponentCount;
int32 Offset;
MutableMeshVertexBuffers.GetChannel(Buffer, 0, &Semantic, &SemanticIndex, &Format, &ComponentCount, &Offset);
// colour buffer?
if (Semantic == mu::EMeshBufferSemantic::Color)
{
LODResource.StaticVertexBuffers.ColorVertexBuffer.Init(1, bAllowCPUAccess);
LODResource.StaticVertexBuffers.ColorVertexBuffer.SetMetaData(LODResource.StaticVertexBuffers.ColorVertexBuffer.GetStride(), NumVertices);
check(LODResource.StaticVertexBuffers.ColorVertexBuffer.GetStride() == MutableMeshVertexBuffers.GetElementSize(Buffer));
}
}
}
}
void CopyMutableVertexBuffers(
FSkeletalMeshLODRenderData& LODResource,
const mu::FMesh* MutableMesh,
const bool bAllowCPUAccess)
{
MUTABLE_CPUPROFILER_SCOPE(CopyMutableVertexBuffers);
const mu::FMeshBufferSet& MutableMeshVertexBuffers = MutableMesh->GetVertexBuffers();
const int32 NumVertices = MutableMeshVertexBuffers.IsDescriptor()
? 0
: MutableMeshVertexBuffers.GetElementCount();
//const ESkeletalMeshVertexFlags BuildFlags = SkeletalMesh->GetVertexBufferFlags();
//const bool bUseFullPrecisionUVs = EnumHasAllFlags(BuildFlags, ESkeletalMeshVertexFlags::UseFullPrecisionUVs);
bool bUseFullPrecisionUVs = true;
{
int32 TexCoordsBufferIndex = -1;
int32 TexCoordsChannelIndex = -1;
MutableMesh->VertexBuffers.FindChannel(mu::EMeshBufferSemantic::TexCoords, 0, &TexCoordsBufferIndex, &TexCoordsChannelIndex);
if (TexCoordsBufferIndex >= 0 && TexCoordsChannelIndex >= 0)
{
bUseFullPrecisionUVs = MutableMesh->VertexBuffers.Buffers[TexCoordsBufferIndex].Channels[TexCoordsChannelIndex].Format == mu::EMeshBufferFormat::Float32;
}
}
const int NumTexCoords = MutableMeshVertexBuffers.GetBufferChannelCount(MUTABLE_VERTEXBUFFER_TEXCOORDS);
FStaticMeshVertexBuffers_InitWithMutableData(
LODResource.StaticVertexBuffers,
NumVertices,
NumTexCoords,
bUseFullPrecisionUVs,
bAllowCPUAccess,
MutableMeshVertexBuffers.GetBufferData(MUTABLE_VERTEXBUFFER_POSITION),
MutableMeshVertexBuffers.GetBufferData(MUTABLE_VERTEXBUFFER_TANGENT),
MutableMeshVertexBuffers.GetBufferData(MUTABLE_VERTEXBUFFER_TEXCOORDS)
);
mu::EMeshBufferFormat BoneIndexFormat = mu::EMeshBufferFormat::None;
int32 NumBoneInfluences = 0;
int32 BoneIndexBuffer = -1;
int32 BoneIndexChannel = -1;
MutableMeshVertexBuffers.FindChannel(mu::EMeshBufferSemantic::BoneIndices, 0, &BoneIndexBuffer, &BoneIndexChannel);
if (BoneIndexBuffer >= 0 || BoneIndexChannel >= 0)
{
MutableMeshVertexBuffers.GetChannel(BoneIndexBuffer, BoneIndexChannel,
nullptr, nullptr, &BoneIndexFormat, &NumBoneInfluences, nullptr);
}
mu::EMeshBufferFormat BoneWeightFormat = mu::EMeshBufferFormat::None;
int32 BoneWeightBuffer = -1;
int32 BoneWeightChannel = -1;
MutableMeshVertexBuffers.FindChannel(mu::EMeshBufferSemantic::BoneWeights, 0, &BoneWeightBuffer, &BoneWeightChannel);
if (BoneWeightBuffer >= 0 || BoneWeightChannel >= 0)
{
MutableMeshVertexBuffers.GetChannel(BoneWeightBuffer, BoneWeightChannel,
nullptr, nullptr, &BoneWeightFormat, nullptr, nullptr);
}
if (BoneIndexFormat == mu::EMeshBufferFormat::UInt16)
{
LODResource.SkinWeightVertexBuffer.SetUse16BitBoneIndex(true);
}
if (BoneWeightFormat == mu::EMeshBufferFormat::NUInt16)
{
LODResource.SkinWeightVertexBuffer.SetUse16BitBoneWeight(true);
}
// Init skin weight buffer
FSkinWeightVertexBuffer_InitWithMutableData(
LODResource.SkinWeightVertexBuffer,
NumVertices,
NumBoneInfluences * NumVertices,
NumBoneInfluences,
bAllowCPUAccess,
MutableMeshVertexBuffers.GetBufferData(BoneIndexBuffer),
MutableMeshVertexBuffers.GetBufferDataSize(BoneIndexBuffer)
);
// Optional buffers
for (int32 Buffer = MUTABLE_VERTEXBUFFER_TEXCOORDS + 1; Buffer < MutableMeshVertexBuffers.GetBufferCount(); ++Buffer)
{
if (MutableMeshVertexBuffers.GetBufferChannelCount(Buffer) > 0)
{
mu::EMeshBufferSemantic Semantic;
mu::EMeshBufferFormat Format;
int32 SemanticIndex;
int32 ComponentCount;
int32 Offset;
MutableMeshVertexBuffers.GetChannel(Buffer, 0, &Semantic, &SemanticIndex, &Format, &ComponentCount, &Offset);
// colour buffer?
if (Semantic == mu::EMeshBufferSemantic::Color)
{
const void* DataPtr = MutableMeshVertexBuffers.GetBufferData(Buffer);
FColorVertexBuffers_InitWithMutableData(LODResource.StaticVertexBuffers, NumVertices, DataPtr);
check(LODResource.StaticVertexBuffers.ColorVertexBuffer.GetStride() == MutableMeshVertexBuffers.GetElementSize(Buffer));
}
}
}
}
void InitIndexBuffersWithDummyData(FSkeletalMeshLODRenderData& LODResource, const mu::FMesh* InMutableMesh)
{
MUTABLE_CPUPROFILER_SCOPE(InitIndexBuffersWithDummyData);
check(InMutableMesh->GetIndexBuffers().GetElementCount() > 0);
const int32 NumIndices = InMutableMesh->GetIndexBuffers().GetElementCount();
const int32 ElementSize = InMutableMesh->GetIndexBuffers().GetElementSize(0);
LODResource.MultiSizeIndexContainer.CreateIndexBuffer(ElementSize);
LODResource.MultiSizeIndexContainer.GetIndexBuffer()->SetMetaData(NumIndices);
}
bool CopyMutableIndexBuffers(
FSkeletalMeshLODRenderData& LODResource,
const mu::FMesh* InMutableMesh,
const TArray<uint32>& SurfaceIDs,
bool& bOutMarkRenderStateDirty)
{
MUTABLE_CPUPROFILER_SCOPE(CopyMutableIndexBuffers);
const int32 IndexCount = InMutableMesh->GetIndexBuffers().GetElementCount();
if (IndexCount == 0)
{
// Copy indices from an empty buffer
UE_LOG(LogMutable, Error, TEXT("UCustomizableInstancePrivateData::BuildSkeletalMeshRenderData is converting an empty mesh."));
return false;
}
if (InMutableMesh->GetIndexBuffers().IsDescriptor())
{
UE_LOG(LogMutable, Error, TEXT("UCustomizableInstancePrivateData::BuildSkeletalMeshRenderData is converting a mesh descriptor."));
return false;
}
const uint8* DataPtr = InMutableMesh->GetIndexBuffers().GetBufferData(0);
const int32 ElementSize = InMutableMesh->GetIndexBuffers().GetElementSize(0);
if (!LODResource.MultiSizeIndexContainer.IsIndexBufferValid())
{
LODResource.MultiSizeIndexContainer.CreateIndexBuffer(ElementSize);
}
check(LODResource.MultiSizeIndexContainer.GetDataTypeSize() == ElementSize);
// Don't assume the buffer is empty, otherwise we may add extra elements using Insert().
LODResource.MultiSizeIndexContainer.GetIndexBuffer()->Empty(IndexCount);
LODResource.MultiSizeIndexContainer.GetIndexBuffer()->Insert(0, IndexCount);
FMemory::Memcpy(LODResource.MultiSizeIndexContainer.GetIndexBuffer()->GetPointerTo(0), DataPtr, IndexCount * ElementSize);
for (int32 SectionIndex = 0; SectionIndex < LODResource.RenderSections.Num(); ++SectionIndex)
{
FSkelMeshRenderSection& Section = LODResource.RenderSections[SectionIndex];
const int32 SurfaceID = SurfaceIDs[SectionIndex];
const mu::FMeshSurface* Surface = InMutableMesh->Surfaces.FindByPredicate([SurfaceID](const mu::FMeshSurface& Surface)
{
return Surface.Id == SurfaceID;
});
if (Surface)
{
const int32 NumVertices = Surface->SubMeshes.Last().VertexEnd - Surface->SubMeshes[0].VertexBegin;
bOutMarkRenderStateDirty |= Section.NumVertices != NumVertices;
Section.NumVertices = NumVertices;
const int32 IndexBegin = Surface->SubMeshes[0].IndexBegin;
const int32 IndexEnd = Surface->SubMeshes.Last().IndexEnd;
const int32 NumTriangles = (IndexEnd - IndexBegin) / 3;
bOutMarkRenderStateDirty |= Section.NumTriangles != NumTriangles;
Section.NumTriangles = NumTriangles;
bOutMarkRenderStateDirty |= Section.BaseIndex != IndexBegin;
Section.BaseIndex = IndexBegin;
const int32 VertexBegin = Surface->SubMeshes[0].VertexBegin;
bOutMarkRenderStateDirty |= Section.BaseVertexIndex != VertexBegin;
Section.BaseVertexIndex = VertexBegin;
}
else
{
Section.bDisabled = true;
Section.NumTriangles = 0;
Section.NumVertices = 0;
Section.BaseIndex = 0;
Section.BaseVertexIndex = 0;
bOutMarkRenderStateDirty = true;
}
}
return true;
}
struct FAuxMutableSkinWeightVertexKey
{
FAuxMutableSkinWeightVertexKey(const uint8* InKey, uint8 InKeySize, uint32 InHash)
: Key(InKey), KeySize(InKeySize), Hash(InHash)
{
};
const uint8* Key;
uint8 KeySize;
uint32 Hash;
friend uint32 GetTypeHash(const FAuxMutableSkinWeightVertexKey& InKey)
{
return InKey.Hash;
}
bool operator==(const FAuxMutableSkinWeightVertexKey& o) const
{
return FMemory::Memcmp(o.Key, Key, KeySize) == 0;
}
};
void CopyMutableSkinWeightProfilesBuffers(
FSkeletalMeshLODRenderData& LODResource,
USkeletalMesh& SkeletalMesh,
int32 LODIndex,
const mu::FMesh* InMutableMesh,
const TArray<TPair<uint32, FName>>& ActiveSkinWeightProfiles)
{
MUTABLE_CPUPROFILER_SCOPE(CopyMutableSkinWeightProfilesBuffers);
const uint8 NumInfluences = LODResource.GetVertexBufferMaxBoneInfluences();
const bool b16BitBoneIndices = LODResource.DoesVertexBufferUse16BitBoneIndex();
const int32 BoneIndexTypeByteSize = LODResource.SkinWeightVertexBuffer.GetBoneIndexByteSize();
const uint8 BoneIndicesDataSize = NumInfluences * BoneIndexTypeByteSize;
const int32 BoneWeightTypeByteSize = LODResource.SkinWeightVertexBuffer.GetBoneWeightByteSize();
const uint8 BoneWeightsDataSize = NumInfluences * BoneWeightTypeByteSize;
TMap<FAuxMutableSkinWeightVertexKey, int32> HashToUniqueWeightIndexMap;
const mu::FMeshBufferSet& MutableMeshVertexBuffers = InMutableMesh->GetVertexBuffers();
for (const TPair<uint32, FName>& Profile : ActiveSkinWeightProfiles)
{
int32 BufferIndex, ChannelIndex;
mu::EMeshBufferFormat Format;
int32 MutNumInfluences;
MutableMeshVertexBuffers.FindChannel(mu::EMeshBufferSemantic::AltSkinWeight, Profile.Key, &BufferIndex, &ChannelIndex);
if (BufferIndex == INDEX_NONE)
{
continue;
}
// Basic Buffer override settings
FRuntimeSkinWeightProfileData& Override = LODResource.SkinWeightProfilesData.AddOverrideData(Profile.Value);
Override.NumWeightsPerVertex = NumInfluences;
Override.b16BitBoneIndices = b16BitBoneIndices;
// BoneIndices channel info
MutableMeshVertexBuffers.GetChannel(BufferIndex, 1, nullptr, nullptr, &Format, &MutNumInfluences, nullptr);
const uint8 MutBoneIndexTypeByteSize = Format == mu::EMeshBufferFormat::UInt16 ? 2 : 1;
const uint8 MutBoneIndicesDataSize = MutBoneIndexTypeByteSize * MutNumInfluences;
check(BoneIndexTypeByteSize == MutBoneIndexTypeByteSize);
// BoneWeights channel info
MutableMeshVertexBuffers.GetChannel(BufferIndex, 2, nullptr, nullptr, &Format, &MutNumInfluences, nullptr);
const uint8 MutBoneWeightTypeByteSize = Format == mu::EMeshBufferFormat::NUInt16 ? 2 : 1;
const uint8 MutBoneWeightsDataSize = MutBoneWeightTypeByteSize * MutNumInfluences;
check(BoneWeightTypeByteSize == MutBoneWeightTypeByteSize);
const uint8 MutSkinWeightVertexSize = MutBoneIndicesDataSize + MutBoneWeightsDataSize;
const int32 ElementCount = MutableMeshVertexBuffers.GetElementCount();
Override.BoneIDs.Reserve(ElementCount * BoneIndicesDataSize);
Override.BoneWeights.Reserve(ElementCount * BoneWeightsDataSize);
Override.VertexIndexToInfluenceOffset.Reserve(ElementCount);
HashToUniqueWeightIndexMap.Empty(ElementCount);
int32 UniqueWeightsCount = 0;
const uint8* SkinWeightsBuffer = reinterpret_cast<const uint8*>(MutableMeshVertexBuffers.GetBufferData(BufferIndex));
for (int32 ElementIndex = 0; ElementIndex < ElementCount; ++ElementIndex)
{
uint32 ElementHash;
FMemory::Memcpy(&ElementHash, SkinWeightsBuffer, sizeof(int32));
SkinWeightsBuffer += sizeof(int32);
if (ElementHash > 0)
{
if (int32* OverrideIndex = HashToUniqueWeightIndexMap.Find({ SkinWeightsBuffer, MutSkinWeightVertexSize, ElementHash }))
{
Override.VertexIndexToInfluenceOffset.Add(ElementIndex, *OverrideIndex);
SkinWeightsBuffer += MutSkinWeightVertexSize;
}
else
{
Override.VertexIndexToInfluenceOffset.Add(ElementIndex, UniqueWeightsCount);
HashToUniqueWeightIndexMap.Add({ SkinWeightsBuffer, MutSkinWeightVertexSize, ElementHash }, UniqueWeightsCount);
Override.BoneIDs.SetNumUninitialized((UniqueWeightsCount + 1) * BoneIndicesDataSize, EAllowShrinking::No);
FMemory::Memcpy(&Override.BoneIDs[UniqueWeightsCount * BoneIndicesDataSize], SkinWeightsBuffer, BoneIndicesDataSize);
SkinWeightsBuffer += MutBoneIndicesDataSize;
Override.BoneWeights.SetNumUninitialized((UniqueWeightsCount + 1) * BoneWeightsDataSize, EAllowShrinking::No);
FMemory::Memcpy(&Override.BoneWeights[UniqueWeightsCount * BoneWeightsDataSize], SkinWeightsBuffer, BoneWeightsDataSize);
SkinWeightsBuffer += MutBoneWeightsDataSize;
++UniqueWeightsCount;
}
}
else
{
SkinWeightsBuffer += MutSkinWeightVertexSize;
}
}
Override.BoneIDs.Shrink();
Override.BoneWeights.Shrink();
Override.VertexIndexToInfluenceOffset.Shrink();
}
LODResource.SkinWeightProfilesData.Init(&LODResource.SkinWeightVertexBuffer);
SkeletalMesh.SetSkinWeightProfilesData(LODIndex, LODResource.SkinWeightProfilesData);
}
void CopySkeletalMeshLODRenderData(
FSkeletalMeshLODRenderData& LODResource,
FSkeletalMeshLODRenderData& SourceLODResource,
USkeletalMesh& SkeletalMesh,
int32 LODIndex,
const bool bAllowCPUAccess
)
{
MUTABLE_CPUPROFILER_SCOPE(CopySkeletalMeshLODRenderData);
// Copying render sections
{
const int32 SurfaceCount = SourceLODResource.RenderSections.Num();
for (int32 SurfaceIndex = 0; SurfaceIndex < SurfaceCount; ++SurfaceIndex)
{
const FSkelMeshRenderSection& SrcSection = SourceLODResource.RenderSections[SurfaceIndex];
FSkelMeshRenderSection* DestSection = new(LODResource.RenderSections) FSkelMeshRenderSection();
DestSection->DuplicatedVerticesBuffer.Init(1, TMap<int, TArray<int32>>());
DestSection->bDisabled = SrcSection.bDisabled;
if (!DestSection->bDisabled)
{
DestSection->BaseIndex = SrcSection.BaseIndex;
DestSection->NumTriangles = SrcSection.NumTriangles;
DestSection->BaseVertexIndex = SrcSection.BaseVertexIndex;
DestSection->MaxBoneInfluences = SrcSection.MaxBoneInfluences;
DestSection->NumVertices = SrcSection.NumVertices;
DestSection->BoneMap = SrcSection.BoneMap;
DestSection->bCastShadow = SrcSection.bCastShadow;
}
}
}
const FStaticMeshVertexBuffers& SrcStaticVertexBuffer = SourceLODResource.StaticVertexBuffers;
FStaticMeshVertexBuffers& DestStaticVertexBuffer = LODResource.StaticVertexBuffers;
const int32 NumVertices = SrcStaticVertexBuffer.PositionVertexBuffer.GetNumVertices();
const int32 NumTexCoords = SrcStaticVertexBuffer.StaticMeshVertexBuffer.GetNumTexCoords();
// Copying Static Vertex Buffers
{
// Position buffer
DestStaticVertexBuffer.PositionVertexBuffer.Init(NumVertices, bAllowCPUAccess);
FMemory::Memcpy(DestStaticVertexBuffer.PositionVertexBuffer.GetVertexData(), SrcStaticVertexBuffer.PositionVertexBuffer.GetVertexData(), NumVertices * DestStaticVertexBuffer.PositionVertexBuffer.GetStride());
// Tangent and Texture coords buffers
DestStaticVertexBuffer.StaticMeshVertexBuffer.SetUseFullPrecisionUVs(true);
DestStaticVertexBuffer.StaticMeshVertexBuffer.SetUseHighPrecisionTangentBasis(false);
DestStaticVertexBuffer.StaticMeshVertexBuffer.Init(NumVertices, NumTexCoords, bAllowCPUAccess);
FMemory::Memcpy(DestStaticVertexBuffer.StaticMeshVertexBuffer.GetTangentData(), SrcStaticVertexBuffer.StaticMeshVertexBuffer.GetTangentData(), DestStaticVertexBuffer.StaticMeshVertexBuffer.GetTangentSize());
FMemory::Memcpy(DestStaticVertexBuffer.StaticMeshVertexBuffer.GetTexCoordData(), SrcStaticVertexBuffer.StaticMeshVertexBuffer.GetTexCoordData(), DestStaticVertexBuffer.StaticMeshVertexBuffer.GetTexCoordSize());
// Color buffer
if (LODResource.StaticVertexBuffers.ColorVertexBuffer.GetNumVertices() > 0)
{
DestStaticVertexBuffer.ColorVertexBuffer.Init(NumVertices);
FMemory::Memcpy(DestStaticVertexBuffer.ColorVertexBuffer.GetVertexData(), SrcStaticVertexBuffer.ColorVertexBuffer.GetVertexData(), NumVertices * DestStaticVertexBuffer.ColorVertexBuffer.GetStride());
}
}
// Copying Skin Buffers
{
const FSkinWeightVertexBuffer& SrcSkinWeightBuffer = SourceLODResource.SkinWeightVertexBuffer;
FSkinWeightVertexBuffer& DestSkinWeightBuffer = LODResource.SkinWeightVertexBuffer;
int32 NumBoneInfluences = SrcSkinWeightBuffer.GetDataVertexBuffer()->GetMaxBoneInfluences();
int32 NumBones = SrcSkinWeightBuffer.GetDataVertexBuffer()->GetNumBoneWeights();
DestSkinWeightBuffer.SetUse16BitBoneIndex(SrcSkinWeightBuffer.Use16BitBoneIndex());
FSkinWeightDataVertexBuffer* SkinWeightDataVertexBuffer = DestSkinWeightBuffer.GetDataVertexBuffer();
SkinWeightDataVertexBuffer->SetMaxBoneInfluences(NumBoneInfluences);
SkinWeightDataVertexBuffer->Init(NumBones, NumVertices);
if (NumVertices)
{
DestSkinWeightBuffer.SetNeedsCPUAccess(bAllowCPUAccess);
const void* SrcData = SrcSkinWeightBuffer.GetDataVertexBuffer()->GetWeightData();
void* Data = SkinWeightDataVertexBuffer->GetWeightData();
check(SrcData);
check(Data);
FMemory::Memcpy(Data, SrcData, DestSkinWeightBuffer.GetVertexDataSize());
}
}
// Copying Skin Weight Profiles Buffers
{
const int32 NumSkinWeightProfiles = SkeletalMesh.GetSkinWeightProfiles().Num();
for (int32 ProfileIndex = 0; ProfileIndex < NumSkinWeightProfiles; ++ProfileIndex)
{
const FName& ProfileName = SkeletalMesh.GetSkinWeightProfiles()[ProfileIndex].Name;
const FRuntimeSkinWeightProfileData* SourceProfile = SourceLODResource.SkinWeightProfilesData.GetOverrideData(ProfileName);
FRuntimeSkinWeightProfileData& DestProfile = LODResource.SkinWeightProfilesData.AddOverrideData(ProfileName);
DestProfile = *SourceProfile;
}
LODResource.SkinWeightProfilesData.Init(&LODResource.SkinWeightVertexBuffer);
SkeletalMesh.SetSkinWeightProfilesData(LODIndex, LODResource.SkinWeightProfilesData);
}
// Copying Indices
{
if (SourceLODResource.MultiSizeIndexContainer.IsIndexBufferValid())
{
int32 IndexCount = SourceLODResource.MultiSizeIndexContainer.GetIndexBuffer()->Num();
int32 ElementSize = SourceLODResource.MultiSizeIndexContainer.GetDataTypeSize();
const void* Data = SourceLODResource.MultiSizeIndexContainer.GetIndexBuffer()->GetPointerTo(0);
LODResource.MultiSizeIndexContainer.CreateIndexBuffer(ElementSize);
LODResource.MultiSizeIndexContainer.GetIndexBuffer()->Insert(0, IndexCount);
FMemory::Memcpy(LODResource.MultiSizeIndexContainer.GetIndexBuffer()->GetPointerTo(0), Data, IndexCount * ElementSize);
}
}
LODResource.ActiveBoneIndices.Append(SourceLODResource.ActiveBoneIndices);
LODResource.RequiredBones.Append(SourceLODResource.RequiredBones);
LODResource.bIsLODOptional = SourceLODResource.bIsLODOptional;
LODResource.bStreamedDataInlined = SourceLODResource.bStreamedDataInlined;
LODResource.BuffersSize = SourceLODResource.BuffersSize;
}
void UpdateSkeletalMeshLODRenderDataBuffersSize(FSkeletalMeshLODRenderData& LODResource)
{
LODResource.BuffersSize = 0;
// Add VertexBuffers' size
LODResource.BuffersSize += LODResource.StaticVertexBuffers.PositionVertexBuffer.GetAllocatedSize();
LODResource.BuffersSize += LODResource.StaticVertexBuffers.StaticMeshVertexBuffer.GetResourceSize();
LODResource.BuffersSize += LODResource.StaticVertexBuffers.ColorVertexBuffer.GetAllocatedSize();
LODResource.BuffersSize += LODResource.SkinWeightVertexBuffer.GetVertexDataSize();
// Add Optional VertexBuffers' size
LODResource.BuffersSize += LODResource.ClothVertexBuffer.GetVertexDataSize();
LODResource.BuffersSize += LODResource.SkinWeightProfilesData.GetResourcesSize();
LODResource.BuffersSize += LODResource.MorphTargetVertexInfoBuffers.GetMorphDataSizeInBytes();
// Add IndexBuffer's size
if (LODResource.MultiSizeIndexContainer.IsIndexBufferValid())
{
LODResource.BuffersSize += LODResource.MultiSizeIndexContainer.GetIndexBuffer()->GetResourceDataSize();
}
}
void MorphTargetVertexInfoBuffers(FSkeletalMeshLODRenderData& LODResource, const USkeletalMesh& Owner, const mu::FMesh& MutableMesh, const TMap<uint32, FMorphTargetMeshData>& MorphTargetMeshData, int32 LODIndex)
{
if (Owner.GetMorphTargets().IsEmpty())
{
return;
}
// Get the global names.
const TMap<FName, int32>& IndexMap = Owner.GetMorphTargetIndexMap();
int32 MaxIndex = 0;
for (const TPair<FName, int32>& Pair : IndexMap)
{
MaxIndex = FMath::Max(MaxIndex, Pair.Value);
}
TArray<FName> GlobalNames;
GlobalNames.SetNum(MaxIndex + 1);
for (const TPair<FName, int32>& Pair : IndexMap)
{
GlobalNames[Pair.Value] = Pair.Key;
}
// Map the local names to the global names.
TMap<uint32, FMappedMorphTargetMeshData> GlobalMorphTargets; // Key is the block id. Value data needed to reconstruct the Morp Targets.
for (const TPair<uint32, FMorphTargetMeshData>& Pair : MorphTargetMeshData)
{
FMappedMorphTargetMeshData& MappedMorphsData = GlobalMorphTargets.FindOrAdd(Pair.Key);
// Remapping to global names.
MappedMorphsData.NameResolutionMap.SetNum(Pair.Value.NameResolutionMap.Num());
for (int32 NameIndex = 0; NameIndex < Pair.Value.NameResolutionMap.Num(); ++NameIndex)
{
MappedMorphsData.NameResolutionMap[NameIndex] = GlobalNames.Find(Pair.Value.NameResolutionMap[NameIndex]);
}
// Pointer to the serialized data
MappedMorphsData.DataView = &Pair.Value.Data;
}
// Reconstruct the final Morph Targets using the global names.
TArray<FMorphTargetLODModel> MorphTargetLODs;
ReconstructMorphTargets(MutableMesh, GlobalNames, GlobalMorphTargets, MorphTargetLODs); // FMorphTargetLODModel::SectionIndices not initialized here
TArray<const FMorphTargetLODModel*> MorphsToInit;
MorphsToInit.Reserve(MorphTargetLODs.Num());
for (FMorphTargetLODModel& MorphTargetLOD : MorphTargetLODs)
{
MorphsToInit.Add(&MorphTargetLOD);
}
const TBitArray UsesBuiltinMorphTargetCompression(true, MorphTargetLODs.Num());
const float ErrorTolerance = Owner.GetLODInfo(LODIndex)->MorphTargetPositionErrorTolerance;
LODResource.MorphTargetVertexInfoBuffers.InitMorphResourcesStreaming(LODResource.RenderSections, MorphsToInit, LODResource.StaticVertexBuffers.StaticMeshVertexBuffer.GetNumVertices(), ErrorTolerance);
}
UE::Tasks::FTask ConvertSkeletalMeshFromRuntimeData(USkeletalMesh* SkeletalMesh, int32 LODIndex, int32 SectionIndex, UModelResources* ModelResources, mu::FMesh* Result)
{
MUTABLE_CPUPROFILER_SCOPE(ConvertSkeletalMeshFromRuntimeData);
if (!SkeletalMesh || !Result)
{
ensure(false);
return UE::Tasks::MakeCompletedTask<void>();
}
FSkeletalMeshRenderData* Data = SkeletalMesh->GetResourceForRendering();
if (!Data->LODRenderData.IsValidIndex(LODIndex))
{
// This may happen if we are requesting a LOD that is not present in the provided mesh.
// TODO: an empty mesh is returned, but should we use the last LOD instead?
UE_LOG(LogMutable, Warning, TEXT("Trying to convert mesh [%s] LOD %d but it only has %d LODs"),
*SkeletalMesh->GetName(), LODIndex, Data->LODRenderData.Num() );
return UE::Tasks::MakeCompletedTask<void>();
}
FSkeletalMeshLODRenderData* LOD = &Data->LODRenderData[LODIndex];
if (!LOD->RenderSections.IsValidIndex(SectionIndex))
{
// This may happen in the provided mesh in the parameter value doesn't have the expected number of sections
UE_LOG(LogMutable, Warning, TEXT("Trying to convert mesh [%s] section %d in LOD %d but it only has %d sections"),
*SkeletalMesh->GetName(), SectionIndex, LODIndex, LOD->RenderSections.Num());
return UE::Tasks::MakeCompletedTask<void>();
}
const FSkelMeshRenderSection& Section = LOD->RenderSections[SectionIndex];
// Skeleton data
bool bBoneMapModified = false;
TArray<FBoneIndexType> BoneMap;
TArray<FBoneIndexType> RemappedBoneMapIndices;
bool bIgnoreSkeleton = false;
if (!bIgnoreSkeleton)
{
USkeleton* Skeleton = SkeletalMesh->GetSkeleton();
if (!Skeleton)
{
ensure(false);
return UE::Tasks::MakeCompletedTask<void>();
}
// Add the skeleton to the list of referenced skeletons and add its index to the mesh
if (ModelResources)
{
const int32 SkeletonID = ModelResources->Skeletons.AddUnique(Skeleton);
Result->AddSkeletonID(SkeletonID);
}
const TArray<uint16>& SourceRequiredBones = LOD->RequiredBones;
// Build RequiredBones array
TArray<FBoneIndexType> RequiredBones;
RequiredBones.Reserve(SourceRequiredBones.Num());
for (const FBoneIndexType& RequiredBoneIndex : SourceRequiredBones)
{
RequiredBones.AddUnique(RequiredBoneIndex);
}
// Rebuild BoneMap
const TArray<uint16>& SourceBoneMap = Section.BoneMap;
const int32 NumBonesInBoneMap = SourceBoneMap.Num();
for (int32 BoneIndex = 0; BoneIndex < NumBonesInBoneMap; ++BoneIndex)
{
const FBoneIndexType FinalBoneIndex = SourceBoneMap[BoneIndex];
const int32 BoneMapIndex = BoneMap.AddUnique(FinalBoneIndex);
RemappedBoneMapIndices.Add(BoneMapIndex);
bBoneMapModified = bBoneMapModified || SourceBoneMap[BoneIndex] != FinalBoneIndex;
}
// Create the skeleton, poses, and BoneMap for this mesh
TSharedPtr<mu::FSkeleton> MutableSkeleton = MakeShared<mu::FSkeleton>();
Result->SetSkeleton(MutableSkeleton);
const int32 NumRequiredBones = RequiredBones.Num();
Result->SetBonePoseCount(NumRequiredBones);
MutableSkeleton->SetBoneCount(NumRequiredBones);
// MutableBoneMap will not keep an index to the Skeleton, but to the BoneName
TArray<mu::FBoneName> MutableBoneMap;
MutableBoneMap.SetNum(BoneMap.Num());
TArray<FMatrix> ComposedRefPoseMatrices;
ComposedRefPoseMatrices.SetNum(NumRequiredBones);
const TArray<FMeshBoneInfo>& RefBoneInfo = SkeletalMesh->GetRefSkeleton().GetRefBoneInfo();
for (int32 BoneIndex = 0; BoneIndex < NumRequiredBones; ++BoneIndex)
{
const int32 RefSkeletonBoneIndex = RequiredBones[BoneIndex];
const FMeshBoneInfo& BoneInfo = RefBoneInfo[RefSkeletonBoneIndex];
const int32 ParentBoneIndex = RequiredBones.Find(BoneInfo.ParentIndex);
// Set bone hierarchy
// Get the bone id
mu::FBoneName BoneName;
if (ModelResources)
{
// See if the bone exists in the compiled CO data
const FString BoneNameString = BoneInfo.Name.ToString().ToLower();
uint32* BoneId = ModelResources->BoneNamesMap.Find(BoneNameString);
if (BoneId)
{
BoneName = *BoneId;
}
else
{
// Go the slow way and add it to get a unique ID that the core can work with.
uint32 NewBoneId = CityHash32(reinterpret_cast<const char*>(*BoneNameString), BoneNameString.Len() * sizeof(FString::ElementType));
// See if the hash collides with an existing bone
bool bUnique = false;
while (!bUnique)
{
bUnique = true;
TMap<FString, uint32>::TConstIterator MapIterator = ModelResources->BoneNamesMap.CreateConstIterator();
while (MapIterator)
{
if (MapIterator.Value() == NewBoneId)
{
bUnique = false;
break;
}
++MapIterator;
}
if (!bUnique)
{
NewBoneId++;
}
}
ModelResources->BoneNamesMap.Add(BoneNameString, NewBoneId);
BoneName = NewBoneId;
}
}
MutableSkeleton->SetBoneName(BoneIndex, BoneName);
MutableSkeleton->SetBoneParent(BoneIndex, ParentBoneIndex);
// Debug. Will not be serialized
MutableSkeleton->SetDebugName(BoneIndex, BoneInfo.Name);
// BoneMap: Convert RefSkeletonBoneIndex to BoneId
const int32 BoneMapIndex = BoneMap.Find(RefSkeletonBoneIndex);
if (BoneMapIndex != INDEX_NONE)
{
MutableBoneMap[BoneMapIndex] = BoneName;
}
if (ParentBoneIndex >= 0)
{
ComposedRefPoseMatrices[BoneIndex] = SkeletalMesh->GetRefPoseMatrix(RefSkeletonBoneIndex) * ComposedRefPoseMatrices[ParentBoneIndex];
}
else
{
ComposedRefPoseMatrices[BoneIndex] = SkeletalMesh->GetRefPoseMatrix(RefSkeletonBoneIndex);
}
// Set bone pose
FTransform3f BoneTransform;
BoneTransform.SetFromMatrix(FMatrix44f(ComposedRefPoseMatrices[BoneIndex]));
mu::EBoneUsageFlags BoneUsageFlags = mu::EBoneUsageFlags::None;
EnumAddFlags(BoneUsageFlags, BoneMapIndex != INDEX_NONE ? mu::EBoneUsageFlags::Skinning : mu::EBoneUsageFlags::None);
EnumAddFlags(BoneUsageFlags, ParentBoneIndex == INDEX_NONE ? mu::EBoneUsageFlags::Root : mu::EBoneUsageFlags::None);
Result->SetBonePose(BoneIndex, BoneName, BoneTransform, BoneUsageFlags);
}
Result->SetBoneMap(MutableBoneMap);
}
const bool bUseStreaming = !LOD->bStreamedDataInlined;
UE::Tasks::FTaskEvent LoadedEvent(TEXT("MutableMeshParamLoadCompleted"));
// This context stores the information that travels between tasks.
struct FMeshConversionContext
{
FSkeletalMeshLODRenderData StreamedLOD;
FSkeletalMeshLODRenderData* OriginalLOD = nullptr;
FSkeletalMeshLODRenderData* DataLOD = nullptr;
TUniquePtr<IBulkDataIORequest> Request;
FMeshConversionContext()
: StreamedLOD(false)
{
}
};
TSharedPtr<FMeshConversionContext> Context = MakeShared<FMeshConversionContext>();
Context->OriginalLOD = LOD;
Context->DataLOD = LOD;
if (!bUseStreaming)
{
LoadedEvent.Trigger();
}
else
{
MUTABLE_CPUPROFILER_SCOPE(MeshStreaming);
FBulkDataIORequestCallBack RequestCallback = [LoadedEvent, SkeletalMesh, Context, LODIndex](bool bWasCancelled, IBulkDataIORequest* IORequest) mutable
{
if (!bWasCancelled)
{
uint8* BulkData = IORequest->GetReadResults();
check(BulkData != nullptr);
{
MUTABLE_CPUPROFILER_SCOPE(MeshStreamingSerialization);
FMemoryReaderView Ar(TArrayView<uint8>(BulkData, IORequest->GetSize()), true);
constexpr uint8 DummyStripFlags = 0;
const bool bForceKeepCPUResources = true;
const bool bNeedsCPUAccess = true;
Context->StreamedLOD.SerializeStreamedData(Ar, const_cast<USkeletalMesh*>(SkeletalMesh), LODIndex, DummyStripFlags, bNeedsCPUAccess, bForceKeepCPUResources);
Context->DataLOD = &Context->StreamedLOD;
}
}
else
{
// Can this happen even if we don't cancel ourselves?
check(false);
}
LoadedEvent.Trigger();
};
Context->Request.Reset( LOD->StreamingBulkData.CreateStreamingRequest( EAsyncIOPriorityAndFlags::AIOP_High, &RequestCallback, nullptr) );
}
UE::Tasks::FTask FinalConversionTask = UE::Tasks::Launch( TEXT("MutableRuntimeMeshConversionFinal"),
[Context, SectionIndex, Result]()
{
MUTABLE_CPUPROFILER_SCOPE(MeshConversion);
const FSkelMeshRenderSection& Section = Context->OriginalLOD->RenderSections[SectionIndex];
// Mesh data
int32 FirstVertexIndex = Context->OriginalLOD->RenderSections[SectionIndex].BaseVertexIndex;
int32 VertexCount = Section.GetNumVertices();
mu::FMeshBufferSet& Vertices = Result->GetVertexBuffers();
Vertices.SetElementCount(VertexCount, mu::EMemoryInitPolicy::Zeroed);
Vertices.SetBufferCount(5);
// Position buffer
int32 CurrentVertexBuffer = 0;
{
MutableMeshBufferUtils::SetupVertexPositionsBuffer(CurrentVertexBuffer, Vertices);
int32 ElementSize = Vertices.Buffers[CurrentVertexBuffer].ElementSize;
const uint8* SourceVertexData = ((const uint8*)Context->DataLOD->StaticVertexBuffers.PositionVertexBuffer.GetVertexData()) + FirstVertexIndex * ElementSize;
check(ElementSize * (FirstVertexIndex + VertexCount) <= Context->DataLOD->StaticVertexBuffers.PositionVertexBuffer.GetAllocatedSize());
FMemory::Memcpy(Vertices.GetBufferData(CurrentVertexBuffer), SourceVertexData, ElementSize * VertexCount);
++CurrentVertexBuffer;
}
// Tangent buffer
{
bool bIsHighPrecision = Context->DataLOD->StaticVertexBuffers.StaticMeshVertexBuffer.GetUseHighPrecisionTangentBasis();
const mu::EMeshBufferSemantic Semantics[2] = { mu::EMeshBufferSemantic::Tangent, mu::EMeshBufferSemantic::Normal };
const int32 SemanticIndices[2] = { 0, 0 };
const int32 Components[2] = { 4, 4 };
mu::EMeshBufferFormat Formats[2] = { mu::EMeshBufferFormat::PackedDirS8, mu::EMeshBufferFormat::PackedDirS8_W_TangentSign };
int32 Offsets[2] = { 0, 4 };
int32 ElementSize = 8;
if (bIsHighPrecision)
{
// Not really supported
ensure(false);
Formats[0] = mu::EMeshBufferFormat::Int16;
Formats[1] = mu::EMeshBufferFormat::Int16;
Offsets[1] = 8;
ElementSize = 16;
}
Vertices.SetBuffer(CurrentVertexBuffer, ElementSize, 2, Semantics, SemanticIndices, Formats, Components, Offsets);
const uint8* SourceVertexData = ((const uint8*)Context->DataLOD->StaticVertexBuffers.StaticMeshVertexBuffer.GetTangentData()) + FirstVertexIndex * ElementSize;
check((int32)ElementSize * (FirstVertexIndex + VertexCount) <= Context->DataLOD->StaticVertexBuffers.StaticMeshVertexBuffer.GetTangentSize());
FMemory::Memcpy(Vertices.GetBufferData(CurrentVertexBuffer), SourceVertexData, ElementSize * VertexCount);
++CurrentVertexBuffer;
}
// Texture coords buffer
{
int32 NumTexCoords = Context->OriginalLOD->GetNumTexCoords();
constexpr int32 MaxChannelCount = 4;
mu::EMeshBufferSemantic Semantics[MaxChannelCount];
int32 SemanticIndices[MaxChannelCount];
mu::EMeshBufferFormat Formats[MaxChannelCount];
int32 Components[MaxChannelCount];
int32 Offsets[MaxChannelCount];
int32 UVSize = Context->DataLOD->StaticVertexBuffers.StaticMeshVertexBuffer.GetUseFullPrecisionUVs() ? 8 : 4;
const int32 ElementSize = UVSize * NumTexCoords;
for (int32 UV = 0; UV < NumTexCoords; ++UV)
{
Semantics[UV] = mu::EMeshBufferSemantic::TexCoords;
SemanticIndices[UV] = UV;
Formats[UV] = (UVSize == 8) ? mu::EMeshBufferFormat::Float32 : mu::EMeshBufferFormat::Float16;
Components[UV] = 2;
Offsets[UV] = UVSize * UV;
}
Vertices.SetBuffer(CurrentVertexBuffer, ElementSize, NumTexCoords, Semantics, SemanticIndices, Formats, Components, Offsets);
const uint8* SourceVertexData = ((const uint8*)Context->DataLOD->StaticVertexBuffers.StaticMeshVertexBuffer.GetTexCoordData()) + FirstVertexIndex * UVSize * NumTexCoords;
check(UVSize * NumTexCoords * (FirstVertexIndex + VertexCount) <= Context->DataLOD->StaticVertexBuffers.StaticMeshVertexBuffer.GetTexCoordSize());
FMemory::Memcpy(Vertices.GetBufferData(CurrentVertexBuffer), SourceVertexData, Vertices.Buffers[CurrentVertexBuffer].ElementSize * VertexCount);
++CurrentVertexBuffer;
}
// Skin buffer
if (Section.MaxBoneInfluences > 0)
{
const FSkinWeightVertexBuffer* SkinBuffer = Context->DataLOD->GetSkinWeightVertexBuffer();
const int32 MaxBoneIndexTypeSizeBytes = SkinBuffer->GetBoneIndexByteSize();
const int32 MaxBoneWeightTypeSizeBytes = SkinBuffer->GetBoneWeightByteSize();
const int32 MaxBonesPerVertex = SkinBuffer->GetMaxBoneInfluences();
MutableMeshBufferUtils::SetupSkinBuffer(CurrentVertexBuffer, MaxBoneIndexTypeSizeBytes, MaxBoneWeightTypeSizeBytes, MaxBonesPerVertex, Vertices);
int32 ElementSize = Vertices.Buffers[CurrentVertexBuffer].ElementSize;
const uint8* SourceVertexData = ((const uint8*)SkinBuffer->GetDataVertexBuffer()->GetWeightData()) + FirstVertexIndex * ElementSize;
check(ElementSize * (FirstVertexIndex + VertexCount) <= (int32)SkinBuffer->GetVertexDataSize());
FMemory::Memcpy(Vertices.GetBufferData(CurrentVertexBuffer), SourceVertexData, ElementSize * VertexCount);
++CurrentVertexBuffer;
}
// Colour buffer
if (Context->DataLOD->StaticVertexBuffers.ColorVertexBuffer.GetNumVertices() > 0)
{
const FColorVertexBuffer& ColorBuffer = Context->DataLOD->StaticVertexBuffers.ColorVertexBuffer;
MutableMeshBufferUtils::SetupVertexColorBuffer(CurrentVertexBuffer, Vertices);
int32 ElementSize = Vertices.Buffers[CurrentVertexBuffer].ElementSize;
const uint8* SourceVertexData = ((const uint8*)ColorBuffer.GetVertexData()) + FirstVertexIndex * ElementSize;
check(ElementSize * (FirstVertexIndex + VertexCount) <= (int32)ColorBuffer.GetAllocatedSize());
check(ElementSize == ColorBuffer.GetStride());
FMemory::Memcpy(Vertices.GetBufferData(CurrentVertexBuffer), SourceVertexData, ElementSize * VertexCount);
++CurrentVertexBuffer;
}
// Indices
{
int32 FirstIndexIndex = Section.BaseIndex;
int32 IndexCount = Section.NumTriangles * 3;
int32 ElementSize = Context->DataLOD->MultiSizeIndexContainer.GetDataTypeSize();
mu::FMeshBufferSet& Indices = Result->GetIndexBuffers();
Indices.SetBufferCount(1);
Indices.SetElementCount(IndexCount);
constexpr int32 ChannelCount = 1;
const mu::EMeshBufferSemantic Semantics[ChannelCount] = { mu::EMeshBufferSemantic::VertexIndex };
const int32 SemanticIndices[ChannelCount] = { 0 };
mu::EMeshBufferFormat Formats[ChannelCount] = { ElementSize == 4 ? mu::EMeshBufferFormat::UInt32 : mu::EMeshBufferFormat::UInt16 };
const int32 Components[ChannelCount] = { 1 };
const int32 Offsets[ChannelCount] = { 0 };
Indices.SetBuffer(0, ElementSize, ChannelCount, Semantics, SemanticIndices, Formats, Components, Offsets);
const uint8* SourceData = (const uint8*)Context->DataLOD->MultiSizeIndexContainer.GetIndexBuffer()->GetPointerTo(FirstIndexIndex);
uint8* TargetData = Indices.GetBufferData(0);
if (FirstVertexIndex == 0)
{
FMemory::Memcpy(TargetData, SourceData, IndexCount * ElementSize);
}
else
{
// Apply vertex offset
switch (ElementSize)
{
case 2:
for (int32 Index = 0; Index < IndexCount; ++Index)
{
const uint16* Source = ((const uint16*)SourceData) + Index;
uint16* Target = ((uint16*)TargetData) + Index;
check(*Source >= FirstVertexIndex);
*Target = *Source - FirstVertexIndex;
}
break;
case 4:
for (int32 Index = 0; Index < IndexCount; ++Index)
{
const uint32* Source = ((const uint32*)SourceData) + Index;
uint32* Target = ((uint32*)TargetData) + Index;
check(*Source >= uint32(FirstVertexIndex));
*Target = *Source - FirstVertexIndex;
}
break;
default:
// Index size not implemented
break;
}
}
}
Result->EnsureSurfaceData();
},
// Dependencies
LoadedEvent
);
return FinalConversionTask;
}
void GetSectionClothData(const mu::FMesh& MutableMesh, int32 LODIndex, const TMap<uint32, FClothingMeshData>& ClothingMeshData, TArray<FSectionClothData>& SectionsClothData, int32& NumClothingDataNotFound)
{
MUTABLE_CPUPROFILER_SCOPE(GetSectionClothData);
const mu::FMeshBufferSet& MeshSet = MutableMesh.GetVertexBuffers();
int32 ClothingIndexBuffer, ClothingIndexChannel;
MeshSet.FindChannel(mu::EMeshBufferSemantic::Other, 2, &ClothingIndexBuffer, &ClothingIndexChannel);
int32 ClothingResourceBuffer, ClothingResourceChannel;
MeshSet.FindChannel(mu::EMeshBufferSemantic::Other, 3, &ClothingResourceBuffer, &ClothingResourceChannel);
if (ClothingIndexBuffer >= 0 && ClothingResourceBuffer >= 0)
{
const int32* const ClothingDataBuffer = reinterpret_cast<const int32*>(MeshSet.GetBufferData(ClothingIndexBuffer));
const uint32* const ClothingDataResource = reinterpret_cast<const uint32*>(MeshSet.GetBufferData(ClothingResourceBuffer));
const int32 SurfaceCount = MutableMesh.GetSurfaceCount();
for (int32 SectionIndex = 0; SectionIndex < SurfaceCount; ++SectionIndex)
{
int32 FirstVertex, VerticesCount, FirstIndex, IndicesCount, UnusedBoneIndex, UnusedBoneCount;
MutableMesh.GetSurface(SectionIndex, FirstVertex, VerticesCount, FirstIndex, IndicesCount, UnusedBoneIndex, UnusedBoneCount);
if (VerticesCount == 0 || IndicesCount == 0)
{
continue;
}
// A Section has cloth data on all its vertices or it does not have it in any.
// It can be determined if this section has clothing data just looking at the
// first vertex of the section.
TArrayView<const int32> ClothingDataView(ClothingDataBuffer + FirstVertex, VerticesCount);
TArrayView<const uint32> ClothingResourceView(ClothingDataResource + FirstVertex, VerticesCount);
const int32 IndexCount = MutableMesh.GetIndexBuffers().GetElementCount();
TArrayView<const uint16> IndicesView16Bits;
TArrayView<const uint32> IndicesView32Bits;
if (IndexCount)
{
if (MutableMesh.GetIndexBuffers().GetElementSize(0) == 2)
{
const uint16* IndexPtr = (const uint16*)MutableMesh.GetIndexBuffers().GetBufferData(0);
IndicesView16Bits = TArrayView<const uint16>(IndexPtr + FirstIndex, IndicesCount);
}
else
{
const uint32* IndexPtr = (const uint32*)MutableMesh.GetIndexBuffers().GetBufferData(0);
IndicesView32Bits = TArrayView<const uint32>(IndexPtr + FirstIndex, IndicesCount);
}
}
if (!ClothingDataView.Num())
{
continue;
}
const uint32 ClothResourceId = ClothingResourceView[0];
if (ClothResourceId == 0)
{
continue;
}
const FClothingMeshData* SectionClothingData = ClothingMeshData.Find(ClothResourceId);
if (!SectionClothingData)
{
++NumClothingDataNotFound;
continue;
}
check(SectionClothingData->Data.Num());
SectionsClothData.Add(FSectionClothData {
SectionIndex,
LODIndex,
FirstVertex,
IndicesView16Bits,
IndicesView32Bits,
ClothingDataView,
MakeArrayView(SectionClothingData->Data.GetData(), SectionClothingData->Data.Num()),
TArray<FMeshToMeshVertData>() });
}
}
}
void CopyMeshToMeshClothData(TArray<FSectionClothData>& SectionsClothData)
{
MUTABLE_CPUPROFILER_SCOPE(ClothCopyMeshToMeshData)
// Copy MeshToMeshData.
for (FSectionClothData& SectionWithCloth : SectionsClothData)
{
const int32 NumVertices = SectionWithCloth.ClothingDataIndicesView.Num();
TArray<FMeshToMeshVertData>& ClothMappingData = SectionWithCloth.MappingData;
ClothMappingData.SetNum(NumVertices);
// Copy mesh to mesh data indexed by the index stored per vertex at compile time.
for (int32 VertexIdx = 0; VertexIdx < NumVertices; ++VertexIdx)
{
// Possible Optimization: Gather consecutive indices in ClothingDataView and Memcpy the whole range.
// FMeshToMeshVertData and FCustomizableObjectMeshToMeshVertData have the same memory footprint and
// bytes in a FCustomizableObjectMeshToMeshVertData form a valid FMeshToMeshVertData (not the other way around).
static_assert(sizeof(FCustomizableObjectMeshToMeshVertData) == sizeof(FMeshToMeshVertData));
static_assert(TIsTrivial<FCustomizableObjectMeshToMeshVertData>::Value);
static_assert(TIsTrivial<FMeshToMeshVertData>::Value);
const int32 VertexDataIndex = SectionWithCloth.ClothingDataIndicesView[VertexIdx];
check(VertexDataIndex >= 0);
const FCustomizableObjectMeshToMeshVertData& SrcData = SectionWithCloth.ClothingDataView[VertexDataIndex];
FMeshToMeshVertData& DstData = ClothMappingData[VertexIdx];
FMemory::Memcpy(&DstData, &SrcData, sizeof(FMeshToMeshVertData));
}
}
}
void CreateClothMapping(const FSectionClothData& SectionClothData, TArray<FMeshToMeshVertData>& MappingData, TArray<FClothBufferIndexMapping>& ClothIndexMapping)
{
ClothIndexMapping[SectionClothData.SectionIndex] = FClothBufferIndexMapping {
static_cast<uint32>(SectionClothData.BaseVertex),
static_cast<uint32>(MappingData.Num()),
static_cast<uint32>(SectionClothData.MappingData.Num()) };
MappingData.Append(SectionClothData.MappingData);
}
void ClothVertexBuffers(FSkeletalMeshLODRenderData& LODResource, const mu::FMesh& MutableMesh, const TMap<uint32, FClothingMeshData>& ClothingMeshData, int32 LODIndex)
{
TArray<FSectionClothData> SectionsClothData;
SectionsClothData.Reserve(32);
int32 NumClothingDataNotFound = 0;
GetSectionClothData(MutableMesh, LODIndex, ClothingMeshData, SectionsClothData, NumClothingDataNotFound);
if (NumClothingDataNotFound > 0)
{
UE_LOG(LogMutable, Error, TEXT("Some clothing data could not be loaded properly, clothing assets may not behave as expected."));
}
if (!SectionsClothData.Num())
{
return; // Nothing to do.
}
CopyMeshToMeshClothData(SectionsClothData);
TArray<FMeshToMeshVertData> MappingData;
MappingData.Reserve(Algo::Accumulate(SectionsClothData, 0, [](int32 Sum, const FSectionClothData& Element){ return Sum + Element.MappingData.Num(); })); // Optimization.
TArray<FClothBufferIndexMapping> ClothIndexMapping;
ClothIndexMapping.SetNumZeroed(LODResource.RenderSections.Num());
for (const FSectionClothData& Data : SectionsClothData)
{
CreateClothMapping(Data, MappingData, ClothIndexMapping);
}
LODResource.ClothVertexBuffer.Init(MappingData, ClothIndexMapping);
}
}