// 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/ASTOpConditional.h" #include "MuT/ASTOpConstantBool.h" #include "MuT/ASTOpConstantResource.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/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/ASTOpParameter.h" #include "MuT/ASTOpLayoutRemoveBlocks.h" #include "MuT/ASTOpLayoutFromMesh.h" #include "MuT/ASTOpLayoutMerge.h" #include "MuT/ASTOpLayoutPack.h" #include "MuT/CodeGenerator_SecondPass.h" #include "MuT/CodeOptimiser.h" #include "MuT/CompilerPrivate.h" #include "MuT/ErrorLogPrivate.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/NodeImageFormatPrivate.h" #include "MuT/NodeImageMipmap.h" #include "MuT/NodeImageMipmapPrivate.h" #include "MuT/NodeImageSwizzlePrivate.h" #include "MuT/NodeMatrixConstant.h" #include "MuT/NodeMesh.h" #include "MuT/NodeMeshClipMorphPlane.h" #include "MuT/NodeMeshClipWithMesh.h" #include "MuT/NodeMeshConstantPrivate.h" #include "MuT/NodeMeshFormat.h" #include "MuT/NodeMeshFragment.h" #include "MuT/NodeMeshGeometryOperation.h" #include "MuT/NodeMeshInterpolate.h" #include "MuT/NodeMeshMorph.h" #include "MuT/NodeMeshReshape.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/NodeObjectGroupPrivate.h" #include "MuT/NodeObjectNew.h" #include "MuT/NodePrivate.h" #include "MuT/NodeRange.h" #include "MuT/NodeRangeFromScalar.h" #include "MuT/NodeScalar.h" #include "MuT/NodeScalarConstant.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) { CompilerOptions = options; // Create the message log ErrorLog = new mu::ErrorLog; // Add the parent at the top of the hierarchy CurrentParents.Add(FParentKey()); } //--------------------------------------------------------------------------------------------- void CodeGenerator::GenerateRoot(const Ptr pNode) { MUTABLE_CPUPROFILER_SCOPE(Generate); // First pass FirstPass.Generate(ErrorLog, pNode.get(), CompilerOptions->bIgnoreStates, this); // Second pass SecondPassGenerator SecondPass(&FirstPass, CompilerOptions); bool bSuccess = SecondPass.Generate(ErrorLog, pNode.get()); if (!bSuccess) { return; } // Main pass for each state { MUTABLE_CPUPROFILER_SCOPE(MainPass); int32 CurrentStateIndex = 0; for (const TPair& s : FirstPass.States) { MUTABLE_CPUPROFILER_SCOPE(MainPassState); FGenericGenerationOptions Options; Options.State = CurrentStateIndex; Ptr stateRoot = Generate_Generic(pNode, Options); States.Emplace(s.Key, stateRoot); AdditionalComponents.Empty(); ++CurrentStateIndex; } } } Ptr CodeGenerator::Generate_Generic(const Ptr pNode, const FGenericGenerationOptions& Options ) { if (!pNode) { return nullptr; } // Type-specific generation if (pNode->GetType()->IsA(NodeScalar::GetStaticType())) { const NodeScalar* ScalarNode = static_cast(pNode.get()); FScalarGenerationResult ScalarResult; GenerateScalar(ScalarResult, Options, ScalarNode); return ScalarResult.op; } else if (pNode->GetType()->IsA(NodeColour::GetStaticType())) { const NodeColour* ColorNode = static_cast(pNode.get()); FColorGenerationResult Result; GenerateColor(Result, Options, ColorNode); return Result.op; } else if (pNode->GetType()->IsA(NodeProjector::GetStaticType())) { const NodeProjector* projNode = static_cast(pNode.get()); FProjectorGenerationResult ProjResult; GenerateProjector(ProjResult, Options, projNode); return ProjResult.op; } else if (pNode->GetType()->IsA(NodeSurfaceNew::GetStaticType())) { const NodeSurfaceNew* surfNode = static_cast(pNode.get()); // This happens only if we generate a node graph that has a NodeSurfaceNew at the root. FSurfaceGenerationResult surfResult; FSurfaceGenerationOptions SurfaceOptions(Options); GenerateSurface(surfResult, SurfaceOptions, surfNode); return surfResult.surfaceOp; } else if (pNode->GetType()->IsA(NodeSurfaceVariation::GetStaticType())) { // This happens only if we generate a node graph that has a NodeSurfaceVariation at the root. return nullptr; } else if (pNode->GetType()->IsA(NodeSurfaceSwitch::GetStaticType())) { // This happens only if we generate a node graph that has a NodeSurfaceSwitch at the root. return nullptr; } else if (pNode->GetType()->IsA(NodeModifier::GetStaticType())) { // This happens only if we generate a node graph that has a modifier at the root. return nullptr; } else if (pNode->GetType()->IsA(NodeComponent::GetStaticType())) { const NodeComponent* ComponentNode = static_cast(pNode.get()); FComponentGenerationOptions ComponentOptions( Options, nullptr ); FGenericGenerationResult Result; GenerateComponent(ComponentOptions, Result, ComponentNode); return Result.op; } Ptr ResultOp; // See if it was already generated FGeneratedCacheKey Key; Key.Node = pNode; Key.Options = Options; FGeneratedGenericNodesMap::ValueType* it = GeneratedGenericNodes.Find(Key); if (it) { ResultOp = it->op; } else { FGenericGenerationResult Result; // Generate for each different type of node if (pNode->GetType() == NodeObjectNew::GetStaticType()) { Generate_ObjectNew(Options, Result, static_cast(pNode.get())); } else if (pNode->GetType()==NodeObjectGroup::GetStaticType()) { Generate_ObjectGroup(Options, Result, static_cast(pNode.get())); } else { check(false); } ResultOp = Result.op; GeneratedGenericNodes.Add(Key, Result); } // debug: expensive check of all code generation // if (ResultOp) // { // ASTOpList roots; // roots.push_back(ResultOp); // ASTOp::FullAssert(roots); // } return ResultOp; } void CodeGenerator::GenerateRange(FRangeGenerationResult& Result, const FGenericGenerationOptions& Options, Ptr Untyped) { if (!Untyped) { Result = FRangeGenerationResult(); return; } // See if it was already generated FGeneratedCacheKey Key; Key.Node = Untyped; Key.Options = Options; FGeneratedRangeMap::ValueType* it = GeneratedRanges.Find(Key); if (it) { Result = *it; return; } // Generate for each different type of node if (Untyped->GetType()==NodeRangeFromScalar::GetStaticType()) { const NodeRangeFromScalar* FromScalar = static_cast(Untyped.get()); Result = FRangeGenerationResult(); Result.rangeName = FromScalar->GetName(); FScalarGenerationResult ChildResult; GenerateScalar(ChildResult, Options, FromScalar->GetSize()); Result.sizeOp = ChildResult.op; } else { check(false); } // Cache the result GeneratedRanges.Add(Key, Result); } Ptr CodeGenerator::GenerateTableVariable( Ptr InNode, const FTableCacheKey& CacheKey, bool bAddNoneOption, const FString& DefaultRowName) { Ptr result; FParameterDesc param; param.m_name = CacheKey.ParameterName; if (param.m_name.Len() == 0) { param.m_name = CacheKey.Table->GetName(); } param.m_type = PARAMETER_TYPE::T_INT; param.m_defaultValue.Set(0); // 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 cols = CacheKey.Table->GetPrivate()->Columns.Num(); for (int32 c = 0; c < cols && nameCol < 0; ++c) { if (CacheKey.Table->GetPrivate()->Columns[c].Type == ETableColumnType::String) { nameCol = c; } } if (bAddNoneOption) { FParameterDesc::FIntValueDesc nullValue; nullValue.m_value = -1; nullValue.m_name = "None"; param.m_possibleValues.Add(nullValue); param.m_defaultValue.Set(nullValue.m_value); } // Add every row int32 RowCount = CacheKey.Table->GetPrivate()->Rows.Num(); check(RowCount < MAX_int16) // max FIntValueDesc allows for (int32 RowIndex = 0; RowIndex < RowCount; ++RowIndex) { FParameterDesc::FIntValueDesc value; value.m_value = RowIndex; if (nameCol > -1) { value.m_name = CacheKey.Table->GetPrivate()->Rows[RowIndex].Values[nameCol].String; } param.m_possibleValues.Add(value); // Set the first row as the default one (if there is none option) if (RowIndex == 0 && !bAddNoneOption) { param.m_defaultValue.Set(value.m_value); } // Set the selected row as default (if exists) if (value.m_name == DefaultRowName) { param.m_defaultValue.Set(value.m_value); } } } Ptr op = new ASTOpParameter(); op->type = OP_TYPE::NU_PARAMETER; op->parameter = param; FirstPass.ParameterNodes.Add(InNode, op); return op; } Ptr CodeGenerator::GenerateLayout(Ptr SourceLayout, uint32 MeshIDPrefix) { Ptr* it = GeneratedLayouts.Find({ SourceLayout,MeshIDPrefix }); if (it) { return *it; } Ptr GeneratedLayout = new Layout; 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); GeneratedLayouts.Add({ SourceLayout,MeshIDPrefix }, GeneratedLayout); return GeneratedLayout; } Ptr CodeGenerator::GenerateImageBlockPatch(Ptr InBlockOp, const NodeModifierSurfaceEdit::FTexture& Patch, Ptr PatchMask, Ptr conditionAd, const FImageGenerationOptions& ImageOptions ) { // Blend operation Ptr FinalOp; { MUTABLE_CPUPROFILER_SCOPE(PatchBlend); Ptr 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 BlendOp; if (ImageNode) { FImageGenerationResult BlendResult; GenerateImage(ImageOptions, BlendResult, ImageNode); BlendOp = BlendResult.op; } else { BlendOp = GenerateMissingImageCode(TEXT("Patch top image"), EImageFormat::IF_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 RectConstantOp; { Ptr pNode = new NodeImageConstant(); pNode->SetValue(PatchMask.get()); FImageGenerationOptions ConstantOptions; FImageGenerationResult ConstantResult; GenerateImage(ConstantOptions, ConstantResult, pNode); RectConstantOp = ConstantResult.op; } NodeImage* MaskNode = Patch.PatchMask.get(); Ptr MaskOp; if (MaskNode) { // Combine the block rect mask with the user provided mask. FImageGenerationResult MaskResult; GenerateImage(ImageOptions, MaskResult, MaskNode); MaskOp = MaskResult.op; Ptr PatchCombineOp = new ASTOpImageLayer; PatchCombineOp->base = MaskOp; PatchCombineOp->blend = RectConstantOp; PatchCombineOp->blendType = EBlendType::BT_MULTIPLY; MaskOp = PatchCombineOp; } else { MaskOp = RectConstantOp; } MaskOp = GenerateImageFormat(MaskOp, EImageFormat::IF_L_UBYTE); MaskOp = GenerateImageSize(MaskOp, ImageOptions.RectSize); LayerOp->mask = MaskOp; FinalOp = LayerOp; } // Condition to enable this patch if (conditionAd) { Ptr conditionalAd; { Ptr op = new ASTOpConditional(); op->type = OP_TYPE::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; GeneratedComponentMap::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(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(InUntypedNode)); OutResult.op = InOptions.BaseInstance; } else if (Type == NodeComponentSwitch::GetStaticType()) { GenerateComponent_Switch(InOptions, OutResult, static_cast(InUntypedNode)); } else if (Type == NodeComponentVariation::GetStaticType()) { GenerateComponent_Variation(InOptions, OutResult, static_cast(InUntypedNode)); } else { check(false); } // Cache the result GeneratedComponents.Add(Key, OutResult); } void CodeGenerator::GenerateComponent_New(const FComponentGenerationOptions& Options, FGenericGenerationResult& Result, const NodeComponentNew* InNode) { const NodeComponentNew& node = *InNode; // Create the expression for each component in this object Ptr LODsOp = new ASTOpAddLOD(); for (int32 LODIndex = 0; LODIndex < node.LODs.Num(); ++LODIndex) { if (const NodeLOD* LODNode = node.LODs[LODIndex].get()) { CurrentParents.Last().Lod = LODIndex; FLODGenerationOptions LODOptions(Options, LODIndex, InNode ); FGenericGenerationResult LODResult; Generate_LOD(LODOptions, LODResult, LODNode); LODsOp->lods.Emplace(LODsOp, LODResult.op); } } Ptr InstanceOp = new ASTOpInstanceAdd(); InstanceOp->type = OP_TYPE::IN_ADDCOMPONENT; InstanceOp->instance = Options.BaseInstance; InstanceOp->value = LODsOp; InstanceOp->id = node.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 ConditionOp = new ASTOpFixed(); ConditionOp->op.type = OP_TYPE::BO_AND; ConditionOp->SetChild(ConditionOp->op.args.BoolBinary.a, Component.ObjectCondition); ConditionOp->SetChild(ConditionOp->op.args.BoolBinary.b, Component.ComponentCondition); Ptr IfOp = new ASTOpConditional(); IfOp->type = OP_TYPE::IN_CONDITIONAL; IfOp->no = Options.BaseInstance; IfOp->yes = Result.op; IfOp->condition = ConditionOp; Result.op = IfOp; } } } 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 Op = new ASTOpSwitch(); Op->type = OP_TYPE::IN_SWITCH; // Variable value if (Node->Parameter) { Op->variable = Generate_Generic(Node->Parameter.get(), Options); } 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 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 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->GetPrivate()->Add( FString::Printf(TEXT("Unknown tag found in component variation [%s]."), *Tag), ELMT_WARNING, Node->GetMessageContext(), ELMSB_UNKNOWN_TAG ); continue; } Ptr VariationMeshOp = Options.BaseInstance; if (Node->Variations[VariationIndex].Component) { FGenericGenerationResult BranchResults; GenerateComponent(Options, BranchResults, Node->Variations[VariationIndex].Component.get()); VariationMeshOp = BranchResults.op; } Ptr Conditional = new ASTOpConditional; Conditional->type = OP_TYPE::IN_CONDITIONAL; Conditional->no = CurrentMeshOp; Conditional->yes = VariationMeshOp; Conditional->condition = FirstPass.Tags[TagIndex].GenericCondition; CurrentMeshOp = Conditional; } Result.op = CurrentMeshOp; } Ptr CodeGenerator::ApplyTiling(Ptr Source, UE::Math::TIntVector2 Size, EImageFormat Format) { // For now always apply tiling if (CompilerOptions->ImageTiling==0) { return Source; } int32 TileSize = CompilerOptions->ImageTiling; int32 TilesX = FMath::DivideAndRoundUp(Size[0], TileSize); int32 TilesY = FMath::DivideAndRoundUp(Size[1], TileSize); if (TilesX * TilesY <= 2) { return Source; } Ptr BaseImage = new ASTOpFixed; BaseImage->op.type = OP_TYPE::IM_PLAINCOLOUR; BaseImage->op.args.ImagePlainColour.size[0] = Size[0]; BaseImage->op.args.ImagePlainColour.size[1] = Size[1]; BaseImage->op.args.ImagePlainColour.format = Format; BaseImage->op.args.ImagePlainColour.LODs = 1; Ptr 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 TileImage = new ASTOpImageCrop(); TileImage->Source = Source; TileImage->Min[0] = MinX; TileImage->Min[1] = MinY; TileImage->Size[0] = TileSizeX; TileImage->Size[1] = TileSizeY; Ptr PatchedImage = new ASTOpImagePatch(); PatchedImage->base = CurrentImage; PatchedImage->patch = TileImage; PatchedImage->location[0] = MinX; PatchedImage->location[1] = MinY; CurrentImage = PatchedImage; } } return CurrentImage; } Ptr CodeGenerator::GenerateImageBlockPatchMask(const NodeModifierSurfaceEdit::FTexture& Patch, FIntPoint GridSize, int32 BlockPixelsX, int32 BlockPixelsY, box RectInCells ) { // Create a patching mask for the block Ptr 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 = new mu::Image(BlockSize[0], BlockSize[1], 1, mu::EImageFormat::IF_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; } //--------------------------------------------------------------------------------------------- void CodeGenerator::GenerateSurface( FSurfaceGenerationResult& SurfaceResult, const FSurfaceGenerationOptions& Options, Ptr SurfaceNode ) { MUTABLE_CPUPROFILER_SCOPE(GenerateSurface); // Build a series of operations to assemble the surface Ptr LastSurfOp; // Generate the mesh //------------------------------------------------------------------------ FMeshGenerationResult MeshResults; // 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? FirstPassGenerator::FSurface* TargetSurface = nullptr; 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.ResultSurfaceOp) { // Reuse the entire surface SurfaceResult.surfaceOp = Surface.ResultSurfaceOp; return; } else { // Not already generated, we will generate this TargetSurface = &Surface; } } if (!TargetSurface) { return; } // 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 = SurfaceNode->SharedSurfaceId != INDEX_NONE && !SharedMeshOptionsMap.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. const bool bShareSurface = SurfaceNode->SharedSurfaceId != INDEX_NONE && !bIsBaseForSharedSurface; // Gather all modifiers that apply to this surface TArray 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? GetModifiersFor(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); TBitArray<> LayoutFromExtension; //if (SurfaceNode->Mesh) { MUTABLE_CPUPROFILER_SCOPE(SurfaceMesh); Ptr LastMeshOp; // Generate the mesh FMeshGenerationOptions MeshOptions; MeshOptions.bLayouts = true; MeshOptions.State = Options.State; MeshOptions.ActiveTags = SurfaceNode->Tags; const FMeshGenerationResult* SharedMeshResults = nullptr; if (bShareSurface) { // Do we have the surface we need to share it with? SharedMeshResults = SharedMeshOptionsMap.Find(SurfaceNode->SharedSurfaceId); check(SharedMeshResults); // Override the layouts with the ones from the surface we share MeshOptions.OverrideLayouts = SharedMeshResults->GeneratedLayouts; } // 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(); MeshOptions.bNormalizeUVs = bNormalizeUVs; // 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. MeshOptions.bClampUVIslands = bShareSurface && bNormalizeUVs; GenerateMesh(MeshOptions, MeshResults, SurfaceNode->Mesh); // Apply the modifier for the post-normal operations stage. LastMeshOp = ApplyMeshModifiers(Modifiers, MeshOptions, MeshResults, SharedMeshResults, SurfaceNode->GetMessageContext(), nullptr); // Base mesh is allowed to be missing, aggregate all layouts and operations per layout indices in the // generated mesh, base and extends. TArray SurfaceReferenceLayouts; TArray> 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); LayoutFromExtension.Init(false, MaxLayoutNum); // 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]; if (SharedMeshResults) { check(SharedMeshResults->LayoutOps.IsValidIndex(LayoutIndex)); SurfaceLayoutOps[LayoutIndex] = SharedMeshResults->LayoutOps[LayoutIndex]; } else { Ptr ConstantLayoutOp = new ASTOpConstantResource(); ConstantLayoutOp->Type = OP_TYPE::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& 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 (SharedMeshResults) { if (!SurfaceLayoutOps[LayoutIndex] && bLayoutSetByThisExtension) { check(SharedMeshResults->LayoutOps.IsValidIndex(LayoutIndex)); SurfaceLayoutOps[LayoutIndex] = SharedMeshResults->LayoutOps[LayoutIndex]; } } else { Ptr LayoutFragmentConstantOp = new ASTOpConstantResource(); LayoutFragmentConstantOp->Type = OP_TYPE::LA_CONSTANT; LayoutFragmentConstantOp->SetValue( ExtraLayoutsData.GeneratedLayouts[LayoutIndex].Layout, CompilerOptions->OptimisationOptions.DiskCacheContext); Ptr 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 ConditionalOp = new ASTOpConditional(); ConditionalOp->type = OP_TYPE::LA_CONDITIONAL; ConditionalOp->no = SurfaceLayoutOps[LayoutIndex]; ConditionalOp->yes = LayoutMergeOp; ConditionalOp->condition = ExtraLayoutsData.Condition; SurfaceLayoutOps[LayoutIndex] = ConditionalOp; } else { SurfaceLayoutOps[LayoutIndex] = LayoutMergeOp; } } } } 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 (!SharedMeshResults) { // Make sure we removed unnecessary blocks Ptr ExtractOp = new ASTOpLayoutFromMesh(); ExtractOp->Mesh = LastMeshOp; check(LayoutIndex < 256); ExtractOp->LayoutIndex = uint8(LayoutIndex); Ptr RemoveOp = new ASTOpLayoutRemoveBlocks(); RemoveOp->Source = SurfaceLayoutOps[LayoutIndex]; RemoveOp->ReferenceLayout = ExtractOp; SurfaceLayoutOps[LayoutIndex] = RemoveOp; // Pack uv blocks Ptr LayoutPackOp = new ASTOpLayoutPack(); LayoutPackOp->Source = SurfaceLayoutOps[LayoutIndex]; SurfaceLayoutOps[LayoutIndex] = LayoutPackOp; } // Create the expression to apply the layout to the mesh { Ptr ApplyLayoutOp = new ASTOpFixed(); ApplyLayoutOp->op.type = OP_TYPE::ME_APPLYLAYOUT; ApplyLayoutOp->SetChild(ApplyLayoutOp->op.args.MeshApplyLayout.mesh, LastMeshOp); ApplyLayoutOp->SetChild(ApplyLayoutOp->op.args.MeshApplyLayout.layout, SurfaceLayoutOps[LayoutIndex]); ApplyLayoutOp->op.args.MeshApplyLayout.channel = (uint16)LayoutIndex; LastMeshOp = ApplyLayoutOp; } } MeshResults.GeneratedLayouts = MoveTemp(SurfaceReferenceLayouts); MeshResults.LayoutOps = MoveTemp(SurfaceLayoutOps); // Store in the surface for later use. TargetSurface->ResultMeshOp = LastMeshOp; } // 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 mipmapNode; Ptr formatNode; Ptr swizzleNode; bool bFound = false; Ptr pImageNode = SurfaceNode->Images[ImageIndex].Image; while (!bFound && pImageNode) { if (pImageNode->GetType()==NodeImageMipmap::GetStaticType()) { NodeImageMipmap* tm = static_cast(pImageNode.get()); if (!mipmapNode) mipmapNode = tm; pImageNode = tm->GetSource(); } else if (pImageNode->GetType() == NodeImageFormat::GetStaticType()) { NodeImageFormat* tf = static_cast(pImageNode.get()); if (!formatNode) formatNode = tf; pImageNode = tf->GetSource(); } else if (pImageNode->GetType() == NodeImageSwizzle::GetStaticType()) { NodeImageSwizzle* ts = static_cast(pImageNode.get()); if (!ts->GetPrivate()->m_sources.IsEmpty()) { NodeImage* Source = ts->GetSource(0).get(); bool bAllSourcesAreTheSame = true; for (int32 SourceIndex=1; SourceIndexGetPrivate()->m_sources.Num(); ++SourceIndex) { bAllSourcesAreTheSame = bAllSourcesAreTheSame && (Source == ts->GetSource(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; ImageOptions.State = Options.State; ImageOptions.ImageLayoutStrategy = ImageLayoutStrategy; ImageOptions.ActiveTags = SurfaceNode->Tags; ImageOptions.RectSize = { 0, 0 }; FImageGenerationResult Result; GenerateImage(ImageOptions, Result, pImageNode); Ptr 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 fop = new ASTOpImageSwizzle(); fop->Format = swizzleNode->GetPrivate()->m_format; fop->Sources[0] = imageAd; fop->Sources[1] = imageAd; fop->Sources[2] = imageAd; fop->Sources[3] = imageAd; fop->SourceChannels[0] = swizzleNode->GetPrivate()->m_sourceChannels[0]; fop->SourceChannels[1] = swizzleNode->GetPrivate()->m_sourceChannels[1]; fop->SourceChannels[2] = swizzleNode->GetPrivate()->m_sourceChannels[2]; fop->SourceChannels[3] = swizzleNode->GetPrivate()->m_sourceChannels[3]; check(fop->Format != EImageFormat::IF_NONE); imageAd = fop; } if (mipmapNode) { Ptr op = new ASTOpImageMipmap(); op->Levels = 0; op->Source = imageAd; op->BlockLevels = 0; op->AddressMode = mipmapNode->GetPrivate()->m_settings.AddressMode; op->FilterType = mipmapNode->GetPrivate()->m_settings.FilterType; imageAd = op; } if (formatNode) { Ptr fop = new ASTOpImagePixelFormat(); fop->Format = formatNode->GetPrivate()->m_format; fop->FormatIfAlpha = formatNode->GetPrivate()->m_formatIfAlpha; fop->Source = imageAd; check(fop->Format != EImageFormat::IF_NONE); imageAd = fop; } Ptr op = new ASTOpInstanceAdd(); op->type = OP_TYPE::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->GetPrivate()->Add("Missing layout in object, or its parent.", ELMT_ERROR, SurfaceNode->GetMessageContext()); } else { const Layout* pLayout = MeshResults.GeneratedLayouts[LayoutIndex].Layout.get(); check(pLayout); Ptr op = new ASTOpInstanceAdd(); op->type = OP_TYPE::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->GetPrivate()->m_formatIfAlpha; if (LayoutBlockDesc.FinalFormat == EImageFormat::IF_NONE) { LayoutBlockDesc.FinalFormat = formatNode->GetPrivate()->m_format; } } bool bImageSizeWarning = false; // Start with a blank image. It will be completed later with the blockSize, format and mips information Ptr BlankImageOp; Ptr imageAd; { BlankImageOp = new ASTOpFixed(); BlankImageOp->op.type = OP_TYPE::IM_BLANKLAYOUT; BlankImageOp->SetChild(BlankImageOp->op.args.ImageBlankLayout.layout, MeshResults.LayoutOps[LayoutIndex]); // The rest ok the op will be completed below BlankImageOp->op.args.ImageBlankLayout.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; 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 blockAd = ImageResult.op; if (!blockAd) { // The GenerateImage(...) above has failed, skip this block SurfaceResult.surfaceOp = nullptr; 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(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 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 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); FMeshGenerationOptions ModifierOptions; 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->op.args.ImageBlankLayout.blockSize[0] = uint16(LayoutBlockDesc.BlockPixelsX); BlankImageOp->op.args.ImageBlankLayout.blockSize[1] = uint16(LayoutBlockDesc.BlockPixelsY); BlankImageOp->op.args.ImageBlankLayout.format = GetUncompressedFormat(LayoutBlockDesc.FinalFormat); BlankImageOp->op.args.ImageBlankLayout.generateMipmaps = LayoutBlockDesc.bBlocksHaveMips; BlankImageOp->op.args.ImageBlankLayout.mipmapCount = 0; if (swizzleNode) { Ptr fop = new ASTOpImageSwizzle(); fop->Format = swizzleNode->GetPrivate()->m_format; for (int32 ChannelIndex = 0; ChannelIndex < swizzleNode->GetPrivate()->m_sourceChannels.Num(); ++ChannelIndex) { fop->Sources[ChannelIndex] = imageAd; fop->SourceChannels[ChannelIndex] = swizzleNode->GetPrivate()->m_sourceChannels[ChannelIndex]; } check(fop->Format != EImageFormat::IF_NONE); imageAd = fop; } // Apply mipmap and format if necessary, skip if format is IF_NONE (possibly because a block was skipped above) bool bNeedsMips = (mipmapNode && LayoutBlockDesc.FinalFormat != EImageFormat::IF_NONE) || LayoutBlockDesc.bBlocksHaveMips; if (bNeedsMips) { Ptr 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->GetPrivate()->m_settings.AddressMode; mop->FilterType = mipmapNode->GetPrivate()->m_settings.FilterType; } imageAd = mop; } if (formatNode) { Ptr fop = new ASTOpImagePixelFormat(); fop->Format = formatNode->GetPrivate()->m_format; fop->FormatIfAlpha = formatNode->GetPrivate()->m_formatIfAlpha; fop->Source = imageAd; check(fop->Format != EImageFormat::IF_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 pVectorNode = SurfaceNode->Vectors[t].Vector) { Ptr op = new ASTOpInstanceAdd(); op->type = OP_TYPE::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 (NodeScalarPtr pScalarNode = SurfaceNode->Scalars[t].Scalar) { Ptr op = new ASTOpInstanceAdd(); op->type = OP_TYPE::IN_ADDSCALAR; op->instance = LastSurfOp; // Scalar FScalarGenerationResult ScalarResult; GenerateScalar(ScalarResult, Options, pScalarNode); 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 (NodeStringPtr pStringNode = SurfaceNode->Strings[t].String) { Ptr op = new ASTOpInstanceAdd(); op->type = OP_TYPE::IN_ADDSTRING; op->instance = LastSurfOp; FStringGenerationResult StringResult; GenerateString(StringResult, Options, pStringNode); op->value = StringResult.op; // Name op->name = SurfaceNode->Strings[t].Name; LastSurfOp = op; } } } SurfaceResult.surfaceOp = LastSurfOp; TargetSurface->ResultSurfaceOp = LastSurfOp; // If we are going to share this surface properties, remember it. if (bIsBaseForSharedSurface) { check(!SharedMeshOptionsMap.Contains(SurfaceNode->SharedSurfaceId)); SharedMeshOptionsMap.Add(SurfaceNode->SharedSurfaceId, MeshResults); } } //--------------------------------------------------------------------------------------------- void CodeGenerator::Generate_LOD(const FLODGenerationOptions& Options, FGenericGenerationResult& Result, const NodeLOD* InNode) { const NodeLOD& node = *InNode; MUTABLE_CPUPROFILER_SCOPE(Generate_LOD); // Build a series of operations to assemble the component Ptr lastCompOp; Ptr lastMeshOp; FString lastMeshName; // This generates a different ID for each surface. It can be used to match it to the // mesh surface, or for debugging. It cannot be 0 because it is a special case for the // merge operation. int32 surfaceID=1; // Look for all surfaces that belong to this component for (int32 i = 0; i= 0) { enabledInThisState = (Options.State < its.StateCondition.Num()) && ( its.StateCondition[Options.State] ); } if (!enabledInThisState) { continue; } } Ptr sop = new ASTOpInstanceAdd(); sop->type = OP_TYPE::IN_ADDSURFACE; sop->name = its.Node->Name; sop->instance = lastCompOp; FSurfaceGenerationOptions SurfaceOptions(Options); FSurfaceGenerationResult surfaceGenerationResult; GenerateSurface( surfaceGenerationResult, SurfaceOptions, its.Node ); sop->value = surfaceGenerationResult.surfaceOp; sop->id = surfaceID; sop->ExternalId = its.Node->ExternalId; sop->SharedSurfaceId = its.Node->SharedSurfaceId; Ptr surfaceAt = sop; Ptr SurfaceConditionOp = its.FinalCondition; { Ptr op = new ASTOpConditional(); op->type = OP_TYPE::IN_CONDITIONAL; op->no = lastCompOp; op->yes = surfaceAt; 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 mergeAd; { Ptr added = its.ResultMeshOp; Ptr mop = new ASTOpFixed(); mop->op.type = OP_TYPE::ME_MERGE; mop->SetChild(mop->op.args.MeshMerge.base, lastMeshOp ); mop->SetChild(mop->op.args.MeshMerge.added, added ); mop->op.args.MeshMerge.newSurfaceID = surfaceID; mergeAd = mop; } if (SurfaceConditionOp) { Ptr op = new ASTOpConditional(); op->type = OP_TYPE::ME_CONDITIONAL; op->no = lastMeshOp; op->yes = mergeAd; op->condition = SurfaceConditionOp; lastMeshOp = op; } else { lastMeshOp = mergeAd; } } } // Add op to optimize the skinning of the resulting mesh { Ptr mop = new ASTOpMeshOptimizeSkinning(); mop->source = lastMeshOp; lastMeshOp = mop; } // Add the component mesh { Ptr iop = new ASTOpInstanceAdd(); iop->type = OP_TYPE::IN_ADDMESH; iop->instance = lastCompOp; iop->value = lastMeshOp; lastCompOp = iop; } Result.op = lastCompOp; } //--------------------------------------------------------------------------------------------- // Ptr CodeGenerator::Visit( const NodeObjectNew::Private& node ) void CodeGenerator::Generate_ObjectNew(const FGenericGenerationOptions& Options, FGenericGenerationResult& Result, const NodeObjectNew* InNode) { MUTABLE_CPUPROFILER_SCOPE(NodeObjectNew); // There is always at least a null parent bool bIsChildObject = CurrentParents.Num() > 1; // Add this object as current parent CurrentParents.Add( FParentKey() ); CurrentParents.Last().ObjectNode = InNode; // Parse the child objects first, which will accumulate operations in the patching lists for ( int32 t=0; t< InNode->Children.Num(); ++t ) { if ( const NodeObject* pChildNode = InNode->Children[t].get() ) { Ptr paramOp; // If there are parent objects, the condition of this object depends on the // condition of the parent object if ( CurrentObject.Num() ) { paramOp = CurrentObject.Last().Condition; } else { // In case there is no group node, we generate a constant true condition // This condition will be overwritten by the group nodes. Ptr op = new ASTOpConstantBool(); op->value = true; paramOp = op; } FObjectGenerationData data; data.Condition = paramOp; CurrentObject.Add( data ); // This op is ignored: everything is stored as patches to apply to the parent when it is compiled. Generate_Generic( pChildNode, Options ); CurrentObject.Pop(); } } // Create the expression adding all the components Ptr LastCompOp; Ptr PlaceholderOp; if (bIsChildObject) { PlaceholderOp = new ASTOpInstanceAdd; LastCompOp = PlaceholderOp; } // Add the components in this node for ( int32 t=0; t< InNode->Components.Num(); ++t ) { const NodeComponent* ComponentNode = InNode->Components[t].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 FAdditionalComponentKey ThisKey; ThisKey.ObjectNode = CurrentParents.Last().ObjectNode; TArray* ThisAdditionalComponents = AdditionalComponents.Find(ThisKey); if (LastCompOp && ThisAdditionalComponents) { for (const 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 // 2 is because there must be a parent and there is always a null element as well. if (LastCompOp && bIsChildObject) { // TODO: Directly to the root object? const FParentKey& ParentObjectKey = CurrentParents[CurrentParents.Num() - 2]; FAdditionalComponentKey ParentKey; ParentKey.ObjectNode = ParentObjectKey.ObjectNode; FAdditionalComponentData Data; Data.ComponentOp = LastCompOp; Data.PlaceholderOp = PlaceholderOp; AdditionalComponents.FindOrAdd(ParentKey).Add(Data); } Ptr RootOp = LastCompOp; // 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(); if (CurrentObject.Num() > 0) { SavedOp.Condition = CurrentObject.Last().Condition; } SavedOp.ExtensionDataOp = ChildResult.Op; SavedOp.ExtensionDataName = NamedNode.Name; } if (CurrentObject.Num() == 0) { for (const FConditionalExtensionDataOp& SavedOp : ConditionalExtensionDataOps) { Ptr ExtensionPinOp = new ASTOpAddExtensionData(); ExtensionPinOp->Instance = ASTChild(ExtensionPinOp, RootOp); ExtensionPinOp->ExtensionData = ASTChild(ExtensionPinOp, SavedOp.ExtensionDataOp); ExtensionPinOp->ExtensionDataName = SavedOp.ExtensionDataName; if (SavedOp.Condition.get()) { Ptr ConditionOp = new ASTOpConditional(); ConditionOp->type = OP_TYPE::IN_CONDITIONAL; ConditionOp->no = RootOp; ConditionOp->yes = ExtensionPinOp; ConditionOp->condition = ASTChild(ConditionOp, SavedOp.Condition); RootOp = ConditionOp; } else { RootOp = ExtensionPinOp; } } } CurrentParents.Pop(); Result.op = RootOp; } //--------------------------------------------------------------------------------------------- //Ptr CodeGenerator::Visit( const NodeObjectGroup::Private& node ) void CodeGenerator::Generate_ObjectGroup(const FGenericGenerationOptions& Options, FGenericGenerationResult& Result, const NodeObjectGroup* InNode) { const NodeObjectGroup::Private& node = *InNode->GetPrivate(); TArray usedNames; // Parse the child objects first, which will accumulate operations in the patching lists for ( int32 t=0; t conditionOp; bool found = false; for( int32 i = 0; !found && i != FirstPass.Objects.Num(); i++ ) { FirstPassGenerator::FObject& it = FirstPass.Objects[i]; if (it.Node == pChildNode) { found = true; conditionOp = it.Condition; } } // \todo // TrunkStraight_02_BranchTop crash // It may happen with partial compilations? // check(found); FObjectGenerationData data; data.Condition = conditionOp; CurrentObject.Add( data ); // This op is ignored: everything is stored as patches to apply to the parent when // it is compiled. Generate_Generic( pChildNode, Options ); CurrentObject.Pop(); // Check for duplicated child names FString strChildName = pChildNode->GetName(); if (usedNames.Contains(strChildName)) { FString Msg = FString::Printf(TEXT("Object group has more than one children with the same name [%s]."), *strChildName ); ErrorLog->GetPrivate()->Add(Msg, ELMT_WARNING, InNode->GetMessageContext()); } else { usedNames.Add(strChildName); } } } } //--------------------------------------------------------------------------------------------- Ptr 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->GetPrivate()->Add( Msg, ELMT_ERROR, ErrorContext); // Create a constant node Ptr pNode = new NodeBoolConstant(); pNode->SetValue(Value); FBoolGenerationResult ChildResult; FGenericGenerationOptions Options; GenerateBool(ChildResult,Options, pNode ); return ChildResult.op; } //--------------------------------------------------------------------------------------------- void CodeGenerator::GetModifiersFor( const TArray& SurfaceTags, bool bModifiersForBeforeOperations, TArray& OutModifiers) { MUTABLE_CPUPROFILER_SCOPE(GetModifiersFor); if (SurfaceTags.IsEmpty()) { return; } for (const FirstPassGenerator::FModifier& m: FirstPass.Modifiers) { // Correct stage? if (m.Node->bApplyBeforeNormalOperations != bModifiersForBeforeOperations) { continue; } // Already there? bool bAlreadyAdded = OutModifiers.FindByPredicate( [&m](const FirstPassGenerator::FModifier& c) {return c.Node == m.Node; }) != nullptr; if (bAlreadyAdded) { continue; } // Matching tags? bool bApply = false; switch (m.Node->MultipleTagsPolicy) { case EMutableMultipleTagPolicy::OnlyOneRequired: { for (const FString& Tag: m.Node->RequiredTags) { if (SurfaceTags.Contains(Tag)) { bApply = true; break; } } break; } case EMutableMultipleTagPolicy::AllRequired: { bApply = true; for (const FString& Tag : m.Node->RequiredTags) { if (!SurfaceTags.Contains(Tag)) { bApply = false; break; } } } } if (bApply) { OutModifiers.Add(m); } } } //--------------------------------------------------------------------------------------------- Ptr CodeGenerator::ApplyMeshModifiers( const TArray& Modifiers, const FMeshGenerationOptions& Options, FMeshGenerationResult& BaseMeshResult, const FMeshGenerationResult* SharedMeshResults, const void* ErrorContext, const NodeMeshConstant* OriginalMeshNode ) { Ptr LastMeshOp = BaseMeshResult.MeshOp; Ptr PreModifiersMesh = LastMeshOp; int32 CurrentLOD = CurrentParents.Last().Lod; // Process mesh extend modifiers (from edit modifiers) int32 EditIndex = 0; for (const FirstPassGenerator::FModifier& m : Modifiers) { if (ModifiersToIgnore.Contains(m)) { // Prevent recursion. continue; } if (m.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType()) { const NodeModifierSurfaceEdit* Edit = static_cast(m.Node); BaseMeshResult.ExtraMeshLayouts.Emplace(); bool bAffectsCurrentLOD = Edit->LODs.IsValidIndex(CurrentLOD); if (bAffectsCurrentLOD && Edit->LODs[CurrentLOD].MeshAdd) { Ptr pAdd = Edit->LODs[CurrentLOD].MeshAdd; // Store the data necessary to apply modifiers for the pre-normal operations stage. FMeshGenerationOptions MergedMeshOptions(Options); MergedMeshOptions.ActiveTags = Edit->EnableTags; // TODO: Append to current? if (SharedMeshResults) { check(SharedMeshResults->ExtraMeshLayouts.IsValidIndex(EditIndex)); MergedMeshOptions.OverrideLayouts = SharedMeshResults->ExtraMeshLayouts[EditIndex].GeneratedLayouts; } FMeshGenerationResult AddResults; GenerateMesh(MergedMeshOptions, AddResults, pAdd); // Apply the modifier for the post-normal operations stage to the added mesh FMeshGenerationOptions ModifierOptions(Options); ModifierOptions.ActiveTags = Edit->EnableTags; TArray ChildModifiers; constexpr bool bModifiersForBeforeOperations = false; GetModifiersFor(ModifierOptions.ActiveTags, bModifiersForBeforeOperations, ChildModifiers); ModifiersToIgnore.Push(m); Ptr AddedMeshOp = ApplyMeshModifiers(ChildModifiers, ModifierOptions, AddResults, SharedMeshResults, ErrorContext, nullptr); ModifiersToIgnore.Pop(); FMeshGenerationResult::FExtraLayouts data; data.GeneratedLayouts = AddResults.GeneratedLayouts; data.Condition = m.FinalCondition; data.MeshFragment = AddedMeshOp; BaseMeshResult.ExtraMeshLayouts[EditIndex] = data; Ptr mop = new ASTOpFixed(); mop->op.type = OP_TYPE::ME_MERGE; mop->SetChild(mop->op.args.MeshMerge.base, LastMeshOp); mop->SetChild(mop->op.args.MeshMerge.added, AddedMeshOp); // will merge the meshes under the same surface mop->op.args.MeshMerge.newSurfaceID = 0; // Condition to apply if (m.FinalCondition) { Ptr conditionalAd = new ASTOpConditional(); conditionalAd->type = OP_TYPE::ME_CONDITIONAL; conditionalAd->no = LastMeshOp; conditionalAd->yes = mop; conditionalAd->condition = m.FinalCondition; LastMeshOp = conditionalAd; } else { LastMeshOp = mop; } } ++EditIndex; } } // "remove" operation to group all the removes Ptr RemoveOp; // Process mesh remove modifiers (from edit modifiers) for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType()) { const NodeModifierSurfaceEdit* Edit = static_cast(m.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 pRemove = Edit->LODs[CurrentLOD].MeshRemove; FMeshGenerationResult removeResults; FMeshGenerationOptions RemoveMeshOptions; RemoveMeshOptions.bLayouts = false; RemoveMeshOptions.State = Options.State; RemoveMeshOptions.ActiveTags = Edit->EnableTags; GenerateMesh(RemoveMeshOptions, removeResults, pRemove); Ptr maskOp = new ASTOpFixed(); maskOp->op.type = OP_TYPE::ME_MASKDIFF; // By default, remove from the base Ptr removeFrom = BaseMeshResult.BaseMeshOp; maskOp->SetChild(maskOp->op.args.MeshMaskDiff.source, removeFrom); maskOp->SetChild(maskOp->op.args.MeshMaskDiff.fragment, removeResults.MeshOp); if (!RemoveOp) { RemoveOp = new ASTOpMeshRemoveMask(); RemoveOp->source = LastMeshOp; RemoveOp->FaceCullStrategy = Edit->FaceCullStrategy; LastMeshOp = RemoveOp; } RemoveOp->AddRemove(m.FinalCondition, maskOp); } } } // Process mesh morph modifiers (from edit modifiers) for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType()) { const NodeModifierSurfaceEdit* Edit = static_cast(m.Node); if (Edit->MeshMorph.IsEmpty()) { continue; } check(OriginalMeshNode); Ptr TargetMesh = OriginalMeshNode->FindMorph(Edit->MeshMorph); if (!TargetMesh) { continue; } { // Target mesh Ptr TargetMeshOp = new ASTOpConstantResource; TargetMeshOp->Type = OP_TYPE::ME_CONSTANT; TargetMeshOp->SetValue(TargetMesh->Clone(), CompilerOptions->OptimisationOptions.DiskCacheContext); TargetMeshOp->SourceDataDescriptor = OriginalMeshNode->SourceDataDescriptor; // Morph generation through mesh diff Ptr diffAd; { Ptr op = new ASTOpMeshDifference(); op->Base = BaseMeshResult.BaseMeshOp; op->Target = TargetMeshOp; // Morphing tex coords here is not supported: // Generating the homogoneous UVs is difficult since we don't have the base // layout yet. op->bIgnoreTextureCoords = true; diffAd = op; } // Morph operation Ptr morphAd; { Ptr op = new ASTOpMeshMorph(); // Factor if (Edit->MorphFactor) { FScalarGenerationResult ChildResult; GenerateScalar(ChildResult, Options, Edit->MorphFactor); op->Factor = ChildResult.op; } else { Ptr auxNode = new NodeScalarConstant(); auxNode->SetValue(1.0f); FScalarGenerationResult ChildResult; GenerateScalar(ChildResult, Options, auxNode); op->Factor = ChildResult.op; } // Base op->Base = LastMeshOp; // Targets op->Target = diffAd; morphAd = op; } // Condition to apply the morph if (m.FinalCondition) { Ptr conditionalAd = new ASTOpConditional(); conditionalAd->type = OP_TYPE::ME_CONDITIONAL; conditionalAd->no = LastMeshOp; conditionalAd->yes = morphAd; conditionalAd->condition = m.FinalCondition; LastMeshOp = conditionalAd; } else { LastMeshOp = morphAd; } } } } // Process clip-with-mesh modifiers RemoveOp = nullptr; for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.Node->GetType()== NodeModifierMeshClipWithMesh::GetStaticType()) { const NodeModifierMeshClipWithMesh* TypedClipNode = static_cast(m.Node); Ptr op = new ASTOpMeshMaskClipMesh(); op->source = PreModifiersMesh; // Parameters FMeshGenerationOptions ClipOptions; ClipOptions.bLayouts = false; ClipOptions.State = Options.State; FMeshGenerationResult clipResult; GenerateMesh(ClipOptions, clipResult, TypedClipNode->ClipMesh); op->clip = clipResult.MeshOp; if (!op->clip) { ErrorLog->GetPrivate()->Add("Clip mesh has not been generated", ELMT_ERROR, ErrorContext); continue; } Ptr maskAt = op; if (!RemoveOp) { RemoveOp = new ASTOpMeshRemoveMask(); RemoveOp->source = LastMeshOp; RemoveOp->FaceCullStrategy = TypedClipNode->FaceCullStrategy; LastMeshOp = RemoveOp; } Ptr fullCondition = m.FinalCondition; RemoveOp->AddRemove(fullCondition, maskAt); } } // Process clip-with-mask modifiers for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.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); const Mesh* OriginalMesh = OriginalMeshNode->GetPrivate()->Value.get(); Ptr UVMeshOp = new ASTOpConstantResource(); UVMeshOp->Type = OP_TYPE::ME_CONSTANT; UVMeshOp->SetValue(OriginalMesh->Clone(), CompilerOptions->OptimisationOptions.DiskCacheContext); UVMeshOp->SourceDataDescriptor = OriginalMeshNode->SourceDataDescriptor; const NodeModifierMeshClipWithUVMask* TypedClipNode = static_cast(m.Node); Ptr MeshMaskAt; Ptr op = new ASTOpMeshMaskClipUVMask(); MeshMaskAt = op; op->Source = BaseMeshResult.BaseMeshOp; op->UVSource = UVMeshOp; op->LayoutIndex = TypedClipNode->LayoutIndex; if (TypedClipNode->ClipMask) { // Parameters to generate the mask image FImageGenerationOptions ClipOptions; ClipOptions.ImageLayoutStrategy = CompilerOptions::TextureLayoutStrategy::None; ClipOptions.LayoutBlockId = FLayoutBlock::InvalidBlockId; ClipOptions.State = Options.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. op->MaskImage = GenerateImageFormat(ClipMaskResult.op, mu::EImageFormat::IF_L_UBYTE); if (!op->MaskImage) { ErrorLog->GetPrivate()->Add("Clip UV mask has not been generated", ELMT_ERROR, ErrorContext); continue; } } else if (TypedClipNode->ClipLayout) { // Generate the layout with blocks to extract Ptr Layout = GenerateLayout(TypedClipNode->ClipLayout, 0); Ptr LayoutOp = new ASTOpConstantResource(); LayoutOp->Type = OP_TYPE::LA_CONSTANT; LayoutOp->SetValue(Layout, CompilerOptions->OptimisationOptions.DiskCacheContext); op->MaskLayout = LayoutOp; } else { // No mask or layout specified to clip. Don't clip anything. } if (MeshMaskAt) { if (!RemoveOp) { RemoveOp = new ASTOpMeshRemoveMask(); RemoveOp->source = LastMeshOp; RemoveOp->FaceCullStrategy = TypedClipNode->FaceCullStrategy; LastMeshOp = RemoveOp; } Ptr fullCondition = m.FinalCondition; RemoveOp->AddRemove(fullCondition, MeshMaskAt); } } } // Process clip-morph-plane modifiers for (const FirstPassGenerator::FModifier& m : Modifiers) { Ptr modifiedMeshOp; if (m.Node->GetType() == NodeModifierMeshClipMorphPlane::GetStaticType()) { const NodeModifierMeshClipMorphPlane* TypedNode = static_cast(m.Node); Ptr op = new ASTOpMeshClipMorphPlane(); op->source = LastMeshOp; op->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 aux_base(0.f, 1.f, 0.f); if (FMath::Abs(FVector3f::DotProduct(TypedNode->Parameters.Normal, aux_base)) > 0.95f) { aux_base = FVector3f(0.f, 0.f, 1.f); } morphShape.side = FVector3f::CrossProduct(TypedNode->Parameters.Normal, aux_base); } op->morphShape = morphShape; } // Selection box op->VertexSelectionType = TypedNode->Parameters.VertexSelectionType; if (op->VertexSelectionType == EClipVertexSelectionType::Shape) { FShape selectionShape; selectionShape.type = (uint8)FShape::Type::AABox; selectionShape.position = TypedNode->Parameters.SelectionBoxOrigin; selectionShape.size = TypedNode->Parameters.SelectionBoxRadius; op->selectionShape = selectionShape; } else if (op->VertexSelectionType == EClipVertexSelectionType::BoneHierarchy) { op->vertexSelectionBone = TypedNode->Parameters.VertexSelectionBone; op->vertexSelectionBoneMaxRadius = TypedNode->Parameters.MaxEffectRadius; } // Parameters op->dist = TypedNode->Parameters.DistanceToPlane; op->factor = TypedNode->Parameters.LinearityFactor; modifiedMeshOp = op; Ptr fullCondition = m.FinalCondition; Ptr ConditionalOp = new ASTOpConditional(); ConditionalOp->type = OP_TYPE::ME_CONDITIONAL; ConditionalOp->no = LastMeshOp; ConditionalOp->yes = modifiedMeshOp; ConditionalOp->condition = fullCondition; LastMeshOp = ConditionalOp; } } // Process clip deform modifiers. for (const FirstPassGenerator::FModifier& M : Modifiers) { Ptr ModifiedMeshOp; if (M.Node->GetType()==NodeModifierMeshClipDeform::GetStaticType()) { const NodeModifierMeshClipDeform* TypedClipNode = static_cast(M.Node); Ptr BindOp = new ASTOpMeshBindShape(); Ptr ClipOp = new ASTOpMeshClipDeform(); ClipOp->FaceCullStrategy = TypedClipNode->FaceCullStrategy; FMeshGenerationOptions ClipOptions; ClipOptions.bLayouts = false; ClipOptions.State = Options.State; FMeshGenerationResult ClipShapeResult; GenerateMesh(ClipOptions, ClipShapeResult, TypedClipNode->ClipMesh); ClipOp->ClipShape = ClipShapeResult.MeshOp; BindOp->Mesh = LastMeshOp; BindOp->Shape = ClipShapeResult.MeshOp; BindOp->BindingMethod = static_cast(TypedClipNode->BindingMethod); ClipOp->Mesh = BindOp; if (!ClipOp->ClipShape) { ErrorLog->GetPrivate()->Add ("Clip shape mesh has not been generated", ELMT_ERROR, ErrorContext); } else { ModifiedMeshOp = ClipOp; } } if (ModifiedMeshOp) { Ptr FullCondition = M.FinalCondition; Ptr Op = new ASTOpConditional(); Op->type = OP_TYPE::ME_CONDITIONAL; Op->no = LastMeshOp; Op->yes = ModifiedMeshOp; Op->condition = FullCondition; LastMeshOp = Op; } } // Process transform mesh within mesh modifiers. for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.Node->GetType()== NodeModifierMeshTransformInMesh::GetStaticType()) { const NodeModifierMeshTransformInMesh* TypedTransformNode = static_cast(m.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) { Ptr transformOp = new ASTOpMeshTransformWithBoundingMesh(); transformOp->source = LastMeshOp; // Transform matrix. if (TypedTransformNode->MatrixNode) { FMatrixGenerationResult ChildResult; GenerateMatrix(ChildResult, Options, TypedTransformNode->MatrixNode); transformOp->matrix = ChildResult.op; } if (TypedTransformNode->BoundingMesh) { // Parameters FMeshGenerationOptions MeshOptions; MeshOptions.bLayouts = false; MeshOptions.State = Options.State; FMeshGenerationResult BoundingMeshResult; GenerateMesh(MeshOptions, BoundingMeshResult, TypedTransformNode->BoundingMesh); transformOp->boundingMesh = BoundingMeshResult.MeshOp; if (!transformOp->boundingMesh) { ErrorLog->GetPrivate()->Add("Bounding mesh has not been generated", ELMT_ERROR, ErrorContext); continue; } } // Condition to apply the transform op if (m.FinalCondition) { Ptr conditionalAd = new ASTOpConditional(); conditionalAd->type = OP_TYPE::ME_CONDITIONAL; conditionalAd->no = LastMeshOp; conditionalAd->yes = transformOp; conditionalAd->condition = m.FinalCondition; LastMeshOp = conditionalAd; } else { LastMeshOp = transformOp; } } } } return LastMeshOp; } Ptr CodeGenerator::ApplyImageBlockModifiers( const TArray& Modifiers, const FImageGenerationOptions& Options, Ptr BaseImageOp, const NodeSurfaceNew::FImageData& ImageData, FIntPoint GridSize, const FLayoutBlockDesc& LayoutBlockDesc, box< FIntVector2 > RectInCells, const void* ErrorContext) { Ptr LastImageOp = BaseImageOp; int32 CurrentLOD = CurrentParents.Last().Lod; // Process patch image modifiers (from edit modifiers) for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType()) { const NodeModifierSurfaceEdit* Edit = static_cast(m.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. Ptr PatchMask = GenerateImageBlockPatchMask(*MatchingEdit, GridSize, LayoutBlockDesc.BlockPixelsX, LayoutBlockDesc.BlockPixelsY, RectInCells); if (PatchMask) { LastImageOp = GenerateImageBlockPatch(LastImageOp, *MatchingEdit, PatchMask, m.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::IF_NONE) { Out.FinalFormat = BlockDesc.m_format; } } }; Ptr CodeGenerator::ApplyImageExtendModifiers( const TArray& Modifiers, const FGenericGenerationOptions& Options, const FMeshGenerationResult& BaseMeshResults, Ptr BaseImageOp, CompilerOptions::TextureLayoutStrategy ImageLayoutStrategy, int32 LayoutIndex, const NodeSurfaceNew::FImageData& ImageData, FIntPoint GridSize, CodeGenerator::FLayoutBlockDesc& InOutLayoutBlockDesc, const void* ModifiedNodeErrorContext) { Ptr LastImageOp = BaseImageOp; int32 CurrentLOD = CurrentParents.Last().Lod; // Process mesh extend modifiers (from edit modifiers) int32 EditIndex = 0; for (const FirstPassGenerator::FModifier& m : Modifiers) { if (m.Node->GetType() == NodeModifierSurfaceEdit::GetStaticType()) { const NodeModifierSurfaceEdit* Edit = static_cast(m.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->GetPrivate()->Add(Msg, ELMT_INFO, Edit->GetMessageContext(), ModifiedNodeErrorContext); } continue; } const TArray& ExtraLayouts = BaseMeshResults.ExtraMeshLayouts[ThisEditIndex].GeneratedLayouts; if (LayoutIndex >= ExtraLayouts.Num() || !ExtraLayouts[LayoutIndex].Layout) { ErrorLog->GetPrivate()->Add(TEXT("Trying to extend a layout that doesn't exist."), ELMT_WARNING, Edit->GetMessageContext(), ModifiedNodeErrorContext); } else { Ptr pExtendLayout = ExtraLayouts[LayoutIndex].Layout; Ptr lastBase = LastImageOp; for (int32 b = 0; b < pExtendLayout->GetBlockCount(); ++b) { // Generate the image block FImageGenerationOptions ImageOptions; ImageOptions.State = Options.State; ImageOptions.ImageLayoutStrategy = ImageLayoutStrategy; ImageOptions.ActiveTags = Edit->EnableTags; // TODO: Merge with current tags? ImageOptions.RectSize = { 0,0 }; ImageOptions.LayoutToApply = pExtendLayout; ImageOptions.LayoutBlockId = pExtendLayout->Blocks[b].Id; FImageGenerationResult ExtendResult; GenerateImage(ImageOptions, ExtendResult, MatchingEdit->Extend); Ptr fragmentAd = ExtendResult.op; // Block in layout grid units box< FIntVector2 > rectInCells; rectInCells.min = pExtendLayout->Blocks[b].Min; rectInCells.size = pExtendLayout->Blocks[b].Size; FImageDesc ExtendDesc = fragmentAd->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. //fragmentAd = GenerateImageFormat(fragmentAd, FinalImageFormat); UE::Math::TIntVector2 expectedSize; expectedSize[0] = InOutLayoutBlockDesc.BlockPixelsX * rectInCells.size[0]; expectedSize[1] = InOutLayoutBlockDesc.BlockPixelsY * rectInCells.size[1]; fragmentAd = GenerateImageSize(fragmentAd, expectedSize); // Apply tiling to avoid generating chunks of image that are too big. fragmentAd = ApplyTiling(fragmentAd, expectedSize, InOutLayoutBlockDesc.FinalFormat); // Compose operation Ptr composeOp = new ASTOpImageCompose(); composeOp->Layout = BaseMeshResults.LayoutOps[LayoutIndex]; composeOp->Base = lastBase; composeOp->BlockImage = fragmentAd; // Set the absolute block index. check(pExtendLayout->Blocks[b].Id != FLayoutBlock::InvalidBlockId); composeOp->BlockId = pExtendLayout->Blocks[b].Id; lastBase = composeOp; } // Condition to enable this image extension if (m.FinalCondition) { Ptr conditionalAd; Ptr cop = new ASTOpConditional(); cop->type = OP_TYPE::IM_CONDITIONAL; cop->no = LastImageOp; cop->yes = lastBase; cop->condition = m.FinalCondition; conditionalAd = cop; LastImageOp = conditionalAd; } else { LastImageOp = lastBase; } } } } return LastImageOp; } void CodeGenerator::CheckModifiersForSurface(const NodeSurfaceNew& Node, const TArray& Modifiers ) { int32 CurrentLOD = CurrentParents.Last().Lod; 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(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->GetPrivate()->Add(TEXT("A mesh section modifier applies to a section but no texture matches."), ELMT_WARNING, Edit->GetMessageContext(), Node.GetMessageContext()); } } } } Ptr CodeGenerator::GenerateDefaultTableValue(ETableColumnType NodeType) { switch (NodeType) { case mu::ETableColumnType::Scalar: { //TODO(Max):MTBL-1660 //Ptr pNode = new NodeScalarConstant(); //pNode->SetValue(-UE_MAX_FLT); // //return Generate(pNode); return nullptr; } case mu::ETableColumnType::Color: { mu::Ptr pNode = new NodeColourConstant(); pNode->Value = mu::DefaultMutableColorValue; FColorGenerationResult ChildResult; FGenericGenerationOptions Options; GenerateColor(ChildResult, Options, pNode); return ChildResult.op; } case mu::ETableColumnType::Image: { //TODO(Max):MTBL-1660 //FImageGenerationOptions DummyOptions; //FImageGenerationResult DefaultValue; // //mu::Ptr ImageNode = new mu::NodeImageReference(); //ImageNode->SetImageReference(-1); // //GenerateImage(DummyOptions, DefaultValue, ImageNode); // //return DefaultValue.op; return nullptr; } case mu::ETableColumnType::Mesh: /* The default mesh is always null */ return nullptr; default: break; } return nullptr; } }