// Copyright Epic Games, Inc. All Rights Reserved. #include "MuT/ASTOpMeshFormat.h" #include "HAL/UnrealMemory.h" #include "Misc/AssertionMacros.h" #include "MuR/Mesh.h" #include "MuR/MeshBufferSet.h" #include "MuR/ModelPrivate.h" #include "MuR/MutableTrace.h" #include "MuR/RefCounted.h" #include "MuR/System.h" #include "MuR/Types.h" #include "MuT/ASTOpConditional.h" #include "MuT/ASTOpConstantResource.h" #include "MuT/ASTOpMeshClipMorphPlane.h" #include "MuT/ASTOpMeshTransformWithBoundingMesh.h" #include "MuT/ASTOpMeshRemoveMask.h" #include "MuT/ASTOpMeshMorph.h" #include "MuT/ASTOpMeshAddMetadata.h" #include "MuT/ASTOpMeshApplyPose.h" #include "MuT/ASTOpMeshApplyLayout.h" #include "MuT/ASTOpMeshMerge.h" #include "MuT/ASTOpMeshSetSkeleton.h" #include "MuT/ASTOpSwitch.h" #include "GPUSkinPublicDefs.h" namespace mu { //------------------------------------------------------------------------------------------------- ASTOpMeshFormat::ASTOpMeshFormat() : Source(this) , Format(this) { } ASTOpMeshFormat::~ASTOpMeshFormat() { // Explicit call needed to avoid recursive destruction ASTOp::RemoveChildren(); } bool ASTOpMeshFormat::IsEqual(const ASTOp& otherUntyped) const { if (otherUntyped.GetOpType() == GetOpType()) { const ASTOpMeshFormat* other = static_cast(&otherUntyped); return Source==other->Source && Format==other->Format && Flags ==other->Flags; } return false; } uint64 ASTOpMeshFormat::Hash() const { uint64 res = std::hash()(Source.child().get() ); hash_combine( res, Format.child().get() ); return res; } mu::Ptr ASTOpMeshFormat::Clone(MapChildFuncRef mapChild) const { mu::Ptr n = new ASTOpMeshFormat(); n->Source = mapChild(Source.child()); n->Format = mapChild(Format.child()); n->Flags = Flags; n->bOptimizeBuffers = bOptimizeBuffers; return n; } void ASTOpMeshFormat::ForEachChild(const TFunctionRef f ) { f( Source ); f( Format ); } void ASTOpMeshFormat::Link( FProgram& program, FLinkerOptions* ) { // Already linked? if (!linkedAddress) { OP::MeshFormatArgs Args; FMemory::Memzero(Args); Args.Flags = Flags; if (bOptimizeBuffers) { Args.Flags = Args.Flags | OP::MeshFormatArgs::OptimizeBuffers; } if (Source) Args.source = Source->linkedAddress; if (Format) Args.format = Format->linkedAddress; linkedAddress = (OP::ADDRESS)program.OpAddress.Num(); program.OpAddress.Add((uint32)program.ByteCode.Num()); AppendCode(program.ByteCode,EOpType::ME_FORMAT); AppendCode(program.ByteCode,Args); } } mu::Ptr ASTOpMeshFormat::OptimiseSink(const FModelOptimizationOptions& options, FOptimizeSinkContext& context) const { mu::Ptr at = context.MeshFormatSinker.Apply(this); return at; } FSourceDataDescriptor ASTOpMeshFormat::GetSourceDataDescriptor(FGetSourceDataDescriptorContext* Context) const { if (Source) { return Source->GetSourceDataDescriptor(Context); } return {}; } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- mu::Ptr Sink_MeshFormatAST::Apply(const ASTOpMeshFormat* root) { Root = root; OldToNew.Reset(); InitialSource = Root->Source.child(); mu::Ptr newSource = Visit(InitialSource, Root); // If there is any change, it is the new root. if (newSource != InitialSource) { return newSource; } return nullptr; } namespace { TSharedPtr FindBaseMeshConstant(mu::Ptr at) { TSharedPtr res; switch (at->GetOpType()) { case EOpType::ME_CONSTANT: { const ASTOpConstantResource* typed = static_cast(at.get()); res = StaticCastSharedPtr(typed->GetValue()); break; } default: check(false); } check(res); return res; } // Make a mesh format suitable to morph a particular other format. TSharedPtr MakeMorphTargetFormat(TSharedPtr pTargetFormat) { MUTABLE_CPUPROFILER_SCOPE(MakeMorphTargetFormat); // Make a morph format by adding all the vertex channels from the base into a single // vertex buffer int32 offset = 0; int32 numChannels = 0; TArray semantics; TArray semanticIndices; TArray formats; TArray components; TArray offsets; // Add the vertex channels from the new format for (int32 vb = 0; vb < pTargetFormat->GetVertexBuffers().GetBufferCount(); ++vb) { for (int32 c = 0; c < pTargetFormat->GetVertexBuffers().GetBufferChannelCount(vb); ++c) { // Channel info EMeshBufferSemantic semantic; int semanticIndex; EMeshBufferFormat format; int component; pTargetFormat->GetVertexBuffers().GetChannel(vb, c, &semantic, &semanticIndex, &format, &component, nullptr); // TODO: Filter useless semantics for morphing. // Maybe some formats like the ones with a packed tangent sign need to be tweaked here, to make sense of the whole buffer. semantics.Add(semantic); semanticIndices.Add(semanticIndex); formats.Add(format); components.Add(component); offsets.Add(offset); offset += components[numChannels] * GetMeshFormatData(formats[numChannels]).SizeInBytes; numChannels++; } } TSharedPtr pTargetMorphFormat = MakeShared(); pTargetMorphFormat->GetVertexBuffers().SetBufferCount(1); pTargetMorphFormat->GetVertexBuffers().SetBuffer(0, offset, numChannels, semantics.GetData(), semanticIndices.GetData(), formats.GetData(), components.GetData(), offsets.GetData()); return pTargetMorphFormat; } TSharedPtr EnsureFormatHasSkinningBuffers(TSharedPtr& FormatMesh) { const FMeshBufferSet& FormatMeshVertexBuffers = FormatMesh->GetVertexBuffers(); int32 SourceSkinningBufferIndex = -1; int32 SourceSkinningChannelIndex = -1; // Assume bone indices implies it also has weights. FormatMeshVertexBuffers.FindChannel(EMeshBufferSemantic::BoneIndices, 0, &SourceSkinningBufferIndex, &SourceSkinningChannelIndex); bool bSourceHasSkinningData = SourceSkinningBufferIndex != -1; if (bSourceHasSkinningData) { return FormatMesh; } TSharedPtr NewMesh = FormatMesh->Clone(); FMeshBufferSet& MeshBuffers = NewMesh->GetVertexBuffers(); FMeshBuffer& Buffer = MeshBuffers.Buffers.AddDefaulted_GetRef(); FMeshBufferChannel BoneIndices; BoneIndices.Semantic = EMeshBufferSemantic::BoneIndices; BoneIndices.Format = EMeshBufferFormat::UInt16; BoneIndices.SemanticIndex = 0; BoneIndices.Offset = 0; BoneIndices.ComponentCount = MAX_TOTAL_INFLUENCES; FMeshBufferChannel BoneWeights; BoneWeights.Semantic = EMeshBufferSemantic::BoneWeights; BoneWeights.Format = EMeshBufferFormat::NUInt16; BoneWeights.SemanticIndex = 0; BoneWeights.Offset = MAX_TOTAL_INFLUENCES*2; BoneWeights.ComponentCount = MAX_TOTAL_INFLUENCES; Buffer.ElementSize = MAX_TOTAL_INFLUENCES*4; Buffer.Channels.Add(BoneIndices); Buffer.Channels.Add(BoneWeights); return NewMesh; } } mu::Ptr Sink_MeshFormatAST::Visit(const mu::Ptr& at, const ASTOpMeshFormat* currentFormatOp) { if (!at) { return nullptr; } // Already visited? const Ptr* Cached = OldToNew.Find({ at,currentFormatOp }); if (Cached) { return *Cached; } mu::Ptr newAt = at; switch (at->GetOpType()) { case EOpType::ME_APPLYLAYOUT: { Ptr NewOp = mu::Clone(at); NewOp->Mesh = Visit(NewOp->Mesh.child(), currentFormatOp); newAt = NewOp; break; } case EOpType::ME_SETSKELETON: { Ptr NewOp = mu::Clone(at); NewOp->Source = Visit(NewOp->Source.child(), currentFormatOp); newAt = NewOp; break; } case EOpType::ME_ADDMETADATA: { Ptr newOp = mu::Clone(at); newOp->Source = Visit(newOp->Source.child(), currentFormatOp); newAt = newOp; break; } case EOpType::ME_CLIPMORPHPLANE: { Ptr newOp = mu::Clone(at); newOp->Source = Visit(newOp->Source.child(), currentFormatOp); newAt = newOp; break; } case EOpType::ME_TRANSFORMWITHMESH: { Ptr NewOp = mu::Clone(at); NewOp->source = Visit(NewOp->source.child(), currentFormatOp); // Don't transform the bounding mesh: it should be optimized with a different specific format elsewhere (TODO). // NewOp->boundingMesh = Visit(NewOp->boundingMesh.child(), currentFormatOp); newAt = NewOp; break; } case EOpType::ME_MORPH: { // Move the format down the base of the morph Ptr NewOp = mu::Clone(at); NewOp->Base = Visit(NewOp->Base.child(), currentFormatOp); // Reformat the morph targets to match the new format. TSharedPtr pTargetFormat = FindBaseMeshConstant(currentFormatOp->Format.child()); TSharedPtr pTargetMorphFormat = MakeMorphTargetFormat(pTargetFormat); mu::Ptr NewFormatConstant = new ASTOpConstantResource(); NewFormatConstant->Type = EOpType::ME_CONSTANT; NewFormatConstant->SetValue(pTargetMorphFormat, nullptr); NewFormatConstant->SourceDataDescriptor = at->GetSourceDataDescriptor(); if (NewOp->Target) { mu::Ptr newFormat = mu::Clone(currentFormatOp); newFormat->Flags = OP::MeshFormatArgs::Vertex | OP::MeshFormatArgs::IgnoreMissing; newFormat->Format = NewFormatConstant; NewOp->Target = Visit(NewOp->Target.child(), newFormat.get()); } newAt = NewOp; break; } case EOpType::ME_MERGE: { Ptr NewOp = mu::Clone(at); NewOp->Base = Visit(NewOp->Base.child(), currentFormatOp); NewOp->Added = Visit(NewOp->Added.child(), currentFormatOp); newAt = NewOp; break; } case EOpType::ME_APPLYPOSE: { TSharedPtr TargetFormat = FindBaseMeshConstant(currentFormatOp->Format.child()); TargetFormat = EnsureFormatHasSkinningBuffers(TargetFormat); Ptr NewOp = mu::Clone(at); mu::Ptr NewFormat = mu::Clone(currentFormatOp); mu::Ptr NewFormatConstant = new ASTOpConstantResource(); NewFormatConstant->Type = EOpType::ME_CONSTANT; NewFormatConstant->SetValue(TargetFormat, nullptr); NewFormatConstant->SourceDataDescriptor = at->GetSourceDataDescriptor(); NewFormat->Flags = NewFormat->Flags | OP::MeshFormatArgs::OptimizeBuffers; // TODO: Optimize, in case no skinning data is found in the format mesh a generic buffer that can represent // all possible skinning formats is added. This is not optimal, we may want to add a flag to the format op // to indicate it should copy the skinning from the base mesh. NewFormat->Format = NewFormatConstant; NewOp->Base = Visit(NewOp->Base.child(), NewFormat.get()); newAt = NewOp; break; } case EOpType::ME_REMOVEMASK: { Ptr newOp = mu::Clone(at); newOp->source = Visit(newOp->source.child(), currentFormatOp); newAt = newOp; break; } case EOpType::ME_CONDITIONAL: { Ptr newOp = mu::Clone(at); newOp->yes = Visit(newOp->yes.child(), currentFormatOp); newOp->no = Visit(newOp->no.child(), currentFormatOp); newAt = newOp; break; } case EOpType::ME_SWITCH: { Ptr newOp = mu::Clone(at); newOp->Default = Visit(newOp->Default.child(), currentFormatOp); for (ASTOpSwitch::FCase& c : newOp->Cases) { c.Branch = Visit(c.Branch.child(), currentFormatOp); } newAt = newOp; break; } case EOpType::ME_FORMAT: // TODO: The child format can be removed. // Unless channels are removed and re-added, which would change their content? break; // This operation should not be optimized. case EOpType::ME_DIFFERENCE: // If we reach here it means the operation type has not bee optimized. default: if (at != InitialSource) { mu::Ptr newOp = mu::Clone(currentFormatOp); newOp->Source = at; newAt = newOp; } break; } OldToNew.Add({ at,currentFormatOp }, newAt); return newAt; } }