Files
2025-05-18 13:04:45 +08:00

3179 lines
105 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuT/CodeGenerator.h"
#include "ASTOpMeshTransformWithBoundingMesh.h"
#include "Containers/Array.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Math/IntPoint.h"
#include "Math/UnrealMathUtility.h"
#include "MuR/ImagePrivate.h"
#include "MuR/Layout.h"
#include "MuR/MutableTrace.h"
#include "MuR/Operations.h"
#include "MuR/ParametersPrivate.h"
#include "MuR/Platform.h"
#include "MuT/ASTOpAddLOD.h"
#include "MuT/ASTOpAddExtensionData.h"
#include "MuT/ASTOpAddOverlayMaterial.h"
#include "MuT/ASTOpConditional.h"
#include "MuT/ASTOpConstantBool.h"
#include "MuT/ASTOpConstantResource.h"
#include "MuT/ASTOpBoolAnd.h"
#include "MuT/ASTOpImageCompose.h"
#include "MuT/ASTOpImageMipmap.h"
#include "MuT/ASTOpImagePixelFormat.h"
#include "MuT/ASTOpImageSwizzle.h"
#include "MuT/ASTOpImageLayer.h"
#include "MuT/ASTOpImageLayerColor.h"
#include "MuT/ASTOpImagePatch.h"
#include "MuT/ASTOpImageCrop.h"
#include "MuT/ASTOpImageBlankLayout.h"
#include "MuT/ASTOpImagePlainColor.h"
#include "MuT/ASTOpInstanceAdd.h"
#include "MuT/ASTOpMeshBindShape.h"
#include "MuT/ASTOpMeshClipDeform.h"
#include "MuT/ASTOpMeshClipMorphPlane.h"
#include "MuT/ASTOpMeshMaskClipMesh.h"
#include "MuT/ASTOpMeshMaskClipUVMask.h"
#include "MuT/ASTOpMeshRemoveMask.h"
#include "MuT/ASTOpMeshDifference.h"
#include "MuT/ASTOpMeshMorph.h"
#include "MuT/ASTOpMeshOptimizeSkinning.h"
#include "MuT/ASTOpMeshExtractLayoutBlocks.h"
#include "MuT/ASTOpMeshApplyLayout.h"
#include "MuT/ASTOpMeshMerge.h"
#include "MuT/ASTOpMeshMaskDiff.h"
#include "MuT/ASTOpParameter.h"
#include "MuT/ASTOpLayoutRemoveBlocks.h"
#include "MuT/ASTOpLayoutFromMesh.h"
#include "MuT/ASTOpLayoutMerge.h"
#include "MuT/ASTOpLayoutPack.h"
#include "MuT/ASTOpReferenceResource.h"
#include "MuT/CodeGenerator_SecondPass.h"
#include "MuT/CodeOptimiser.h"
#include "MuT/CompilerPrivate.h"
#include "MuT/ErrorLog.h"
#include "MuT/NodeColour.h"
#include "MuT/NodeColourConstant.h"
#include "MuT/NodeComponent.h"
#include "MuT/NodeComponentSwitch.h"
#include "MuT/NodeComponentVariation.h"
#include "MuT/NodeImage.h"
#include "MuT/NodeImageConstant.h"
#include "MuT/NodeImageFormat.h"
#include "MuT/NodeImageMipmap.h"
#include "MuT/NodeImageSwizzle.h"
#include "MuT/NodeMatrixConstant.h"
#include "MuT/NodeMesh.h"
#include "MuT/NodeMeshClipMorphPlane.h"
#include "MuT/NodeMeshClipWithMesh.h"
#include "MuT/NodeMeshFormat.h"
#include "MuT/NodeMeshFragment.h"
#include "MuT/NodeMeshMorph.h"
#include "MuT/NodeMeshReshape.h"
#include "MuT/NodeMeshConstant.h"
#include "MuT/NodeModifier.h"
#include "MuT/NodeModifierMeshClipDeform.h"
#include "MuT/NodeModifierMeshClipMorphPlane.h"
#include "MuT/NodeModifierMeshClipWithMesh.h"
#include "MuT/NodeModifierMeshClipWithUVMask.h"
#include "MuT/NodeModifierMeshTransformInMesh.h"
#include "MuT/NodeModifierSurfaceEdit.h"
#include "MuT/NodeObject.h"
#include "MuT/NodeObjectNew.h"
#include "MuT/NodeRange.h"
#include "MuT/NodeRangeFromScalar.h"
#include "MuT/NodeScalar.h"
#include "MuT/NodeScalarConstant.h"
#include "MuT/NodeScalarEnumParameter.h"
#include "MuT/NodeSurface.h"
#include "MuT/NodeSurfaceNew.h"
#include "MuT/NodeSurfaceVariation.h"
#include "MuT/NodeSurfaceSwitch.h"
#include "MuT/TablePrivate.h"
#include "Trace/Detail/Channel.h"
namespace mu
{
CodeGenerator::CodeGenerator(CompilerOptions::Private* Options, TFunction<void()>& InWaitCallback)
: WaitCallback(InWaitCallback),
LocalPipe(TEXT("CodeGeneratorPipe")),
GenerateMeshConstantPipe(TEXT("GenerateMeshConstantPipe"))
{
CompilerOptions = Options;
// Create the message log
ErrorLog = MakeShared<FErrorLog>();
}
void CodeGenerator::GenerateRoot(const Ptr<const Node> InNode)
{
MUTABLE_CPUPROFILER_SCOPE(Generate);
// First pass
FirstPass.Generate(ErrorLog, InNode.get(), CompilerOptions->bIgnoreStates, this);
// Second pass
SecondPassGenerator SecondPass(&FirstPass, CompilerOptions);
bool bSuccess = SecondPass.Generate(ErrorLog, InNode.get());
if (!bSuccess)
{
return;
}
// Main pass for each state
{
MUTABLE_CPUPROFILER_SCOPE(MainPass);
int32 CurrentStateIndex = 0;
for (const FObjectState& State : FirstPass.States)
{
MUTABLE_CPUPROFILER_SCOPE(MainPassState);
FGenericGenerationOptions Options;
Options.State = CurrentStateIndex;
FGenericGenerationResult Result;
GenerateGeneric(Options, Result, InNode.get());
Ptr<ASTOp> StateRoot = Result.Op;
States.Emplace(State, StateRoot);
++CurrentStateIndex;
}
}
}
void CodeGenerator::GenerateGeneric(const FGenericGenerationOptions& Options, FGenericGenerationResult& OutResult, const Ptr<const Node> InNode )
{
if (!InNode)
{
return;
}
// Type-specific generation
if (InNode->GetType()->IsA(NodeObject::GetStaticType()))
{
const NodeObject* ObjectNode = static_cast<const NodeObject*>(InNode.get());
FObjectGenerationOptions ObjectOptions;
ObjectOptions.ActiveTags = Options.ActiveTags;
ObjectOptions.bIsImage = Options.bIsImage;
ObjectOptions.State = Options.State;
FObjectGenerationResult ObjectResult;
GenerateObject(ObjectOptions, ObjectResult, ObjectNode);
OutResult.Op = ObjectResult.Op;
return;
}
else if (InNode->GetType()->IsA(NodeScalar::GetStaticType()))
{
const NodeScalar* ScalarNode = static_cast<const NodeScalar*>(InNode.get());
FScalarGenerationResult ScalarResult;
GenerateScalar(ScalarResult, Options, ScalarNode);
OutResult.Op = ScalarResult.op;
return;
}
else if (InNode->GetType()->IsA(NodeColour::GetStaticType()))
{
const NodeColour* ColorNode = static_cast<const NodeColour*>(InNode.get());
FColorGenerationResult Result;
GenerateColor(Result, Options, ColorNode);
OutResult.Op = Result.op;
return;
}
else if (InNode->GetType()->IsA(NodeProjector::GetStaticType()))
{
const NodeProjector* projNode = static_cast<const NodeProjector*>(InNode.get());
FProjectorGenerationResult ProjResult;
GenerateProjector(ProjResult, Options, projNode);
OutResult.Op = ProjResult.op;
return;
}
else if (InNode->GetType()->IsA(NodeSurfaceNew::GetStaticType()))
{
// This no longer happens with the current tools.
check(false);
return;
}
else if (InNode->GetType()->IsA(NodeSurfaceVariation::GetStaticType()))
{
// This happens only if we generate a node graph that has a NodeSurfaceVariation at the root.
return;
}
else if (InNode->GetType()->IsA(NodeSurfaceSwitch::GetStaticType()))
{
// This happens only if we generate a node graph that has a NodeSurfaceSwitch at the root.
return;
}
else if (InNode->GetType()->IsA(NodeModifier::GetStaticType()))
{
// This happens only if we generate a node graph that has a modifier at the root.
return;
}
else if (InNode->GetType()->IsA(NodeComponent::GetStaticType()))
{
const NodeComponent* ComponentNode = static_cast<const NodeComponent*>(InNode.get());
FComponentGenerationOptions ComponentOptions(Options, nullptr);
GenerateComponent(ComponentOptions, OutResult, ComponentNode);
return;
}
else
{
// Unsupported node.
check(false);
}
}
void CodeGenerator::GenerateObject(const FObjectGenerationOptions& InOptions, FObjectGenerationResult& OutResult, const NodeObject* InUntypedNode)
{
if (!InUntypedNode)
{
OutResult = {};
return;
}
// See if it was already generated
FGeneratedObjectCacheKey Key;
Key.Node = InUntypedNode;
Key.Options = InOptions;
FGeneratedObjectsMap::ValueType* Found = GeneratedObjects.Find(Key);
if (Found)
{
OutResult = *Found;
return;
}
// Generate for each different type of node
const FNodeType* Type = InUntypedNode->GetType();
if (Type == NodeObjectNew::GetStaticType())
{
GenerateObject_New(InOptions, OutResult, static_cast<const NodeObjectNew*>(InUntypedNode));
}
else if (Type == NodeObjectGroup::GetStaticType())
{
GenerateObject_Group(InOptions, OutResult, static_cast<const NodeObjectGroup*>(InUntypedNode));
}
else
{
check(false);
}
// Cache the result
GeneratedObjects.Add(Key, OutResult);
}
void CodeGenerator::GenerateRange(FRangeGenerationResult& Result, const FGenericGenerationOptions& Options, Ptr<const NodeRange> Untyped)
{
if (!Untyped)
{
Result = FRangeGenerationResult();
return;
}
// See if it was already generated
FGeneratedCacheKey Key;
Key.Node = Untyped;
Key.Options = Options;
{
UE::TUniqueLock Lock(GeneratedRanges.Mutex);
FGeneratedRangeMap::ValueType* Found = GeneratedRanges.Map.Find(Key);
if (Found)
{
Result = *Found;
return;
}
}
// Generate for each different type of node
if (Untyped->GetType()==NodeRangeFromScalar::GetStaticType())
{
const NodeRangeFromScalar* FromScalar = static_cast<const NodeRangeFromScalar*>(Untyped.get());
Result = FRangeGenerationResult();
Result.rangeName = FromScalar->Name;
FScalarGenerationResult ChildResult;
GenerateScalar(ChildResult, Options, FromScalar->Size);
Result.sizeOp = ChildResult.op;
}
else
{
check(false);
}
// Cache the result
{
UE::TUniqueLock Lock(GeneratedRanges.Mutex);
GeneratedRanges.Map.Add(Key, Result);
}
}
Ptr<NodeScalar> CodeGenerator::GenerateTableVariableNode(Ptr<const Node> InNode, const FTableCacheKey& CacheKey, bool bAddNoneOption, const FString& DefaultRowName)
{
Ptr<NodeScalarEnumParameter> Result = new NodeScalarEnumParameter;
FString ParamName = CacheKey.ParameterName;
if (ParamName.Len() == 0)
{
ParamName = CacheKey.Table->GetName();
}
Result->Name = ParamName;
Result->DefaultValue = 0;
int32 CurrentRow = 0;
int32 RowCount = CacheKey.Table->GetPrivate()->Rows.Num();
check(RowCount < MAX_int16) // max FIntValueDesc allows
if (bAddNoneOption)
{
Result->Options.SetNum(RowCount);
Result->Options[CurrentRow].Value = -1;
Result->Options[CurrentRow].Name = "None";
}
else
{
Result->Options.SetNum(RowCount-1);
}
// Add the possible values
{
// See if there is a string column. If there is one, we will use it as names for the
// options. Only the first string column will be used.
int32 NameCol = -1;
int32 NumCols = CacheKey.Table->GetPrivate()->Columns.Num();
for (int32 ColumnIndex=0; ColumnIndex<NumCols && NameCol<0; ++ColumnIndex)
{
if (CacheKey.Table->GetPrivate()->Columns[ColumnIndex].Type == ETableColumnType::String)
{
NameCol = ColumnIndex;
}
}
// Skip "None" option (first row) if it's not required
int32 StarCount = bAddNoneOption ? 0 : 1;
for (int32 RowIndex = StarCount; RowIndex < RowCount; ++RowIndex)
{
FString ValueName;
if (NameCol > -1)
{
ValueName = CacheKey.Table->GetPrivate()->Rows[RowIndex].Values[NameCol].String;
}
Result->Options[CurrentRow].Value = RowIndex;
Result->Options[CurrentRow].Name = ValueName;
// Set the first row or the selected row as the default one.
if(RowIndex == StarCount || ValueName == DefaultRowName)
{
Result->DefaultValue = RowIndex;
}
// Set the selected row as default (if exists)
if (ValueName == DefaultRowName)
{
Result->DefaultValue = RowIndex;
}
++CurrentRow;
}
}
return Result;
}
TSharedPtr<const FLayout> CodeGenerator::GenerateLayout(Ptr<const NodeLayout> SourceLayout, uint32 MeshIDPrefix)
{
// This can run in any thread.
UE::TUniqueLock Lock(GenerateLayoutConstantState.Mutex);
TSharedPtr<const FLayout>* CachedLayout = GenerateLayoutConstantState.GeneratedLayouts.Find({ SourceLayout,MeshIDPrefix });
if (CachedLayout)
{
return *CachedLayout;
}
TSharedPtr<FLayout> GeneratedLayout = MakeShared<FLayout>();
GeneratedLayout->Size = SourceLayout->Size;
GeneratedLayout->MaxSize = SourceLayout->MaxSize;
GeneratedLayout->Strategy = SourceLayout->Strategy;
GeneratedLayout->ReductionMethod = SourceLayout->ReductionMethod;
const int32 BlockCount = SourceLayout->Blocks.Num();
GeneratedLayout->Blocks.SetNum(BlockCount);
for (int32 BlockIndex = 0; BlockIndex < BlockCount; ++BlockIndex)
{
const FSourceLayoutBlock& From = SourceLayout->Blocks[BlockIndex];
FLayoutBlock& To = GeneratedLayout->Blocks[BlockIndex];
To.Min = From.Min;
To.Size = From.Size;
To.Priority = From.Priority;
To.bReduceBothAxes = From.bReduceBothAxes;
To.bReduceByTwo = From.bReduceByTwo;
// Assign unique ids to each layout block
uint64 Id = uint64(MeshIDPrefix) << 32 | uint64(BlockIndex);
To.Id = Id;
}
check(GeneratedLayout->Blocks.IsEmpty() || GeneratedLayout->Blocks[0].Id != FLayoutBlock::InvalidBlockId);
GenerateLayoutConstantState.GeneratedLayouts.Add({ SourceLayout,MeshIDPrefix }, GeneratedLayout);
return GeneratedLayout;
}
Ptr<ASTOp> CodeGenerator::GenerateImageBlockPatch(Ptr<ASTOp> InBlockOp,
const NodeModifierSurfaceEdit::FTexture& Patch,
TSharedPtr<FImage> PatchMask,
Ptr<ASTOp> conditionAd,
const FImageGenerationOptions& ImageOptions )
{
// Blend operation
Ptr<ASTOp> FinalOp;
{
MUTABLE_CPUPROFILER_SCOPE(PatchBlend);
Ptr<ASTOpImageLayer> LayerOp = new ASTOpImageLayer();
LayerOp->blendType = Patch.PatchBlendType;
LayerOp->base = InBlockOp;
// When we patch from edit nodes, we want to apply it to all the channels.
// \todo: since we can choose the patch function, maybe we want to be able to select this as well.
LayerOp->Flags = Patch.bPatchApplyToAlpha ? OP::ImageLayerArgs::F_APPLY_TO_ALPHA : 0;
NodeImage* ImageNode = Patch.PatchImage.get();
Ptr<ASTOp> BlendOp;
if (ImageNode)
{
FImageGenerationResult BlendResult;
GenerateImage(ImageOptions, BlendResult, ImageNode);
BlendOp = BlendResult.op;
}
else
{
BlendOp = GenerateMissingImageCode(TEXT("Patch top image"), EImageFormat::RGB_UByte, nullptr, ImageOptions);
}
BlendOp = GenerateImageFormat(BlendOp, InBlockOp->GetImageDesc().m_format);
BlendOp = GenerateImageSize(BlendOp, ImageOptions.RectSize);
LayerOp->blend = BlendOp;
// Create the rect mask constant
Ptr<ASTOp> RectConstantOp;
{
Ptr<NodeImageConstant> pNode = new NodeImageConstant();
pNode->SetValue(PatchMask);
FImageGenerationOptions ConstantOptions(-1,-1);
FImageGenerationResult ConstantResult;
GenerateImage(ConstantOptions, ConstantResult, pNode);
RectConstantOp = ConstantResult.op;
}
NodeImage* MaskNode = Patch.PatchMask.get();
Ptr<ASTOp> MaskOp;
if (MaskNode)
{
// Combine the block rect mask with the user provided mask.
FImageGenerationResult MaskResult;
GenerateImage(ImageOptions, MaskResult, MaskNode);
MaskOp = MaskResult.op;
Ptr<ASTOpImageLayer> PatchCombineOp = new ASTOpImageLayer;
PatchCombineOp->base = MaskOp;
PatchCombineOp->blend = RectConstantOp;
PatchCombineOp->blendType = EBlendType::BT_MULTIPLY;
MaskOp = PatchCombineOp;
}
else
{
MaskOp = RectConstantOp;
}
MaskOp = GenerateImageFormat(MaskOp, EImageFormat::L_UByte);
MaskOp = GenerateImageSize(MaskOp, ImageOptions.RectSize);
LayerOp->mask = MaskOp;
FinalOp = LayerOp;
}
// Condition to enable this patch
if (conditionAd)
{
Ptr<ASTOp> conditionalAd;
{
Ptr<ASTOpConditional> op = new ASTOpConditional();
op->type = EOpType::IM_CONDITIONAL;
op->no = InBlockOp;
op->yes = FinalOp;
op->condition = conditionAd;
conditionalAd = op;
}
FinalOp = conditionalAd;
}
return FinalOp;
}
//---------------------------------------------------------------------------------------------
void CodeGenerator::GenerateComponent(const FComponentGenerationOptions& InOptions, FGenericGenerationResult& OutResult, const NodeComponent* InUntypedNode)
{
if (!InUntypedNode)
{
OutResult = FGenericGenerationResult();
return;
}
// See if it was already generated
FGeneratedComponentCacheKey Key;
Key.Node = InUntypedNode;
Key.Options = InOptions;
FGeneratedComponentMap::ValueType* it = GeneratedComponents.Find(Key);
if (it)
{
OutResult = *it;
return;
}
// Generate for each different type of node
const FNodeType* Type = InUntypedNode->GetType();
if (Type == NodeComponentNew::GetStaticType())
{
GenerateComponent_New(InOptions, OutResult, static_cast<const NodeComponentNew*>(InUntypedNode));
}
else if (Type == NodeComponentEdit::GetStaticType())
{
// Nothing to do because it is all preprocessed in the first code generator stage
//GenerateComponent_Edit(InOptions, OutResult, static_cast<const NodeComponentEdit*>(InUntypedNode));
OutResult.Op = InOptions.BaseInstance;
}
else if (Type == NodeComponentSwitch::GetStaticType())
{
GenerateComponent_Switch(InOptions, OutResult, static_cast<const NodeComponentSwitch*>(InUntypedNode));
}
else if (Type == NodeComponentVariation::GetStaticType())
{
GenerateComponent_Variation(InOptions, OutResult, static_cast<const NodeComponentVariation*>(InUntypedNode));
}
else
{
check(false);
}
// Cache the result
GeneratedComponents.Add(Key, OutResult);
}
void CodeGenerator::GenerateComponent_New(const FComponentGenerationOptions& Options, FGenericGenerationResult& Result, const NodeComponentNew* InNode)
{
TArray<FLODTask> LODTasks;
LODTasks.Reserve(InNode->LODs.Num());
// Launch tasks for each lod, making sure each LOD depends on the previous one.
FLODTask LastLODTask;
for (int32 LODIndex = 0; LODIndex < InNode->LODs.Num(); ++LODIndex)
{
if (const NodeLOD* LODNode = InNode->LODs[LODIndex].get())
{
FLODGenerationOptions LODOptions(Options, LODIndex, InNode );
bool bWasEmpty = false;
LastLODTask = GenerateLOD(LODOptions, LODNode, LastLODTask);
LODTasks.Add(LastLODTask);
}
}
// Launch the task that generates the component
// There could be more concurrency here, but it doesn't look like it is relevant yet.
FComponentTask ComponentTask = LocalPipe.Launch(TEXT("MutableComponentNew"),
[
LODTasks, InNode, Options,
this
]
() mutable
{
FGenericGenerationResult Result;
// Create the expression for each component in this object
Ptr<ASTOpAddLOD> LODsOp = new ASTOpAddLOD();
for (FLODTask& LODTask : LODTasks)
{
FGenericGenerationResult LODResult = LODTask.GetResult();
LODsOp->lods.Emplace(LODsOp, LODResult.Op);
}
Ptr<ASTOp> LastInstOp = LODsOp;
if (Ptr<NodeScalar> pScalarNode = InNode->OverlayMaterial)
{
Ptr<ASTOpAddOverlayMaterial> AddOverlayMaterialOp = new ASTOpAddOverlayMaterial();
AddOverlayMaterialOp->Instance = LastInstOp;
// Scalar
FScalarGenerationResult ScalarResult;
GenerateScalar(ScalarResult, Options, pScalarNode);
AddOverlayMaterialOp->OverlayMaterialId = ScalarResult.op;
LastInstOp = AddOverlayMaterialOp;
}
Ptr<ASTOpInstanceAdd> InstanceOp = new ASTOpInstanceAdd();
InstanceOp->type = EOpType::IN_ADDCOMPONENT;
InstanceOp->instance = Options.BaseInstance;
InstanceOp->value = LastInstOp;
InstanceOp->ExternalId = InNode->Id;
Result.Op = InstanceOp;
// Add a conditional if this component has conditions
for (const FirstPassGenerator::FComponent& Component : FirstPass.Components)
{
if (Component.Component != InNode)
{
continue;
}
if (Component.ComponentCondition || Component.ObjectCondition)
{
// TODO: This could be done earlier?
Ptr<ASTOpBoolAnd> ConditionOp = new ASTOpBoolAnd();
ConditionOp->A = Component.ObjectCondition;
ConditionOp->B = Component.ComponentCondition;
Ptr<ASTOpConditional> IfOp = new ASTOpConditional();
IfOp->type = EOpType::IN_CONDITIONAL;
IfOp->no = Options.BaseInstance;
IfOp->yes = Result.Op;
IfOp->condition = ConditionOp;
Result.Op = IfOp;
}
}
return Result;
},
LODTasks
);
// Sync point: we currently don't support task-based generation beyond components so we wait here.
// Otherwise we could return the component task to be chained with higher level tasks.
if (WaitCallback.IsSet())
{
while (!ComponentTask.IsCompleted())
{
WaitCallback();
}
}
Result = ComponentTask.GetResult();
}
void CodeGenerator::GenerateComponent_Switch(const FComponentGenerationOptions& Options, FGenericGenerationResult& Result, const NodeComponentSwitch* Node)
{
MUTABLE_CPUPROFILER_SCOPE(NodeComponentSwitch);
if (Node->Options.Num() == 0)
{
// No options in the switch!
Result.Op = Options.BaseInstance;
return;
}
Ptr<ASTOpSwitch> Op = new ASTOpSwitch();
Op->Type = EOpType::IN_SWITCH;
// Variable value
if (Node->Parameter)
{
FScalarGenerationResult ParamResult;
GenerateScalar(ParamResult, Options, Node->Parameter.get());
Op->Variable = ParamResult.op;
}
else
{
// This argument is required
Op->Variable = GenerateMissingScalarCode(TEXT("Switch variable"), 0.0f, Node->GetMessageContext());
}
// Options
for (int32 OptionIndex = 0; OptionIndex < Node->Options.Num(); ++OptionIndex)
{
Ptr<ASTOp> Branch;
if (Node->Options[OptionIndex])
{
FGenericGenerationResult BaseResult;
GenerateComponent(Options, BaseResult, Node->Options[OptionIndex].get());
Branch = BaseResult.Op;
}
else
{
// This argument is not required
Branch = Options.BaseInstance;
}
Op->Cases.Emplace(OptionIndex, Op, Branch);
}
Result.Op = Op;
}
void CodeGenerator::GenerateComponent_Variation(const FComponentGenerationOptions& Options, FGenericGenerationResult& Result, const NodeComponentVariation* Node)
{
Ptr<ASTOp> CurrentMeshOp = Options.BaseInstance;
// Default case
if (Node->DefaultComponent)
{
FGenericGenerationResult BranchResults;
GenerateComponent(Options, BranchResults, Node->DefaultComponent.get());
CurrentMeshOp = BranchResults.Op;
}
// Process variations in reverse order, since conditionals are built bottom-up.
for (int32 VariationIndex = Node->Variations.Num() - 1; VariationIndex >= 0; --VariationIndex)
{
int32 TagIndex = -1;
const FString& Tag = Node->Variations[VariationIndex].Tag;
for (int32 i = 0; i < FirstPass.Tags.Num(); ++i)
{
if (FirstPass.Tags[i].Tag == Tag)
{
TagIndex = i;
}
}
if (TagIndex < 0)
{
ErrorLog->Add(
FString::Printf(TEXT("Unknown tag found in component variation [%s]."), *Tag),
ELMT_WARNING,
Node->GetMessageContext(),
ELMSB_UNKNOWN_TAG
);
continue;
}
Ptr<ASTOp> VariationMeshOp = Options.BaseInstance;
if (Node->Variations[VariationIndex].Component)
{
FGenericGenerationResult BranchResults;
GenerateComponent(Options, BranchResults, Node->Variations[VariationIndex].Component.get());
VariationMeshOp = BranchResults.Op;
}
Ptr<ASTOpConditional> Conditional = new ASTOpConditional;
Conditional->type = EOpType::IN_CONDITIONAL;
Conditional->no = CurrentMeshOp;
Conditional->yes = VariationMeshOp;
Conditional->condition = FirstPass.Tags[TagIndex].GenericCondition;
CurrentMeshOp = Conditional;
}
Result.Op = CurrentMeshOp;
}
Ptr<ASTOp> CodeGenerator::ApplyTiling(Ptr<ASTOp> Source, UE::Math::TIntVector2<int32> Size, EImageFormat Format)
{
// For now always apply tiling
if (CompilerOptions->ImageTiling==0)
{
return Source;
}
int32 TileSize = CompilerOptions->ImageTiling;
int32 TilesX = FMath::DivideAndRoundUp<int32>(Size[0], TileSize);
int32 TilesY = FMath::DivideAndRoundUp<int32>(Size[1], TileSize);
if (TilesX * TilesY <= 2)
{
return Source;
}
Ptr<ASTOpImagePlainColor> BaseImage = new ASTOpImagePlainColor;
BaseImage->Size[0] = Size[0];
BaseImage->Size[1] = Size[1];
BaseImage->Format = Format;
BaseImage->LODs = 1;
Ptr<ASTOp> CurrentImage = BaseImage;
for (int32 Y = 0; Y < TilesY; ++Y)
{
for (int32 X = 0; X < TilesX; ++X)
{
int32 MinX = X * TileSize;
int32 MinY = Y * TileSize;
int32 TileSizeX = FMath::Min(TileSize, Size[0] - MinX);
int32 TileSizeY = FMath::Min(TileSize, Size[1] - MinY);
Ptr<ASTOpImageCrop> TileImage = new ASTOpImageCrop();
TileImage->Source = Source;
TileImage->Min[0] = MinX;
TileImage->Min[1] = MinY;
TileImage->Size[0] = TileSizeX;
TileImage->Size[1] = TileSizeY;
Ptr<ASTOpImagePatch> PatchedImage = new ASTOpImagePatch();
PatchedImage->base = CurrentImage;
PatchedImage->patch = TileImage;
PatchedImage->location[0] = MinX;
PatchedImage->location[1] = MinY;
CurrentImage = PatchedImage;
}
}
return CurrentImage;
}
TSharedPtr<FImage> CodeGenerator::GenerateImageBlockPatchMask(const NodeModifierSurfaceEdit::FTexture& Patch, FIntPoint GridSize, int32 BlockPixelsX, int32 BlockPixelsY, box<FIntVector2> RectInCells )
{
// Create a patching mask for the block
TSharedPtr<FImage> PatchMask;
FIntVector2 SourceTextureSize = { GridSize[0] * BlockPixelsX, GridSize[1] * BlockPixelsY };
FInt32Rect BlockRectInPixels;
BlockRectInPixels.Min = { RectInCells.min[0] * BlockPixelsX, RectInCells.min[1] * BlockPixelsY };
BlockRectInPixels.Max = { (RectInCells.min[0] + RectInCells.size[0]) * BlockPixelsX, (RectInCells.min[1] + RectInCells.size[1]) * BlockPixelsY };
for (const FBox2f& PatchRect : Patch.PatchBlocks)
{
// Does the patch rect intersects the current block at all?
FInt32Rect PatchRectInPixels;
PatchRectInPixels.Min = { int32(PatchRect.Min[0] * SourceTextureSize[0]), int32(PatchRect.Min[1] * SourceTextureSize[1]) };
PatchRectInPixels.Max = { int32(PatchRect.Max[0] * SourceTextureSize[0]), int32(PatchRect.Max[1] * SourceTextureSize[1]) };
FInt32Rect BlockPatchRect = PatchRectInPixels;
BlockPatchRect.Clip(BlockRectInPixels);
if (BlockPatchRect.Area() > 0)
{
FInt32Point BlockSize = BlockRectInPixels.Size();
if (!PatchMask)
{
PatchMask = MakeShared<FImage>(BlockSize[0], BlockSize[1], 1, mu::EImageFormat::L_UByte, mu::EInitializationType::Black);
}
uint8* Pixels = PatchMask->GetMipData(0);
FInt32Point BlockPatchOffset = BlockPatchRect.Min - BlockRectInPixels.Min;
FInt32Point BlockPatchSize = BlockPatchRect.Size();
for (int32 RowIndex = BlockPatchOffset[1]; RowIndex < BlockPatchOffset[1]+BlockPatchSize[1]; ++RowIndex)
{
uint8* RowPixels = Pixels + RowIndex * BlockSize[0] + BlockPatchOffset[0];
FMemory::Memset(RowPixels, 255, BlockPatchSize[0]);
}
}
}
return PatchMask;
}
FSurfaceTask CodeGenerator::GenerateSurface( const FSurfaceGenerationOptions& Options, Ptr<const NodeSurfaceNew> SurfaceNode, FLODTask PreviousLODTask )
{
MUTABLE_CPUPROFILER_SCOPE(GenerateSurface);
// Generate the mesh
//------------------------------------------------------------------------
// We don't add the mesh here, since it will be added directly at the top of the
// component expression in the NodeComponentNew generator with the right merges
// and conditions.
// But we store it to be used then.
// Do we need to generate the mesh? Or was it already generated for state conditions
// accepting the current state?
TArray<FirstPassGenerator::FSurface*> TargetSurfaces;
TargetSurfaces.Reserve(FirstPass.Surfaces.Num());
for (FirstPassGenerator::FSurface& Surface : FirstPass.Surfaces)
{
if (Surface.Node != SurfaceNode)
{
continue;
}
// Check state conditions
const bool bSurfaceValidForThisState =
Options.State >= Surface.StateCondition.Num() ||
Surface.StateCondition[Options.State];
if (!bSurfaceValidForThisState)
{
continue;
}
if (Surface.ResultSurfaceTask.IsValid())
{
// Reuse the entire surface
return Surface.ResultSurfaceTask;
}
else
{
// Not already generated, we will generate this
TargetSurfaces.Add( &Surface );
}
}
if (TargetSurfaces.IsEmpty())
{
return UE::Tasks::MakeCompletedTask<FSurfaceGenerationResult>();
}
// Gather all modifiers that apply to this surface
TArray<FirstPassGenerator::FModifier> Modifiers;
constexpr bool bModifiersForBeforeOperations = false;
// Store the data necessary to apply modifiers for the pre-normal operations stage.
// TODO: Should we merge with currently active tags from the InOptions?
int32 ComponentId = Options.Component ? Options.Component->Id : -1;
GetModifiersFor(ComponentId, SurfaceNode->Tags, bModifiersForBeforeOperations, Modifiers);
// This pass on the modifiers is only to detect errors that cannot be detected at the point they are applied.
CheckModifiersForSurface(*SurfaceNode, Modifiers, Options.LODIndex);
// Generate the mesh
FMeshGenerationStaticOptions MeshStaticOptions(ComponentId, Options.LODIndex);
MeshStaticOptions.ActiveTags = SurfaceNode->Tags;
MeshStaticOptions.State = Options.State;
FMeshGenerationDynamicOptions MeshDynamicOptions;
MeshDynamicOptions.bLayouts = true;
// Normalize UVs if we're going to work with images and layouts.
// TODO: This should come from per-layout settings!
const bool bNormalizeUVs = false; // !SurfaceNode->Images.IsEmpty();
MeshDynamicOptions.bNormalizeUVs = bNormalizeUVs;
// The options depenend on the shared surface being generated, so we need to add the previous lod dependency
FMeshOptionsTask MeshOptionsTask = LocalPipe.Launch(TEXT("MutableSurfaceMeshOptions"),
[MeshDynamicOptions, SharedSurfaceId=SurfaceNode->SharedSurfaceId, this]() mutable
{
// This assumes that the lods are processed in order. It checks it this way because some platforms may have empty lods at the top.
bool bIsBaseForSharedSurface = SharedSurfaceId != INDEX_NONE;
if (bIsBaseForSharedSurface)
{
UE::TUniqueLock Lock(SharedMeshOptions.Mutex);
bIsBaseForSharedSurface = !SharedMeshOptions.Map.Contains(SharedSurfaceId);
}
// If this is true, we will reuse the surface properties from a higher LOD, se we can skip the generation of material properties and images.
const bool bShareSurface = SharedSurfaceId != INDEX_NONE && !bIsBaseForSharedSurface;
const FMeshGenerationResult* SharedMeshResults = nullptr;
if (bShareSurface)
{
UE::TUniqueLock Lock(SharedMeshOptions.Mutex);
// Do we have the surface we need to share it with?
SharedMeshResults = SharedMeshOptions.Map.Find(SharedSurfaceId);
check(SharedMeshResults);
// Override the layouts with the ones from the surface we share
MeshDynamicOptions.OverrideLayouts = SharedMeshResults->GeneratedLayouts;
}
// Ensure UV islands remain within their main layout block on lower LODs to avoid unexpected reordering
// of the layout blocks when reusing a surface between LODs. Used to fix small displacements on vertices
// that may cause them to fall on a different block.
MeshDynamicOptions.bClampUVIslands = bShareSurface;
return MeshDynamicOptions;
},
PreviousLODTask
);
FMeshTask MeshTask = GenerateMesh(MeshStaticOptions, MeshOptionsTask, SurfaceNode->Mesh);
// Apply the modifier for the post-normal operations stage.
MeshTask = ApplyMeshModifiers(Modifiers,
MeshStaticOptions, MeshOptionsTask,
MeshTask, SurfaceNode->SharedSurfaceId, SurfaceNode->GetMessageContext(), nullptr);
FSurfaceTask SurfaceTask = LocalPipe.Launch(TEXT("MutableSurface"),
[
MeshTask,
SurfaceNode,
ComponentId, Options,
Modifiers,
TargetSurfaces,
// We need to call some local methods which should be fine sinc4e.
this
]
() mutable
{
FMeshGenerationResult MeshResults = MeshTask.GetResult();
// Base mesh is allowed to be missing, aggregate all layouts and operations per layout indices in the
// generated mesh, base and extends.
TArray<FGeneratedLayout> SurfaceReferenceLayouts;
TArray<Ptr<ASTOp>> SurfaceLayoutOps;
int32 MaxLayoutNum = MeshResults.GeneratedLayouts.Num();
for (const FMeshGenerationResult::FExtraLayouts& ExtraLayoutData : MeshResults.ExtraMeshLayouts)
{
MaxLayoutNum = FMath::Max(MaxLayoutNum, ExtraLayoutData.GeneratedLayouts.Num());
}
SurfaceReferenceLayouts.SetNum(MaxLayoutNum);
SurfaceLayoutOps.SetNum(MaxLayoutNum);
TBitArray<> LayoutFromExtension;
LayoutFromExtension.Init(false, MaxLayoutNum);
// Scope for access control to shared data
bool bIsBaseForSharedSurface = false;
bool bShareSurface = false;
TArray<Ptr<ASTOp>> SharedResultLayoutOps;
{
UE::TUniqueLock Lock(SharedMeshOptions.Mutex);
bIsBaseForSharedSurface = SurfaceNode->SharedSurfaceId!=INDEX_NONE
&&
!SharedMeshOptions.Map.Contains(SurfaceNode->SharedSurfaceId);
// If this is true, we will reuse the surface properties from a higher LOD, se we can skip the generation of material properties and images.
bShareSurface = SurfaceNode->SharedSurfaceId != INDEX_NONE && !bIsBaseForSharedSurface;
const FMeshGenerationResult* SharedMeshResults = nullptr;
if (bShareSurface)
{
// Do we have the surface we need to share it with?
SharedMeshResults = SharedMeshOptions.Map.Find(SurfaceNode->SharedSurfaceId);
check(SharedMeshResults);
SharedResultLayoutOps = SharedMeshResults->LayoutOps;
}
// Add layouts form the base mesh.
for (int32 LayoutIndex = 0; LayoutIndex < MeshResults.GeneratedLayouts.Num(); ++LayoutIndex)
{
if (!MeshResults.GeneratedLayouts[LayoutIndex].Layout)
{
continue;
}
SurfaceReferenceLayouts[LayoutIndex] = MeshResults.GeneratedLayouts[LayoutIndex];
bool bSharedHasThisLayout = SharedMeshResults
&&
SharedMeshResults->LayoutOps.IsValidIndex(LayoutIndex)
&&
SharedMeshResults->LayoutOps[LayoutIndex];
if (bSharedHasThisLayout)
{
SurfaceLayoutOps[LayoutIndex] = SharedMeshResults->LayoutOps[LayoutIndex];
}
else
{
Ptr<ASTOpConstantResource> ConstantLayoutOp = new ASTOpConstantResource();
ConstantLayoutOp->Type = EOpType::LA_CONSTANT;
ConstantLayoutOp->SetValue(
SurfaceReferenceLayouts[LayoutIndex].Layout,
CompilerOptions->OptimisationOptions.DiskCacheContext);
SurfaceLayoutOps[LayoutIndex] = ConstantLayoutOp;
}
}
}
// Add extra layouts. In case there is a missing reference layout, the first visited will
// take the role.
for (const FMeshGenerationResult::FExtraLayouts& ExtraLayoutsData : MeshResults.ExtraMeshLayouts)
{
if (!ExtraLayoutsData.MeshFragment)
{
// No mesh to add, we assume there are no layouts to add either.
check(ExtraLayoutsData.GeneratedLayouts.IsEmpty());
continue;
}
const TArray<FGeneratedLayout>& ExtraGeneratedLayouts = ExtraLayoutsData.GeneratedLayouts;
for (int32 LayoutIndex = 0; LayoutIndex < ExtraGeneratedLayouts.Num(); ++LayoutIndex)
{
if (!ExtraGeneratedLayouts[LayoutIndex].Layout)
{
continue;
}
bool bLayoutSetByThisExtension = false;
if (!SurfaceReferenceLayouts[LayoutIndex].Layout)
{
// This Layout slot is not set by the base surface, set it as reference.
SurfaceReferenceLayouts[LayoutIndex] = ExtraGeneratedLayouts[LayoutIndex];
bLayoutSetByThisExtension = true;
LayoutFromExtension[LayoutIndex] = bLayoutSetByThisExtension;
}
if (bShareSurface)
{
if (!SurfaceLayoutOps[LayoutIndex] && bLayoutSetByThisExtension)
{
check(SharedResultLayoutOps.IsValidIndex(LayoutIndex));
SurfaceLayoutOps[LayoutIndex] = SharedResultLayoutOps[LayoutIndex];
}
}
else
{
Ptr<ASTOpConstantResource> LayoutFragmentConstantOp = new ASTOpConstantResource();
LayoutFragmentConstantOp->Type = EOpType::LA_CONSTANT;
LayoutFragmentConstantOp->SetValue(
ExtraLayoutsData.GeneratedLayouts[LayoutIndex].Layout,
CompilerOptions->OptimisationOptions.DiskCacheContext);
Ptr<ASTOpLayoutMerge> LayoutMergeOp = new ASTOpLayoutMerge();
// Base may be null if the base does not have a mesh with a layout at LayoutIndex.
// In that case, when applying the condition this can generate null layouts.
LayoutMergeOp->Base = SurfaceLayoutOps[LayoutIndex];
LayoutMergeOp->Added = LayoutFragmentConstantOp;
if (ExtraLayoutsData.Condition)
{
Ptr<ASTOpConditional> ConditionalOp = new ASTOpConditional();
ConditionalOp->type = EOpType::LA_CONDITIONAL;
ConditionalOp->no = SurfaceLayoutOps[LayoutIndex];
ConditionalOp->yes = LayoutMergeOp;
ConditionalOp->condition = ExtraLayoutsData.Condition;
SurfaceLayoutOps[LayoutIndex] = ConditionalOp;
}
else
{
SurfaceLayoutOps[LayoutIndex] = LayoutMergeOp;
}
}
}
}
Ptr<ASTOp> LastMeshOp = MeshResults.MeshOp;
check(SurfaceReferenceLayouts.Num() == SurfaceLayoutOps.Num());
for (int32 LayoutIndex = 0; LayoutIndex < SurfaceReferenceLayouts.Num(); ++LayoutIndex)
{
if (!SurfaceReferenceLayouts[LayoutIndex].Layout)
{
continue;
}
if (SurfaceReferenceLayouts[LayoutIndex].Layout->GetLayoutPackingStrategy() == mu::EPackStrategy::Overlay)
{
continue;
}
// Add layout packing instructions
if (!bShareSurface)
{
// Make sure we removed unnecessary blocks
Ptr<ASTOpLayoutFromMesh> ExtractOp = new ASTOpLayoutFromMesh();
ExtractOp->Mesh = LastMeshOp;
check(LayoutIndex < 256);
ExtractOp->LayoutIndex = uint8(LayoutIndex);
Ptr<ASTOpLayoutRemoveBlocks> RemoveOp = new ASTOpLayoutRemoveBlocks();
RemoveOp->Source = SurfaceLayoutOps[LayoutIndex];
RemoveOp->ReferenceLayout = ExtractOp;
SurfaceLayoutOps[LayoutIndex] = RemoveOp;
// Pack uv blocks
Ptr<ASTOpLayoutPack> LayoutPackOp = new ASTOpLayoutPack();
LayoutPackOp->Source = SurfaceLayoutOps[LayoutIndex];
SurfaceLayoutOps[LayoutIndex] = LayoutPackOp;
}
// Create the expression to apply the layout to the mesh
{
Ptr<ASTOpMeshApplyLayout> ApplyLayoutOp = new ASTOpMeshApplyLayout();
ApplyLayoutOp->Mesh = LastMeshOp;
ApplyLayoutOp->Layout = SurfaceLayoutOps[LayoutIndex];
ApplyLayoutOp->Channel = (uint16)LayoutIndex;
LastMeshOp = ApplyLayoutOp;
}
}
MeshResults.GeneratedLayouts = MoveTemp(SurfaceReferenceLayouts);
MeshResults.LayoutOps = MoveTemp(SurfaceLayoutOps);
// Store in the surface for later use.
for (FirstPassGenerator::FSurface* TargetSurface : TargetSurfaces)
{
TargetSurface->ResultMeshOp = LastMeshOp;
}
// Build a series of operations to assemble the surface
Ptr<ASTOp> LastSurfOp;
// Create the expression for each texture, if we are not reusing the surface from another LOD.
//------------------------------------------------------------------------
if (!bShareSurface)
{
for (int32 ImageIndex = 0; ImageIndex < SurfaceNode->Images.Num(); ++ImageIndex)
{
MUTABLE_CPUPROFILER_SCOPE(SurfaceTexture);
// Any image-specific format or mipmapping needs to be applied at the end
Ptr<NodeImageMipmap> mipmapNode;
Ptr<NodeImageFormat> formatNode;
Ptr<NodeImageSwizzle> swizzleNode;
bool bFound = false;
Ptr<NodeImage> pImageNode = SurfaceNode->Images[ImageIndex].Image;
while (!bFound && pImageNode)
{
if (pImageNode->GetType() == NodeImageMipmap::GetStaticType())
{
NodeImageMipmap* tm = static_cast<NodeImageMipmap*>(pImageNode.get());
if (!mipmapNode) mipmapNode = tm;
pImageNode = tm->Source;
}
else if (pImageNode->GetType() == NodeImageFormat::GetStaticType())
{
NodeImageFormat* tf = static_cast<NodeImageFormat*>(pImageNode.get());
if (!formatNode) formatNode = tf;
pImageNode = tf->Source;
}
else if (pImageNode->GetType() == NodeImageSwizzle::GetStaticType())
{
NodeImageSwizzle* ts = static_cast<NodeImageSwizzle*>(pImageNode.get());
if (!ts->Sources.IsEmpty())
{
NodeImage* Source = ts->Sources[0].get();
bool bAllSourcesAreTheSame = true;
for (int32 SourceIndex = 1; SourceIndex < ts->Sources.Num(); ++SourceIndex)
{
bAllSourcesAreTheSame = bAllSourcesAreTheSame && (Source == ts->Sources[SourceIndex]);
}
if (!swizzleNode && bAllSourcesAreTheSame)
{
swizzleNode = ts;
pImageNode = Source;
}
else
{
bFound = true;
}
}
else
{
// break loop if swizzle has no sources.
bFound = true;
}
}
else
{
bFound = true;
}
}
if (bFound)
{
const NodeSurfaceNew::FImageData& ImageData = SurfaceNode->Images[ImageIndex];
const int32 LayoutIndex = ImageData.LayoutIndex;
// If the layout index has been set to negative, it means we should ignore the layout for this image.
CompilerOptions::TextureLayoutStrategy ImageLayoutStrategy = (LayoutIndex < 0)
? CompilerOptions::TextureLayoutStrategy::None
: CompilerOptions::TextureLayoutStrategy::Pack
;
if (ImageLayoutStrategy == CompilerOptions::TextureLayoutStrategy::None)
{
// Generate the image
FImageGenerationOptions ImageOptions(ComponentId, Options.LODIndex);
ImageOptions.State = Options.State;
ImageOptions.ImageLayoutStrategy = ImageLayoutStrategy;
ImageOptions.ActiveTags = SurfaceNode->Tags;
ImageOptions.RectSize = { 0, 0 };
// TODO: To tasks
FImageGenerationResult Result;
GenerateImage(ImageOptions, Result, pImageNode);
Ptr<ASTOp> imageAd = Result.op;
// Placeholder block. Ideally this should be the actual image size
constexpr int32 FakeLayoutSize = 256;
FIntPoint GridSize(FakeLayoutSize, FakeLayoutSize);
FLayoutBlockDesc LayoutBlockDesc;
LayoutBlockDesc.BlockPixelsX = 1;
LayoutBlockDesc.BlockPixelsY = 1;
box< FIntVector2 > RectInCells;
RectInCells.min = { 0,0 };
RectInCells.size = { FakeLayoutSize ,FakeLayoutSize };
imageAd = ApplyImageBlockModifiers(Modifiers, ImageOptions, imageAd, ImageData, GridSize, LayoutBlockDesc, RectInCells, SurfaceNode->GetMessageContext());
check(imageAd);
if (swizzleNode)
{
Ptr<ASTOpImageSwizzle> fop = new ASTOpImageSwizzle();
fop->Format = swizzleNode->NewFormat;
fop->Sources[0] = imageAd;
fop->Sources[1] = imageAd;
fop->Sources[2] = imageAd;
fop->Sources[3] = imageAd;
fop->SourceChannels[0] = swizzleNode->SourceChannels[0];
fop->SourceChannels[1] = swizzleNode->SourceChannels[1];
fop->SourceChannels[2] = swizzleNode->SourceChannels[2];
fop->SourceChannels[3] = swizzleNode->SourceChannels[3];
check(fop->Format != EImageFormat::None);
imageAd = fop;
}
if (mipmapNode)
{
Ptr<ASTOpImageMipmap> op = new ASTOpImageMipmap();
op->Levels = 0;
op->Source = imageAd;
op->BlockLevels = 0;
op->AddressMode = mipmapNode->Settings.AddressMode;
op->FilterType = mipmapNode->Settings.FilterType;
imageAd = op;
}
if (formatNode)
{
Ptr<ASTOpImagePixelFormat> fop = new ASTOpImagePixelFormat();
fop->Format = formatNode->Format;
fop->FormatIfAlpha = formatNode->FormatIfAlpha;
fop->Source = imageAd;
check(fop->Format != EImageFormat::None);
imageAd = fop;
}
Ptr<ASTOpInstanceAdd> op = new ASTOpInstanceAdd();
op->type = EOpType::IN_ADDIMAGE;
op->instance = LastSurfOp;
op->value = imageAd;
op->name = SurfaceNode->Images[ImageIndex].Name;
LastSurfOp = op;
}
else if (ImageLayoutStrategy == CompilerOptions::TextureLayoutStrategy::Pack) //-V547
{
if (LayoutIndex >= MeshResults.GeneratedLayouts.Num() ||
LayoutIndex >= MeshResults.LayoutOps.Num())
{
ErrorLog->Add("Missing layout in object, or its parent.", ELMT_ERROR, SurfaceNode->GetMessageContext());
}
else
{
TSharedPtr<const FLayout> pLayout = MeshResults.GeneratedLayouts[LayoutIndex].Layout;
check(pLayout);
Ptr<ASTOpInstanceAdd> op = new ASTOpInstanceAdd();
op->type = EOpType::IN_ADDIMAGE;
op->instance = LastSurfOp;
// Image
//-------------------------------------
// Size of a layout block in pixels
FIntPoint GridSize = pLayout->GetGridSize();
// Try to guess the layout block description from the first valid block that is generated.
FLayoutBlockDesc LayoutBlockDesc;
if (formatNode)
{
LayoutBlockDesc.FinalFormat = formatNode->FormatIfAlpha;
if (LayoutBlockDesc.FinalFormat == EImageFormat::None)
{
LayoutBlockDesc.FinalFormat = formatNode->Format;
}
}
bool bImageSizeWarning = false;
// Start with a blank image. It will be completed later with the blockSize, format and mips information
Ptr<ASTOpImageBlankLayout> BlankImageOp;
Ptr<ASTOp> imageAd;
{
BlankImageOp = new ASTOpImageBlankLayout();
BlankImageOp->Layout = MeshResults.LayoutOps[LayoutIndex];
// The rest ok the op will be completed below
BlankImageOp->MipmapCount = 0;
imageAd = BlankImageOp;
}
// Skip the block addition for this image if the layout was from a extension.
if (!LayoutFromExtension[LayoutIndex])
{
for (int32 BlockIndex = 0; BlockIndex < pLayout->GetBlockCount(); ++BlockIndex)
{
// Generate the image
FImageGenerationOptions ImageOptions(ComponentId, Options.LODIndex);
ImageOptions.State = Options.State;
ImageOptions.ImageLayoutStrategy = ImageLayoutStrategy;
ImageOptions.RectSize = { 0,0 };
ImageOptions.ActiveTags = SurfaceNode->Tags;
ImageOptions.LayoutToApply = pLayout;
ImageOptions.LayoutBlockId = pLayout->Blocks[BlockIndex].Id;
FImageGenerationResult ImageResult;
GenerateImage(ImageOptions, ImageResult, pImageNode);
Ptr<ASTOp> blockAd = ImageResult.op;
if (!blockAd)
{
// The GenerateImage(...) above has failed, skip this block
continue;
}
// Calculate the desc of the generated block.
constexpr bool bReturnBestOption = true;
FImageDesc BlockDesc = blockAd->GetImageDesc(bReturnBestOption, nullptr);
// Block in layout grid units (cells)
box< FIntVector2 > RectInCells;
RectInCells.min = pLayout->Blocks[BlockIndex].Min;
RectInCells.size = pLayout->Blocks[BlockIndex].Size;
// Try to update the layout block desc if we don't know it yet.
UpdateLayoutBlockDesc(LayoutBlockDesc, BlockDesc, RectInCells.size);
// Even if we force the size afterwards, we need some size hint in some cases, like image projections.
ImageOptions.RectSize = UE::Math::TIntVector2<int32>(BlockDesc.m_size);
blockAd = ApplyImageBlockModifiers(Modifiers, ImageOptions, blockAd, ImageData, GridSize, LayoutBlockDesc, RectInCells, SurfaceNode->GetMessageContext());
// Enforce block size and optimizations
blockAd = GenerateImageSize(blockAd, FIntVector2(BlockDesc.m_size));
EImageFormat baseFormat = imageAd->GetImageDesc().m_format;
// Actually don't do it, it will be propagated from the top format operation.
//Ptr<ASTOp> blockAd = GenerateImageFormat(blockAd, baseFormat);
// Apply tiling to avoid generating chunks of image that are too big.
blockAd = ApplyTiling(blockAd, ImageOptions.RectSize, LayoutBlockDesc.FinalFormat);
// Compose layout operation
Ptr<ASTOpImageCompose> composeOp = new ASTOpImageCompose();
composeOp->Layout = MeshResults.LayoutOps[LayoutIndex];
composeOp->Base = imageAd;
composeOp->BlockImage = blockAd;
// Set the absolute block index.
check(pLayout->Blocks[BlockIndex].Id != FLayoutBlock::InvalidBlockId);
composeOp->BlockId = pLayout->Blocks[BlockIndex].Id;
imageAd = composeOp;
}
}
check(imageAd);
FMeshGenerationStaticOptions ModifierOptions(ComponentId, Options.LODIndex);
ModifierOptions.State = Options.State;
ModifierOptions.ActiveTags = SurfaceNode->Tags;
imageAd = ApplyImageExtendModifiers(Modifiers, ModifierOptions, MeshResults, imageAd, ImageLayoutStrategy,
LayoutIndex, ImageData, GridSize, LayoutBlockDesc,
SurfaceNode->GetMessageContext());
// Complete the base op
BlankImageOp->BlockSize[0] = uint16(LayoutBlockDesc.BlockPixelsX);
BlankImageOp->BlockSize[1] = uint16(LayoutBlockDesc.BlockPixelsY);
BlankImageOp->Format = GetUncompressedFormat(LayoutBlockDesc.FinalFormat);
BlankImageOp->GenerateMipmaps = LayoutBlockDesc.bBlocksHaveMips;
BlankImageOp->MipmapCount = 0;
if (swizzleNode)
{
Ptr<ASTOpImageSwizzle> fop = new ASTOpImageSwizzle();
fop->Format = swizzleNode->NewFormat;
for (int32 ChannelIndex = 0; ChannelIndex < swizzleNode->SourceChannels.Num(); ++ChannelIndex)
{
fop->Sources[ChannelIndex] = imageAd;
fop->SourceChannels[ChannelIndex] = swizzleNode->SourceChannels[ChannelIndex];
}
check(fop->Format != EImageFormat::None);
imageAd = fop;
}
// Apply mipmap and format if necessary, skip if format is None (possibly because a block was skipped above)
bool bNeedsMips =
(mipmapNode && LayoutBlockDesc.FinalFormat != EImageFormat::None)
||
LayoutBlockDesc.bBlocksHaveMips;
if (bNeedsMips)
{
Ptr<ASTOpImageMipmap> mop = new ASTOpImageMipmap();
// At the end of the day, we want all the mipmaps. Maybe the code
// optimiser will split the process later.
mop->Levels = 0;
mop->bOnlyTail = false;
mop->Source = imageAd;
// We have to avoid mips smaller than the image format block size, so
// we will devide the layout block by the format block
const FImageFormatData& PixelFormatInfo = GetImageFormatData(LayoutBlockDesc.FinalFormat);
int32 mipsX = FMath::CeilLogTwo(LayoutBlockDesc.BlockPixelsX / PixelFormatInfo.PixelsPerBlockX);
int32 mipsY = FMath::CeilLogTwo(LayoutBlockDesc.BlockPixelsY / PixelFormatInfo.PixelsPerBlockY);
mop->BlockLevels = (uint8)FMath::Max(mipsX, mipsY);
if (LayoutBlockDesc.BlockPixelsX < PixelFormatInfo.PixelsPerBlockX || LayoutBlockDesc.BlockPixelsY < PixelFormatInfo.PixelsPerBlockY)
{
// In this case, the mipmap will never be useful for blocks, so we indicate that
// it should make the mips at the root of the expression.
mop->bOnlyTail = true;
}
mop->AddressMode = EAddressMode::ClampToEdge;
mop->FilterType = EMipmapFilterType::SimpleAverage;
if (mipmapNode)
{
mop->AddressMode = mipmapNode->Settings.AddressMode;
mop->FilterType = mipmapNode->Settings.FilterType;
}
imageAd = mop;
}
if (formatNode)
{
Ptr<ASTOpImagePixelFormat> fop = new ASTOpImagePixelFormat();
fop->Format = formatNode->Format;
fop->FormatIfAlpha = formatNode->FormatIfAlpha;
fop->Source = imageAd;
check(fop->Format != EImageFormat::None);
imageAd = fop;
}
op->value = imageAd;
// Name
op->name = SurfaceNode->Images[ImageIndex].Name;
LastSurfOp = op;
}
}
else
{
// Unimplemented texture layout strategy
check(false);
}
}
}
// Create the expression for each vector
//------------------------------------------------------------------------
for (int32 t = 0; t < SurfaceNode->Vectors.Num(); ++t)
{
//MUTABLE_CPUPROFILER_SCOPE(SurfaceVector);
if (Ptr<NodeColour> pVectorNode = SurfaceNode->Vectors[t].Vector)
{
Ptr<ASTOpInstanceAdd> op = new ASTOpInstanceAdd();
op->type = EOpType::IN_ADDVECTOR;
op->instance = LastSurfOp;
// Vector
FColorGenerationResult VectorResult;
GenerateColor(VectorResult, Options, pVectorNode);
op->value = VectorResult.op;
// Name
op->name = SurfaceNode->Vectors[t].Name;
LastSurfOp = op;
}
}
// Create the expression for each scalar
//------------------------------------------------------------------------
for (int32 t = 0; t < SurfaceNode->Scalars.Num(); ++t)
{
// MUTABLE_CPUPROFILER_SCOPE(SurfaceScalar);
if (Ptr<NodeScalar> ScalarNode = SurfaceNode->Scalars[t].Scalar)
{
Ptr<ASTOpInstanceAdd> op = new ASTOpInstanceAdd();
op->type = EOpType::IN_ADDSCALAR;
op->instance = LastSurfOp;
// Scalar
FScalarGenerationResult ScalarResult;
GenerateScalar(ScalarResult, Options, ScalarNode);
op->value = ScalarResult.op;
// Name
op->name = SurfaceNode->Scalars[t].Name;
LastSurfOp = op;
}
}
// Create the expression for each string
//------------------------------------------------------------------------
for (int32 t = 0; t < SurfaceNode->Strings.Num(); ++t)
{
if (Ptr<NodeString> StringNode = SurfaceNode->Strings[t].String)
{
Ptr<ASTOpInstanceAdd> op = new ASTOpInstanceAdd();
op->type = EOpType::IN_ADDSTRING;
op->instance = LastSurfOp;
FStringGenerationResult StringResult;
GenerateString(StringResult, Options, StringNode);
op->value = StringResult.op;
// Name
op->name = SurfaceNode->Strings[t].Name;
LastSurfOp = op;
}
}
}
FSurfaceGenerationResult SurfaceResult;
SurfaceResult.SurfaceOp = LastSurfOp;
// If we are going to share this surface properties, remember it.
if (bIsBaseForSharedSurface)
{
UE::TUniqueLock Lock(SharedMeshOptions.Mutex);
check(!SharedMeshOptions.Map.Contains(SurfaceNode->SharedSurfaceId));
SharedMeshOptions.Map.Add(SurfaceNode->SharedSurfaceId, MeshResults);
}
return SurfaceResult;
}
,
UE::Tasks::Prerequisites(MeshTask, MeshOptionsTask, PreviousLODTask)
);
for (FirstPassGenerator::FSurface* TargetSurface : TargetSurfaces)
{
TargetSurface->ResultSurfaceTask = SurfaceTask;
}
return SurfaceTask;
}
FLODTask CodeGenerator::GenerateLOD(const FLODGenerationOptions& Options, const NodeLOD* InNode, FLODTask PreviousLODTask )
{
MUTABLE_CPUPROFILER_SCOPE(GenerateLOD);
// Look for all surfaces that belong to this component
TArray<int32> SurfaceIndices;
SurfaceIndices.Reserve(FirstPass.Surfaces.Num());
TArray<FSurfaceTask> SurfaceTasks;
SurfaceTasks.Reserve(FirstPass.Surfaces.Num());
for (int32 SurfaceIndex = 0; SurfaceIndex < FirstPass.Surfaces.Num(); ++SurfaceIndex)
{
const FirstPassGenerator::FSurface& SurfaceData = FirstPass.Surfaces[SurfaceIndex];
if (SurfaceData.Component == Options.Component
&&
SurfaceData.LOD == Options.LODIndex)
{
// Apply state conditions: only generate it if it enabled in this state
{
bool bEnabledInThisState = true;
if (SurfaceData.StateCondition.Num() && Options.State >= 0)
{
bEnabledInThisState =
(Options.State < SurfaceData.StateCondition.Num())
&&
(SurfaceData.StateCondition[Options.State]);
}
if (!bEnabledInThisState)
{
continue;
}
}
FSurfaceGenerationOptions SurfaceOptions(Options);
FSurfaceTask SurfaceTask = GenerateSurface(SurfaceOptions, SurfaceData.Node, PreviousLODTask);
SurfaceTasks.Add(SurfaceTask);
SurfaceIndices.Add(SurfaceIndex);
}
}
TArray<UE::Tasks::FTask> Requisites;
Requisites.Reserve( SurfaceTasks.Num() + 1);
Requisites.Append(SurfaceTasks);
if (PreviousLODTask.IsValid())
{
Requisites.Add(PreviousLODTask);
}
FLODTask LODTask = LocalPipe.Launch(TEXT("MutableLOD"),
[
SurfaceTasks, SurfaceIndices,
this
]
() mutable
{
// Build a series of operations to assemble the component
Ptr<ASTOp> LastCompOp;
Ptr<ASTOp> LastMeshOp;
// This generates a different ID for each surface in the LOD and the component. It can be used to match it to the
// mesh surface. It cannot be 0 because it is a special case for the merge operation.
int32 surfaceID = 1;
for (int32 SelectedSurfaceIndex=0; SelectedSurfaceIndex <SurfaceTasks.Num(); ++SelectedSurfaceIndex)
{
FSurfaceTask& SurfaceTask = SurfaceTasks[SelectedSurfaceIndex];
FSurfaceGenerationResult SurfaceGenerationResult = SurfaceTask.GetResult();
const FirstPassGenerator::FSurface& SurfaceData = FirstPass.Surfaces[SurfaceIndices[SelectedSurfaceIndex]];
Ptr<ASTOpInstanceAdd> SurfaceOp = new ASTOpInstanceAdd();
SurfaceOp->type = EOpType::IN_ADDSURFACE;
SurfaceOp->name = SurfaceData.Node->Name;
SurfaceOp->instance = LastCompOp;
SurfaceOp->value = SurfaceGenerationResult.SurfaceOp;
SurfaceOp->id = surfaceID;
SurfaceOp->ExternalId = SurfaceData.Node->ExternalId;
SurfaceOp->SharedSurfaceId = SurfaceData.Node->SharedSurfaceId;
Ptr<ASTOp> SurfaceConditionOp = SurfaceData.FinalCondition;
{
Ptr<ASTOpConditional> op = new ASTOpConditional();
op->type = EOpType::IN_CONDITIONAL;
op->no = LastCompOp;
op->yes = SurfaceOp;
op->condition = SurfaceConditionOp;
LastCompOp = op;
}
// Add the mesh with its condition
// We add the merge op even for the first mesh, so that we set the surface id.
Ptr<ASTOp> mergeAd;
{
Ptr<ASTOp> Added = SurfaceData.ResultMeshOp;
Ptr<ASTOpMeshMerge> MergeOp = new ASTOpMeshMerge();
MergeOp->Base = LastMeshOp;
MergeOp->Added = Added;
MergeOp->NewSurfaceID = surfaceID;
mergeAd = MergeOp;
}
if (SurfaceConditionOp)
{
Ptr<ASTOpConditional> op = new ASTOpConditional();
op->type = EOpType::ME_CONDITIONAL;
op->no = LastMeshOp;
op->yes = mergeAd;
op->condition = SurfaceConditionOp;
LastMeshOp = op;
}
else
{
LastMeshOp = mergeAd;
}
++surfaceID;
}
// Add op to optimize the skinning of the resulting mesh
{
Ptr<ASTOpMeshOptimizeSkinning> mop = new ASTOpMeshOptimizeSkinning();
mop->Source = LastMeshOp;
LastMeshOp = mop;
}
// Add the component mesh
{
Ptr<ASTOpInstanceAdd> iop = new ASTOpInstanceAdd();
iop->type = EOpType::IN_ADDMESH;
iop->instance = LastCompOp;
iop->value = LastMeshOp;
LastCompOp = iop;
}
FGenericGenerationResult Result;
Result.Op = LastCompOp;
return Result;
},
Requisites
);
return LODTask;
}
void CodeGenerator::GenerateObject_New(const FObjectGenerationOptions& Options, FObjectGenerationResult& OutResult, const NodeObjectNew* InNode)
{
MUTABLE_CPUPROFILER_SCOPE(NodeObjectNew);
// There is always at least a null parent
bool bIsChildObject = Options.ParentObjectNode != nullptr;
// Add this object as current parent
FObjectGenerationOptions ChildOptions = Options;
ChildOptions.ParentObjectNode = InNode;
// Parse the child objects first, which will accumulate operations in the patching lists
for ( int32 ChildIndex=0; ChildIndex < InNode->Children.Num(); ++ChildIndex)
{
if ( const NodeObject* pChildNode = InNode->Children[ChildIndex].get() )
{
// If there are parent objects, the condition of this object depends on the condition of the parent object
FObjectGenerationOptions ThisChildOptions = ChildOptions;
if (!ThisChildOptions.CurrentObjectCondition)
{
// In case there is no group node, we generate a constant true condition
// This condition will be overwritten by the group nodes.
ThisChildOptions.CurrentObjectCondition = new ASTOpConstantBool(true);
}
// This op is ignored: everything is stored as patches to apply to the parent when it is compiled.
FObjectGenerationResult ThisResult;
GenerateObject(ThisChildOptions, ThisResult, pChildNode);
OutResult.AdditionalComponents.Append(ThisResult.AdditionalComponents);
}
}
// Create the expression adding all the components
Ptr<ASTOp> LastCompOp;
Ptr<ASTOp> PlaceholderOp;
if (bIsChildObject)
{
PlaceholderOp = new ASTOpInstanceAdd;
LastCompOp = PlaceholderOp;
}
// Add the components in this node
for ( int32 ComponentIndex=0; ComponentIndex < InNode->Components.Num(); ++ComponentIndex)
{
const NodeComponent* ComponentNode = InNode->Components[ComponentIndex].get();
if (ComponentNode)
{
FComponentGenerationOptions ComponentOptions( Options, LastCompOp );
FGenericGenerationResult ComponentResult;
GenerateComponent(ComponentOptions, ComponentResult, ComponentNode);
LastCompOp = ComponentResult.Op;
}
}
// If we didn't generate anything, make sure we don't use the placeholder.
if (LastCompOp == PlaceholderOp)
{
LastCompOp = nullptr;
PlaceholderOp = nullptr;
}
// Add the components from child objects
FObjectGenerationResult::FAdditionalComponentKey ThisKey;
ThisKey.ObjectNode = InNode;
TArray<TArray<FObjectGenerationResult::FAdditionalComponentData>> MultiAdditionalComponents;
OutResult.AdditionalComponents.MultiFind(ThisKey, MultiAdditionalComponents, true);
if (LastCompOp && !MultiAdditionalComponents.IsEmpty())
{
for (TArray<FObjectGenerationResult::FAdditionalComponentData> ThisAdditionalComponents : MultiAdditionalComponents)
{
for (const FObjectGenerationResult::FAdditionalComponentData& Additional : ThisAdditionalComponents)
{
check(Additional.PlaceholderOp);
ASTOp::Replace(Additional.PlaceholderOp, LastCompOp);
LastCompOp = Additional.ComponentOp;
}
}
}
// Store this chain of components for use in parent objects if necessary
if (LastCompOp && bIsChildObject)
{
FObjectGenerationResult::FAdditionalComponentKey ParentKey;
ParentKey.ObjectNode = Options.ParentObjectNode;
FObjectGenerationResult::FAdditionalComponentData Data;
Data.ComponentOp = LastCompOp;
Data.PlaceholderOp = PlaceholderOp;
OutResult.AdditionalComponents.FindOrAdd(ParentKey).Add(Data);
}
// Add an ASTOpAddExtensionData for each connected ExtensionData node
for (const NodeObjectNew::FNamedExtensionDataNode& NamedNode : InNode->ExtensionDataNodes)
{
if (!NamedNode.Node.get())
{
// No node connected
continue;
}
// Name must be valid
check(NamedNode.Name.Len() > 0);
FExtensionDataGenerationResult ChildResult;
GenerateExtensionData(ChildResult, Options, NamedNode.Node);
if (!ChildResult.Op.get())
{
// Failed to generate anything for this node
continue;
}
FConditionalExtensionDataOp& SavedOp = ConditionalExtensionDataOps.AddDefaulted_GetRef();
SavedOp.Condition = Options.CurrentObjectCondition;
SavedOp.ExtensionDataOp = ChildResult.Op;
SavedOp.ExtensionDataName = NamedNode.Name;
}
Ptr<ASTOp> RootOp = LastCompOp;
if (!Options.ParentObjectNode)
{
for (const FConditionalExtensionDataOp& SavedOp : ConditionalExtensionDataOps)
{
Ptr<ASTOpAddExtensionData> ExtensionPinOp = new ASTOpAddExtensionData();
ExtensionPinOp->Instance = ASTChild(ExtensionPinOp, RootOp);
ExtensionPinOp->ExtensionData = ASTChild(ExtensionPinOp, SavedOp.ExtensionDataOp);
ExtensionPinOp->ExtensionDataName = SavedOp.ExtensionDataName;
if (SavedOp.Condition.get())
{
Ptr<ASTOpConditional> ConditionOp = new ASTOpConditional();
ConditionOp->type = EOpType::IN_CONDITIONAL;
ConditionOp->no = RootOp;
ConditionOp->yes = ExtensionPinOp;
ConditionOp->condition = ASTChild(ConditionOp, SavedOp.Condition);
RootOp = ConditionOp;
}
else
{
RootOp = ExtensionPinOp;
}
}
}
OutResult.Op = RootOp;
}
void CodeGenerator::GenerateObject_Group(const FObjectGenerationOptions& Options, FObjectGenerationResult& OutResult, const NodeObjectGroup* Node)
{
TArray<FString> UsedNames;
// Parse the child objects first, which will accumulate operations in the patching lists
for ( int32 ChildIndex=0; ChildIndex < Node->Children.Num(); ++ChildIndex)
{
if ( const NodeObject* pChildNode = Node->Children[ChildIndex].get() )
{
// Look for the child condition in the first pass
Ptr<ASTOp> ConditionOp;
bool bFound = false;
for( int32 ObjectIndex = 0; !bFound && ObjectIndex<FirstPass.Objects.Num(); ObjectIndex++ )
{
FirstPassGenerator::FObject& Candidate = FirstPass.Objects[ObjectIndex];
if (Candidate.Node == pChildNode)
{
bFound = true;
ConditionOp = Candidate.Condition;
}
}
FObjectGenerationOptions ChildOptions = Options;
ChildOptions.CurrentObjectCondition = ConditionOp;
// The result op is ignored: everything is stored as data to apply when the parent is compiled.
FObjectGenerationResult ChildResult;
GenerateObject(ChildOptions, ChildResult, pChildNode );
OutResult.AdditionalComponents.Append(ChildResult.AdditionalComponents);
// Check for duplicated child names
FString ChildName = pChildNode->GetName();
if (UsedNames.Contains(ChildName))
{
FString Msg = FString::Printf(TEXT("Object group has more than one children with the same name [%s]."), *ChildName );
ErrorLog->Add(Msg, ELMT_WARNING, Node->GetMessageContext());
}
else
{
UsedNames.Add(ChildName);
}
}
}
}
//---------------------------------------------------------------------------------------------
Ptr<ASTOp> CodeGenerator::GenerateMissingBoolCode(const TCHAR* Where, bool Value, const void* ErrorContext )
{
// Log a warning
FString Msg = FString::Printf(TEXT("Required connection not found: %s"), Where);
ErrorLog->Add( Msg, ELMT_ERROR, ErrorContext);
// Create a constant node
Ptr<NodeBoolConstant> pNode = new NodeBoolConstant();
pNode->Value = Value;
FBoolGenerationResult ChildResult;
FGenericGenerationOptions Options;
GenerateBool(ChildResult,Options, pNode );
return ChildResult.op;
}
//---------------------------------------------------------------------------------------------
void CodeGenerator::GetModifiersFor(
int32 ComponentId,
const TArray<FString>& SurfaceTags,
bool bModifiersForBeforeOperations,
TArray<FirstPassGenerator::FModifier>& OutModifiers) const
{
MUTABLE_CPUPROFILER_SCOPE(GetModifiersFor);
if (SurfaceTags.IsEmpty())
{
return;
}
for (const FirstPassGenerator::FModifier& Modifier: FirstPass.Modifiers)
{
if (!Modifier.Node)
{
continue;
}
// Correct stage?
if (Modifier.Node->bApplyBeforeNormalOperations != bModifiersForBeforeOperations)
{
continue;
}
// Correct component?
if (Modifier.Node->RequiredComponentId>=0 && Modifier.Node->RequiredComponentId!=ComponentId)
{
continue;
}
// Already there?
bool bAlreadyAdded =
OutModifiers.FindByPredicate( [&Modifier](const FirstPassGenerator::FModifier& c) {return c.Node == Modifier.Node; })
!=
nullptr;
if (bAlreadyAdded)
{
continue;
}
// Matching tags?
bool bApply = false;
switch (Modifier.Node->MultipleTagsPolicy)
{
case EMutableMultipleTagPolicy::OnlyOneRequired:
{
for (const FString& Tag: Modifier.Node->RequiredTags)
{
if (SurfaceTags.Contains(Tag))
{
bApply = true;
break;
}
}
break;
}
case EMutableMultipleTagPolicy::AllRequired:
{
bApply = true;
for (const FString& Tag : Modifier.Node->RequiredTags)
{
if (!SurfaceTags.Contains(Tag))
{
bApply = false;
break;
}
}
}
}
if (bApply)
{
OutModifiers.Add(Modifier);
}
}
}
//---------------------------------------------------------------------------------------------
FMeshTask CodeGenerator::ApplyMeshModifiers(
const TArray<FirstPassGenerator::FModifier>& Modifiers,
const FMeshGenerationStaticOptions& StaticOptions,
FMeshOptionsTask Options,
FMeshTask BaseTask,
int32 SharedSurfaceId,
const void* ErrorContext,
const NodeMeshConstant* OriginalMeshNode)
{
FMeshTask LastMeshTask = BaseTask;
FMeshTask PreModifiersTask = BaseTask;
int32 CurrentLOD = StaticOptions.LODIndex;
check(CurrentLOD>=0);
// Process mesh extend modifiers (from edit modifiers)
int32 EditIndex = 0;
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType())
{
const NodeModifierSurfaceEdit* Edit = static_cast<const NodeModifierSurfaceEdit*>(Modifier.Node);
bool bAffectsCurrentLOD = Edit->LODs.IsValidIndex(CurrentLOD);
if (bAffectsCurrentLOD && Edit->LODs[CurrentLOD].MeshAdd)
{
Ptr<NodeMesh> pAdd = Edit->LODs[CurrentLOD].MeshAdd;
// Store the data necessary to apply modifiers for the pre-normal operations stage.
FMeshGenerationStaticOptions MergedMeshStaticOptions(StaticOptions);
MergedMeshStaticOptions.ActiveTags = Edit->EnableTags; // TODO: Append to current?
MergedMeshStaticOptions.ModifiersToIgnore.Add(Modifier);
FMeshOptionsTask MergedMeshOptionsTask = UE::Tasks::Launch(TEXT("MutableMergedMeshOptions"),
[Options, SharedSurfaceId, EditIndex, MergedMeshStaticOptions, this]() mutable
{
FMeshGenerationDynamicOptions Result = Options.GetResult();
Result.bEnsureAllVerticesHaveLayoutBlock = false;
UE::TUniqueLock Lock(SharedMeshOptions.Mutex);
// This assumes that the lods are processed in order. It checks it this way because some platforms may have empty lods at the top.
const bool bIsBaseForSharedSurface = SharedSurfaceId != INDEX_NONE && !SharedMeshOptions.Map.Contains(SharedSurfaceId);
// If this is true, we will reuse the surface properties from a higher LOD, se we can skip the generation of material properties and images.
const bool bShareSurface = SharedSurfaceId != INDEX_NONE && !bIsBaseForSharedSurface;
const FMeshGenerationResult* SharedMeshResults = nullptr;
if (bShareSurface)
{
// Do we have the surface we need to share it with?
SharedMeshResults = SharedMeshOptions.Map.Find(SharedSurfaceId);
check(SharedMeshResults);
if (SharedMeshResults)
{
check(SharedMeshResults->ExtraMeshLayouts.IsValidIndex(EditIndex));
Result.OverrideLayouts = SharedMeshResults->ExtraMeshLayouts[EditIndex].GeneratedLayouts;
}
}
return Result;
},
Options);
FMeshTask AddBaseTask = GenerateMesh(MergedMeshStaticOptions, MergedMeshOptionsTask, pAdd);
FMeshTask AddTask = UE::Tasks::Launch(TEXT("MutableMergedMeshAdd"),
[AddBaseTask, LastMeshTask, ErrorLog=ErrorLog,Edit,ErrorContext]() mutable
{
FMeshGenerationResult AddResults = AddBaseTask.GetResult();
FMeshGenerationResult BaseMeshResult = LastMeshTask.GetResult();
// Warn about discrepancies on layout strategy between the added and the base
int32 LayoutIndexThatHasBlocks = -1;
{
if (BaseMeshResult.GeneratedLayouts.Num() != AddResults.GeneratedLayouts.Num())
{
// When extending a mesh section the added mesh sectio will use the layout strategy of the base one
FString Msg = FString::Printf(TEXT("Extended mesh section layout count is differenta than the mesh being extended."));
ErrorLog->Add(Msg, ELMT_INFO, Edit->GetMessageContext(), ErrorContext);
}
for (int32 LayoutIndex = 0; LayoutIndex < BaseMeshResult.GeneratedLayouts.Num(); ++LayoutIndex)
{
if (BaseMeshResult.GeneratedLayouts[LayoutIndex].Layout
&&
BaseMeshResult.GeneratedLayouts[LayoutIndex].Layout->Strategy != EPackStrategy::Overlay)
{
LayoutIndexThatHasBlocks = LayoutIndex;
}
if (BaseMeshResult.GeneratedLayouts[LayoutIndex].Layout
&&
AddResults.GeneratedLayouts.IsValidIndex(LayoutIndex)
&&
AddResults.GeneratedLayouts[LayoutIndex].Layout
&&
BaseMeshResult.GeneratedLayouts[LayoutIndex].Layout->Strategy != AddResults.GeneratedLayouts[LayoutIndex].Layout->Strategy)
{
// When extending a mesh section the added mesh sectio will use the layout strategy of the base one
FString Msg = FString::Printf(TEXT("Extended mesh section layout [%d] is using a different strategy than the section being extended. The base strategy will be used."), LayoutIndex);
ErrorLog->Add(Msg, ELMT_INFO, Edit->GetMessageContext(), ErrorContext);
}
}
}
// Add the operation to extract the relevant layout blocks if necessary
// TODO: Handle multiple layouts defining blocks: what to extract?
if (LayoutIndexThatHasBlocks >= 0)
{
Ptr<ASTOpMeshExtractLayoutBlocks> ExtractOp = new ASTOpMeshExtractLayoutBlocks();
ExtractOp->Source = AddResults.MeshOp;
ExtractOp->LayoutIndex = LayoutIndexThatHasBlocks;
AddResults.MeshOp = ExtractOp;
}
return AddResults;
},
UE::Tasks::Prerequisites(AddBaseTask, LastMeshTask)
);
// Apply the modifiers for the post-normal operations stage to the added mesh
FMeshGenerationStaticOptions ModifierOptions(StaticOptions);
ModifierOptions.ActiveTags = Edit->EnableTags;
ModifierOptions.ModifiersToIgnore.AddUnique(Modifier);
TArray<FirstPassGenerator::FModifier> ChildModifiers;
constexpr bool bModifiersForBeforeOperations = false;
GetModifiersFor(StaticOptions.ComponentId, ModifierOptions.ActiveTags, bModifiersForBeforeOperations, ChildModifiers);
FMeshTask AddWithModifiersTask = ApplyMeshModifiers(ChildModifiers, ModifierOptions, Options, AddTask, SharedSurfaceId, ErrorContext, nullptr);
LastMeshTask = UE::Tasks::Launch(TEXT("MutableMeshMergeModifier"),
[AddBaseTask, AddWithModifiersTask, LastMeshTask, Modifier, EditIndex]() mutable
{
FMeshGenerationResult AddResults = AddBaseTask.GetResult();
FMeshGenerationResult AddFinalResults = AddWithModifiersTask.GetResult();
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
FMeshGenerationResult::FExtraLayouts Data;
Data.GeneratedLayouts = AddResults.GeneratedLayouts;
Data.Condition = Modifier.FinalCondition;
Data.MeshFragment = AddFinalResults.MeshOp;
if (LastMeshResults.ExtraMeshLayouts.Num() <= EditIndex)
{
LastMeshResults.ExtraMeshLayouts.SetNum(EditIndex+1);
}
LastMeshResults.ExtraMeshLayouts[EditIndex] = Data;
Ptr<ASTOpMeshMerge> MergeOp = new ASTOpMeshMerge;
MergeOp->Base = LastMeshResults.MeshOp;
MergeOp->Added = AddFinalResults.MeshOp;
// will merge the meshes under the same surface
MergeOp->NewSurfaceID = 0;
// Condition to apply
if (Modifier.FinalCondition)
{
Ptr<ASTOpConditional> ConditionalOp = new ASTOpConditional();
ConditionalOp->type = EOpType::ME_CONDITIONAL;
ConditionalOp->no = LastMeshResults.MeshOp;
ConditionalOp->yes = MergeOp;
ConditionalOp->condition = Modifier.FinalCondition;
LastMeshResults.MeshOp = ConditionalOp;
}
else
{
LastMeshResults.MeshOp = MergeOp;
}
return LastMeshResults;
},
UE::Tasks::Prerequisites(AddWithModifiersTask, LastMeshTask)
);
}
++EditIndex;
}
}
// Process mesh remove modifiers (from edit modifiers)
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType())
{
const NodeModifierSurfaceEdit* Edit = static_cast<const NodeModifierSurfaceEdit*>(Modifier.Node);
bool bAffectsCurrentLOD = Edit->LODs.IsValidIndex(CurrentLOD);
// Apply mesh removes from child objects "edit surface" nodes.
// "Removes" need to come after "Adds" because some removes may refer to added meshes,
// and not the base.
// \TODO: Apply base removes first, and then "added meshes" removes here. It may have lower memory footprint during generation.
if (bAffectsCurrentLOD && Edit->LODs[CurrentLOD].MeshRemove)
{
Ptr<NodeMesh> MeshRemove = Edit->LODs[CurrentLOD].MeshRemove;
FMeshGenerationStaticOptions RemoveMeshStaticOptions(StaticOptions);
RemoveMeshStaticOptions.ActiveTags = Edit->EnableTags;
RemoveMeshStaticOptions.ModifiersToIgnore.Add(Modifier);
FMeshGenerationDynamicOptions RemoveMeshDynamicOptions;
RemoveMeshDynamicOptions.bLayouts = false;
FMeshTask RemoveMeshTask = GenerateMesh(RemoveMeshStaticOptions,UE::Tasks::MakeCompletedTask<FMeshGenerationDynamicOptions>(RemoveMeshDynamicOptions), MeshRemove);
LastMeshTask = UE::Tasks::Launch(TEXT("MutableMeshMergeModifier"),
[RemoveMeshTask, LastMeshTask, PreModifiersTask, Edit, Modifier]() mutable
{
FMeshGenerationResult RemoveResults = RemoveMeshTask.GetResult();
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
FMeshGenerationResult BaseResults = PreModifiersTask.GetResult();
Ptr<ASTOpMeshMaskDiff> MaskOp = new ASTOpMeshMaskDiff;
// By default, remove from the base
Ptr<ASTOp> RemoveFrom = BaseResults.BaseMeshOp;
MaskOp->Source = RemoveFrom;
MaskOp->Fragment = RemoveResults.MeshOp;
Ptr<ASTOpMeshRemoveMask> RemoveOp = new ASTOpMeshRemoveMask();
RemoveOp->source = LastMeshResults.MeshOp;
RemoveOp->FaceCullStrategy = Edit->FaceCullStrategy;
RemoveOp->AddRemove(Modifier.FinalCondition, MaskOp);
LastMeshResults.MeshOp = RemoveOp;
return LastMeshResults;
},
UE::Tasks::Prerequisites(RemoveMeshTask, LastMeshTask)
);
}
}
}
// Process mesh morph modifiers (from edit modifiers)
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType())
{
const NodeModifierSurfaceEdit* Edit = static_cast<const NodeModifierSurfaceEdit*>(Modifier.Node);
if (Edit->MeshMorph.IsEmpty())
{
continue;
}
check(OriginalMeshNode);
// Request morphed skeletal mesh
TSharedPtr< TSharedPtr<FMesh> > ResolveMorphedMesh = MakeShared<TSharedPtr<FMesh>>();
UE::Tasks::FTask TargetMeshTask;
{
FMesh* OriginalMesh = OriginalMeshNode->Value.Get();
check(OriginalMesh->IsReference());
uint32 OriginalMeshID = OriginalMesh->GetReferencedMesh();
bool bRunImmediatlyIfPossible = IsInGameThread();
TargetMeshTask = CompilerOptions->OptimisationOptions.ReferencedMeshResourceProvider(
OriginalMeshID,
Edit->MeshMorph,
ResolveMorphedMesh,
bRunImmediatlyIfPossible );
}
// Factor
Ptr<ASTOp> FactorOp;
if (Edit->MorphFactor)
{
FScalarGenerationResult ChildResult;
GenerateScalar(ChildResult, StaticOptions, Edit->MorphFactor);
FactorOp = ChildResult.op;
}
else
{
Ptr<NodeScalarConstant> auxNode = new NodeScalarConstant();
auxNode->Value = 1.0f;
FScalarGenerationResult ChildResult;
GenerateScalar(ChildResult, StaticOptions, auxNode);
FactorOp = ChildResult.op;
}
Ptr<const NodeMeshConstant> OriginalMeshCopy = OriginalMeshNode;
LastMeshTask = UE::Tasks::Launch(TEXT("MutableMeshMorphModifier"),
[TargetMeshTask, LastMeshTask, PreModifiersTask, Edit, Modifier, ResolveMorphedMesh, FactorOp, CompilerOptions=CompilerOptions, OriginalMeshCopy]() mutable
{
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
FMeshGenerationResult BaseResults = PreModifiersTask.GetResult();
TSharedPtr<FMesh> TargetMesh = *ResolveMorphedMesh;
if (!TargetMesh)
{
return LastMeshResults;
}
// Target mesh
Ptr<ASTOpConstantResource> TargetMeshOp = new ASTOpConstantResource;
TargetMeshOp->Type = EOpType::ME_CONSTANT;
TargetMeshOp->SetValue(TargetMesh->Clone(), CompilerOptions->OptimisationOptions.DiskCacheContext);
TargetMeshOp->SourceDataDescriptor = OriginalMeshCopy->SourceDataDescriptor;
// Morph generation through mesh diff
Ptr<ASTOpMeshDifference> DiffOp;
{
DiffOp = new ASTOpMeshDifference();
DiffOp->Base = BaseResults.BaseMeshOp;
DiffOp->Target = TargetMeshOp;
// Morphing tex coords here is not supported:
// Generating the homogoneous UVs is difficult since we don't have the base
// layout yet.
DiffOp->bIgnoreTextureCoords = true;
}
// Morph operation
Ptr<ASTOpMeshMorph> MorphOp = new ASTOpMeshMorph();
{
MorphOp->Base = LastMeshResults.MeshOp;
MorphOp->Target = DiffOp;
MorphOp->Factor = FactorOp;
}
// Condition to apply the morph
if (Modifier.FinalCondition)
{
Ptr<ASTOpConditional> ConditionalOp = new ASTOpConditional();
ConditionalOp->type = EOpType::ME_CONDITIONAL;
ConditionalOp->no = LastMeshResults.MeshOp;
ConditionalOp->yes = MorphOp;
ConditionalOp->condition = Modifier.FinalCondition;
LastMeshResults.MeshOp = ConditionalOp;
}
else
{
LastMeshResults.MeshOp = MorphOp;
}
return LastMeshResults;
},
UE::Tasks::Prerequisites(TargetMeshTask, LastMeshTask)
);
}
}
// Process clip-with-mesh modifiers
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType()== NodeModifierMeshClipWithMesh::GetStaticType())
{
const NodeModifierMeshClipWithMesh* TypedClipNode = static_cast<const NodeModifierMeshClipWithMesh*>(Modifier.Node);
FMeshGenerationDynamicOptions RemoveMeshDynamicOptions;
RemoveMeshDynamicOptions.bLayouts = false;
FMeshGenerationStaticOptions ClipStaticOptions(StaticOptions);
ClipStaticOptions.ModifiersToIgnore.AddUnique(Modifier);
ClipStaticOptions.ActiveTags.Empty();
FMeshTask ClipMeshTask = GenerateMesh(ClipStaticOptions, UE::Tasks::MakeCompletedTask<FMeshGenerationDynamicOptions>(RemoveMeshDynamicOptions), TypedClipNode->ClipMesh);
LastMeshTask = UE::Tasks::Launch(TEXT("MutableMeshMergeModifier"),
[ClipMeshTask, LastMeshTask, PreModifiersTask, Modifier, TypedClipNode, ErrorLog=ErrorLog, ErrorContext]() mutable
{
FMeshGenerationResult ClipResults = ClipMeshTask.GetResult();
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
FMeshGenerationResult BaseResults = PreModifiersTask.GetResult();
if (!ClipResults.MeshOp)
{
ErrorLog->Add("Clip mesh has not been generated", ELMT_ERROR, ErrorContext);
return LastMeshResults;
}
Ptr<ASTOpMeshMaskClipMesh> MaskOp = new ASTOpMeshMaskClipMesh();
MaskOp->source = BaseResults.MeshOp;
MaskOp->clip = ClipResults.MeshOp;
Ptr<ASTOpMeshRemoveMask> RemoveOp = new ASTOpMeshRemoveMask();
RemoveOp->source = LastMeshResults.MeshOp;
RemoveOp->FaceCullStrategy = TypedClipNode->FaceCullStrategy;
RemoveOp->AddRemove(Modifier.FinalCondition, MaskOp);
LastMeshResults.MeshOp = RemoveOp;
return LastMeshResults;
},
UE::Tasks::Prerequisites(ClipMeshTask, LastMeshTask)
);
}
}
// Process clip-with-mask modifiers
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType() == NodeModifierMeshClipWithUVMask::GetStaticType())
{
// Create a constant mesh with the original UVs required by this modifier.
// TODO: Optimize, by caching.
// TODO: Optimize by formatting and keeping only UVs
check(OriginalMeshNode);
TSharedPtr< TSharedPtr<FMesh> > ResolveOriginalMesh = MakeShared<TSharedPtr<FMesh>>();
UE::Tasks::FTask TargetMeshTask;
{
FMesh* OriginalMesh = OriginalMeshNode->Value.Get();
check(OriginalMesh->IsReference());
uint32 OriginalMeshID = OriginalMesh->GetReferencedMesh();
bool bRunImmediatlyIfPossible = IsInGameThread();
FString NoMorph;
TargetMeshTask = CompilerOptions->OptimisationOptions.ReferencedMeshResourceProvider(
OriginalMeshID,
NoMorph,
ResolveOriginalMesh,
bRunImmediatlyIfPossible);
}
const NodeModifierMeshClipWithUVMask* TypedClipNode = static_cast<const NodeModifierMeshClipWithUVMask*>(Modifier.Node);
Ptr<ASTOp> MaskImage;
TSharedPtr<const FLayout> Layout;
if (TypedClipNode->ClipMask)
{
FImageGenerationOptions ClipOptions(StaticOptions.ComponentId, StaticOptions.LODIndex);
ClipOptions.ImageLayoutStrategy = CompilerOptions::TextureLayoutStrategy::None;
ClipOptions.LayoutBlockId = FLayoutBlock::InvalidBlockId;
ClipOptions.State = StaticOptions.State;
FImageGenerationResult ClipMaskResult;
GenerateImage(ClipOptions, ClipMaskResult, TypedClipNode->ClipMask);
// It could be IF_L_UBIT, but since this should be optimized out at compile time, leave the most cpu efficient.
MaskImage = GenerateImageFormat(ClipMaskResult.op, mu::EImageFormat::L_UByte);
}
else if (TypedClipNode->ClipLayout)
{
// Generate the layout with blocks to extract
Layout = GenerateLayout(TypedClipNode->ClipLayout, 0);
}
Ptr<const NodeMeshConstant> OriginalMeshCopy = OriginalMeshNode;
LastMeshTask = UE::Tasks::Launch(TEXT("MutableModifier"),
[Layout, MaskImage, ResolveOriginalMesh, LastMeshTask, PreModifiersTask, Modifier, TypedClipNode, CompilerOptions = CompilerOptions, ErrorLog = ErrorLog, ErrorContext, OriginalMeshCopy]() mutable
{
TSharedPtr<FMesh> TargetMesh = *ResolveOriginalMesh;
if (!TargetMesh)
{
return FMeshGenerationResult();
}
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
FMeshGenerationResult BaseResults = PreModifiersTask.GetResult();
Ptr<ASTOpConstantResource> UVMeshOp = new ASTOpConstantResource;
UVMeshOp->Type = EOpType::ME_CONSTANT;
UVMeshOp->SetValue(TargetMesh->Clone(), CompilerOptions->OptimisationOptions.DiskCacheContext);
UVMeshOp->SourceDataDescriptor = OriginalMeshCopy->SourceDataDescriptor;
Ptr<ASTOpMeshMaskClipUVMask> MeshMaskAt = new ASTOpMeshMaskClipUVMask();
MeshMaskAt->Source = BaseResults.BaseMeshOp;
MeshMaskAt->UVSource = UVMeshOp;
MeshMaskAt->LayoutIndex = TypedClipNode->LayoutIndex;
if (TypedClipNode->ClipMask)
{
MeshMaskAt->MaskImage = MaskImage;
if (!MeshMaskAt->MaskImage)
{
ErrorLog->Add("Clip UV mask has not been generated", ELMT_ERROR, ErrorContext);
return LastMeshResults;
}
}
else if (TypedClipNode->ClipLayout)
{
Ptr<ASTOpConstantResource> LayoutOp = new ASTOpConstantResource();
LayoutOp->Type = EOpType::LA_CONSTANT;
LayoutOp->SetValue(Layout, CompilerOptions->OptimisationOptions.DiskCacheContext);
MeshMaskAt->MaskLayout = LayoutOp;
}
else
{
// No mask or layout specified to clip. Don't clip anything.
return LastMeshResults;
}
if (MeshMaskAt)
{
Ptr<ASTOpMeshRemoveMask> RemoveOp = new ASTOpMeshRemoveMask();
RemoveOp->source = LastMeshResults.MeshOp;
RemoveOp->FaceCullStrategy = TypedClipNode->FaceCullStrategy;
RemoveOp->AddRemove(Modifier.FinalCondition, MeshMaskAt);
LastMeshResults.MeshOp = RemoveOp;
}
return LastMeshResults;
},
UE::Tasks::Prerequisites(TargetMeshTask, LastMeshTask)
);
}
}
// Process clip-morph-plane modifiers
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType() == NodeModifierMeshClipMorphPlane::GetStaticType())
{
const NodeModifierMeshClipMorphPlane* TypedNode = static_cast<const NodeModifierMeshClipMorphPlane*>(Modifier.Node);
LastMeshTask = UE::Tasks::Launch(TEXT("MutableModifier"),
[LastMeshTask, PreModifiersTask, Modifier, TypedNode]() mutable
{
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
FMeshGenerationResult BaseResults = PreModifiersTask.GetResult();
Ptr<ASTOpMeshClipMorphPlane> ClipOp = new ASTOpMeshClipMorphPlane();
ClipOp->Source = LastMeshResults.MeshOp;
ClipOp->FaceCullStrategy = TypedNode->Parameters.FaceCullStrategy;
// Morph to an ellipse
{
FShape MorphShape;
MorphShape.type = (uint8_t)FShape::Type::Ellipse;
MorphShape.position = TypedNode->Parameters.Origin;
MorphShape.up = TypedNode->Parameters.Normal;
// TODO: Move rotation to ellipse rotation reference base instead of passing it directly
MorphShape.size = FVector3f(TypedNode->Parameters.Radius1, TypedNode->Parameters.Radius2, TypedNode->Parameters.Rotation);
// Generate a "side" vector.
// \todo: make generic and move to the vector class
{
// Generate vector perpendicular to normal for ellipse rotation reference base
FVector3f AuxBase(0.f, 1.f, 0.f);
if (FMath::Abs(FVector3f::DotProduct(TypedNode->Parameters.Normal, AuxBase)) > 0.95f)
{
AuxBase = FVector3f(0.f, 0.f, 1.f);
}
MorphShape.side = FVector3f::CrossProduct(TypedNode->Parameters.Normal, AuxBase);
}
ClipOp->MorphShape = MorphShape;
}
// Selection box
ClipOp->VertexSelectionType = TypedNode->Parameters.VertexSelectionType;
if (ClipOp->VertexSelectionType == EClipVertexSelectionType::Shape)
{
FShape SelectionShape;
SelectionShape.type = (uint8)FShape::Type::AABox;
SelectionShape.position = TypedNode->Parameters.SelectionBoxOrigin;
SelectionShape.size = TypedNode->Parameters.SelectionBoxRadius;
ClipOp->SelectionShape = SelectionShape;
}
else if (ClipOp->VertexSelectionType == EClipVertexSelectionType::BoneHierarchy)
{
ClipOp->VertexSelectionBone = TypedNode->Parameters.VertexSelectionBone;
ClipOp->VertexSelectionBoneMaxRadius = TypedNode->Parameters.MaxEffectRadius;
}
ClipOp->Dist = TypedNode->Parameters.DistanceToPlane;
ClipOp->Factor = TypedNode->Parameters.LinearityFactor;
Ptr<ASTOpConditional> ConditionalOp = new ASTOpConditional();
ConditionalOp->type = EOpType::ME_CONDITIONAL;
ConditionalOp->no = LastMeshResults.MeshOp;
ConditionalOp->yes = ClipOp;
ConditionalOp->condition = Modifier.FinalCondition;
LastMeshResults.MeshOp = ConditionalOp;
return LastMeshResults;
},
UE::Tasks::Prerequisites(LastMeshTask)
);
}
}
// Process clip deform modifiers.
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType() == NodeModifierMeshClipDeform::GetStaticType())
{
const NodeModifierMeshClipDeform* TypedClipNode = static_cast<const NodeModifierMeshClipDeform*>(Modifier.Node);
FMeshGenerationStaticOptions ClipStaticOptions(StaticOptions);
ClipStaticOptions.ActiveTags.Empty();
ClipStaticOptions.ModifiersToIgnore.AddUnique(Modifier);
FMeshGenerationDynamicOptions ClipMeshDynamicOptions;
ClipMeshDynamicOptions.bLayouts = false;
FMeshTask ClipMeshTask = GenerateMesh(ClipStaticOptions, UE::Tasks::MakeCompletedTask<FMeshGenerationDynamicOptions>(ClipMeshDynamicOptions), TypedClipNode->ClipMesh);
LastMeshTask = UE::Tasks::Launch(TEXT("MutableMeshMergeModifier"),
[ClipMeshTask, LastMeshTask, Modifier, TypedClipNode, ErrorLog = ErrorLog, ErrorContext]() mutable
{
FMeshGenerationResult ClipResults = ClipMeshTask.GetResult();
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
Ptr<ASTOpMeshBindShape> BindOp = new ASTOpMeshBindShape();
BindOp->Mesh = LastMeshResults.MeshOp;
BindOp->Shape = ClipResults.MeshOp;
BindOp->BindingMethod = static_cast<uint32>(TypedClipNode->BindingMethod);
Ptr<ASTOpMeshClipDeform> ClipOp = new ASTOpMeshClipDeform();
ClipOp->FaceCullStrategy = TypedClipNode->FaceCullStrategy;
ClipOp->ClipShape = ClipResults.MeshOp;
ClipOp->Mesh = BindOp;
if (!ClipOp->ClipShape)
{
ErrorLog->Add("Clip shape mesh has not been generated", ELMT_ERROR, ErrorContext);
return LastMeshResults;
}
Ptr<ASTOpConditional> Op = new ASTOpConditional();
Op->type = EOpType::ME_CONDITIONAL;
Op->no = LastMeshResults.MeshOp;
Op->yes = ClipOp;
Op->condition = Modifier.FinalCondition;
LastMeshResults.MeshOp = Op;
return LastMeshResults;
},
UE::Tasks::Prerequisites(ClipMeshTask, LastMeshTask)
);
}
}
// Process transform mesh within mesh modifiers.
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (StaticOptions.ModifiersToIgnore.Contains(Modifier))
{
// Prevent recursion.
continue;
}
if (Modifier.Node->GetType()== NodeModifierMeshTransformInMesh::GetStaticType())
{
const NodeModifierMeshTransformInMesh* TypedTransformNode = static_cast<const NodeModifierMeshTransformInMesh*>(Modifier.Node);
// If a matrix node is not connected, the op won't do anything, so let's not create it at all.
if (TypedTransformNode->MatrixNode)
{
if (!TypedTransformNode->MatrixNode || !TypedTransformNode->BoundingMesh)
{
ErrorLog->Add("Bounding mesh or matrix have not been generated", ELMT_ERROR, ErrorContext);
continue;
}
// Transform matrix.
Ptr<ASTOp> MatrixOp;
FMatrixGenerationResult ChildResult;
GenerateMatrix(ChildResult, StaticOptions, TypedTransformNode->MatrixNode);
MatrixOp = ChildResult.op;
// Bounding mesh
FMeshGenerationStaticOptions ClipStaticOptions(StaticOptions);
ClipStaticOptions.ActiveTags.Empty();
ClipStaticOptions.ModifiersToIgnore.AddUnique(Modifier);
FMeshGenerationDynamicOptions BoundingMeshDynamicOptions;
BoundingMeshDynamicOptions.bLayouts = false;
FMeshTask BoundingMeshTask = GenerateMesh(ClipStaticOptions, UE::Tasks::MakeCompletedTask<FMeshGenerationDynamicOptions>(BoundingMeshDynamicOptions), TypedTransformNode->BoundingMesh);
LastMeshTask = UE::Tasks::Launch(TEXT("MutableMeshMergeModifier"),
[BoundingMeshTask, LastMeshTask, Modifier, MatrixOp, ErrorLog = ErrorLog, ErrorContext]() mutable
{
FMeshGenerationResult BoundingResults = BoundingMeshTask.GetResult();
FMeshGenerationResult LastMeshResults = LastMeshTask.GetResult();
Ptr<ASTOpMeshTransformWithBoundingMesh> TransformOp = new ASTOpMeshTransformWithBoundingMesh();
TransformOp->source = LastMeshResults.MeshOp;
TransformOp->matrix = MatrixOp;
TransformOp->boundingMesh = BoundingResults.MeshOp;
if (TransformOp->boundingMesh)
{
ASTOp::EClosedMeshTest IsClosed = TransformOp->boundingMesh->IsClosedMesh();
if (IsClosed == ASTOp::EClosedMeshTest::No)
{
ErrorLog->Add("Mesh used for clipping is not closed.", ELMT_WARNING, ErrorContext);
}
}
// Condition to apply the transform op
if (Modifier.FinalCondition)
{
Ptr<ASTOpConditional> ConditionalOp = new ASTOpConditional();
ConditionalOp->type = EOpType::ME_CONDITIONAL;
ConditionalOp->no = LastMeshResults.MeshOp;
ConditionalOp->yes = TransformOp;
ConditionalOp->condition = Modifier.FinalCondition;
LastMeshResults.MeshOp = ConditionalOp;
}
else
{
LastMeshResults.MeshOp = TransformOp;
}
return LastMeshResults;
},
UE::Tasks::Prerequisites(BoundingMeshTask, LastMeshTask)
);
}
}
}
return LastMeshTask;
}
Ptr<ASTOp> CodeGenerator::ApplyImageBlockModifiers(
const TArray<FirstPassGenerator::FModifier>& Modifiers,
const FImageGenerationOptions& Options, Ptr<ASTOp> BaseImageOp,
const NodeSurfaceNew::FImageData& ImageData,
FIntPoint GridSize,
const FLayoutBlockDesc& LayoutBlockDesc,
box< FIntVector2 > RectInCells,
const void* ErrorContext)
{
Ptr<ASTOp> LastImageOp = BaseImageOp;
int32 CurrentLOD = Options.LODIndex;
check(CurrentLOD >= 0);
// Process patch image modifiers (from edit modifiers)
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (Modifier.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType())
{
const NodeModifierSurfaceEdit* Edit = static_cast<const NodeModifierSurfaceEdit*>(Modifier.Node);
bool bAffectsCurrentLOD = Edit->LODs.IsValidIndex(CurrentLOD);
if (!bAffectsCurrentLOD)
{
continue;
}
const NodeModifierSurfaceEdit::FTexture* MatchingEdit = Edit->LODs[CurrentLOD].Textures.FindByPredicate(
[&](const NodeModifierSurfaceEdit::FTexture& Candidate)
{
return (Candidate.MaterialParameterName == ImageData.MaterialParameterName);
});
if ( !MatchingEdit)
{
continue;
}
if (MatchingEdit->PatchImage.get())
{
// Does the current block need to be patched? Find out by building a mask.
TSharedPtr<FImage> PatchMask = GenerateImageBlockPatchMask(*MatchingEdit, GridSize, LayoutBlockDesc.BlockPixelsX, LayoutBlockDesc.BlockPixelsY, RectInCells);
if (PatchMask)
{
LastImageOp = GenerateImageBlockPatch(LastImageOp, *MatchingEdit, PatchMask, Modifier.FinalCondition, Options);
}
}
}
else
{
// This modifier doesn't affect the per-block image operations.
}
}
return LastImageOp;
}
void CodeGenerator::UpdateLayoutBlockDesc(CodeGenerator::FLayoutBlockDesc& Out, FImageDesc BlockDesc, FIntVector2 LayoutCellSize)
{
if (Out.BlockPixelsX == 0 && LayoutCellSize.X > 0 && LayoutCellSize.Y > 0)
{
Out.BlockPixelsX = FMath::Max(1, BlockDesc.m_size[0] / LayoutCellSize[0]);
Out.BlockPixelsY = FMath::Max(1, BlockDesc.m_size[1] / LayoutCellSize[1]);
Out.bBlocksHaveMips = BlockDesc.m_lods > 1;
if (Out.FinalFormat==EImageFormat::None)
{
Out.FinalFormat = BlockDesc.m_format;
}
}
};
Ptr<ASTOp> CodeGenerator::ApplyImageExtendModifiers(
const TArray<FirstPassGenerator::FModifier>& Modifiers,
const FMeshGenerationStaticOptions& Options,
const FMeshGenerationResult& BaseMeshResults,
Ptr<ASTOp> BaseImageOp,
CompilerOptions::TextureLayoutStrategy ImageLayoutStrategy,
int32 LayoutIndex,
const NodeSurfaceNew::FImageData& ImageData,
FIntPoint GridSize,
CodeGenerator::FLayoutBlockDesc& InOutLayoutBlockDesc,
const void* ModifiedNodeErrorContext)
{
Ptr<ASTOp> LastImageOp = BaseImageOp;
int32 CurrentLOD = Options.LODIndex;
check(CurrentLOD >= 0);
// Process mesh extend modifiers (from edit modifiers)
int32 EditIndex = 0;
for (const FirstPassGenerator::FModifier& Modifier : Modifiers)
{
if (Modifier.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType())
{
const NodeModifierSurfaceEdit* Edit = static_cast<const NodeModifierSurfaceEdit*>(Modifier.Node);
int32 ThisEditIndex = EditIndex;
++EditIndex;
bool bAffectsCurrentLOD = Edit->LODs.IsValidIndex(CurrentLOD);
if (!bAffectsCurrentLOD)
{
continue;
}
const NodeModifierSurfaceEdit::FTexture* MatchingEdit = Edit->LODs[CurrentLOD].Textures.FindByPredicate(
[&](const NodeModifierSurfaceEdit::FTexture& Candidate)
{
return (Candidate.MaterialParameterName == ImageData.MaterialParameterName);
});
if (!MatchingEdit || (MatchingEdit && !MatchingEdit->Extend) )
{
if (Edit->LODs[CurrentLOD].MeshAdd)
{
// When extending a mesh section it is mandatory to provide textures for all section textures handled by Mutable.
FString Msg = FString::Printf(TEXT("Required texture [%s] is missing when trying to extend a mesh section."), *ImageData.MaterialParameterName);
ErrorLog->Add(Msg, ELMT_INFO, Edit->GetMessageContext(), ModifiedNodeErrorContext);
}
continue;
}
if (!BaseMeshResults.ExtraMeshLayouts.IsValidIndex(ThisEditIndex))
{
ErrorLog->Add(TEXT("Trying to extend a layout that doesn't exist."), ELMT_WARNING, Edit->GetMessageContext(), ModifiedNodeErrorContext);
continue;
}
const TArray<FGeneratedLayout>& ExtraLayouts = BaseMeshResults.ExtraMeshLayouts[ThisEditIndex].GeneratedLayouts;
if (LayoutIndex >= ExtraLayouts.Num() || !ExtraLayouts[LayoutIndex].Layout)
{
ErrorLog->Add(TEXT("Trying to extend a layout that doesn't exist."), ELMT_WARNING, Edit->GetMessageContext(), ModifiedNodeErrorContext);
continue;
}
TSharedPtr<const FLayout> ExtendLayout = ExtraLayouts[LayoutIndex].Layout;
Ptr<ASTOp> LastBase = LastImageOp;
for (int32 BlockIndex = 0; BlockIndex < ExtendLayout->GetBlockCount(); ++BlockIndex)
{
// Generate the image block
FImageGenerationOptions ImageOptions(Options.ComponentId,Options.LODIndex);
ImageOptions.State = Options.State;
ImageOptions.ImageLayoutStrategy = ImageLayoutStrategy;
ImageOptions.ActiveTags = Edit->EnableTags; // TODO: Merge with current tags?
ImageOptions.RectSize = { 0,0 };
ImageOptions.LayoutToApply = ExtendLayout;
ImageOptions.LayoutBlockId = ExtendLayout->Blocks[BlockIndex].Id;
FImageGenerationResult ExtendResult;
GenerateImage(ImageOptions, ExtendResult, MatchingEdit->Extend);
Ptr<ASTOp> FragmentOp = ExtendResult.op;
// Block in layout grid units
box< FIntVector2 > RectInCells;
RectInCells.min = ExtendLayout->Blocks[BlockIndex].Min;
RectInCells.size = ExtendLayout->Blocks[BlockIndex].Size;
FImageDesc ExtendDesc = FragmentOp->GetImageDesc();
// If we don't know the size of a layout block in pixels, calculate it
UpdateLayoutBlockDesc(InOutLayoutBlockDesc, ExtendDesc, RectInCells.size);
// Adjust the format and size of the block to be added
// Actually don't do it, it will be propagated from the top format operation.
//FragmentOp = GenerateImageFormat(FragmentOp, FinalImageFormat);
UE::Math::TIntVector2<int32> ExpectedSize;
ExpectedSize[0] = InOutLayoutBlockDesc.BlockPixelsX * RectInCells.size[0];
ExpectedSize[1] = InOutLayoutBlockDesc.BlockPixelsY * RectInCells.size[1];
FragmentOp = GenerateImageSize(FragmentOp, ExpectedSize);
// Apply tiling to avoid generating chunks of image that are too big.
FragmentOp = ApplyTiling(FragmentOp, ExpectedSize, InOutLayoutBlockDesc.FinalFormat);
// Compose operation
Ptr<ASTOpImageCompose> ComposeOp = new ASTOpImageCompose();
ComposeOp->Layout = BaseMeshResults.LayoutOps[LayoutIndex];
ComposeOp->Base = LastBase;
ComposeOp->BlockImage = FragmentOp;
// Set the absolute block index.
check(ExtendLayout->Blocks[BlockIndex].Id != FLayoutBlock::InvalidBlockId);
ComposeOp->BlockId = ExtendLayout->Blocks[BlockIndex].Id;
LastBase = ComposeOp;
}
// Condition to enable this image extension
if (Modifier.FinalCondition)
{
Ptr<ASTOpConditional> Op = new ASTOpConditional();
Op->type = EOpType::IM_CONDITIONAL;
Op->no = LastImageOp;
Op->yes = LastBase;
Op->condition = Modifier.FinalCondition;
LastImageOp = Op;
}
else
{
LastImageOp = LastBase;
}
}
}
return LastImageOp;
}
void CodeGenerator::CheckModifiersForSurface(const NodeSurfaceNew& Node, const TArray<FirstPassGenerator::FModifier>& Modifiers, int32 LODIndex )
{
int32 CurrentLOD = LODIndex;
check(CurrentLOD >= 0);
for (const FirstPassGenerator::FModifier& Mod : Modifiers)
{
// A mistake in the surface edit modifier usually results in no change visible. Try to detect it.
if (Mod.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType())
{
const NodeModifierSurfaceEdit* Edit = static_cast<const NodeModifierSurfaceEdit*>(Mod.Node);
bool bAffectsCurrentLOD = Edit->LODs.IsValidIndex(CurrentLOD);
if (!bAffectsCurrentLOD)
{
continue;
}
if (Node.Images.IsEmpty() || Edit->LODs[CurrentLOD].Textures.IsEmpty())
{
continue;
}
bool bAtLeastSomeTexture = false;
for (NodeSurfaceNew::FImageData Data : Node.Images)
{
const NodeModifierSurfaceEdit::FTexture* MatchingEdit = Edit->LODs[CurrentLOD].Textures.FindByPredicate(
[&](const NodeModifierSurfaceEdit::FTexture& Candidate)
{
return (Candidate.MaterialParameterName == Data.MaterialParameterName);
});
if (MatchingEdit)
{
bAtLeastSomeTexture = true;
break;
}
}
if (!bAtLeastSomeTexture)
{
ErrorLog->Add(TEXT("A mesh section modifier applies to a section but no texture matches."), ELMT_WARNING, Edit->GetMessageContext(), Node.GetMessageContext());
}
}
}
}
}