// Copyright Epic Games, Inc. All Rights Reserved. #include "MuR/OpMeshFormat.h" #include "Containers/Array.h" #include "Containers/Map.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "HAL/UnrealMemory.h" #include "Misc/AssertionMacros.h" #include "MuR/ConvertData.h" #include "MuR/Layout.h" #include "MuR/MeshBufferSet.h" #include "MuR/MeshPrivate.h" #include "MuR/MutableMath.h" #include "MuR/MutableTrace.h" #include "MuR/Ptr.h" #include "MuR/RefCounted.h" #include "MuR/MutableRuntimeModule.h" #include "GPUSkinPublicDefs.h" namespace mu { //------------------------------------------------------------------------------------------------- void MeshFormatBuffer( const FMeshBufferSet& Source, FMeshBuffer& ResultBuffer, int32 ResultOffsetElements, bool bHasSpecialSemantics, uint32 IDPrefix ) { int32 SourceElementCount = Source.GetElementCount(); if (!SourceElementCount) { return; } // This happens in the debugger if (ResultBuffer.Channels.IsEmpty() || ResultBuffer.ElementSize==0) { return; } check(ResultBuffer.ElementSize); int32 ResultElementCount = ResultBuffer.Data.Num() / ResultBuffer.ElementSize; check(SourceElementCount + ResultOffsetElements <= ResultElementCount); // For every channel in this buffer for (int32 ResultChannelIndex = 0; ResultChannelIndex < ResultBuffer.Channels.Num(); ++ResultChannelIndex) { FMeshBufferChannel& ResultChannel = ResultBuffer.Channels[ResultChannelIndex]; // Find this channel in the source mesh int32 SourceBufferIndex; int32 SourceChannelIndex; Source.FindChannel(ResultChannel.Semantic, ResultChannel.SemanticIndex, &SourceBufferIndex, &SourceChannelIndex); int32 ResultElemSize = ResultBuffer.ElementSize; int32 ResultComponents = ResultChannel.ComponentCount; int32 ResultChannelSize = GetMeshFormatData(ResultChannel.Format).SizeInBytes * ResultComponents; uint8* ResultBuf = ResultBuffer.Data.GetData() + ResultOffsetElements*ResultBuffer.ElementSize; ResultBuf += ResultChannel.Offset; // Case 1: Special semantics that may be implicit or relative //--------------------------------------------------------------------------------- if (bHasSpecialSemantics && ResultChannel.Semantic == EMeshBufferSemantic::VertexIndex) { bool bHasVertexIndices = (SourceBufferIndex >=0 ); if (bHasVertexIndices) { check(SourceChannelIndex == 0 && Source.Buffers[SourceBufferIndex].Channels.Num() == 1); const FMeshBuffer& SourceBuffer = Source.Buffers[SourceBufferIndex]; const FMeshBufferChannel& SourceChannel = SourceBuffer.Channels[SourceChannelIndex]; bool bHasSameFormat = SourceChannel.Format == ResultChannel.Format; if (bHasSameFormat) { check(SourceChannel == ResultChannel); FMemory::Memcpy(ResultBuf,SourceBuffer.Data.GetData(),SourceBuffer.Data.Num()); } else { // Relative vertex IDs check(IDPrefix); check(SourceChannel.Format == EMeshBufferFormat::UInt32); const uint32* SourceData = reinterpret_cast(SourceBuffer.Data.GetData()); check(ResultChannel.Format == EMeshBufferFormat::UInt64); uint64* ResultData = reinterpret_cast(ResultBuf); for (int32 i = 0; i < SourceElementCount; ++i) { uint32 SourceId = *SourceData++; uint64 Id = (uint64(IDPrefix) << 32) | uint64(SourceId); *ResultData = Id; ++ResultData; } } } else { // Implicit IDs check(IDPrefix); check(ResultChannel.Format == EMeshBufferFormat::UInt64); uint64* ResultData = reinterpret_cast(ResultBuf); for (int32 VertexIndex = 0; VertexIndex < SourceElementCount; ++VertexIndex) { uint64 Id = (uint64(IDPrefix) << 32) | uint64(VertexIndex); *ResultData = Id; ++ResultData; } } continue; } else if (bHasSpecialSemantics && ResultChannel.Semantic == EMeshBufferSemantic::LayoutBlock) { bool bHasBlockIds = (SourceBufferIndex >= 0); if (bHasBlockIds) { const FMeshBuffer& SourceBuffer = Source.Buffers[SourceBufferIndex]; const FMeshBufferChannel& SourceChannel = SourceBuffer.Channels[SourceChannelIndex]; bool bHasSameFormat = SourceChannel.Format == ResultChannel.Format; if (bHasSameFormat) { check(SourceChannel==ResultChannel); FMemory::Memcpy(ResultBuf, SourceBuffer.Data.GetData(), SourceBuffer.Data.Num()); } else { // Relative vertex IDs check(SourceChannel.Format == EMeshBufferFormat::UInt16); const uint16* SourceData = reinterpret_cast(SourceBuffer.Data.GetData()); check(ResultChannel.Format == EMeshBufferFormat::UInt64); uint64* ResultData = reinterpret_cast(ResultBuf); for (int32 i = 0; i < SourceElementCount; ++i) { uint16 SourceId = *SourceData++; uint64 Id = (uint64(IDPrefix) << 32) | uint64(SourceId); *ResultData = Id; ++ResultData; } } continue; } else { // This seems to happen with objects that mix meshes with layouts with meshes without layouts. // If we don't do anything here, it will be filled with zeros in the following code. //ensure(false); } } // Case 2: Not found in source: generate with default values, depending on semantic //--------------------------------------------------------------------------------- if (SourceBufferIndex < 0) { // Not found: fill with zeros. // Special case for derived channel data bool generated = false; // If we have to add colour channels, we will add them as white, to be neutral. // \todo: normal channels also should have special values. if (ResultChannel.Semantic == EMeshBufferSemantic::Color) { generated = true; switch (ResultChannel.Format) { case EMeshBufferFormat::Float32: { for (int32 v = 0; v < SourceElementCount; ++v) { float* pTypedResultBuf = (float*)ResultBuf; for (int32 i = 0; i < ResultComponents; ++i) { pTypedResultBuf[i] = 1.0f; } ResultBuf += ResultElemSize; } break; } case EMeshBufferFormat::NUInt8: { for (int32 v = 0; v < SourceElementCount; ++v) { uint8* pTypedResultBuf = (uint8*)ResultBuf; for (int32 i = 0; i < ResultComponents; ++i) { pTypedResultBuf[i] = 255; } ResultBuf += ResultElemSize; } break; } case EMeshBufferFormat::NUInt16: { for (int32 v = 0; v < SourceElementCount; ++v) { uint16* pTypedResultBuf = (uint16*)ResultBuf; for (int32 i = 0; i < ResultComponents; ++i) { pTypedResultBuf[i] = 65535; } ResultBuf += ResultElemSize; } break; } default: // Format not implemented check(false); break; } } if (!generated) { // TODO: and maybe raise a warning? for (int32 v = 0; v < SourceElementCount; ++v) { FMemory::Memzero(ResultBuf, ResultChannelSize); ResultBuf += ResultElemSize; } } continue; } // Case 3: Convert element by element //--------------------------------------------------------------------------------- { // Get the data about the source format EMeshBufferSemantic SourceSemantic; int32 SourceSemanticIndex; EMeshBufferFormat SourceFormat; int32 SourceComponents; int32 SourceOffset; Source.GetChannel ( SourceBufferIndex, SourceChannelIndex, &SourceSemantic, &SourceSemanticIndex, &SourceFormat, &SourceComponents, &SourceOffset ); check(SourceSemantic == ResultChannel.Semantic && SourceSemanticIndex == ResultChannel.SemanticIndex); int32 SourceElemSize = Source.GetElementSize(SourceBufferIndex); const uint8* SourceBuf = Source.GetBufferData(SourceBufferIndex); SourceBuf += SourceOffset; // Copy element by element if (ResultChannel.Format == SourceFormat && ResultComponents == SourceComponents) { for (int32 v = 0; v < SourceElementCount; ++v) { FMemory::Memcpy(ResultBuf, SourceBuf, ResultChannelSize); ResultBuf += ResultElemSize; SourceBuf += SourceElemSize; } } else if (ResultChannel.Format == EMeshBufferFormat::PackedDir8_W_TangentSign || ResultChannel.Format == EMeshBufferFormat::PackedDirS8_W_TangentSign) { check(SourceComponents >= 3); check(ResultComponents == 4); // Look for the full tangent space if not done already int32 tanXBuf = -1, tanXChan = -1, tanYBuf = -1, tanYChan = -1, tanZBuf = -1, tanZChan = -1; Source.FindChannel(EMeshBufferSemantic::Tangent, ResultChannel.SemanticIndex, &tanXBuf, &tanXChan); Source.FindChannel(EMeshBufferSemantic::Binormal, ResultChannel.SemanticIndex, &tanYBuf, &tanYChan); Source.FindChannel(EMeshBufferSemantic::Normal, ResultChannel.SemanticIndex, &tanZBuf, &tanZChan); UntypedMeshBufferIteratorConst xIt(Source, EMeshBufferSemantic::Tangent, ResultChannel.SemanticIndex); UntypedMeshBufferIteratorConst yIt(Source, EMeshBufferSemantic::Binormal, ResultChannel.SemanticIndex); UntypedMeshBufferIteratorConst zIt(Source, EMeshBufferSemantic::Normal, ResultChannel.SemanticIndex); for (int32 v = 0; v < SourceElementCount; ++v) { // convert the 3 first components for (int32 i = 0; i < 3; ++i) { if (i < SourceComponents) { ConvertData ( i, ResultBuf, ResultChannel.Format, SourceBuf, SourceFormat ); } } // Add the tangent sign if (tanXBuf >= 0 && tanYBuf >= 0 && tanZBuf >= 0) { FMatrix44f Mat(xIt.GetAsVec3f(), yIt.GetAsVec3f(), zIt.GetAsVec3f(), FVector3f(0, 0, 0)); if (ResultChannel.Format == EMeshBufferFormat::PackedDir8_W_TangentSign) { uint8* pData = reinterpret_cast(ResultBuf); uint8 Sign = Mat.RotDeterminant() < 0 ? 0 : 255; pData[3] = Sign; } else if (ResultChannel.Format == EMeshBufferFormat::PackedDirS8_W_TangentSign) { int8* pData = reinterpret_cast(ResultBuf); int8 Sign = Mat.RotDeterminant() < 0 ? -128 : 127; pData[3] = Sign; } xIt++; yIt++; zIt++; } else { // At least initialize it to avoid garbage. int8* pData = reinterpret_cast(ResultBuf); pData[3] = 0; } ResultBuf += ResultElemSize; SourceBuf += SourceElemSize; } } else { for (int32 v = 0; v < SourceElementCount; ++v) { // Convert formats for (int32 i = 0; i < ResultComponents; ++i) { if (i < SourceComponents) { ConvertData ( i, ResultBuf, ResultChannel.Format, SourceBuf, SourceFormat ); } else { // Add zeros. TODO: Warning? FMemory::Memzero ( ResultBuf + GetMeshFormatData(ResultChannel.Format).SizeInBytes * i, GetMeshFormatData(ResultChannel.Format).SizeInBytes ); } } // Extra step to normalise some semantics in some formats // TODO: Make it optional, and add different normalisation types n, n^2 // TODO: Optimise if (SourceSemantic == EMeshBufferSemantic::BoneWeights) { if (ResultChannel.Format == EMeshBufferFormat::NUInt8) { uint8* Data = (uint8*)ResultBuf; uint8 Accum = 0; for (int32 i = 0; i < ResultComponents; ++i) { Accum += Data[i]; } Data[0] += 255 - Accum; } else if (ResultChannel.Format == EMeshBufferFormat::NUInt16) { uint16* Data = (uint16*)ResultBuf; uint16 Accum = 0; for (int32 i = 0; i < ResultComponents; ++i) { Accum += Data[i]; } Data[0] += 65535 - Accum; } } ResultBuf += ResultElemSize; SourceBuf += SourceElemSize; } } } } } //------------------------------------------------------------------------------------------------- void FormatBufferSet ( const FMeshBufferSet& Source, FMeshBufferSet& Result, bool bKeepSystemBuffers, bool bIgnoreMissingChannels, bool bIsVertexBuffer, uint32 IDPrefix = 0 ) { if (bIgnoreMissingChannels) { // Remove from the result the channels that are not present in the source, and re-pack the // offsets. for (int32 b = 0; b < Result.GetBufferCount(); ++b) { TArray resultSemantics; TArray resultSemanticIndexs; TArray resultFormats; TArray resultComponentss; TArray resultOffsets; int32 offset = 0; // For every channel in this buffer for (int32 c = 0; c < Result.GetBufferChannelCount(b); ++c) { EMeshBufferSemantic resultSemantic; int32 resultSemanticIndex; EMeshBufferFormat resultFormat; int32 resultComponents; // Find this channel in the source mesh Result.GetChannel ( b, c, &resultSemantic, &resultSemanticIndex, &resultFormat, &resultComponents, nullptr ); int32 sourceBuffer; int32 sourceChannel; Source.FindChannel (resultSemantic, resultSemanticIndex, &sourceBuffer, &sourceChannel); if (sourceBuffer >= 0) { resultSemantics.Add(resultSemantic); resultSemanticIndexs.Add(resultSemanticIndex); resultFormats.Add(resultFormat); resultComponentss.Add(resultComponents); resultOffsets.Add(offset); offset += GetMeshFormatData(resultFormat).SizeInBytes * resultComponents; } } if (resultSemantics.IsEmpty()) { Result.SetBuffer(b, 0, 0, nullptr, nullptr, nullptr, nullptr, nullptr); } else { Result.SetBuffer(b, offset, resultSemantics.Num(), &resultSemantics[0], &resultSemanticIndexs[0], &resultFormats[0], &resultComponentss[0], &resultOffsets[0]); } } } if (Source.IsDescriptor()) { EnumAddFlags(Result.Flags, EMeshBufferSetFlags::IsDescriptor); } int32 VertexCount = Source.GetElementCount(); Result.SetElementCount(VertexCount); if (!Source.IsDescriptor()) { // For every vertex buffer in result for (int32 BufferIndex = 0; BufferIndex < Result.GetBufferCount(); ++BufferIndex) { MeshFormatBuffer(Source, Result.Buffers[BufferIndex], 0, bIsVertexBuffer, IDPrefix); } } // Detect internal system buffers and clone them unmodified. if (bKeepSystemBuffers) { for (int32 b = 0; b < Source.GetBufferCount(); ++b) { // Detect system buffers and clone them unmodified. if (Source.GetBufferChannelCount(b) == 1) { EMeshBufferSemantic sourceSemantic; int32 sourceSemanticIndex; EMeshBufferFormat sourceFormat; int32 sourceComponents; int32 sourceOffset; Source.GetChannel ( b, 0, &sourceSemantic, &sourceSemanticIndex, &sourceFormat, &sourceComponents, &sourceOffset ); if (sourceSemantic == EMeshBufferSemantic::LayoutBlock || (bIsVertexBuffer && sourceSemantic == EMeshBufferSemantic::VertexIndex)) { // Add it if it wasn't already there, which could happen if it was included in the format mesh. int32 AlreadyExistingBufIndex = -1; int32 AlreadyExistingChannelIndex = -1; Result.FindChannel(sourceSemantic, sourceSemanticIndex, &AlreadyExistingBufIndex, &AlreadyExistingChannelIndex); if (AlreadyExistingBufIndex==-1) { Result.AddBuffer(Source, b); } else { // Replace the buffer check(Result.Buffers[AlreadyExistingBufIndex].Channels.Num()==1); Result.Buffers[AlreadyExistingBufIndex] = Source.Buffers[b]; } } } } } } int32 FindHighestBoneIndexInUse(const FMesh* InMesh, const FMeshBufferChannel& Channel) { const FMeshBufferSet& VertexBuffers = InMesh->GetVertexBuffers(); // If InMesh buffers are descriptors, use the bone map to get an upper bound of the highest index // we will find in the mesh. Otherwise, find it from the vertex buffers data. if (VertexBuffers.IsDescriptor()) { return FMath::Max(0, InMesh->BoneMap.Num() - 1); } const UntypedMeshBufferIteratorConst BoneIndicesBegin( VertexBuffers, EMeshBufferSemantic::BoneIndices, Channel.SemanticIndex); if (!BoneIndicesBegin.ptr()) { return 0; } const int32 NumVertices = VertexBuffers.GetElementCount(); const int32 NumInfluences = BoneIndicesBegin.GetComponents(); int32 HighestBoneIndex = 0; for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { // If MAX_TOTAL_INFLUENCES ever changed, the next line would no longer work or compile and // GetAsVec12i would need to be changed accordingly int32 VertexInfluences[MAX_TOTAL_INFLUENCES]; (BoneIndicesBegin + VertexIndex).GetAsInt32Vec(VertexInfluences, MAX_TOTAL_INFLUENCES); for (int32 InfluenceIndex = 0; InfluenceIndex < NumInfluences; ++InfluenceIndex) { HighestBoneIndex = FMath::Max(HighestBoneIndex, VertexInfluences[InfluenceIndex]); } } return HighestBoneIndex; } /** Updates the InOutFormat bone index semantic format if it cannot represent the highest bone index in use. */ void UpdateBoneIndexFormatIfNeeded(const FMesh* InMesh, FMeshBufferSet& InOutFormatBufferSet) { const FMeshBufferSet& VertexBuffers = InMesh->GetVertexBuffers(); const int32 BufferCount = VertexBuffers.GetBufferCount(); for (int32 BufferIndex = 0; BufferIndex < BufferCount; ++BufferIndex) { const int32 ChannelCount = VertexBuffers.GetBufferChannelCount(BufferIndex); for (int32 ChannelIndex = 0; ChannelIndex < ChannelCount; ++ChannelIndex) { const FMeshBufferChannel& Channel = VertexBuffers.Buffers[BufferIndex].Channels[ChannelIndex]; if (Channel.Semantic == EMeshBufferSemantic::BoneIndices) { int32 ResultBuf = 0; int32 ResultChan = 0; InOutFormatBufferSet.FindChannel(EMeshBufferSemantic::BoneIndices, Channel.SemanticIndex, &ResultBuf, &ResultChan); if (ResultBuf >= 0) { int32 HighestBoneIndexInUse = FindHighestBoneIndexInUse(InMesh, Channel); check(HighestBoneIndexInUse >= 0); EMeshBufferFormat& BoneIndexFormat = InOutFormatBufferSet.Buffers[ResultBuf].Channels[ResultChan].Format; if (HighestBoneIndexInUse > 0xffff && (BoneIndexFormat == EMeshBufferFormat::UInt8 || BoneIndexFormat == EMeshBufferFormat::UInt16)) { BoneIndexFormat = EMeshBufferFormat::UInt32; InOutFormatBufferSet.UpdateOffsets(ResultBuf); } else if (HighestBoneIndexInUse > 0x7fff && (BoneIndexFormat == EMeshBufferFormat::UInt8 || BoneIndexFormat == EMeshBufferFormat::UInt16)) { BoneIndexFormat = EMeshBufferFormat::UInt32; InOutFormatBufferSet.UpdateOffsets(ResultBuf); } else if (HighestBoneIndexInUse > 0xff && BoneIndexFormat == EMeshBufferFormat::UInt8) { BoneIndexFormat = EMeshBufferFormat::UInt16; InOutFormatBufferSet.UpdateOffsets(ResultBuf); } else if (HighestBoneIndexInUse > 0x7f && BoneIndexFormat == EMeshBufferFormat::UInt8) { BoneIndexFormat = EMeshBufferFormat::UInt16; InOutFormatBufferSet.UpdateOffsets(ResultBuf); } } } } } } //------------------------------------------------------------------------------------------------- void MeshFormat ( FMesh* Result, const FMesh* Source, const FMesh* Format, bool bKeepSystemBuffers, bool bFormatVertices, bool bFormatIndices, bool bIgnoreMissingChannels, bool& bOutSuccess ) { MUTABLE_CPUPROFILER_SCOPE(MeshFormat); bOutSuccess = true; if (!Source) { check(false); bOutSuccess = false; return; } if (!Format) { check(false); bOutSuccess = false; return; } Result->CopyFrom(*Format); Result->MeshIDPrefix = Source->MeshIDPrefix; if (bFormatVertices) { UpdateBoneIndexFormatIfNeeded(Source, Result->GetVertexBuffers()); FormatBufferSet(Source->GetVertexBuffers(), Result->GetVertexBuffers(), bKeepSystemBuffers, bIgnoreMissingChannels, true, Source->MeshIDPrefix); } else { Result->VertexBuffers = Source->GetVertexBuffers(); } if (bFormatIndices) { // \todo Make sure that the vertex indices will fit in this format, or extend it. FormatBufferSet(Source->GetIndexBuffers(), Result->GetIndexBuffers(), bKeepSystemBuffers, bIgnoreMissingChannels, false); } else { Result->IndexBuffers = Source->GetIndexBuffers(); } // Copy the rest of the data Result->SetSkeleton(Source->GetSkeleton()); Result->SetPhysicsBody(Source->GetPhysicsBody()); Result->Layouts.Empty(); for (const TSharedPtr& Layout : Source->Layouts) { Result->Layouts.Add(Layout->Clone()); } Result->Tags = Source->Tags; Result->StreamedResources = Source->StreamedResources; Result->AdditionalBuffers = Source->AdditionalBuffers; Result->BonePoses = Source->BonePoses; Result->BoneMap = Source->BoneMap; Result->SkeletonIDs = Source->SkeletonIDs; // A shallow copy is done here, it should not be a problem. Result->AdditionalPhysicsBodies = Source->AdditionalPhysicsBodies; Result->Surfaces = Source->Surfaces; Result->ResetStaticFormatFlags(); Result->EnsureSurfaceData(); } void MeshOptimizeBuffers( FMesh* InMesh ) { MUTABLE_CPUPROFILER_SCOPE(MeshOptimizeBuffers); if (!InMesh) { return; } FMeshBufferSet& VertexBuffers = InMesh->VertexBuffers; // Ignore the operation if the MeshBuffersSet is a descriptor. if (VertexBuffers.IsDescriptor()) { return; } // Reduce the number of influences if possible constexpr int32 SemanticIndex = 0; UntypedMeshBufferIteratorConst WeightIt(VertexBuffers, EMeshBufferSemantic::BoneWeights, SemanticIndex); if (WeightIt.ptr()) { int32 BufferInfluences = WeightIt.GetComponents(); int32 RealInfluences = 0; for (int32 VertexIndex = 0; VertexIndex < VertexBuffers.GetElementCount(); ++VertexIndex) { int32 ThisVertexInfluences = 0; switch (WeightIt.GetFormat()) { case EMeshBufferFormat::NUInt8: { const uint8* Data = reinterpret_cast(WeightIt.ptr()); for (int32 InfluenceIndex = 0; InfluenceIndex < BufferInfluences; ++InfluenceIndex) { if (*Data>0) { ++ThisVertexInfluences; } ++Data; } break; } case EMeshBufferFormat::NUInt16: { const uint16* Data = reinterpret_cast(WeightIt.ptr()); for (int32 InfluenceIndex = 0; InfluenceIndex < BufferInfluences; ++InfluenceIndex) { if (*Data > 0) { ++ThisVertexInfluences; } ++Data; } break; } default: // Unsupported check(false); break; } ++WeightIt; RealInfluences = FMath::Max(RealInfluences,ThisVertexInfluences); } if (RealInfluencesVertexBuffers = NewVertexBuffers; } } } }