// Copyright Epic Games, Inc. All Rights Reserved. #include "MuR/CodeRunner.h" #include "OpMeshTransformWithMesh.h" #include "GenericPlatform/GenericPlatformMath.h" #include "HAL/UnrealMemory.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Math/IntPoint.h" #include "Math/UnrealMathUtility.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "MuR/ImagePrivate.h" #include "MuR/Instance.h" #include "MuR/InstancePrivate.h" #include "MuR/Mesh.h" #include "MuR/MeshBufferSet.h" #include "MuR/Model.h" #include "MuR/ModelPrivate.h" #include "MuR/MutableMath.h" #include "MuR/MutableString.h" #include "MuR/MutableTrace.h" #include "MuR/OpImageApplyComposite.h" #include "MuR/OpImageBinarise.h" #include "MuR/OpImageBlend.h" #include "MuR/OpImageColourMap.h" #include "MuR/OpImageDisplace.h" #include "MuR/OpImageInterpolate.h" #include "MuR/OpImageLuminance.h" #include "MuR/OpImageNormalCombine.h" #include "MuR/OpImageProject.h" #include "MuR/OpImageRasterMesh.h" #include "MuR/OpImageTransform.h" #include "MuR/OpLayoutPack.h" #include "MuR/OpLayoutRemoveBlocks.h" #include "MuR/OpMeshApplyLayout.h" #include "MuR/OpMeshPrepareLayout.h" #include "MuR/OpMeshApplyPose.h" #include "MuR/OpMeshBind.h" #include "MuR/OpMeshClipDeform.h" #include "MuR/OpMeshClipMorphPlane.h" #include "MuR/OpMeshClipWithMesh.h" #include "MuR/OpMeshDifference.h" #include "MuR/OpMeshExtractLayoutBlock.h" #include "MuR/OpMeshFormat.h" #include "MuR/OpMeshGeometryOperation.h" #include "MuR/OpMeshMerge.h" #include "MuR/OpMeshMorph.h" #include "MuR/OpMeshRemove.h" #include "MuR/OpMeshReshape.h" #include "MuR/OpMeshTransform.h" #include "MuR/OpMeshOptimizeSkinning.h" #include "MuR/Operations.h" #include "MuR/Parameters.h" #include "MuR/ParametersPrivate.h" #include "MuR/PhysicsBody.h" #include "MuR/Skeleton.h" #include "MuR/SystemPrivate.h" #include "Templates/Tuple.h" #if MUTABLE_DEBUG_CODERUNNER_TASK_SCHEDULE_CALLSTACK #include "GenericPlatform/GenericPlatformStackWalk.h" #endif namespace { int32 ForcedProjectionMode = -1; static FAutoConsoleVariableRef CVarForceProjectionSamplingMode ( TEXT("mutable.ForceProjectionMode"), ForcedProjectionMode, TEXT("force mutable to use an specific projection mode, 0 = Point + None, 1 = Bilinear + TotalAreaHeuristic, -1 uses the values provided by the projector."), ECVF_Default); float GlobalProjectionLodBias = 0.0f; static FAutoConsoleVariableRef CVarGlobalProjectionLodBias ( TEXT("mutable.GlobalProjectionLodBias"), GlobalProjectionLodBias, TEXT("Lod bias applied to the lod resulting form the best mip computation for ImageProject operations, only used if a min filter method different than None is used."), ECVF_Default); bool bUseProjectionVectorImpl = true; static FAutoConsoleVariableRef CVarUseProjectionVectorImpl ( TEXT("mutable.UseProjectionVectorImpl"), bUseProjectionVectorImpl, TEXT("If set to true, enables the vectorized implementation of the projection pixel processing."), ECVF_Default); float GlobalImageTransformLodBias = 0.0f; static FAutoConsoleVariableRef CVarGlobalImageTransformLodBias ( TEXT("mutable.GlobalImageTransformLodBias"), GlobalImageTransformLodBias, TEXT("Lod bias applied to the lod resulting form the best mip computation for ImageTransform operations"), ECVF_Default); bool bUseImageTransformVectorImpl = true; static FAutoConsoleVariableRef CVarUseImageTransformVectorImpl ( TEXT("mutable.UseImageTransformVectorImpl"), bUseImageTransformVectorImpl, TEXT("If set to true, enables the vectorized implementation of the image transform pixel processing."), ECVF_Default); } #if MUTABLE_DEBUG_CODERUNNER_TASK_SCHEDULE_CALLSTACK #define AddOp(...) Invoke([&](){ AddOp(__VA_ARGS__); }); namespace mu::Private { FString DumpItemScheduledCallstack(const FScheduledOp& Item) { constexpr SIZE_T MaxStringSize = 16 * 1024; ANSICHAR StackTrace[MaxStringSize]; FString OutputString; constexpr uint32 EntriesToSkip = 3; for (uint32 Index = EntriesToSkip; Index < Item.StackDepth; ++Index) { StackTrace[0] = 0; FPlatformStackWalk::ProgramCounterToHumanReadableString(Index, Item.ScheduleCallstack[Index], StackTrace, MaxStringSize, nullptr); OutputString += FString::Printf(TEXT("\t\t%d %s\n"), Index - EntriesToSkip, ANSI_TO_TCHAR(StackTrace)); } return OutputString; } } #endif namespace mu::MemoryCounters { std::atomic& FStreamingMemoryCounter::Get() { static std::atomic Counter{0}; return Counter; } } namespace mu { TSharedRef CodeRunner::Create( const FSettings& InSettings, class FSystem::Private* InSystem, EExecutionStrategy InExecutionStrategy, const TSharedPtr& InModel, const FParameters* InParams, OP::ADDRESS At, uint32 InLODMask, uint8 ExecutionOptions, int32 InImageLOD, FScheduledOp::EType Type) { return MakeShared(FPrivateToken {}, InSettings, InSystem, InExecutionStrategy, InModel, InParams, At, InLODMask, ExecutionOptions, InImageLOD, Type); } CodeRunner::CodeRunner(FPrivateToken PrivateToken, const FSettings& InSettings, class FSystem::Private* InSystem, EExecutionStrategy InExecutionStrategy, const TSharedPtr& InModel, const FParameters* InParams, OP::ADDRESS At, uint32 InLodMask, uint8 ExecutionOptions, int32 InImageLOD, FScheduledOp::EType Type ) : Settings(InSettings) , RunnerCompletionEvent(TEXT("CodeRunnerCompletioneEventInit")) , ExecutionStrategy(InExecutionStrategy) , System(InSystem) , Model(InModel) , Params(InParams) , LODMask(InLodMask) { MUTABLE_CPUPROFILER_SCOPE(CodeRunner_Create) const FProgram& Program = Model->GetPrivate()->Program; ScheduledStagePerOp.resize(Program.OpAddress.Num()); if (Type == FScheduledOp::EType::ImageDesc) { ImageDescResults.Reserve(64); ImageDescConstantImages.Reserve(32); } // We will read this in the end, so make sure we keep it. if (Type == FScheduledOp::EType::Full) { GetMemory().IncreaseHitCount(FCacheAddress(At, 0, ExecutionOptions)); } // Start with a completed Event. This is checked at StartRun() to make sure StartRun is not called while there is // a Run in progress. RunnerCompletionEvent.Trigger(); ImageLOD = InImageLOD; // Push the root operation FScheduledOp RootOp; RootOp.At = At; RootOp.ExecutionOptions = ExecutionOptions; RootOp.Type = Type; AddOp(RootOp); } FProgramCache& CodeRunner::GetMemory() { return *System->WorkingMemoryManager.CurrentInstanceCache; } TTuple> CodeRunner::LoadExternalImageAsync(FExternalResourceId Id, uint8 MipmapsToSkip, TFunction)>& ResultCallback) { MUTABLE_CPUPROFILER_SCOPE(LoadExternalImageAsync); check(System); if (System->ExternalResourceProvider) { if (Id.ReferenceResourceId < 0) { // It's a parameter image return System->ExternalResourceProvider->GetImageAsync(Id.ImageParameter, MipmapsToSkip, ResultCallback); } else { // It's an image reference return System->ExternalResourceProvider->GetReferencedImageAsync(Model.Get(), Id.ReferenceResourceId, MipmapsToSkip, ResultCallback); } } else { // Not found and there is no generator! check(false); } return MakeTuple(UE::Tasks::MakeCompletedTask(), []() -> void {}); } TTuple> CodeRunner::LoadExternalMeshAsync(FExternalResourceId Id, int32 LODIndex, int32 SectionIndex, TFunction)>& ResultCallback) { MUTABLE_CPUPROFILER_SCOPE(LoadExternalImageAsync); check(System); if (System->ExternalResourceProvider) { if (Id.ReferenceResourceId < 0) { // It's a parameter mesh return System->ExternalResourceProvider->GetMeshAsync(Id.MeshParameter, LODIndex, SectionIndex, ResultCallback); } else { // It's a mesh reference check(false); } } else { // Not found and there is no generator! check(false); } return MakeTuple(UE::Tasks::MakeCompletedTask(), []() -> void {}); } FExtendedImageDesc CodeRunner::GetExternalImageDesc(UTexture* Image) { MUTABLE_CPUPROFILER_SCOPE(GetExternalImageDesc); check(System); if (System->ExternalResourceProvider) { return System->ExternalResourceProvider->GetImageDesc(Image); } else { // Not found and there is no generator! check(false); } return FExtendedImageDesc(); } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Conditional( const FScheduledOp& Item, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Conditional); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); OP::ConditionalArgs Args = Program.GetOpArgs(Item.At); // Conditionals have the following execution stages: // 0: we need to run the condition // 1: we need to run the branch // 2: we need to fetch the result and store it in this op switch( Item.Stage ) { case 0: { AddOp( FScheduledOp( Item.At,Item,1 ), FScheduledOp( Args.condition, Item ) ); break; } case 1: { // Get the condition result // If there is no expression, we'll assume true. bool value = true; value = LoadBool( FCacheAddress(Args.condition, Item.ExecutionIndex, Item.ExecutionOptions) ); OP::ADDRESS resultAt = value ? Args.yes : Args.no; // Schedule the end of this instruction if necessary AddOp( FScheduledOp( Item.At, Item, 2, (uint32)value), FScheduledOp( resultAt, Item) ); break; } case 2: { OP::ADDRESS resultAt = Item.CustomState ? Args.yes : Args.no; // Store the final result FCacheAddress cat( Item ); FCacheAddress rat( resultAt, Item ); switch (GetOpDataType(type)) { case EDataType::Bool: StoreBool( cat, LoadBool(rat) ); break; case EDataType::Int: StoreInt( cat, LoadInt(rat) ); break; case EDataType::Scalar: StoreScalar( cat, LoadScalar(rat) ); break; case EDataType::String: StoreString( cat, LoadString( rat ) ); break; case EDataType::Color: StoreColor( cat, LoadColor( rat ) ); break; case EDataType::Projector: StoreProjector( cat, LoadProjector(rat) ); break; case EDataType::Mesh: StoreMesh( cat, LoadMesh(rat) ); break; case EDataType::Image: StoreImage( cat, LoadImage(rat) ); break; case EDataType::Layout: StoreLayout( cat, LoadLayout(rat) ); break; case EDataType::Instance: StoreInstance( cat, LoadInstance(rat) ); break; case EDataType::ExtensionData: StoreExtensionData( cat, LoadExtensionData(rat) ); break; default: // Not implemented check( false ); } break; } default: check(false); } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Switch(const FScheduledOp& Item, const FModel* InModel ) { const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); const uint8* data = Program.GetOpArgsPointer(Item.At); OP::ADDRESS VarAddress; FMemory::Memcpy(&VarAddress, data, sizeof(OP::ADDRESS)); data += sizeof(OP::ADDRESS); OP::ADDRESS DefAddress; FMemory::Memcpy(&DefAddress, data, sizeof(OP::ADDRESS)); data += sizeof(OP::ADDRESS); uint32 CaseCount; FMemory::Memcpy(&CaseCount, data, sizeof(uint32)); data += sizeof(uint32); switch (Item.Stage) { case 0: { if (VarAddress) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(VarAddress, Item)); } else { switch (GetOpDataType(type)) { case EDataType::Bool: StoreBool(Item, false); break; case EDataType::Int: StoreInt(Item, 0); break; case EDataType::Scalar: StoreScalar(Item, 0.0f); break; case EDataType::String: StoreString(Item, nullptr); break; case EDataType::Color: StoreColor(Item, FVector4f()); break; case EDataType::Projector: StoreProjector(Item, FProjector()); break; case EDataType::Mesh: StoreMesh(Item, nullptr); break; case EDataType::Image: StoreImage(Item, nullptr); break; case EDataType::Layout: StoreLayout(Item, nullptr); break; case EDataType::Instance: StoreInstance(Item, nullptr); break; case EDataType::ExtensionData: StoreExtensionData(Item, MakeShared()); break; default: // Not implemented check(false); } } break; } case 1: { // Get the variable result int32 var = LoadInt(FCacheAddress(VarAddress, Item)); OP::ADDRESS valueAt = DefAddress; for (uint32 C = 0; C < CaseCount; ++C) { int32 Condition; FMemory::Memcpy(&Condition, data, sizeof(int32)); data += sizeof(int32); OP::ADDRESS At; FMemory::Memcpy(&At, data, sizeof(OP::ADDRESS)); data += sizeof(OP::ADDRESS); if (At && var == (int32)Condition) { valueAt = At; break; } } // Schedule the end of this instruction if necessary AddOp( FScheduledOp( Item.At, Item, 2, valueAt ), FScheduledOp( valueAt, Item ) ); break; } case 2: { OP::ADDRESS resultAt = OP::ADDRESS(Item.CustomState); // Store the final result FCacheAddress cat( Item ); FCacheAddress rat( resultAt, Item ); switch (GetOpDataType(type)) { case EDataType::Bool: StoreBool( cat, LoadBool(rat) ); break; case EDataType::Int: StoreInt( cat, LoadInt(rat) ); break; case EDataType::Scalar: StoreScalar( cat, LoadScalar(rat) ); break; case EDataType::String: StoreString( cat, LoadString( rat ) ); break; case EDataType::Color: StoreColor( cat, LoadColor( rat ) ); break; case EDataType::Projector: StoreProjector( cat, LoadProjector(rat) ); break; case EDataType::Mesh: StoreMesh( cat, LoadMesh(rat) ); break; case EDataType::Image: StoreImage( cat, LoadImage(rat) ); break; case EDataType::Layout: StoreLayout( cat, LoadLayout(rat) ); break; case EDataType::Instance: StoreInstance( cat, LoadInstance(rat) ); break; case EDataType::ExtensionData: StoreExtensionData( cat, LoadExtensionData(rat) ); break; default: // Not implemented check( false ); } break; } default: check(false); } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Instance(const FScheduledOp& Item, const FModel* InModel, uint32 lodMask ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Instance); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::IN_ADDVECTOR: { OP::InstanceAddArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.instance, Item), FScheduledOp( Args.value, Item) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress(Args.instance,Item) ); TSharedPtr Result; if (!Base) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } if ( Args.value ) { FVector4f value = LoadColor( FCacheAddress(Args.value,Item) ); OP::ADDRESS nameAd = Args.name; check( nameAd < (uint32)Program.ConstantStrings.Num() ); const FString& Name = Program.ConstantStrings[ nameAd ]; Result->GetPrivate()->AddVector( 0, 0, 0, value, FName(Name) ); } StoreInstance( Item, Result ); break; } default: check(false); } break; } case EOpType::IN_ADDSCALAR: { OP::InstanceAddArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.instance, Item), FScheduledOp( Args.value, Item) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress(Args.instance,Item) ); TSharedPtr Result; if (!Base) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } if ( Args.value ) { float value = LoadScalar( FCacheAddress(Args.value,Item) ); OP::ADDRESS nameAd = Args.name; check( nameAd < (uint32)Program.ConstantStrings.Num() ); const FString& Name = Program.ConstantStrings[ nameAd ]; Result->GetPrivate()->AddScalar( 0, 0, 0, value, FName(Name)); } StoreInstance( Item, Result ); break; } default: check(false); } break; } case EOpType::IN_ADDSTRING: { OP::InstanceAddArgs Args = Program.GetOpArgs( Item.At ); switch ( Item.Stage ) { case 0: AddOp( FScheduledOp( Item.At, Item, 1 ), FScheduledOp( Args.instance, Item ), FScheduledOp( Args.value, Item ) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress( Args.instance, Item ) ); TSharedPtr Result; if ( !Base ) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } if ( Args.value ) { TSharedPtr value = LoadString( FCacheAddress( Args.value, Item ) ); OP::ADDRESS nameAd = Args.name; check( nameAd < (uint32)Program.ConstantStrings.Num() ); const FString& Name = Program.ConstantStrings[nameAd]; Result->GetPrivate()->AddString( 0, 0, 0, value->GetValue(), FName(Name) ); } StoreInstance( Item, Result ); break; } default: check( false ); } break; } case EOpType::IN_ADDCOMPONENT: { OP::InstanceAddArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.instance, Item), FScheduledOp( Args.value, Item) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress(Args.instance,Item) ); TSharedPtr Result; if (!Base) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } if ( Args.value ) { TSharedPtr pComp = LoadInstance( FCacheAddress(Args.value,Item) ); int32 NewComponentIndex = Result->GetPrivate()->AddComponent(); if ( !pComp->GetPrivate()->Components.IsEmpty() ) { Result->GetPrivate()->Components[NewComponentIndex] = pComp->GetPrivate()->Components[0]; // Id Result->GetPrivate()->Components[NewComponentIndex].Id = Args.ExternalId; } } StoreInstance( Item, Result ); break; } default: check(false); } break; } case EOpType::IN_ADDSURFACE: { OP::InstanceAddArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.instance, Item), FScheduledOp( Args.value, Item) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress(Args.instance,Item) ); TSharedPtr Result; if (Base) { Result = mu::CloneOrTakeOver(Base); } else { Result = MakeShared(); } // Empty surfaces are ok, they still need to be created, because they may contain // additional information like internal or external IDs //if ( Args.value ) { TSharedPtr pSurf = LoadInstance( FCacheAddress(Args.value,Item) ); int32 sindex = Result->GetPrivate()->AddSurface( 0, 0 ); // Surface data if (pSurf && pSurf->GetPrivate()->Components.Num() && pSurf->GetPrivate()->Components[0].LODs.Num() && pSurf->GetPrivate()->Components[0].LODs[0].Surfaces.Num()) { Result->GetPrivate()->Components[0].LODs[0].Surfaces[sindex] = pSurf->GetPrivate()->Components[0].LODs[0].Surfaces[0]; } // Name OP::ADDRESS nameAd = Args.name; check( nameAd < (uint32)Program.ConstantStrings.Num() ); const FString& Name = Program.ConstantStrings[ nameAd ]; Result->GetPrivate()->SetSurfaceName( 0, 0, sindex, FName(Name) ); // IDs Result->GetPrivate()->Components[0].LODs[0].Surfaces[sindex].InternalId = Args.id; Result->GetPrivate()->Components[0].LODs[0].Surfaces[sindex].ExternalId = Args.ExternalId; Result->GetPrivate()->Components[0].LODs[0].Surfaces[sindex].SharedId = Args.SharedSurfaceId; } StoreInstance( Item, Result ); break; } default: check(false); } break; } case EOpType::IN_ADDLOD: { const uint8* Data = Program.GetOpArgsPointer(Item.At); uint8 LODCount; FMemory::Memcpy(&LODCount, Data, sizeof(uint8)); Data += sizeof(uint8); switch (Item.Stage) { case 0: { TArray deps; for (uint8 LODIndex=0; LODIndex Result = MakeShared(); int32 ComponentIndex = Result->GetPrivate()->AddComponent(); for (uint8 LODIndex = 0; LODIndex < LODCount; ++LODIndex) { OP::ADDRESS LODAddress; FMemory::Memcpy(&LODAddress, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); if ( LODAddress ) { bool bIsSelectedLod = ( (1<GetPrivate()->AddLOD(ComponentIndex); if (bIsSelectedLod) { TSharedPtr pLOD = LoadInstance( FCacheAddress(LODAddress,Item) ); // In a degenerated case, the returned pLOD may not have an LOD inside if (!pLOD->GetPrivate()->Components.IsEmpty() && !pLOD->GetPrivate()->Components[0].LODs.IsEmpty()) { Result->GetPrivate()->Components[ComponentIndex].LODs[InstanceLODIndex] = pLOD->GetPrivate()->Components[0].LODs[0]; } } } } StoreInstance( Item, Result ); break; } default: check(false); } break; } case EOpType::IN_ADDEXTENSIONDATA: { OP::InstanceAddExtensionDataArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { // Must pass in an Instance op and FExtensionData op check(Args.Instance); check(Args.ExtensionData); AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Instance, Item), FScheduledOp(Args.ExtensionData, Item)); break; } case 1: { // Assemble result TSharedPtr InstanceOpResult = LoadInstance(FCacheAddress(Args.Instance, Item)); TSharedPtr Result = mu::CloneOrTakeOver(InstanceOpResult); if (TSharedPtr ExtensionData = LoadExtensionData(FCacheAddress(Args.ExtensionData, Item))) { const OP::ADDRESS NameAddress = Args.ExtensionDataName; check(NameAddress < (uint32)Program.ConstantStrings.Num()); const FString& NameString = Program.ConstantStrings[NameAddress]; Result->GetPrivate()->AddExtensionData(ExtensionData, FName(NameString) ); } StoreInstance(Item, Result); break; } default: check(false); } break; } case EOpType::IN_ADDOVERLAYMATERIAL: { OP::InstanceAddOverlayMaterialArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { // Must pass in a Material op check(Args.OverlayMaterialId); AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Instance, Item), FScheduledOp(Args.OverlayMaterialId, Item)); break; } case 1: { TSharedPtr Base = LoadInstance(FCacheAddress(Args.Instance, Item)); TSharedPtr Result; if (!Base) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } Result->GetPrivate()->AddOverlayMaterial(0, LoadScalar(FCacheAddress(Args.OverlayMaterialId, Item))); StoreInstance(Item, Result); break; } default: check(false); } break; } default: check(false); } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_InstanceAddResource(const FScheduledOp& Item, const TSharedPtr& InModel, const FParameters* InParams ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_InstanceAddResource); if (!InModel || !System) { return; } const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::IN_ADDMESH: { OP::InstanceAddArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: // We don't build the resources when building instance: just store ids for them. AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.instance, Item) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress(Args.instance,Item) ); TSharedPtr Result; if (!Base) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } if ( Args.value ) { FResourceID MeshId = System->WorkingMemoryManager.GetResourceKey(InModel,InParams,Args.relevantParametersListIndex, Args.value); OP::ADDRESS NameAd = Args.name; check(NameAd < (uint32)Program.ConstantStrings.Num()); const FString& Name = Program.ConstantStrings[NameAd]; Result->GetPrivate()->SetMesh(0, 0, MeshId, FName(Name)); } StoreInstance( Item, Result ); break; } default: check(false); } break; } case EOpType::IN_ADDIMAGE: { OP::InstanceAddArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: // We don't build the resources when building instance: just store ids for them. AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.instance, Item) ); break; case 1: { TSharedPtr Base = LoadInstance( FCacheAddress(Args.instance,Item) ); TSharedPtr Result; if (!Base) { Result = MakeShared(); } else { Result = mu::CloneOrTakeOver(Base); } if ( Args.value ) { FResourceID ImageId = System->WorkingMemoryManager.GetResourceKey(InModel, InParams, Args.relevantParametersListIndex, Args.value); OP::ADDRESS NameAd = Args.name; check(NameAd < (uint32)Program.ConstantStrings.Num()); const FString& Name = Program.ConstantStrings[NameAd]; Result->GetPrivate()->AddImage(0, 0, 0, ImageId, FName(Name) ); } StoreInstance( Item, Result ); break; } default: check(false); } break; } default: check(false); } } //--------------------------------------------------------------------------------------------- bool CodeRunner::RunCode_ConstantResource(const FScheduledOp& Item, const FModel* InModel) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Constant); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::ME_CONSTANT: { OP::MeshConstantArgs Args = Program.GetOpArgs(Item.At); EMeshContentFlags MeshContentFlags = (EMeshContentFlags)Item.ExecutionOptions; TSharedPtr Source; Program.GetConstant(Args.Value, Args.Skeleton, Source, MeshContentFlags, [this](int32 BudgetReserve){ return CreateMesh(BudgetReserve); }); if (!Source) { return false; } StoreMesh(Item, Source); //UE_LOG(LogMutableCore, Log, TEXT("Set mesh constant %d."), Item.At); break; } case EOpType::IM_CONSTANT: { OP::ResourceConstantArgs Args = Program.GetOpArgs(Item.At); OP::ADDRESS cat = Args.value; int32 MipsToSkip = Item.ExecutionOptions; TSharedPtr Source; Program.GetConstant(cat, Source, MipsToSkip, [this](int32 x, int32 y, int32 m, EImageFormat f, EInitializationType i) { return CreateImage(x, y, m, f, i); }); // Assume the ROM has been loaded previously in a task generated at IssueOp if (!Source) { return false; } StoreImage( Item, Source ); //UE_LOG(LogMutableCore, Log, TEXT("Set image constant %d."), Item.At); break; } case EOpType::ED_CONSTANT: { OP::ResourceConstantArgs Args = Program.GetOpArgs(Item.At); // Assume the ROM has been loaded previously TSharedPtr SourceConst; Program.GetExtensionDataConstant(Args.value, SourceConst); check(SourceConst); StoreExtensionData(Item, SourceConst); break; } default: if (type!=EOpType::NONE) { // Operation not implemented check( false ); } break; } // Success return true; } void CodeRunner::RunCode_Mesh(const FScheduledOp& Item, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Mesh); check((EMeshContentFlags)Item.ExecutionOptions != EMeshContentFlags::None); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::ME_REFERENCE: { OP::ResourceReferenceArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { TSharedPtr Result; if (Args.ForceLoad) { // This should never be reached because it should have been caught as a Task in IssueOp check(false); } else { Result = FMesh::CreateAsReference(Args.ID, false); } StoreMesh(Item, Result); break; } default: check(false); } break; } case EOpType::ME_APPLYLAYOUT: { OP::MeshApplyLayoutArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Mesh, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Mesh, Item), FScheduledOp(Args.Layout, Item)); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_APPLYLAYOUT) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Base = LoadMesh(FCacheAddress(Args.Mesh, Item)); StoreMesh(Item, Base); } else { TSharedPtr Base = LoadMesh(FCacheAddress(Args.Mesh, Item)); TSharedPtr Layout = LoadLayout(FCacheAddress(Args.Layout, Item)); if (Base) { TSharedPtr Result = CloneOrTakeOver(Base); int32 texCoordsSet = Args.Channel; MeshApplyLayout(Result.Get(), Layout.Get(), texCoordsSet); StoreMesh(Item, Result); } else { StoreMesh(Item, nullptr); } } break; } default: check(false); } break; } case EOpType::ME_PREPARELAYOUT: { OP::MeshPrepareLayoutArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Mesh, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Mesh, Item), FScheduledOp(Args.Layout, Item)); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_PREPARELAYOUT); if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Base = LoadMesh(FCacheAddress(Args.Mesh, Item)); StoreMesh(Item, Base); } else { TSharedPtr Base = LoadMesh(FCacheAddress(Args.Mesh, Item)); TSharedPtr Layout = LoadLayout(FCacheAddress(Args.Layout, Item)); if (Base && Layout) { TSharedPtr Result = CloneOrTakeOver(Base); MeshPrepareLayout(*Result, *Layout, Args.LayoutChannel, Args.bNormalizeUVs, Args.bClampUVIslands, Args.bEnsureAllVerticesHaveLayoutBlock, Args.bUseAbsoluteBlockIds); StoreMesh(Item, Result); } else { StoreMesh(Item, Base); } } break; } default: check(false); } break; } case EOpType::ME_DIFFERENCE: { const uint8* data = Program.GetOpArgsPointer(Item.At); OP::ADDRESS BaseAt = 0; FMemory::Memcpy(&BaseAt, data, sizeof(OP::ADDRESS)); data += sizeof(OP::ADDRESS); OP::ADDRESS TargetAt = 0; FMemory::Memcpy(&TargetAt, data, sizeof(OP::ADDRESS)); data += sizeof(OP::ADDRESS); switch (Item.Stage) { case 0: { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Result = CreateMesh(); StoreMesh(Item, Result); } else { if (BaseAt && TargetAt) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(BaseAt, Item), FScheduledOp(TargetAt, Item)); } else { StoreMesh(Item, nullptr); } } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_DIFFERENCE) check(EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) TSharedPtr Base = LoadMesh(FCacheAddress(BaseAt,Item)); TSharedPtr pTarget = LoadMesh(FCacheAddress(TargetAt,Item)); TArray> Semantics; TArray> SemanticIndices; uint8 bIgnoreTextureCoords = 0; FMemory::Memcpy(&bIgnoreTextureCoords, data, sizeof(uint8)); data += sizeof(uint8); uint8 NumChannels = 0; FMemory::Memcpy(&NumChannels, data, sizeof(uint8)); data += sizeof(uint8); for (uint8 i = 0; i < NumChannels; ++i) { uint8 Semantic = 0; FMemory::Memcpy(&Semantic, data, sizeof(uint8)); data += sizeof(uint8); uint8 SemanticIndex = 0; FMemory::Memcpy(&SemanticIndex, data, sizeof(uint8)); data += sizeof(uint8); Semantics.Add(EMeshBufferSemantic(Semantic)); SemanticIndices.Add(SemanticIndex); } TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MeshDifference(Result.Get(), Base.Get(), pTarget.Get(), NumChannels, Semantics.GetData(), SemanticIndices.GetData(), bIgnoreTextureCoords != 0, bOutSuccess); Release(Base); Release(pTarget); StoreMesh(Item, Result); break; } default: check(false); } break; } case EOpType::ME_MORPH: { const uint8* Data = Program.GetOpArgsPointer(Item.At); OP::ADDRESS FactorAt = 0; FMemory::Memcpy(&FactorAt, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); OP::ADDRESS BaseAt = 0; FMemory::Memcpy(&BaseAt, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); OP::ADDRESS TargetAt = 0; FMemory::Memcpy(&TargetAt, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); switch (Item.Stage) { case 0: { if (BaseAt) { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(BaseAt, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(FactorAt, Item)); } } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_MORPH_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Base = LoadMesh(FCacheAddress(BaseAt, Item)); StoreMesh(Item, Base); } else { float Factor = LoadScalar(FCacheAddress(FactorAt, Item)); // Factor goes from -1 to 1 across all targets. [0 - 1] represents positive morphs, while [-1, 0) represent negative morphs. Factor = FMath::Clamp(Factor, -1.0f, 1.0f); // Is the factor not in range [-1, 1], it will index a non existing morph. FScheduledOpData OpHeapData; OpHeapData.Interpolate.Bifactor = Factor; uint32 DataAddress = uint32(HeapData.Add(OpHeapData)); // No morph if (FMath::IsNearlyZero(Factor)) { AddOp(FScheduledOp(Item.At, Item, 2, DataAddress), FScheduledOp(BaseAt, Item)); } // The Morph, partial or full else { // We will need the base again AddOp(FScheduledOp(Item.At, Item, 2, DataAddress), FScheduledOp(BaseAt, Item), FScheduledOp(TargetAt, Item)); } } break; } case 2: { MUTABLE_CPUPROFILER_SCOPE(ME_MORPH_2) check(EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) TSharedPtr BaseMesh = LoadMesh(FCacheAddress(BaseAt, Item)); // Factor from 0 to 1 between the two targets const FScheduledOpData& OpHeapData = HeapData[Item.CustomState]; float Factor = OpHeapData.Interpolate.Bifactor; if (BaseMesh) { // No morph if (FMath::IsNearlyZero(Factor)) { StoreMesh(Item, BaseMesh); } // The Morph, partial or full else { TSharedPtr MorphMesh = LoadMesh(FCacheAddress(TargetAt, Item)); if (MorphMesh) { TSharedPtr Result = CloneOrTakeOver(BaseMesh); MeshMorph(Result.Get(), MorphMesh.Get(), Factor); Release(MorphMesh); StoreMesh(Item, Result); } else { StoreMesh(Item, BaseMesh); } } } else { StoreMesh(Item, nullptr); } break; } default: check(false); } break; } case EOpType::ME_MERGE: { OP::MeshMergeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item), FScheduledOp(Args.Added, Item)); break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_MERGE_1) TSharedPtr pA = LoadMesh(FCacheAddress(Args.Base, Item)); TSharedPtr pB = LoadMesh(FCacheAddress(Args.Added, Item)); if (pA && pB && pA->GetVertexCount() && pB->GetVertexCount()) { check(!pA->IsReference() && !pB->IsReference()); FMeshMergeScratchMeshes Scratch; Scratch.FirstReformat = CreateMesh(); Scratch.SecondReformat = CreateMesh(); TSharedPtr Result = CreateMesh(pA->GetDataSize() + pB->GetDataSize()); MeshMerge(Result.Get(), pA.Get(), pB.Get(), !Args.NewSurfaceID, Scratch); Release(Scratch.FirstReformat); Release(Scratch.SecondReformat); if (Args.NewSurfaceID) { check(pB->GetSurfaceCount() == 1); Result->Surfaces.Last().Id = Args.NewSurfaceID; } Release(pA); Release(pB); StoreMesh(Item, Result); } else if (pA && (pA->GetVertexCount() || pA->IsReference())) { Release(pB); StoreMesh(Item, pA); } else if (pB && (pB->GetVertexCount() || pB->IsReference())) { TSharedPtr Result = CloneOrTakeOver(pB); check(Result->IsReference() || (Result->GetSurfaceCount() == 1) ); if (Result->GetSurfaceCount() > 0 && Args.NewSurfaceID) { Result->Surfaces.Last().Id = Args.NewSurfaceID; } Release(pA); StoreMesh(Item, Result); } else { Release(pA); Release(pB); StoreMesh(Item, CreateMesh()); } break; } default: check(false); } break; } case EOpType::ME_MASKCLIPMESH: { OP::MeshMaskClipMeshArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item), FScheduledOp(Args.clip, Item)); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_MASKCLIPMESH_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Source = LoadMesh(FCacheAddress(Args.source, Item)); StoreMesh(Item, Source); } else { TSharedPtr Source = LoadMesh(FCacheAddress(Args.source, Item)); TSharedPtr pClip = LoadMesh(FCacheAddress(Args.clip, Item)); // Only if both are valid. if (Source.Get() && pClip.Get()) { TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MeshMaskClipMesh(Result.Get(), Source.Get(), pClip.Get(), bOutSuccess); Release(Source); Release(pClip); if (!bOutSuccess) { Release(Result); StoreMesh(Item, nullptr); } else { StoreMesh(Item, Result); } } else { Release(Source); Release(pClip); StoreMesh(Item, nullptr); } } break; } default: check(false); } break; } case EOpType::ME_MASKCLIPUVMASK: { OP::MeshMaskClipUVMaskArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item), FScheduledOp(Args.UVSource, Item), FScheduledOp(Args.MaskImage, Item), FScheduledOp(Args.MaskLayout, Item)); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_MASKCLIPUVMASK_1); if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); StoreMesh(Item, Source); } else { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); TSharedPtr UVSource = LoadMesh(FCacheAddress(Args.UVSource, Item)); TSharedPtr MaskImage = LoadImage(FCacheAddress(Args.MaskImage, Item)); TSharedPtr MaskLayout = LoadLayout(FCacheAddress(Args.MaskLayout, Item)); // Only if both are valid. if (Source.Get() && MaskImage.Get()) { TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MakeMeshMaskFromUVMask(Result.Get(), Source.Get(), UVSource.Get(), MaskImage.Get(), Args.LayoutIndex, bOutSuccess); Release(Source); Release(UVSource); Release(MaskImage); if (!bOutSuccess) { Release(Result); StoreMesh(Item, nullptr); } else { StoreMesh(Item, Result); } } else if (Source.Get() && MaskLayout.Get()) { TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MakeMeshMaskFromLayout(Result.Get(), Source.Get(), UVSource.Get(), MaskLayout.Get(), Args.LayoutIndex, bOutSuccess); Release(Source); Release(UVSource); Release(MaskImage); if (!bOutSuccess) { Release(Result); StoreMesh(Item, nullptr); } else { StoreMesh(Item, Result); } } else { Release(Source); Release(UVSource); Release(MaskImage); StoreMesh(Item, nullptr); } } break; } default: check(false); } break; } case EOpType::ME_MASKDIFF: { OP::MeshMaskDiffArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item), FScheduledOp(Args.Fragment, Item)); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_MASKDIFF_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); StoreMesh(Item, Source); } else { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); TSharedPtr pClip = LoadMesh(FCacheAddress(Args.Fragment, Item)); // Only if both are valid. if (Source.Get() && pClip.Get()) { TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MeshMaskDiff(Result.Get(), Source.Get(), pClip.Get(), bOutSuccess); Release(Source); Release(pClip); if (!bOutSuccess) { Release(Result); StoreMesh(Item, nullptr); } else { StoreMesh(Item, Result); } } else { Release(Source); Release(pClip); StoreMesh(Item, nullptr); } } break; } default: check(false); } break; } case EOpType::ME_FORMAT: { OP::MeshFormatArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.source && Args.format) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item), FScheduledOp(Args.format, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_FORMAT_1) TSharedPtr Source = LoadMesh(FCacheAddress(Args.source,Item)); TSharedPtr Format = LoadMesh(FCacheAddress(Args.format,Item)); if (Source && Source->IsReference()) { Release(Format); StoreMesh(Item, Source); } else if (Source) { uint8 Flags = Args.Flags; if (!Format && !(Flags & OP::MeshFormatArgs::ResetBufferIndices)) { StoreMesh(Item, Source); } else if (!Format) { TSharedPtr Result = CloneOrTakeOver(Source); if (Flags & OP::MeshFormatArgs::ResetBufferIndices) { Result->ResetBufferIndices(); } StoreMesh(Item, Result); } else { TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MeshFormat(Result.Get(), Source.Get(), Format.Get(), true, (Flags & OP::MeshFormatArgs::Vertex) != 0, (Flags & OP::MeshFormatArgs::Index) != 0, (Flags & OP::MeshFormatArgs::IgnoreMissing) != 0, bOutSuccess); check(bOutSuccess); if (Flags & OP::MeshFormatArgs::ResetBufferIndices) { Result->ResetBufferIndices(); } if (Flags & OP::MeshFormatArgs::OptimizeBuffers) { MUTABLE_CPUPROFILER_SCOPE(MeshOptimizeBuffers) MeshOptimizeBuffers(Result.Get()); } Release(Source); Release(Format); StoreMesh(Item, Result); } } else { Release(Format); StoreMesh(Item, nullptr); } break; } default: check(false); } break; } case EOpType::ME_EXTRACTLAYOUTBLOCK: { const uint8* data = Program.GetOpArgsPointer(Item.At); OP::ADDRESS source; FMemory::Memcpy( &source, data, sizeof(OP::ADDRESS) ); data += sizeof(OP::ADDRESS); uint16 LayoutIndex; FMemory::Memcpy( &LayoutIndex, data, sizeof(uint16) ); data += sizeof(uint16); uint16 blockCount; FMemory::Memcpy( &blockCount, data, sizeof(uint16) ); data += sizeof(uint16); switch (Item.Stage) { case 0: { if (source) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(source, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_EXTRACTLAYOUTBLOCK_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr SourceMesh = LoadMesh(FCacheAddress(source, Item)); StoreMesh(Item, SourceMesh); } else { TSharedPtr Source = LoadMesh(FCacheAddress(source, Item)); // Access with memcpy necessary for unaligned memory access issues. uint64 blocks[512]; check(blockCount< 512); FMemory::Memcpy(blocks, data, sizeof(uint64)*FMath::Min(512,int32(blockCount))); if (Source) { TSharedPtr Result = CreateMesh(); bool bOutSuccess; if (blockCount > 0) { MeshExtractLayoutBlock(Result.Get(), Source.Get(), LayoutIndex, blockCount, blocks, bOutSuccess); } else { MeshExtractLayoutBlock(Result.Get(), Source.Get(), LayoutIndex, bOutSuccess); } if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } } else { StoreMesh(Item, nullptr); } } break; } default: check(false); } break; } case EOpType::ME_TRANSFORM: { OP::MeshTransformArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.source) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_TRANSFORM_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Source = LoadMesh(FCacheAddress(Args.source, Item)); StoreMesh(Item, Source); } else { TSharedPtr Source = LoadMesh(FCacheAddress(Args.source,Item)); const FMatrix44f& mat = Program.ConstantMatrices[Args.matrix]; TSharedPtr Result = CreateMesh(Source ? Source->GetDataSize() : 0); bool bOutSuccess = false; MeshTransform(Result.Get(), Source.Get(), mat, bOutSuccess); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } } break; } default: check(false); } break; } case EOpType::ME_CLIPMORPHPLANE: { OP::MeshClipMorphPlaneArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.Source) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_CLIPMORPHPLANE_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); StoreMesh(Item, Source); } else { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); check(Args.MorphShape < (uint32)InModel->GetPrivate()->Program.ConstantShapes.Num()); // Should be an ellipse const FShape& MorphShape = Program.ConstantShapes[Args.MorphShape]; const FVector3f& Origin = MorphShape.position; const FVector3f& Normal = MorphShape.up; bool bRemoveFaceIfAllVerticesCulled = Args.FaceCullStrategy==EFaceCullStrategy::AllVerticesCulled; if (Args.VertexSelectionType == EClipVertexSelectionType::Shape) { check(Args.VertexSelectionShapeOrBone < (uint32)InModel->GetPrivate()->Program.ConstantShapes.Num()); // Should be None or an axis aligned box const FShape& SelectionShape = Program.ConstantShapes[Args.VertexSelectionShapeOrBone]; TSharedPtr Result = CreateMesh(Source ? Source->GetDataSize() : 0); bool bOutSuccess = false; MeshClipMorphPlane(Result.Get(), Source.Get(), Origin, Normal, Args.Dist, Args.Factor, MorphShape.size[0], MorphShape.size[1], MorphShape.size[2], SelectionShape, bRemoveFaceIfAllVerticesCulled, bOutSuccess, nullptr, -1); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } } else if (Args.VertexSelectionType == EClipVertexSelectionType::BoneHierarchy) { FShape SelectionShape; SelectionShape.type = (uint8)FShape::Type::None; TSharedPtr Result = CreateMesh(Source->GetDataSize()); check(Args.VertexSelectionShapeOrBone <= MAX_uint32); const FBoneName Bone(Args.VertexSelectionShapeOrBone); bool bOutSuccess = false; MeshClipMorphPlane(Result.Get(), Source.Get(), Origin, Normal, Args.Dist, Args.Factor, MorphShape.size[0], MorphShape.size[1], MorphShape.size[2], SelectionShape, bRemoveFaceIfAllVerticesCulled, bOutSuccess, &Bone, Args.MaxBoneRadius); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } } else { // No vertex selection FShape SelectionShape; SelectionShape.type = (uint8)FShape::Type::None; TSharedPtr Result = CreateMesh(Source ? Source->GetDataSize() : 0); bool bOutSuccess = false; MeshClipMorphPlane(Result.Get(), Source.Get(), Origin, Normal, Args.Dist, Args.Factor, MorphShape.size[0], MorphShape.size[1], MorphShape.size[2], SelectionShape, bRemoveFaceIfAllVerticesCulled, bOutSuccess, nullptr, -1.0f); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } } } break; } default: check(false); } break; } case EOpType::ME_CLIPWITHMESH: { OP::MeshClipWithMeshArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.Source) { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item), FScheduledOp(Args.ClipMesh, Item)); } } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_CLIPWITHMESH_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); StoreMesh(Item, Source); } else { TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); TSharedPtr Clip = LoadMesh(FCacheAddress(Args.ClipMesh, Item)); // Only if both are valid. if (Source && Clip) { TSharedPtr Result = CreateMesh(Source->GetDataSize()); bool bOutSuccess = false; MeshClipWithMesh(Result.Get(), Source.Get(), Clip.Get(), bOutSuccess); Release(Clip); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } } else { Release(Clip); StoreMesh(Item, Source); } } break; } default: check(false); } break; } case EOpType::ME_CLIPDEFORM: { OP::MeshClipDeformArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.mesh) { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mesh, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mesh, Item), FScheduledOp(Args.clipShape, Item)); } } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_CLIPDEFORM_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr BaseMesh = LoadMesh(FCacheAddress(Args.mesh, Item)); StoreMesh(Item, BaseMesh); } else { TSharedPtr BaseMesh = LoadMesh(FCacheAddress(Args.mesh, Item)); TSharedPtr ClipShape = LoadMesh(FCacheAddress(Args.clipShape, Item)); if (BaseMesh && ClipShape) { TSharedPtr Result = CreateMesh(BaseMesh->GetDataSize()); bool bRemoveIfAllVerticesCulled = Args.FaceCullStrategy == EFaceCullStrategy::AllVerticesCulled; bool bOutSuccess = false; MeshClipDeform(Result.Get(), BaseMesh.Get(), ClipShape.Get(), Args.clipWeightThreshold, bRemoveIfAllVerticesCulled, bOutSuccess); Release(ClipShape); if (!bOutSuccess) { Release(Result); StoreMesh(Item, BaseMesh); } else { Release(BaseMesh); StoreMesh(Item, Result); } } else { Release(ClipShape); StoreMesh(Item, BaseMesh); } } break; } default: check(false); } break; } case EOpType::ME_APPLYPOSE: { OP::MeshApplyPoseArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.base) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item), FScheduledOp(Args.pose, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_APPLYPOSE_1) TSharedPtr Base = LoadMesh(FCacheAddress(Args.base, Item)); TSharedPtr pPose = LoadMesh(FCacheAddress(Args.pose, Item)); // Only if both are valid. if (Base && pPose) { TSharedPtr Result = CreateMesh(Base->GetSkeleton() ? Base->GetDataSize() : 0); bool bOutSuccess = false; MeshApplyPose(Result.Get(), Base.Get(), pPose.Get(), bOutSuccess); Release(pPose); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Base); } else { Release(Base); StoreMesh(Item, Result); } } else { Release(pPose); StoreMesh(Item, Base); } break; } default: check(false); } break; } case EOpType::ME_BINDSHAPE: { OP::MeshBindShapeArgs Args = Program.GetOpArgs(Item.At); const uint8* Data = Program.GetOpArgsPointer(Item.At); constexpr uint8 ShapeContentFilter = (uint8)(EMeshContentFlags::GeometryData | EMeshContentFlags::PoseData); const EShapeBindingMethod BindingMethod = static_cast(Args.bindingMethod); switch (Item.Stage) { case 0: { if (Args.mesh) { if (BindingMethod == EShapeBindingMethod::ReshapeClosestProject) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mesh, Item), FScheduledOp::FromOpAndOptions(Args.shape, Item, ShapeContentFilter)); } else { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mesh, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mesh, Item), FScheduledOp::FromOpAndOptions(Args.shape, Item, ShapeContentFilter)); } } } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_BINDSHAPE_1) if (BindingMethod == EShapeBindingMethod::ReshapeClosestProject) { TSharedPtr BaseMesh = LoadMesh(FCacheAddress(Args.mesh, Item)); TSharedPtr Shape = LoadMesh(FScheduledOp::FromOpAndOptions(Args.shape, Item, ShapeContentFilter)); // Bones are stored after the Args Data += sizeof(Args); // Rebuilding array of bone names ---- int32 NumBones; FMemory::Memcpy(&NumBones, Data, sizeof(int32)); Data += sizeof(int32); TArray BonesToDeform; BonesToDeform.SetNumUninitialized(NumBones); FMemory::Memcpy(BonesToDeform.GetData(), Data, NumBones * sizeof(FBoneName)); Data += NumBones * sizeof(FBoneName); int32 NumPhysicsBodies; FMemory::Memcpy(&NumPhysicsBodies, Data, sizeof(int32)); Data += sizeof(int32); TArray PhysicsToDeform; PhysicsToDeform.SetNumUninitialized(NumPhysicsBodies); FMemory::Memcpy(PhysicsToDeform.GetData(), Data, NumPhysicsBodies * sizeof(FBoneName)); Data += NumPhysicsBodies * sizeof(FBoneName); EMeshBindShapeFlags BindFlags = static_cast(Args.flags); EMeshContentFlags MeshContentFilter = (EMeshContentFlags)Item.ExecutionOptions; if (!EnumHasAnyFlags(MeshContentFilter, EMeshContentFlags::GeometryData)) { EnumRemoveFlags(BindFlags, EMeshBindShapeFlags::EnableRigidParts | EMeshBindShapeFlags::ReshapeVertices | EMeshBindShapeFlags::ApplyLaplacian | EMeshBindShapeFlags::RecomputeNormals); } if (!EnumHasAnyFlags(MeshContentFilter, EMeshContentFlags::PhysicsData)) { EnumRemoveFlags(BindFlags, EMeshBindShapeFlags::ReshapePhysicsVolumes); } if (!EnumHasAnyFlags(MeshContentFilter, EMeshContentFlags::PoseData)) { EnumRemoveFlags(BindFlags, EMeshBindShapeFlags::ReshapeSkeleton); } FMeshBindColorChannelUsages ColorChannelUsages; FMemory::Memcpy(&ColorChannelUsages, &Args.ColorUsage, sizeof(ColorChannelUsages)); static_assert(sizeof(ColorChannelUsages) == sizeof(Args.ColorUsage)); TSharedPtr BindMeshResult = CreateMesh(); bool bOutSuccess = false; MeshBindShapeReshape(BindMeshResult.Get(), BaseMesh.Get(), Shape.Get(), BonesToDeform, PhysicsToDeform, BindFlags, ColorChannelUsages, bOutSuccess); Release(Shape); // not success indicates nothing has bond so the base mesh can be reused. if (!bOutSuccess) { Release(BindMeshResult); StoreMesh(Item, BaseMesh); } else { if (!EnumHasAnyFlags(BindFlags, EMeshBindShapeFlags::ReshapeVertices)) { TSharedPtr BindMeshNoVertsResult = CloneOrTakeOver(BaseMesh); BindMeshNoVertsResult->AdditionalBuffers = MoveTemp(BindMeshResult->AdditionalBuffers); Release(BaseMesh); Release(BindMeshResult); StoreMesh(Item, BindMeshNoVertsResult); } else { Release(BaseMesh); StoreMesh(Item, BindMeshResult); } } } else { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr BaseMesh = LoadMesh(FCacheAddress(Args.mesh, Item)); StoreMesh(Item, BaseMesh); } else { TSharedPtr BaseMesh = LoadMesh(FCacheAddress(Args.mesh, Item)); TSharedPtr Shape = LoadMesh(FScheduledOp::FromOpAndOptions(Args.shape, Item, ShapeContentFilter)); TSharedPtr Result = CreateMesh(BaseMesh ? BaseMesh->GetDataSize() : 0); bool bOutSuccess = false; MeshBindShapeClipDeform(Result.Get(), BaseMesh.Get(), Shape.Get(), BindingMethod, bOutSuccess); Release(Shape); if (!bOutSuccess) { Release(Result); StoreMesh(Item, BaseMesh); } else { Release(BaseMesh); StoreMesh(Item, Result); } } } break; } default: check(false); } break; } case EOpType::ME_APPLYSHAPE: { OP::MeshApplyShapeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.mesh) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mesh, Item), FScheduledOp(Args.shape, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_APPLYSHAPE_1) TSharedPtr BaseMesh = LoadMesh(FCacheAddress(Args.mesh, Item)); TSharedPtr Shape = LoadMesh(FCacheAddress(Args.shape, Item)); EMeshBindShapeFlags ReshapeFlags = static_cast(Args.flags); const EMeshContentFlags MeshContentFilter = (EMeshContentFlags)Item.ExecutionOptions; if (!EnumHasAnyFlags(MeshContentFilter, EMeshContentFlags::GeometryData)) { EnumRemoveFlags(ReshapeFlags, EMeshBindShapeFlags::EnableRigidParts | EMeshBindShapeFlags::ReshapeVertices | EMeshBindShapeFlags::ApplyLaplacian | EMeshBindShapeFlags::RecomputeNormals); } if (!EnumHasAnyFlags(MeshContentFilter, EMeshContentFlags::PhysicsData)) { EnumRemoveFlags(ReshapeFlags, EMeshBindShapeFlags::ReshapePhysicsVolumes); } if (!EnumHasAnyFlags(MeshContentFilter, EMeshContentFlags::PoseData)) { EnumRemoveFlags(ReshapeFlags, EMeshBindShapeFlags::ReshapeSkeleton); } const bool bReshapeVertices = EnumHasAnyFlags(ReshapeFlags, EMeshBindShapeFlags::ReshapeVertices); TSharedPtr ReshapedMeshResult = CreateMesh(BaseMesh ? BaseMesh->GetDataSize() : 0); bool bOutSuccess = false; MeshApplyShape(ReshapedMeshResult.Get(), BaseMesh.Get(), Shape.Get(), ReshapeFlags, bOutSuccess); Release(Shape); if (!bOutSuccess) { Release(ReshapedMeshResult); StoreMesh(Item, BaseMesh); } else { if (!bReshapeVertices) { // Clone without Skeleton, Physics or Poses EMeshCopyFlags CopyFlags = ~( EMeshCopyFlags::WithSkeleton | EMeshCopyFlags::WithPhysicsBody | EMeshCopyFlags::WithAdditionalPhysics | EMeshCopyFlags::WithPoses); TSharedPtr NoVerticesReshpedMesh = CloneOrTakeOver(BaseMesh); NoVerticesReshpedMesh->SetSkeleton(ReshapedMeshResult->GetSkeleton()); NoVerticesReshpedMesh->SetPhysicsBody(ReshapedMeshResult->GetPhysicsBody()); NoVerticesReshpedMesh->AdditionalPhysicsBodies = ReshapedMeshResult->AdditionalPhysicsBodies; NoVerticesReshpedMesh->BonePoses = ReshapedMeshResult->BonePoses; Release(BaseMesh); Release(ReshapedMeshResult); StoreMesh(Item, NoVerticesReshpedMesh); } else { Release(BaseMesh); StoreMesh(Item, ReshapedMeshResult); } } break; } default: check(false); } break; } case EOpType::ME_MORPHRESHAPE: { OP::MeshMorphReshapeArgs Args = Program.GetOpArgs(Item.At); switch(Item.Stage) { case 0: { if (Args.Morph) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Morph, Item), FScheduledOp(Args.Reshape, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_MORPHRESHAPE_1) TSharedPtr MorphedMesh = LoadMesh(FCacheAddress(Args.Morph, Item)); TSharedPtr ReshapeMesh = LoadMesh(FCacheAddress(Args.Reshape, Item)); if (ReshapeMesh && MorphedMesh) { // Copy without Skeleton, Physics or Poses EMeshCopyFlags CopyFlags = ~( EMeshCopyFlags::WithSkeleton | EMeshCopyFlags::WithPhysicsBody | EMeshCopyFlags::WithPoses); TSharedPtr Result = CreateMesh(MorphedMesh->GetDataSize()); Result->CopyFrom(*MorphedMesh, CopyFlags); Result->SetSkeleton(ReshapeMesh->GetSkeleton()); Result->SetPhysicsBody(ReshapeMesh->GetPhysicsBody()); Result->BonePoses = ReshapeMesh->BonePoses; Release(MorphedMesh); Release(ReshapeMesh); StoreMesh(Item, Result); } else { StoreMesh(Item, MorphedMesh); } break; } default: check(false); } break; } case EOpType::ME_SETSKELETON: { OP::MeshSetSkeletonArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.Source) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item), FScheduledOp(Args.Skeleton, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_SETSKELETON_1) TSharedPtr Source = LoadMesh(FCacheAddress(Args.Source, Item)); TSharedPtr Skeleton = LoadMesh(FCacheAddress(Args.Skeleton, Item)); // Only if both are valid. if (Source && Skeleton) { if ( Source->GetSkeleton() && Source->GetSkeleton()->GetBoneCount() > 0 ) { // For some reason we already have bone data, so we can't just overwrite it // or the skinning may break. This may happen because of a problem in the // optimiser that needs investigation. // \TODO Be defensive, for now. UE_LOG(LogMutableCore, Warning, TEXT("Performing a MeshRemapSkeleton, instead of MeshSetSkeletonData because source mesh already has some skeleton.")); TSharedPtr Result = CreateMesh(Source->GetDataSize()); bool bOutSuccess = false; MeshRemapSkeleton(Result.Get(), Source.Get(), Skeleton->GetSkeleton(), bOutSuccess); Release(Skeleton); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { //Result->GetPrivate()->CheckIntegrity(); Release(Source); StoreMesh(Item, Result); } } else { TSharedPtr Result = CloneOrTakeOver(Source); Result->SetSkeleton(Skeleton->GetSkeleton()); //Result->GetPrivate()->CheckIntegrity(); Release(Skeleton); StoreMesh(Item, Result); } } else { Release(Skeleton); StoreMesh(Item, Source); } break; } default: check(false); } break; } case EOpType::ME_REMOVEMASK: { MUTABLE_CPUPROFILER_SCOPE(ME_REMOVEMASK) // Decode op // TODO: Partial decode for each stage const uint8* data = Program.GetOpArgsPointer(Item.At); OP::ADDRESS source; FMemory::Memcpy(&source,data,sizeof(OP::ADDRESS)); data += sizeof(OP::ADDRESS); EFaceCullStrategy FaceCullStrategy; FMemory::Memcpy(&FaceCullStrategy, data, sizeof(EFaceCullStrategy)); data += sizeof(EFaceCullStrategy); TArray conditions; TArray masks; uint16 removes; FMemory::Memcpy(&removes,data,sizeof(uint16)); data += sizeof(uint16); for( uint16 r=0; r Source = LoadMesh(FCacheAddress(source, Item)); StoreMesh(Item, Source); } else { // Request the source and the necessary masks // \todo: store condition values in heap? TArray deps; deps.Emplace( source, Item ); for( int32 r=0; source && r Source = LoadMesh(FCacheAddress(source, Item)); if (Source) { TSharedPtr Result = CloneOrTakeOver(Source); for (int32 r = 0; r < conditions.Num(); ++r) { // If there is no expression, we'll assume true. bool value = true; if (conditions[r].At) { value = LoadBool(FCacheAddress(conditions[r].At, Item)); } if (value) { TSharedPtr Mask = LoadMesh(FCacheAddress(masks[r], Item)); if (Mask) { bool bRemoveIfAllVerticesCulled = FaceCullStrategy == EFaceCullStrategy::AllVerticesCulled; MeshRemoveMaskInline(Result.Get(), Mask.Get(), bRemoveIfAllVerticesCulled); Release(Mask); } } } StoreMesh(Item, Result); } else { StoreMesh(Item, nullptr); } break; } default: check(false); } break; } case EOpType::ME_ADDMETADATA: { MUTABLE_CPUPROFILER_SCOPE(ME_ADDMETADATA) const OP::MeshAddMetadataArgs OpArgs = Program.GetOpArgs(Item.At); // Schedule next stages switch (Item.Stage) { case 0: { if (OpArgs.Source) { // Request the source AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(OpArgs.Source, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_ADDMETADATA_2) TSharedPtr SourceMesh = LoadMesh(FCacheAddress(OpArgs.Source, Item)); TSharedPtr Result = nullptr; if (SourceMesh) { Result = CloneOrTakeOver(SourceMesh); using OpEnumFlags = OP::MeshAddMetadataArgs::EnumFlags; bool bIsTagList = EnumHasAnyFlags(OpArgs.Flags, OpEnumFlags::IsTagList); bool bIsResourceList = EnumHasAnyFlags(OpArgs.Flags, OpEnumFlags::IsResourceList); bool bIsSkeletonList = EnumHasAnyFlags(OpArgs.Flags, OpEnumFlags::IsSkeletonList); if (bIsTagList) { if (Program.ConstantUInt32Lists.IsValidIndex(OpArgs.Tags.ListAddress)) { const TArray& ConstantTagList = Program.ConstantUInt32Lists[OpArgs.Tags.ListAddress]; Result->Tags.Reserve(Result->Tags.Num() + ConstantTagList.Num()); for (uint32 TagIndex : ConstantTagList) { if (Program.ConstantStrings.IsValidIndex(TagIndex)) { Result->Tags.Add(Program.ConstantStrings[TagIndex]); } else { check(false); } } } else { check(false); } } else { if (Program.ConstantStrings.IsValidIndex(OpArgs.Tags.TagAddress)) { Result->Tags.Add(Program.ConstantStrings[OpArgs.Tags.TagAddress]); } else { check(false); } } if (bIsResourceList) { if (Program.ConstantUInt64Lists.IsValidIndex(OpArgs.ResourceIds.ListAddress)) { Result->StreamedResources.Append(Program.ConstantUInt64Lists[OpArgs.ResourceIds.ListAddress]); } else { check(false); } } else { Result->StreamedResources.Add(OpArgs.ResourceIds.ResourceId); } if (bIsSkeletonList) { if (Program.ConstantUInt32Lists.IsValidIndex(OpArgs.SkeletonIds.ListAddress)) { Result->SkeletonIDs.Append(Program.ConstantUInt32Lists[OpArgs.SkeletonIds.ListAddress]); } else { check(false); } } else { Result->SkeletonIDs.Add(OpArgs.SkeletonIds.SkeletonId); } } StoreMesh(Item, Result); break; } default: check(false); } break; } case EOpType::ME_PROJECT: { OP::MeshProjectArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.Mesh) { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Mesh, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Mesh, Item), FScheduledOp(Args.Projector, Item)); } } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_PROJECT_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr Mesh = LoadMesh(FCacheAddress(Args.Mesh, Item)); StoreMesh(Item, Mesh); } else { TSharedPtr Mesh = LoadMesh(FCacheAddress(Args.Mesh, Item)); const FProjector Projector = LoadProjector(FCacheAddress(Args.Projector, Item)); // Only if both are valid. if (Mesh && Mesh->GetVertexBuffers().GetBufferCount() > 0) { TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MeshProject(Result.Get(), Mesh.Get(), Projector, bOutSuccess); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Mesh); } else { // Result->GetPrivate()->CheckIntegrity(); Release(Mesh); StoreMesh(Item, Result); } } else { Release(Mesh); StoreMesh(Item, nullptr); } } break; } default: check(false); } break; } case EOpType::ME_OPTIMIZESKINNING: { OP::MeshOptimizeSkinningArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.source) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item)); } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_OPTIMIZESKINNING_1) TSharedPtr Source = LoadMesh(FCacheAddress(Args.source, Item)); if (Source && Source->IsReference()) { StoreMesh(Item, Source); } TSharedPtr Result = CreateMesh(); bool bOutSuccess = false; MeshOptimizeSkinning(Result.Get(), Source.Get(), bOutSuccess); if (!bOutSuccess) { Release(Result); StoreMesh(Item, Source); } else { Release(Source); StoreMesh(Item, Result); } break; } default: check(false); } break; } case EOpType::ME_TRANSFORMWITHMESH: { OP::MeshTransformWithinMeshArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { if (Args.sourceMesh) { if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.sourceMesh, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.sourceMesh, Item), FScheduledOp(Args.boundingMesh, Item), FScheduledOp(Args.matrix, Item)); } } else { StoreMesh(Item, nullptr); } break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(ME_TRANSFORMWITHMESH_1) if (!EnumHasAnyFlags(EMeshContentFlags(Item.ExecutionOptions), EMeshContentFlags::GeometryData)) { TSharedPtr SourceMesh = LoadMesh(FCacheAddress(Args.sourceMesh, Item)); StoreMesh(Item, SourceMesh); } else { TSharedPtr SourceMesh = LoadMesh(FCacheAddress(Args.sourceMesh,Item)); TSharedPtr BoundingMesh = LoadMesh(FCacheAddress(Args.boundingMesh, Item)); const FMatrix44f& Transform = LoadMatrix(FCacheAddress(Args.matrix, Item)); if (SourceMesh) { TSharedPtr Result = CreateMesh(SourceMesh->GetDataSize()); bool bOutSuccess = false; MeshTransformWithMesh(Result.Get(), SourceMesh.Get(), BoundingMesh.Get(), Transform, bOutSuccess); Release(BoundingMesh); if (!bOutSuccess) { Release(Result); StoreMesh(Item, SourceMesh); } else { Release(SourceMesh); StoreMesh(Item, Result); } } else { Release(BoundingMesh); StoreMesh(Item, SourceMesh); } } break; } default: check(false); } break; } default: if (type!=EOpType::NONE) { // Operation not implemented check( false ); } break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Image(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Image); FImageOperator ImOp = MakeImageOperator(this); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::IM_LAYERCOLOUR: { OP::ImageLayerColourArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.base, Item), FScheduledOp::FromOpAndOptions( Args.colour, Item, 0), FScheduledOp( Args.mask, Item) ); break; case 1: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } break; } case EOpType::IM_LAYER: { OP::ImageLayerArgs Args = Program.GetOpArgs(Item.At); if (ExecutionStrategy == EExecutionStrategy::MinimizeMemory) { switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item)); break; case 1: // Request the rest of the data. AddOp(FScheduledOp(Item.At, Item, 2), FScheduledOp(Args.blended, Item), FScheduledOp(Args.mask, Item)); break; case 2: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } } else { switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item), FScheduledOp(Args.blended, Item), FScheduledOp(Args.mask, Item)); break; case 1: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } } break; } case EOpType::IM_MULTILAYER: { OP::ImageMultiLayerArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.rangeSize, Item ), FScheduledOp(Args.base, Item)); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_MULTILAYER_1) // We now know the number of iterations int32 Iterations = 0; if (Args.rangeSize) { FCacheAddress RangeAddress(Args.rangeSize,Item); // We support both integers and scalars here, which is not common. // \todo: review if this is necessary or we can enforce it at compile time. EDataType RangeSizeType = GetOpDataType( InModel->GetPrivate()->Program.GetOpType(Args.rangeSize) ); if (RangeSizeType == EDataType::Int) { Iterations = LoadInt(RangeAddress); } else if (RangeSizeType == EDataType::Scalar) { Iterations = int32( LoadScalar(RangeAddress) ); } } TSharedPtr Base = LoadImage(FCacheAddress(Args.base, Item)); if (Iterations <= 0) { // There are no layers: return the base StoreImage(Item, Base); } else { // Store the base TSharedPtr New = CloneOrTakeOver(Base); EImageFormat InitialBaseFormat = New->GetFormat(); // Reset relevancy map. New->Flags &= ~FImage::EImageFlags::IF_HAS_RELEVANCY_MAP; // This shouldn't happen in optimised models, but it could happen in editors, etc. // \todo: raise a performance warning? EImageFormat BaseFormat = GetUncompressedFormat(New->GetFormat()); if (New->GetFormat() != BaseFormat) { TSharedPtr Formatted = CreateImage( New->GetSizeX(), New->GetSizeY(), New->GetLODCount(), BaseFormat, EInitializationType::NotInitialized ); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), New.Get()); check(bSuccess); // Decompression cannot fail Release(New); New = Formatted; } FScheduledOpData Data; Data.Resource = New; Data.MultiLayer.Iterations = Iterations; Data.MultiLayer.OriginalBaseFormat = InitialBaseFormat; Data.MultiLayer.bBlendOnlyOneMip = false; int32 DataPos = HeapData.Add(Data); // Request the first layer int32 CurrentIteration = 0; FScheduledOp ItemCopy = Item; ExecutionIndex Index = GetMemory().GetRangeIndex(Item.ExecutionIndex); Index.SetFromModelRangeIndex(Args.rangeId, CurrentIteration); ItemCopy.ExecutionIndex = GetMemory().GetRangeIndexIndex(Index); AddOp(FScheduledOp(Item.At, Item, 2, DataPos), FScheduledOp(Args.base, Item), FScheduledOp(Args.blended, ItemCopy), FScheduledOp(Args.mask, ItemCopy)); } break; } default: { MUTABLE_CPUPROFILER_SCOPE(IM_MULTILAYER_default) FScheduledOpData& Data = HeapData[Item.CustomState]; int32 Iterations = Data.MultiLayer.Iterations; int32 CurrentIteration = Item.Stage - 2; check(CurrentIteration >= 0 && CurrentIteration < 120); TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(*FString::Printf(TEXT("Layer %d of %d"), CurrentIteration, Iterations)); // Process the current layer TSharedPtr Base = StaticCastSharedPtr(ConstCastSharedPtr(Data.Resource)); FScheduledOp itemCopy = Item; ExecutionIndex index = GetMemory().GetRangeIndex( Item.ExecutionIndex ); { index.SetFromModelRangeIndex( Args.rangeId, CurrentIteration); itemCopy.ExecutionIndex = GetMemory().GetRangeIndexIndex(index); itemCopy.CustomState = 0; TSharedPtr Blended = LoadImage( FCacheAddress(Args.blended,itemCopy) ); // This shouldn't happen in optimised models, but it could happen in editors, etc. // \todo: raise a performance warning? if (Blended && Blended->GetFormat()!=Base->GetFormat() ) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_BlendedReformat); TSharedPtr Formatted = CreateImage(Blended->GetSizeX(), Blended->GetSizeY(), Blended->GetLODCount(), Base->GetFormat(), EInitializationType::NotInitialized); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), Blended.Get()); check(bSuccess); Release(Blended); Blended = Formatted; } // TODO: This shouldn't happen, but be defensive. FImageSize ResultSize = Base->GetSize(); if (Blended && Blended->GetSize() != ResultSize) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_BlendedFixForMultilayer); TSharedPtr Resized = CreateImage(ResultSize[0], ResultSize[1], Blended->GetLODCount(), Blended->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, Blended.Get()); Release(Blended); Blended = Resized; } if (Blended->GetLODCount() < Base->GetLODCount()) { Data.MultiLayer.bBlendOnlyOneMip = true; } bool bApplyColorBlendToAlpha = false; bool bDone = false; // This becomes true if we need to update the mips of the resulting image // This could happen in the base image has mips, but one of the blended one doesn't. bool bBlendOnlyOneMip = Data.MultiLayer.bBlendOnlyOneMip; bool bUseBlendSourceFromBlendAlpha = false; // (Args.flags& OP::ImageLayerArgs::F_BLENDED_RGB_FROM_ALPHA) != 0; if (!Args.mask && Args.bUseMaskFromBlended && Args.blendType == uint8(EBlendType::BT_BLEND) && Args.blendTypeAlpha == uint8(EBlendType::BT_LIGHTEN) ) { // This is a frequent critical-path case because of multilayer projectors. bDone = true; constexpr bool bUseVectorImpl = false; if constexpr (bUseVectorImpl) { BufferLayerCompositeVector(Base.Get(), Blended.Get(), bBlendOnlyOneMip, Args.BlendAlphaSourceChannel); } else { BufferLayerComposite(Base.Get(), Blended.Get(), bBlendOnlyOneMip, Args.BlendAlphaSourceChannel); } } if (!bDone && Args.mask) { TSharedPtr Mask = LoadImage( FCacheAddress(Args.mask,itemCopy) ); // TODO: This shouldn't happen, but be defensive. if (Mask && Mask->GetSize() != ResultSize) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_MaskFixForMultilayer); TSharedPtr Resized = CreateImage(ResultSize[0], ResultSize[1], Mask->GetLODCount(), Mask->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, Mask.Get()); Release(Mask); Mask = Resized; } // Not implemented yet check(!bUseBlendSourceFromBlendAlpha); switch (EBlendType(Args.blendType)) { case EBlendType::BT_NORMAL_COMBINE: check(false); break; case EBlendType::BT_SOFTLIGHT: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_HARDLIGHT: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_BURN: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_DODGE: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_SCREEN: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_OVERLAY: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_LIGHTEN: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_MULTIPLY: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_BLEND: BufferLayer(Base.Get(), Base.Get(), Mask.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; default: check(false); } Release(Mask); } else if (!bDone && Args.bUseMaskFromBlended) { // Not implemented yet check(!bUseBlendSourceFromBlendAlpha); switch (EBlendType(Args.blendType)) { case EBlendType::BT_NORMAL_COMBINE: check(false); break; case EBlendType::BT_SOFTLIGHT: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_HARDLIGHT: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_BURN: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_DODGE: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_SCREEN: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_OVERLAY: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_LIGHTEN: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_MULTIPLY: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; case EBlendType::BT_BLEND: BufferLayerEmbeddedMask(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break; default: check(false); } } else if (!bDone) { switch (EBlendType(Args.blendType)) { case EBlendType::BT_NORMAL_COMBINE: check(false); break; case EBlendType::BT_SOFTLIGHT: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_HARDLIGHT: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_BURN: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_DODGE: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_SCREEN: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_OVERLAY: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_LIGHTEN: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_MULTIPLY: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; case EBlendType::BT_BLEND: BufferLayer(Base.Get(), Base.Get(), Blended.Get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break; default: check(false); } } // Apply the separate blend operation for alpha if (!bDone && !bApplyColorBlendToAlpha && Args.blendTypeAlpha != uint8(EBlendType::BT_NONE) ) { // Separate alpha operation ignores the mask. switch (EBlendType(Args.blendTypeAlpha)) { case EBlendType::BT_SOFTLIGHT: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_HARDLIGHT: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_BURN: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_DODGE: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_SCREEN: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_OVERLAY: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_LIGHTEN: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_MULTIPLY: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; case EBlendType::BT_BLEND: BufferLayerInPlace(Base.Get(), Blended.Get(), bBlendOnlyOneMip, 3, Args.BlendAlphaSourceChannel); break; default: check(false); } } Release(Blended); } // Are we done? if (CurrentIteration + 1 == Iterations) { if (Data.MultiLayer.bBlendOnlyOneMip) { MUTABLE_CPUPROFILER_SCOPE(ImageLayer_MipFix); FMipmapGenerationSettings DummyMipSettings{}; ImageMipmapInPlace(Settings.ImageCompressionQuality, Base.Get(), DummyMipSettings); } // TODO: Reconvert to OriginalBaseFormat if necessary? Data.Resource = nullptr; StoreImage(Item, Base); break; } else { // Request a new layer ++CurrentIteration; FScheduledOp ItemCopy = Item; ExecutionIndex Index = GetMemory().GetRangeIndex(Item.ExecutionIndex); Index.SetFromModelRangeIndex(Args.rangeId, CurrentIteration); ItemCopy.ExecutionIndex = GetMemory().GetRangeIndexIndex(Index); AddOp(FScheduledOp(Item.At, Item, 2+CurrentIteration, Item.CustomState), FScheduledOp(Args.blended, ItemCopy), FScheduledOp(Args.mask, ItemCopy)); } break; } } // switch stage break; } case EOpType::IM_NORMALCOMPOSITE: { OP::ImageNormalCompositeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: if (Args.base && Args.normal) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item), FScheduledOp(Args.normal, Item)); } else { StoreImage(Item, nullptr); } break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_NORMALCOMPOSITE_1) TSharedPtr Base = LoadImage(FCacheAddress(Args.base, Item)); TSharedPtr Normal = LoadImage(FCacheAddress(Args.normal, Item)); if (Normal->GetLODCount() < Base->GetLODCount()) { MUTABLE_CPUPROFILER_SCOPE(ImageNormalComposite_EmergencyFix); int32 StartLevel = Normal->GetLODCount() - 1; int32 LevelCount = Base->GetLODCount(); TSharedPtr NormalFix = CloneOrTakeOver(Normal); NormalFix->DataStorage.SetNumLODs(LevelCount); FMipmapGenerationSettings MipSettings{}; ImOp.ImageMipmap(Settings.ImageCompressionQuality, NormalFix.Get(), NormalFix.Get(), StartLevel, LevelCount, MipSettings); Normal = NormalFix; } TSharedPtr Result = CreateImage(Base->GetSizeX(), Base->GetSizeY(), Base->GetLODCount(), Base->GetFormat(), EInitializationType::NotInitialized); ImageNormalComposite(Result.Get(), Base.Get(), Normal.Get(), Args.mode, Args.power); Release(Base); Release(Normal); StoreImage(Item, Result); break; } default: check(false); } break; } case EOpType::IM_PIXELFORMAT: { OP::ImagePixelFormatArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.source, Item) ); break; case 1: { // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; } default: check(false); } break; } case EOpType::IM_MIPMAP: { OP::ImageMipmapArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.source, Item) ); break; case 1: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } break; } case EOpType::IM_RESIZE: { OP::ImageResizeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Source, Item) ); break; case 1: { // This has been moved to a task. It should have been intercepted in IssueOp. check(false); } default: check(false); } break; } case EOpType::IM_RESIZELIKE: { OP::ImageResizeLikeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item), FScheduledOp(Args.SizeSource, Item)); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_RESIZELIKE_1) TSharedPtr Base = LoadImage( FCacheAddress(Args.Source,Item) ); TSharedPtr SizeBase = LoadImage( FCacheAddress(Args.SizeSource,Item) ); FImageSize DestSize = SizeBase->GetSize(); Release(SizeBase); if (Base->GetSize() != DestSize) { int32 BaseLODCount = Base->GetLODCount(); TSharedPtr Result = CreateImage(DestSize[0], DestSize[1], BaseLODCount, Base->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Result.Get(), Settings.ImageCompressionQuality, Base.Get()); Release(Base); // If the source image had mips, generate them as well for the resized image. // This shouldn't happen often since "ResizeLike" should be usually optimised out // during model compilation. The mipmap generation below is not very precise with // the number of mips that are needed and will probably generate too many bool bSourceHasMips = BaseLODCount > 1; if (bSourceHasMips) { int32 LevelCount = FImage::GetMipmapCount(Result->GetSizeX(), Result->GetSizeY()); Result->DataStorage.SetNumLODs(LevelCount); FMipmapGenerationSettings MipSettings{}; ImOp.ImageMipmap(Settings.ImageCompressionQuality, Result.Get(), Result.Get(), 0, LevelCount, MipSettings); } StoreImage(Item, Result); } else { StoreImage(Item, Base); } break; } default: check(false); } break; } case EOpType::IM_RESIZEREL: { OP::ImageResizeRelArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Source, Item) ); break; case 1: { // This has been moved to a task. It should have been intercepted in IssueOp. check(false); } default: check(false); } break; } case EOpType::IM_BLANKLAYOUT: { OP::ImageBlankLayoutArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp::FromOpAndOptions(Args.Layout, Item, 0)); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_BLANKLAYOUT_1) TSharedPtr pLayout = LoadLayout(FScheduledOp::FromOpAndOptions(Args.Layout, Item, 0)); FIntPoint SizeInBlocks = pLayout->GetGridSize(); FIntPoint BlockSizeInPixels(Args.BlockSize[0], Args.BlockSize[1]); // Image size if we don't skip any mipmap FIntPoint FullImageSizeInPixels = SizeInBlocks * BlockSizeInPixels; int32 FullImageMipCount = FImage::GetMipmapCount(FullImageSizeInPixels.X, FullImageSizeInPixels.Y); FIntPoint ImageSizeInPixels = FullImageSizeInPixels; int32 MipsToSkip = Item.ExecutionOptions; MipsToSkip = FMath::Min(MipsToSkip, FullImageMipCount); if (MipsToSkip > 0) { //FIntPoint ReducedBlockSizeInPixels; // This method tries to reduce only the block size, but it fails if the image is still too big // If we want to generate only a subset of mipmaps, reduce the layout block size accordingly. //ReducedBlockSizeInPixels.X = BlockSizeInPixels.X >> MipsToSkip; //ReducedBlockSizeInPixels.Y = BlockSizeInPixels.Y >> MipsToSkip; //const FImageFormatData& FormatData = GetImageFormatData((EImageFormat)Args.format); //int32 MinBlockSize = FMath::Max(FormatData.PixelsPerBlockX, FormatData.PixelsPerBlockY); //ReducedBlockSizeInPixels.X = FMath::Max(ReducedBlockSizeInPixels.X, FormatData.PixelsPerBlockX); //ReducedBlockSizeInPixels.Y = FMath::Max(ReducedBlockSizeInPixels.Y, FormatData.PixelsPerBlockY); //FIntPoint ReducedImageSizeInPixels = SizeInBlocks * ReducedBlockSizeInPixels; // This method simply reduces the size and assumes all the other operations will handle degeenrate cases. ImageSizeInPixels = FullImageSizeInPixels / (1 << MipsToSkip); //if (ReducedImageSizeInPixels!= ImageSizeInPixels) //{ // check(false); //} } int32 MipsToGenerate = 1; if (Args.GenerateMipmaps) { if (Args.MipmapCount == 0) { MipsToGenerate = FImage::GetMipmapCount(ImageSizeInPixels.X, ImageSizeInPixels.Y); } else { MipsToGenerate = FMath::Max(Args.MipmapCount - MipsToSkip, 1); } } // It needs to be initialized in case it has gaps. TSharedPtr New = CreateImage(ImageSizeInPixels.X, ImageSizeInPixels.Y, MipsToGenerate, EImageFormat(Args.Format), EInitializationType::Black ); StoreImage(Item, New); break; } default: check(false); } break; } case EOpType::IM_COMPOSE: { OP::ImageComposeArgs Args = Program.GetOpArgs(Item.At); if (ExecutionStrategy == EExecutionStrategy::MinimizeMemory) { switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp::FromOpAndOptions(Args.layout, Item, 0)); break; case 1: { TSharedPtr ComposeLayout = LoadLayout(FCacheAddress(Args.layout, FScheduledOp::FromOpAndOptions(Args.layout, Item, 0))); FScheduledOpData Data; Data.Resource = ComposeLayout; int32 DataPos = HeapData.Add(Data); int32 RelBlockIndex = ComposeLayout->FindBlock(Args.BlockId); if (RelBlockIndex >= 0) { AddOp(FScheduledOp(Item.At, Item, 2, DataPos), FScheduledOp(Args.base, Item)); } else { // Jump directly to stage 3, no need to load mask or blockImage. AddOp(FScheduledOp(Item.At, Item, 3, DataPos), FScheduledOp(Args.base, Item)); } break; } case 2: { AddOp(FScheduledOp(Item.At, Item, 3, Item.CustomState), FScheduledOp(Args.blockImage, Item), FScheduledOp(Args.mask, Item)); break; } case 3: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } } else { switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp::FromOpAndOptions(Args.layout, Item, 0)); break; case 1: { TSharedPtr ComposeLayout = LoadLayout(FCacheAddress(Args.layout, FScheduledOp::FromOpAndOptions(Args.layout, Item, 0))); FScheduledOpData Data; Data.Resource = ComposeLayout; int32 DataPos = HeapData.Add(Data); int32 RelBlockIndex = ComposeLayout->FindBlock(Args.BlockId); if (RelBlockIndex >= 0) { AddOp(FScheduledOp(Item.At, Item, 2, DataPos), FScheduledOp(Args.base, Item), FScheduledOp(Args.blockImage, Item), FScheduledOp(Args.mask, Item)); } else { AddOp(FScheduledOp(Item.At, Item, 2, DataPos), FScheduledOp(Args.base, Item)); } break; } case 2: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } } break; } case EOpType::IM_INTERPOLATE: { OP::ImageInterpolateArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Factor, Item) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_INTERPOLATE_1) // Targets must be consecutive int32 count = 0; for ( int32 i=0 ; i 1.0f-UE_SMALL_NUMBER ) { AddOp( FScheduledOp( Item.At, Item, 2, dataPos), FScheduledOp( Args.Targets[max], Item) ); } else { AddOp( FScheduledOp( Item.At, Item, 2, dataPos), FScheduledOp( Args.Targets[min], Item), FScheduledOp( Args.Targets[max], Item) ); } break; } case 2: { MUTABLE_CPUPROFILER_SCOPE(IM_INTERPOLATE_2) // Targets must be consecutive int32 count = 0; for ( int32 i=0 ; i Source = LoadImage(FCacheAddress(Args.Targets[Min], Item)); StoreImage(Item, Source); } else if (Bifactor > 1.0f - UE_SMALL_NUMBER) { TSharedPtr Source = LoadImage(FCacheAddress(Args.Targets[Max], Item)); StoreImage(Item, Source); } else { TSharedPtr pMin = LoadImage(FCacheAddress(Args.Targets[Min], Item)); TSharedPtr pMax = LoadImage(FCacheAddress(Args.Targets[Max], Item)); if (pMin && pMax) { TSharedPtr pNew = CloneOrTakeOver(pMin); // Be defensive: ensure image sizes match. if (pNew->GetSize() != pMax->GetSize()) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_ForInterpolate); TSharedPtr Resized = CreateImage(pNew->GetSizeX(), pNew->GetSizeY(), pMax->GetLODCount(), pMax->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, pMax.Get()); Release(pMax); pMax = Resized; } // Be defensive: ensure format matches. if (pNew->GetFormat() != pMax->GetFormat()) { MUTABLE_CPUPROFILER_SCOPE(Format_ForInterpolate); TSharedPtr Formatted = CreateImage(pMax->GetSizeX(), pMax->GetSizeY(), pMax->GetLODCount(), pNew->GetFormat(), EInitializationType::NotInitialized); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), pMax.Get()); check(bSuccess); Release(pMax); pMax = Formatted; } int32 LevelCount = FMath::Max(pNew->GetLODCount(), pMax->GetLODCount()); if (pNew->GetLODCount() != LevelCount) { MUTABLE_CPUPROFILER_SCOPE(Mipmap_ForInterpolate); int32 StartLevel = pNew->GetLODCount() - 1; // pNew is local owned, no need to CloneOrTakeOver. pNew->DataStorage.SetNumLODs(LevelCount); FMipmapGenerationSettings MipSettings{}; ImOp.ImageMipmap(Settings.ImageCompressionQuality, pNew.Get(), pNew.Get(), StartLevel, LevelCount, MipSettings); } if (pMax->GetLODCount() != LevelCount) { MUTABLE_CPUPROFILER_SCOPE(Mipmap_ForInterpolate); int32 StartLevel = pMax->GetLODCount() - 1; TSharedPtr MaxFix = CloneOrTakeOver(pMax); MaxFix->DataStorage.SetNumLODs(LevelCount); FMipmapGenerationSettings MipSettings{}; ImOp.ImageMipmap(Settings.ImageCompressionQuality, MaxFix.Get(), MaxFix.Get(), StartLevel, LevelCount, MipSettings); pMax = MaxFix; } ImageInterpolate(pNew.Get(), pMax.Get(), Bifactor); Release(pMax); StoreImage(Item, pNew); } else if (pMin) { StoreImage(Item, pMin); } else if (pMax) { StoreImage(Item, pMax); } } break; } default: check(false); } break; } case EOpType::IM_SATURATE: { OP::ImageSaturateArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Base, Item ), FScheduledOp::FromOpAndOptions( Args.Factor, Item, 0 )); break; case 1: { // This has been moved to a task. It should have been intercepted in IssueOp. check(false); } default: check(false); } break; } case EOpType::IM_LUMINANCE: { OP::ImageLuminanceArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Base, Item ) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_LUMINANCE_1) TSharedPtr Base = LoadImage( FCacheAddress(Args.Base,Item) ); TSharedPtr Result = CreateImage(Base->GetSizeX(), Base->GetSizeY(), Base->GetLODCount(), EImageFormat::L_UByte, EInitializationType::NotInitialized); ImageLuminance( Result.Get(),Base.Get() ); Release(Base); StoreImage( Item, Result ); break; } default: check(false); } break; } case EOpType::IM_SWIZZLE: { OP::ImageSwizzleArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.sources[0], Item ), FScheduledOp( Args.sources[1], Item ), FScheduledOp( Args.sources[2], Item ), FScheduledOp( Args.sources[3], Item ) ); break; case 1: // This has been moved to a task. It should have been intercepted in IssueOp. check(false); break; default: check(false); } break; } case EOpType::IM_COLOURMAP: { OP::ImageColourMapArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Base, Item ), FScheduledOp( Args.Mask, Item ), FScheduledOp( Args.Map, Item ) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_COLOURMAP_1) TSharedPtr Source = LoadImage( FCacheAddress(Args.Base,Item) ); TSharedPtr Mask = LoadImage( FCacheAddress(Args.Mask,Item) ); TSharedPtr Map = LoadImage( FCacheAddress(Args.Map,Item) ); bool bOnlyOneMip = (Mask->GetLODCount() < Source->GetLODCount()); // Be defensive: ensure image sizes match. if (Mask->GetSize() != Source->GetSize()) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_ForColourmap); TSharedPtr Resized = CreateImage(Source->GetSizeX(), Source->GetSizeY(), 1, Mask->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, Mask.Get()); Release(Mask); Mask = Resized; } TSharedPtr Result = CreateImage(Source->GetSizeX(), Source->GetSizeY(), Source->GetLODCount(), Source->GetFormat(), EInitializationType::NotInitialized); ImageColourMap( Result.Get(), Source.Get(), Mask.Get(), Map.Get(), bOnlyOneMip); if (bOnlyOneMip) { MUTABLE_CPUPROFILER_SCOPE(ImageColourMap_MipFix); FMipmapGenerationSettings DummyMipSettings{}; ImageMipmapInPlace(Settings.ImageCompressionQuality, Result.Get(), DummyMipSettings); } Release(Source); Release(Mask); Release(Map); StoreImage( Item, Result ); break; } default: check(false); } break; } case EOpType::IM_BINARISE: { OP::ImageBinariseArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Base, Item ), FScheduledOp::FromOpAndOptions( Args.Threshold, Item, 0 ) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_BINARISE_1) TSharedPtr pA = LoadImage( FCacheAddress(Args.Base,Item) ); float c = LoadScalar(FScheduledOp::FromOpAndOptions(Args.Threshold, Item, 0)); TSharedPtr Result = CreateImage(pA->GetSizeX(), pA->GetSizeY(), pA->GetLODCount(), EImageFormat::L_UByte, EInitializationType::NotInitialized); ImageBinarise( Result.Get(), pA.Get(), c ); Release(pA); StoreImage( Item, Result ); break; } default: check(false); } break; } case EOpType::IM_INVERT: { OP::ImageInvertArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item)); break; case 1: { // This has been moved to a task. It should have been intercepted in IssueOp. check(false); } default: check(false); } break; } case EOpType::IM_PLAINCOLOUR: { OP::ImagePlainColorArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp::FromOpAndOptions( Args.Color, Item, 0 ) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_PLAINCOLOUR_1) FVector4f c = LoadColor(FScheduledOp::FromOpAndOptions(Args.Color, Item, 0)); uint16 SizeX = Args.Size[0]; uint16 SizeY = Args.Size[1]; int32 LODs = Args.LODs; // This means all the mip chain if (LODs == 0) { LODs = FMath::CeilLogTwo(FMath::Max(SizeX,SizeY)); } for (int32 l=0; l pA = CreateImage( SizeX, SizeY, FMath::Max(LODs,1), EImageFormat(Args.Format), EInitializationType::NotInitialized ); ImOp.FillColor(pA.Get(), c); StoreImage( Item, pA ); break; } default: check(false); } break; } case EOpType::IM_REFERENCE: { OP::ResourceReferenceArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { TSharedPtr Result; if (Args.ForceLoad) { // This should never be reached because it should have been caught as a Task in IssueOp check(false); } else { Result = FImage::CreateAsReference(Args.ID, Args.ImageDesc, false); } StoreImage(Item, Result); break; } default: check(false); } break; } case EOpType::IM_CROP: { OP::ImageCropArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.source, Item ) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_CROP_1) TSharedPtr pA = LoadImage( FCacheAddress(Args.source,Item) ); box< UE::Math::TIntVector2 > rect; rect.min[0] = Args.minX; rect.min[1] = Args.minY; rect.size[0] = Args.sizeX; rect.size[1] = Args.sizeY; // Apply ther mipmap reduction to the crop rectangle. int32 MipsToSkip = Item.ExecutionOptions; while ( MipsToSkip>0 && rect.size[0]>0 && rect.size[1]>0 ) { rect.min[0] /= 2; rect.min[1] /= 2; rect.size[0] /= 2; rect.size[1] /= 2; MipsToSkip--; } TSharedPtr Result; if (!rect.IsEmpty()) { Result = CreateImage( rect.size[0], rect.size[1], 1, pA->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageCrop(Result, Settings.ImageCompressionQuality, pA.Get(), rect); } Release(pA); StoreImage( Item, Result ); break; } default: check(false); } break; } case EOpType::IM_PATCH: { // TODO: This is optimized for memory-usage but base and patch could be requested at the same time OP::ImagePatchArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item)); break; case 1: AddOp(FScheduledOp(Item.At, Item, 2), FScheduledOp(Args.patch, Item)); break; case 2: { MUTABLE_CPUPROFILER_SCOPE(IM_PATCH_1) TSharedPtr pA = LoadImage( FCacheAddress(Args.base,Item) ); TSharedPtr pB = LoadImage( FCacheAddress(Args.patch,Item) ); // Failsafe if (!pA || !pB) { Release(pB); StoreImage(Item, pA); break; } // Apply the mipmap reduction to the crop rectangle. int32 MipsToSkip = Item.ExecutionOptions; box rect; rect.min[0] = Args.minX / (1 << MipsToSkip); rect.min[1] = Args.minY / (1 << MipsToSkip); rect.size[0] = pB->GetSizeX(); rect.size[1] = pB->GetSizeY(); TSharedPtr Result = CloneOrTakeOver(pA); bool bApplyPatch = !rect.IsEmpty(); if (bApplyPatch) { // Change the block image format if it doesn't match the composed image // This is usually enforced at object compilation time. if (Result->GetFormat() != pB->GetFormat()) { MUTABLE_CPUPROFILER_SCOPE(ImagPatchReformat); EImageFormat format = GetMostGenericFormat(Result->GetFormat(), pB->GetFormat()); const FImageFormatData& finfo = GetImageFormatData(format); if (finfo.PixelsPerBlockX == 0) { format = GetUncompressedFormat(format); } if (Result->GetFormat() != format) { TSharedPtr Formatted = CreateImage(Result->GetSizeX(), Result->GetSizeY(), Result->GetLODCount(), format, EInitializationType::NotInitialized); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), Result.Get()); check(bSuccess); Release(Result); Result = Formatted; } if (pB->GetFormat() != format) { TSharedPtr Formatted = CreateImage(pB->GetSizeX(), pB->GetSizeY(), pB->GetLODCount(), format, EInitializationType::NotInitialized); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), pB.Get()); check(bSuccess); Release(pB); pB = Formatted; } } // Don't patch if below the image compression block size. const FImageFormatData& finfo = GetImageFormatData(Result->GetFormat()); bApplyPatch = (rect.min[0] % finfo.PixelsPerBlockX == 0) && (rect.min[1] % finfo.PixelsPerBlockY == 0) && (rect.size[0] % finfo.PixelsPerBlockX == 0) && (rect.size[1] % finfo.PixelsPerBlockY == 0) && (rect.min[0] + rect.size[0]) <= Result->GetSizeX() && (rect.min[1] + rect.size[1]) <= Result->GetSizeY() ; } if (bApplyPatch) { ImOp.ImageCompose(Result.Get(), pB.Get(), rect); Result->Flags = 0; } else { // This happens very often when skipping mips, and floods the log. //UE_LOG( LogMutableCore, Verbose, TEXT("Skipped patch operation for image not fitting the block compression size. Small image? Patch rect is (%d, %d), (%d, %d), base is (%d, %d)"), // rect.min[0], rect.min[1], rect.size[0], rect.size[1], Result->GetSizeX(), Result->GetSizeY()); } Release(pB); StoreImage( Item, Result ); break; } default: check(false); } break; } case EOpType::IM_RASTERMESH: { OP::ImageRasterMeshArgs Args = Program.GetOpArgs(Item.At); constexpr uint8 MeshContentFilter = (uint8)(EMeshContentFlags::GeometryData | EMeshContentFlags::PoseData); switch (Item.Stage) { case 0: if (Args.image) { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp::FromOpAndOptions(Args.mesh, Item, MeshContentFilter), FScheduledOp::FromOpAndOptions(Args.projector, Item, 0)); } else { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp::FromOpAndOptions(Args.mesh, Item, MeshContentFilter)); } break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_RASTERMESH_1) TSharedPtr Mesh = LoadMesh(FScheduledOp::FromOpAndOptions(Args.mesh, Item, MeshContentFilter)); // If no image, we are generating a flat mesh UV raster. This is the final stage in this case. if (!Args.image) { uint16 SizeX = Args.sizeX; uint16 SizeY = Args.sizeY; UE::Math::TIntVector2 CropMin(Args.CropMinX, Args.CropMinY); UE::Math::TIntVector2 UncroppedSize(Args.UncroppedSizeX, Args.UncroppedSizeY); // Drop mips while possible int32 MipsToDrop = Item.ExecutionOptions; bool bUseCrop = UncroppedSize[0] > 0; while (MipsToDrop && !(SizeX % 2) && !(SizeY % 2)) { SizeX = FMath::Max(uint16(1), FMath::DivideAndRoundUp(SizeX, uint16(2))); SizeY = FMath::Max(uint16(1), FMath::DivideAndRoundUp(SizeY, uint16(2))); if (bUseCrop) { CropMin[0] = FMath::DivideAndRoundUp(CropMin[0], uint16(2)); CropMin[1] = FMath::DivideAndRoundUp(CropMin[1], uint16(2)); UncroppedSize[0] = FMath::Max(uint16(1), FMath::DivideAndRoundUp(UncroppedSize[0], uint16(2))); UncroppedSize[1] = FMath::Max(uint16(1), FMath::DivideAndRoundUp(UncroppedSize[1], uint16(2))); } --MipsToDrop; } // Flat mesh UV raster TSharedPtr ResultImage = CreateImage(SizeX, SizeY, 1, EImageFormat::L_UByte, EInitializationType::Black); if (Mesh) { ImageRasterMesh(Mesh.Get(), ResultImage.Get(), Args.LayoutIndex, Args.BlockId, CropMin, UncroppedSize); Release(Mesh); } // Stop execution. StoreImage(Item, ResultImage); break; } const int32 MipsToSkip = Item.ExecutionOptions; int32 ProjectionMip = MipsToSkip; FScheduledOpData Data; Data.RasterMesh.Mip = ProjectionMip; Data.RasterMesh.MipValue = static_cast(ProjectionMip); FProjector Projector = LoadProjector(FScheduledOp::FromOpAndOptions(Args.projector, Item, 0)); EMinFilterMethod MinFilterMethod = Invoke([&]() -> EMinFilterMethod { if (ForcedProjectionMode == 0) { return EMinFilterMethod::None; } else if (ForcedProjectionMode == 1) { return EMinFilterMethod::TotalAreaHeuristic; } return static_cast(Args.MinFilterMethod); }); if (MinFilterMethod == EMinFilterMethod::TotalAreaHeuristic) { FVector2f TargetImageSizeF = FVector2f( FMath::Max(Args.sizeX >> MipsToSkip, 1), FMath::Max(Args.sizeY >> MipsToSkip, 1)); FVector2f SourceImageSizeF = FVector2f(Args.SourceSizeX, Args.SourceSizeY); if (Mesh) { const float ComputedMip = ComputeProjectedFootprintBestMip(Mesh.Get(), Projector, TargetImageSizeF, SourceImageSizeF); Data.RasterMesh.MipValue = FMath::Max(0.0f, ComputedMip + GlobalProjectionLodBias); Data.RasterMesh.Mip = static_cast(FMath::FloorToInt32(Data.RasterMesh.MipValue)); } } const int32 DataHeapAddress = HeapData.Add(Data); // Mesh is need again in the next stage, store it in the heap. HeapData[DataHeapAddress].Resource = Mesh; AddOp(FScheduledOp(Item.At, Item, 2, DataHeapAddress), FScheduledOp::FromOpAndOptions(Args.projector, Item, 0), FScheduledOp::FromOpAndOptions(Args.image, Item, Data.RasterMesh.Mip), FScheduledOp(Args.mask, Item), FScheduledOp::FromOpAndOptions(Args.angleFadeProperties, Item, 0)); break; } case 2: { MUTABLE_CPUPROFILER_SCOPE(IM_RASTERMESH_2) if (!Args.image) { // This case is treated at the previous stage. check(false); StoreImage(Item, nullptr); break; } FScheduledOpData& Data = HeapData[Item.CustomState]; // Unsafe downcast, should be fine as it is known to be a Mesh. TSharedPtr Mesh = StaticCastSharedPtr(ConstCastSharedPtr(Data.Resource)); Data.Resource = nullptr; if (!Mesh) { check(false); StoreImage(Item, nullptr); break; } uint16 SizeX = Args.sizeX; uint16 SizeY = Args.sizeY; UE::Math::TIntVector2 CropMin(Args.CropMinX, Args.CropMinY); UE::Math::TIntVector2 UncroppedSize(Args.UncroppedSizeX, Args.UncroppedSizeY); // Drop mips while possible int32 MipsToDrop = Item.ExecutionOptions; bool bUseCrop = UncroppedSize[0] > 0; while (MipsToDrop && !(SizeX % 2) && !(SizeY % 2)) { SizeX = FMath::Max(uint16(1),FMath::DivideAndRoundUp(SizeX, uint16(2))); SizeY = FMath::Max(uint16(1),FMath::DivideAndRoundUp(SizeY, uint16(2))); if (bUseCrop) { CropMin[0] = FMath::DivideAndRoundUp(CropMin[0], uint16(2)); CropMin[1] = FMath::DivideAndRoundUp(CropMin[1], uint16(2)); UncroppedSize[0] = FMath::Max(uint16(1), FMath::DivideAndRoundUp(UncroppedSize[0], uint16(2))); UncroppedSize[1] = FMath::Max(uint16(1), FMath::DivideAndRoundUp(UncroppedSize[1], uint16(2))); } --MipsToDrop; } // Raster with projection TSharedPtr Source = LoadImage(FCacheAddress(Args.image, Item.ExecutionIndex, Data.RasterMesh.Mip)); TSharedPtr Mask = nullptr; if (Args.mask) { Mask = LoadImage(FCacheAddress(Args.mask, Item)); // TODO: This shouldn't happen, but be defensive. FImageSize ResultSize(SizeX, SizeY); if (Mask && Mask->GetSize()!= ResultSize) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_MaskFixForProjection); TSharedPtr Resized = CreateImage(SizeX, SizeY, Mask->GetLODCount(), Mask->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, Mask.Get()); Release(Mask); Mask = Resized; } } float fadeStart = 180.0f; float fadeEnd = 180.0f; if ( Args.angleFadeProperties ) { FVector4f fadeProperties = LoadColor(FScheduledOp::FromOpAndOptions(Args.angleFadeProperties, Item, 0)); fadeStart = fadeProperties[0]; fadeEnd = fadeProperties[1]; } const float FadeStartRad = FMath::DegreesToRadians(fadeStart); const float FadeEndRad = FMath::DegreesToRadians(fadeEnd); EImageFormat Format = Source ? GetUncompressedFormat(Source->GetFormat()) : EImageFormat::L_UByte; if (Source && Source->GetFormat()!=Format) { MUTABLE_CPUPROFILER_SCOPE(RunCode_RasterMesh_ReformatSource); TSharedPtr Formatted = CreateImage(Source->GetSizeX(), Source->GetSizeY(), Source->GetLODCount(), Format, EInitializationType::NotInitialized); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), Source.Get()); check(bSuccess); Release(Source); Source = Formatted; } EMinFilterMethod MinFilterMethod = Invoke([&]() -> EMinFilterMethod { if (ForcedProjectionMode == 0) { return EMinFilterMethod::None; } else if (ForcedProjectionMode == 1) { return EMinFilterMethod::TotalAreaHeuristic; } return static_cast(Args.MinFilterMethod); }); if (MinFilterMethod == EMinFilterMethod::TotalAreaHeuristic) { const uint16 Mip = Data.RasterMesh.Mip; const FImageSize ExpectedSourceSize = FImageSize( FMath::Max(Args.SourceSizeX >> Mip, 1), FMath::Max(Args.SourceSizeY >> Mip, 1)); if (Source->GetSize() != ExpectedSourceSize) { MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageRasterMesh_SizeFixup); TSharedPtr Resized = CreateImage(ExpectedSourceSize.X, ExpectedSourceSize.Y, 1, Format, EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), Settings.ImageCompressionQuality, Source.Get()); Release(Source); Source = Resized; } } // Allocate memory for the temporary buffers FScratchImageProject Scratch; Scratch.Vertices.SetNum(Mesh->GetVertexCount()); Scratch.CulledVertex.SetNum(Mesh->GetVertexCount()); ESamplingMethod SamplingMethod = Invoke([&]() -> ESamplingMethod { if (ForcedProjectionMode == 0) { return ESamplingMethod::Point; } else if (ForcedProjectionMode == 1) { return ESamplingMethod::BiLinear; } return static_cast(Args.SamplingMethod); }); if (SamplingMethod == ESamplingMethod::BiLinear) { if (Source->GetLODCount() < 2 && Source->GetSizeX() > 1 && Source->GetSizeY() > 1) { MUTABLE_CPUPROFILER_SCOPE(RunCode_RasterMesh_BilinearMipGen); TSharedPtr OwnedSource = CloneOrTakeOver(Source); OwnedSource->DataStorage.SetNumLODs(2); ImageMipmapInPlace(0, OwnedSource.Get(), FMipmapGenerationSettings{}); Source = OwnedSource; } } // Allocate new image after bilinear mip generation to reduce operation memory peak. TSharedPtr New = CreateImage(SizeX, SizeY, 1, Format, EInitializationType::Black); if (Args.projector && Source && Source->GetSizeX() > 0 && Source->GetSizeY() > 0) { FProjector Projector = LoadProjector(FScheduledOp::FromOpAndOptions(Args.projector, Item, 0)); switch (Projector.type) { case EProjectorType::Planar: ImageRasterProjectedPlanar(Mesh.Get(), New.Get(), Source.Get(), Mask.Get(), Args.bIsRGBFadingEnabled, Args.bIsAlphaFadingEnabled, SamplingMethod, FadeStartRad, FadeEndRad, FMath::Frac(Data.RasterMesh.MipValue), Args.LayoutIndex, Args.BlockId, CropMin, UncroppedSize, &Scratch, bUseProjectionVectorImpl); break; case EProjectorType::Wrapping: ImageRasterProjectedWrapping(Mesh.Get(), New.Get(), Source.Get(), Mask.Get(), Args.bIsRGBFadingEnabled, Args.bIsAlphaFadingEnabled, SamplingMethod, FadeStartRad, FadeEndRad, FMath::Frac(Data.RasterMesh.MipValue), Args.LayoutIndex, Args.BlockId, CropMin, UncroppedSize, &Scratch, bUseProjectionVectorImpl); break; case EProjectorType::Cylindrical: ImageRasterProjectedCylindrical(Mesh.Get(), New.Get(), Source.Get(), Mask.Get(), Args.bIsRGBFadingEnabled, Args.bIsAlphaFadingEnabled, SamplingMethod, FadeStartRad, FadeEndRad, FMath::Frac(Data.RasterMesh.MipValue), Args.LayoutIndex, Projector.projectionAngle, CropMin, UncroppedSize, &Scratch, bUseProjectionVectorImpl); break; default: check(false); break; } } Release(Mesh); Release(Source); Release(Mask); StoreImage(Item, New); break; } default: check(false); } break; } case EOpType::IM_MAKEGROWMAP: { OP::ImageMakeGrowMapArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.mask, Item) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_MAKEGROWMAP_1) TSharedPtr Mask = LoadImage( FCacheAddress(Args.mask,Item) ); TSharedPtr Result = CreateImage( Mask->GetSizeX(), Mask->GetSizeY(), Mask->GetLODCount(), EImageFormat::L_UByte, EInitializationType::NotInitialized); TSharedPtr OwnedMask = CloneOrTakeOver(Mask); ImageMakeGrowMap(Result.Get(), OwnedMask.Get(), Args.border); Result->Flags |= FImage::IF_CANNOT_BE_SCALED; Release(Mask); Release(OwnedMask); StoreImage( Item, Result); break; } default: check(false); } break; } case EOpType::IM_DISPLACE: { OP::ImageDisplaceArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Source, Item ), FScheduledOp( Args.DisplacementMap, Item ) ); break; case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_DISPLACE_1) TSharedPtr Source = LoadImage( FCacheAddress(Args.Source,Item) ); TSharedPtr pMap = LoadImage( FCacheAddress(Args.DisplacementMap,Item) ); if (!Source) { Release(pMap); StoreImage(Item, nullptr); break; } // TODO: This shouldn't happen: displacement maps cannot be scaled because their information // is resolution sensitive (pixel offsets). If the size doesn't match, scale the source, apply // displacement and then unscale it. FImageSize OriginalSourceScale = Source->GetSize(); if (OriginalSourceScale[0]>0 && OriginalSourceScale[1]>0 && OriginalSourceScale != pMap->GetSize()) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_EmergencyHackForDisplacementStep1); TSharedPtr Resized = CreateImage(pMap->GetSizeX(), pMap->GetSizeY(), Source->GetLODCount(), Source->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, Source.Get()); Release(Source); Source = Resized; } // This works based on the assumption that displacement maps never read from a position they actually write to. // Since they are used for UV border expansion, this should always be the case. TSharedPtr Result = CloneOrTakeOver(Source); if (OriginalSourceScale[0] > 0 && OriginalSourceScale[1] > 0) { ImageDisplace(Result.Get(), Result.Get(), pMap.Get()); if (OriginalSourceScale != Result->GetSize()) { MUTABLE_CPUPROFILER_SCOPE(ImageResize_EmergencyHackForDisplacementStep2); TSharedPtr Resized = CreateImage(OriginalSourceScale[0], OriginalSourceScale[1], Result->GetLODCount(), Result->GetFormat(), EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), 0, Result.Get()); Release(Result); Result = Resized; } } Release(pMap); StoreImage( Item, Result); break; } default: check(false); } break; } case EOpType::IM_TRANSFORM: { const OP::ImageTransformArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { const TArray> Deps = { FScheduledOp(Args.ScaleX, Item), FScheduledOp(Args.ScaleY, Item), }; AddOp(FScheduledOp(Item.At, Item, 1), Deps); break; } case 1: { MUTABLE_CPUPROFILER_SCOPE(IM_TRANSFORM_1) FVector2f Scale = FVector2f( Args.ScaleX ? LoadScalar(FCacheAddress(Args.ScaleX, Item)) : 1.0f, Args.ScaleY ? LoadScalar(FCacheAddress(Args.ScaleY, Item)) : 1.0f); using FUint16Vector2 = UE::Math::TIntVector2; const FUint16Vector2 DestSizeI = Invoke([&]() { int32 MipsToDrop = Item.ExecutionOptions; FUint16Vector2 Size = FUint16Vector2( Args.SizeX > 0 ? Args.SizeX : Args.SourceSizeX, Args.SizeY > 0 ? Args.SizeY : Args.SourceSizeY); while (MipsToDrop && !(Size.X % 2) && !(Size.Y % 2)) { Size.X = FMath::Max(uint16(1), FMath::DivideAndRoundUp(Size.X, uint16(2))); Size.Y = FMath::Max(uint16(1), FMath::DivideAndRoundUp(Size.Y, uint16(2))); --MipsToDrop; } return FUint16Vector2(FMath::Max(Size.X, uint16(1)), FMath::Max(Size.Y, uint16(1))); }); const FVector2f DestSize = FVector2f(DestSizeI.X, DestSizeI.Y); const FVector2f SourceSize = FVector2f(FMath::Max(Args.SourceSizeX, uint16(1)), FMath::Max(Args.SourceSizeY, uint16(1))); FVector2f AspectCorrectionScale = FVector2f(1.0f, 1.0f); if (Args.bKeepAspectRatio) { const float DestAspectOverSrcAspect = (DestSize.X * SourceSize.Y) / (DestSize.Y * SourceSize.X); AspectCorrectionScale = DestAspectOverSrcAspect > 1.0f ? FVector2f(1.0f/DestAspectOverSrcAspect, 1.0f) : FVector2f(1.0f, DestAspectOverSrcAspect); } const FTransform2f Transform = FTransform2f(FVector2f(-0.5f)) .Concatenate(FTransform2f(FScale2f(Scale))) .Concatenate(FTransform2f(FScale2f(AspectCorrectionScale))) .Concatenate(FTransform2f(FVector2f(0.5f))); FBox2f NormalizedCropRect(ForceInit); NormalizedCropRect += Transform.TransformPoint(FVector2f(0.0f, 0.0f)); NormalizedCropRect += Transform.TransformPoint(FVector2f(1.0f, 0.0f)); NormalizedCropRect += Transform.TransformPoint(FVector2f(0.0f, 1.0f)); NormalizedCropRect += Transform.TransformPoint(FVector2f(1.0f, 1.0f)); const FVector2f ScaledSourceSize = NormalizedCropRect.GetSize() * DestSize; const float BestMip = FMath::Log2(FMath::Max(1.0f, FMath::Square(SourceSize.GetMin()))) * 0.5f - FMath::Log2(FMath::Max(1.0f, FMath::Square(ScaledSourceSize.GetMin()))) * 0.5f; FScheduledOpData OpHeapData; OpHeapData.ImageTransform.SizeX = DestSizeI.X; OpHeapData.ImageTransform.SizeY = DestSizeI.Y; FPlatformMath::StoreHalf(&OpHeapData.ImageTransform.ScaleXEncodedHalf, Scale.X); FPlatformMath::StoreHalf(&OpHeapData.ImageTransform.ScaleYEncodedHalf, Scale.Y); OpHeapData.ImageTransform.MipValue = BestMip + GlobalImageTransformLodBias; const int32 HeapDataAddress = HeapData.Add(OpHeapData); const uint8 Mip = static_cast(FMath::Max(0, FMath::FloorToInt(OpHeapData.ImageTransform.MipValue))); const TArray> Deps = { FScheduledOp::FromOpAndOptions(Args.Base, Item, Mip), FScheduledOp(Args.OffsetX, Item), FScheduledOp(Args.OffsetY, Item), FScheduledOp(Args.Rotation, Item) }; AddOp(FScheduledOp(Item.At, Item, 2, HeapDataAddress), Deps); break; } case 2: { MUTABLE_CPUPROFILER_SCOPE(IM_TRANSFORM_2); const FScheduledOpData OpHeapData = HeapData[Item.CustomState]; const uint8 Mip = static_cast(FMath::Max(0, FMath::FloorToInt(OpHeapData.ImageTransform.MipValue))); TSharedPtr Source = LoadImage(FCacheAddress(Args.Base, Item.ExecutionIndex, Mip)); const FVector2f Offset = FVector2f( Args.OffsetX ? LoadScalar(FCacheAddress(Args.OffsetX, Item)) : 0.0f, Args.OffsetY ? LoadScalar(FCacheAddress(Args.OffsetY, Item)) : 0.0f); FVector2f Scale = FVector2f( FPlatformMath::LoadHalf(&OpHeapData.ImageTransform.ScaleXEncodedHalf), FPlatformMath::LoadHalf(&OpHeapData.ImageTransform.ScaleYEncodedHalf)); FVector2f AspectCorrectionScale = FVector2f(1.0f, 1.0f); if (Args.bKeepAspectRatio) { const FVector2f DestSize = FVector2f(OpHeapData.ImageTransform.SizeX, OpHeapData.ImageTransform.SizeY); const FVector2f SourceSize = FVector2f(FMath::Max(Args.SourceSizeX, uint16(1)), FMath::Max(Args.SourceSizeY, uint16(1))); const float DestAspectOverSrcAspect = (DestSize.X * SourceSize.Y) / (DestSize.Y * SourceSize.X); AspectCorrectionScale = DestAspectOverSrcAspect > 1.0f ? FVector2f(1.0f/DestAspectOverSrcAspect, 1.0f) : FVector2f(1.0f, DestAspectOverSrcAspect); } // Map Range [0..1] to a full rotation const float RotationRad = LoadScalar(FCacheAddress(Args.Rotation, Item)) * UE_TWO_PI; EImageFormat SourceFormat = Source->GetFormat(); EImageFormat Format = GetUncompressedFormat(SourceFormat); if (Format != SourceFormat) { MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageTransform_FormatFixup); TSharedPtr Formatted = CreateImage(Source->GetSizeX(), Source->GetSizeY(), Source->GetLODCount(), Format, EInitializationType::NotInitialized); bool bSuccess = false; ImOp.ImagePixelFormat(bSuccess, Settings.ImageCompressionQuality, Formatted.Get(), Source.Get()); check(bSuccess); Release(Source); Source = Formatted; } const FImageSize ExpectedSourceSize = FImageSize( FMath::Max(Args.SourceSizeX >> (uint16)Mip, 1), FMath::Max(Args.SourceSizeY >> (uint16)Mip, 1)); if (Source->GetSize() != ExpectedSourceSize) { MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageTransform_SizeFixup); TSharedPtr Resized = CreateImage(ExpectedSourceSize.X, ExpectedSourceSize.Y, 1, Format, EInitializationType::NotInitialized); ImOp.ImageResizeLinear(Resized.Get(), Settings.ImageCompressionQuality, Source.Get()); Release(Source); Source = Resized; } if (Source->GetLODCount() < 2 && Source->GetSizeX() > 1 && Source->GetSizeY() > 1) { MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageTransform_BilinearMipGen); TSharedPtr OwnedSource = CloneOrTakeOver(Source); OwnedSource->DataStorage.SetNumLODs(2); ImageMipmapInPlace(0, OwnedSource.Get(), FMipmapGenerationSettings{}); Source = OwnedSource; } Scale.X = FMath::IsNearlyZero(Scale.X, UE_KINDA_SMALL_NUMBER) ? UE_KINDA_SMALL_NUMBER : Scale.X; Scale.Y = FMath::IsNearlyZero(Scale.Y, UE_KINDA_SMALL_NUMBER) ? UE_KINDA_SMALL_NUMBER : Scale.Y; AspectCorrectionScale.X = FMath::IsNearlyZero(AspectCorrectionScale.X, UE_KINDA_SMALL_NUMBER) ? UE_KINDA_SMALL_NUMBER : AspectCorrectionScale.X; AspectCorrectionScale.Y = FMath::IsNearlyZero(AspectCorrectionScale.Y, UE_KINDA_SMALL_NUMBER) ? UE_KINDA_SMALL_NUMBER : AspectCorrectionScale.Y; const FTransform2f Transform = FTransform2f(FVector2f(-0.5f)) .Concatenate(FTransform2f(FScale2f(Scale))) .Concatenate(FTransform2f(FQuat2f(RotationRad))) .Concatenate(FTransform2f(FScale2f(AspectCorrectionScale))) .Concatenate(FTransform2f(Offset + FVector2f(0.5f))); const EAddressMode AddressMode = static_cast(Args.AddressMode); const EInitializationType InitType = AddressMode == EAddressMode::ClampToBlack ? EInitializationType::Black : EInitializationType::NotInitialized; TSharedPtr Result = CreateImage( OpHeapData.ImageTransform.SizeX, OpHeapData.ImageTransform.SizeY, 1, Format, InitType); const float MipFactor = FMath::Frac(FMath::Max(0.0f, OpHeapData.ImageTransform.MipValue)); ImageTransform(Result.Get(), Source.Get(), Transform, MipFactor, AddressMode, bUseImageTransformVectorImpl); Release(Source); StoreImage(Item, Result); break; } default: check(false); } break; } default: if (type!=EOpType::NONE) { // Operation not implemented check( false ); } break; } } //--------------------------------------------------------------------------------------------- TSharedPtr CodeRunner::BuildCurrentOpRangeIndex( const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel, int32 parameterIndex ) { if (!Item.ExecutionIndex) { return nullptr; } // \todo: optimise to avoid allocating the index here, we could access internal // data directly. TSharedPtr Index = pParams->NewRangeIndex( parameterIndex ); if (!Index) { return nullptr; } const FProgram& Program = Model->GetPrivate()->Program; const FParameterDesc& paramDesc = Program.Parameters[ parameterIndex ]; for( int32 rangeIndexInParam=0; rangeIndexInParamValues[rangeIndexInParam] = Position; } return Index; } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Bool(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Bool); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::BO_CONSTANT: { OP::BoolConstantArgs Args = Program.GetOpArgs(Item.At); bool result = Args.bValue; StoreBool( Item, result ); break; } case EOpType::BO_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs(Item.At); bool result = false; TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); result = pParams->GetBoolValue( Args.variable, Index.Get() ); StoreBool( Item, result ); break; } case EOpType::BO_AND: { OP::BoolBinaryArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { // Try to avoid the op entirely if we have some children cached bool skip = false; if ( Args.A && GetMemory().IsValid( FCacheAddress(Args.A,Item) ) ) { bool a = LoadBool( FCacheAddress(Args.A,Item) ); if (!a) { StoreBool( Item, false ); skip=true; } } if ( !skip && Args.B && GetMemory().IsValid( FCacheAddress(Args.B,Item) ) ) { bool b = LoadBool( FCacheAddress(Args.B,Item) ); if (!b) { StoreBool( Item, false ); skip=true; } } if (!skip) { AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.A, Item)); } break; } case 1: { bool a = Args.A ? LoadBool( FCacheAddress(Args.A,Item) ) : true; if (!a) { StoreBool( Item, false ); } else { AddOp( FScheduledOp( Item.At, Item, 2), FScheduledOp( Args.B, Item)); } break; } case 2: { // We arrived here because a is true bool b = Args.B ? LoadBool( FCacheAddress(Args.B,Item) ) : true; StoreBool( Item, b ); break; } default: check(false); } break; } case EOpType::BO_OR: { OP::BoolBinaryArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { // Try to avoid the op entirely if we have some children cached bool skip = false; if ( Args.A && GetMemory().IsValid( FCacheAddress(Args.A,Item) ) ) { bool a = LoadBool( FCacheAddress(Args.A,Item) ); if (a) { StoreBool( Item, true ); skip=true; } } if ( !skip && Args.B && GetMemory().IsValid( FCacheAddress(Args.B,Item) ) ) { bool b = LoadBool( FCacheAddress(Args.B,Item) ); if (b) { StoreBool( Item, true ); skip=true; } } if (!skip) { AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.A, Item)); } break; } case 1: { bool a = Args.A ? LoadBool( FCacheAddress(Args.A,Item) ) : false; if (a) { StoreBool( Item, true ); } else { AddOp( FScheduledOp( Item.At, Item, 2), FScheduledOp( Args.B, Item)); } break; } case 2: { // We arrived here because a is false bool b = Args.B ? LoadBool( FCacheAddress(Args.B,Item) ) : false; StoreBool( Item, b ); break; } default: check(false); } break; } case EOpType::BO_NOT: { OP::BoolNotArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.A, Item) ); break; case 1: { bool result = !LoadBool( FCacheAddress(Args.A,Item) ); StoreBool( Item, result ); break; } default: check(false); } break; } case EOpType::BO_EQUAL_INT_CONST: { OP::BoolEqualScalarConstArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Value, Item) ); break; case 1: { int32 a = LoadInt( FCacheAddress(Args.Value,Item) ); bool result = a == Args.Constant; StoreBool( Item, result ); break; } default: check(false); } break; } default: check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Int(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Int); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::NU_CONSTANT: { OP::IntConstantArgs Args = Program.GetOpArgs(Item.At); int32 result = Args.Value; StoreInt( Item, result ); break; } case EOpType::NU_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs(Item.At); TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); int32 result = pParams->GetIntValue( Args.variable, Index.Get()); // Check that the value is actually valid. Otherwise set the default. if ( pParams->GetIntPossibleValueCount( Args.variable ) ) { bool valid = false; for ( int32 i=0; (!valid) && iGetIntPossibleValueCount( Args.variable ); ++i ) { if ( result == pParams->GetIntPossibleValue( Args.variable, i ) ) { valid = true; } } if (!valid) { result = pParams->GetIntPossibleValue( Args.variable, 0 ); } } StoreInt( Item, result ); break; } default: check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Scalar(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Scalar); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::SC_CONSTANT: { OP::ScalarConstantArgs Args = Program.GetOpArgs(Item.At); float result = Args.Value; StoreScalar( Item, result ); break; } case EOpType::SC_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs(Item.At); TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); float result = pParams->GetFloatValue( Args.variable, Index.Get()); StoreScalar( Item, result ); break; } case EOpType::SC_CURVE: { OP::ScalarCurveArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.time, Item) ); break; case 1: { float time = LoadScalar( FCacheAddress(Args.time,Item) ); const FRichCurve& Curve = Program.ConstantCurves[Args.curve]; float Result = Curve.Eval(time); StoreScalar( Item, Result ); break; } default: check(false); } break; } case EOpType::SC_ARITHMETIC: { OP::ArithmeticArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.A, Item), FScheduledOp( Args.B, Item) ); break; case 1: { float a = LoadScalar( FCacheAddress(Args.A,Item) ); float b = LoadScalar( FCacheAddress(Args.B,Item) ); float result = 1.0f; switch (Args.Operation) { case OP::ArithmeticArgs::ADD: result = a + b; break; case OP::ArithmeticArgs::MULTIPLY: result = a * b; break; case OP::ArithmeticArgs::SUBTRACT: result = a - b; break; case OP::ArithmeticArgs::DIVIDE: result = a / b; break; default: checkf(false, TEXT("Arithmetic operation not implemented.")); break; } StoreScalar( Item, result ); break; } default: check(false); } break; } default: check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_String(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_String ); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType( Item.At ); switch ( type ) { case EOpType::ST_CONSTANT: { OP::ResourceConstantArgs Args = Program.GetOpArgs( Item.At ); check( Args.value < (uint32)InModel->GetPrivate()->Program.ConstantStrings.Num() ); const FString& result = Program.ConstantStrings[Args.value]; StoreString( Item, MakeShared(result) ); break; } case EOpType::ST_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs( Item.At ); TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); FString result; pParams->GetStringValue(Args.variable, result, Index.Get()); StoreString( Item, MakeShared(result) ); break; } default: check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Colour(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Colour); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch ( type ) { case EOpType::CO_CONSTANT: { OP::ColorConstantArgs Args = Program.GetOpArgs(Item.At); StoreColor( Item, Args.Value ); break; } case EOpType::CO_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs(Item.At); TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); FVector4f V; pParams->GetColourValue( Args.variable, V, Index.Get()); StoreColor( Item, V ); break; } case EOpType::CO_SAMPLEIMAGE: { OP::ColourSampleImageArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.X, Item), FScheduledOp( Args.Y, Item), // Don't skip mips for the texture to sample FScheduledOp::FromOpAndOptions( Args.Image, Item, 0) ); break; case 1: { float x = Args.X ? LoadScalar( FCacheAddress(Args.X,Item) ) : 0.5f; float y = Args.Y ? LoadScalar( FCacheAddress(Args.Y,Item) ) : 0.5f; TSharedPtr pImage = LoadImage(FScheduledOp::FromOpAndOptions(Args.Image, Item, 0)); FVector4f result; if (pImage) { if (Args.Filter) { // TODO result = pImage->Sample(FVector2f(x, y)); } else { result = pImage->Sample(FVector2f(x, y)); } } else { result = FVector4f(); } Release(pImage); StoreColor( Item, result ); break; } default: check(false); } break; } case EOpType::CO_SWIZZLE: { OP::ColourSwizzleArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.sources[0], Item), FScheduledOp( Args.sources[1], Item), FScheduledOp( Args.sources[2], Item), FScheduledOp( Args.sources[3], Item) ); break; case 1: { FVector4f result; for (int32 t=0;t(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.V[0], Item), FScheduledOp( Args.V[1], Item), FScheduledOp( Args.V[2], Item), FScheduledOp( Args.V[3], Item)); break; case 1: { FVector4f Result = FVector4f(0, 0, 0, 1); for (int32 t = 0; t < MUTABLE_OP_MAX_SWIZZLE_CHANNELS; ++t) { if (Args.V[t]) { Result[t] = LoadScalar(FCacheAddress(Args.V[t], Item)); } } StoreColor( Item, Result ); break; } default: check(false); } break; } case EOpType::CO_ARITHMETIC: { OP::ArithmeticArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.A, Item), FScheduledOp( Args.B, Item)); break; case 1: { EOpType otype = Program.GetOpType( Args.A ); EDataType dtype = GetOpDataType( otype ); check( dtype == EDataType::Color ); otype = Program.GetOpType( Args.B ); dtype = GetOpDataType( otype ); check( dtype == EDataType::Color ); FVector4f a = Args.A ? LoadColor( FCacheAddress( Args.A, Item ) ) : FVector4f( 0, 0, 0, 0 ); FVector4f b = Args.B ? LoadColor( FCacheAddress( Args.B, Item ) ) : FVector4f( 0, 0, 0, 0 ); FVector4f result = FVector4f(0,0,0,0); switch (Args.Operation) { case OP::ArithmeticArgs::ADD: result = a + b; break; case OP::ArithmeticArgs::MULTIPLY: result = a * b; break; case OP::ArithmeticArgs::SUBTRACT: result = a - b; break; case OP::ArithmeticArgs::DIVIDE: result = a / b; break; default: checkf(false, TEXT("Arithmetic operation not implemented.")); break; } StoreColor( Item, result ); break; } default: check(false); } break; } default: check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Projector(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Projector); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::PR_CONSTANT: { OP::ResourceConstantArgs Args = Program.GetOpArgs(Item.At); FProjector Result = Program.ConstantProjectors[Args.value]; StoreProjector( Item, Result ); break; } case EOpType::PR_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs(Item.At); TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); FProjector Result = pParams->GetPrivate()->GetProjectorValue(Args.variable,Index.Get()); // The type cannot be changed, take it from the default value const FProjector& def = Program.Parameters[Args.variable].DefaultValue.Get(); Result.type = def.type; StoreProjector( Item, Result ); break; } default: check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Matrix(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel ) { MUTABLE_CPUPROFILER_SCOPE(RunCode_Transform); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch ( type ) { case EOpType::MA_CONSTANT: { OP::MatrixConstantArgs Args = Program.GetOpArgs(Item.At); StoreMatrix( Item, Program.ConstantMatrices[Args.value] ); break; } case EOpType::MA_PARAMETER: { OP::ParameterArgs Args = Program.GetOpArgs(Item.At); TSharedPtr Index = BuildCurrentOpRangeIndex( Item, pParams, InModel, Args.variable ); FMatrix44f Value; pParams->GetMatrixValue( Args.variable, Value, Index.Get()); StoreMatrix( Item, Value ); break; } } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode_Layout(const FScheduledOp& Item, const FModel* InModel ) { //MUTABLE_CPUPROFILER_SCOPE(RunCode_Layout); const FProgram& Program = Model->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); switch (type) { case EOpType::LA_CONSTANT: { OP::ResourceConstantArgs Args = Program.GetOpArgs(Item.At); check( Args.value < (uint32)InModel->GetPrivate()->Program.ConstantLayouts.Num() ); TSharedPtr Result = Program.ConstantLayouts [ Args.value ]; StoreLayout( Item, Result ); break; } case EOpType::LA_MERGE: { OP::LayoutMergeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Base, Item), FScheduledOp( Args.Added, Item) ); break; case 1: { TSharedPtr pA = LoadLayout( FCacheAddress(Args.Base,Item) ); TSharedPtr pB = LoadLayout( FCacheAddress(Args.Added,Item) ); TSharedPtr Result; if (pA && pB) { Result = LayoutMerge(pA.Get(),pB.Get()); } else if (pA) { Result = pA->Clone(); } else if (pB) { Result = pB->Clone(); } StoreLayout( Item, Result ); break; } default: check(false); } break; } case EOpType::LA_PACK: { OP::LayoutPackArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp( FScheduledOp( Item.At, Item, 1), FScheduledOp( Args.Source, Item) ); break; case 1: { TSharedPtr Source = LoadLayout( FCacheAddress(Args.Source,Item) ); TSharedPtr Result; if (Source) { Result = Source->Clone(); LayoutPack3(Result.Get(), Source.Get() ); } StoreLayout( Item, Result ); break; } default: check(false); } break; } case EOpType::LA_FROMMESH: { OP::LayoutFromMeshArgs Args = Program.GetOpArgs(Item.At); constexpr uint8 MeshContentFilter = (uint8)(EMeshContentFlags::AllFlags); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp::FromOpAndOptions(Args.Mesh, Item, MeshContentFilter)); break; case 1: { TSharedPtr Mesh = LoadMesh( FScheduledOp::FromOpAndOptions(Args.Mesh, Item, MeshContentFilter)); TSharedPtr Result = LayoutFromMesh_RemoveBlocks(Mesh.Get(), Args.LayoutIndex); Release(Mesh); StoreLayout(Item, Result); break; } default: check(false); } break; } case EOpType::LA_REMOVEBLOCKS: { OP::LayoutRemoveBlocksArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item), FScheduledOp(Args.ReferenceLayout, Item)); break; case 1: { TSharedPtr Source = LoadLayout(FCacheAddress(Args.Source, Item)); TSharedPtr ReferenceLayout = LoadLayout(FCacheAddress(Args.ReferenceLayout, Item)); TSharedPtr Result; if (Source && ReferenceLayout) { Result = LayoutRemoveBlocks(Source.Get(), ReferenceLayout.Get()); } else if (Source) { Result = Source; } StoreLayout(Item, Result); break; } default: check(false); } break; } default: // Operation not implemented check( false ); break; } } //--------------------------------------------------------------------------------------------- void CodeRunner::RunCode( const FScheduledOp& Item, const FParameters* pParams, const TSharedPtr& InModel, uint32 LodMask) { //UE_LOG( LogMutableCore, Log, TEXT("Running :%5d , %d "), Item.At, Item.Stage ); check( Item.Type == FScheduledOp::EType::Full ); const FProgram& Program = InModel->GetPrivate()->Program; EOpType type = Program.GetOpType(Item.At); //UE_LOG(LogMutableCore, Log, TEXT("Running :%5d , %d, of type %d "), Item.At, Item.Stage, type); // Very spammy, for debugging purposes. //if (System) //{ // System->WorkingMemoryManager.LogWorkingMemory( this ); //} switch ( type ) { case EOpType::NONE: break; case EOpType::NU_CONDITIONAL: case EOpType::SC_CONDITIONAL: case EOpType::CO_CONDITIONAL: case EOpType::IM_CONDITIONAL: case EOpType::ME_CONDITIONAL: case EOpType::LA_CONDITIONAL: case EOpType::IN_CONDITIONAL: case EOpType::ED_CONDITIONAL: RunCode_Conditional(Item, InModel.Get()); break; case EOpType::ME_CONSTANT: case EOpType::IM_CONSTANT: case EOpType::ED_CONSTANT: RunCode_ConstantResource(Item, InModel.Get()); break; case EOpType::NU_SWITCH: case EOpType::SC_SWITCH: case EOpType::CO_SWITCH: case EOpType::IM_SWITCH: case EOpType::ME_SWITCH: case EOpType::LA_SWITCH: case EOpType::IN_SWITCH: case EOpType::ED_SWITCH: RunCode_Switch(Item, InModel.Get()); break; case EOpType::IN_ADDMESH: case EOpType::IN_ADDIMAGE: RunCode_InstanceAddResource(Item, InModel, pParams); break; default: { EDataType DataType = GetOpDataType(type); switch (DataType) { case EDataType::Instance: RunCode_Instance(Item, InModel.Get(), LodMask); break; case EDataType::Mesh: RunCode_Mesh(Item, InModel.Get()); break; case EDataType::Image: RunCode_Image(Item, pParams, InModel.Get()); break; case EDataType::Layout: RunCode_Layout(Item, InModel.Get()); break; case EDataType::Bool: RunCode_Bool(Item, pParams, InModel.Get()); break; case EDataType::Scalar: RunCode_Scalar(Item, pParams, InModel.Get()); break; case EDataType::String: RunCode_String(Item, pParams, InModel.Get()); break; case EDataType::Int: RunCode_Int(Item, pParams, InModel.Get()); break; case EDataType::Projector: RunCode_Projector(Item, pParams, InModel.Get()); break; case EDataType::Color: RunCode_Colour(Item, pParams, InModel.Get()); break; case EDataType::Matrix: RunCode_Matrix(Item, pParams, InModel.Get()); break; default: check(false); break; } break; } } } /** */ void CodeRunner::RunCodeImageDesc(const FScheduledOp& Item, const FParameters* pParams, const FModel* InModel, uint32 LodMask ) { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc); check(Item.Type == FScheduledOp::EType::ImageDesc); const FProgram& Program = Model->GetPrivate()->Program; EOpType Type = Program.GetOpType(Item.At); switch (Type) { case EOpType::IM_CONSTANT: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_CONSTANT); check(Item.Stage == 0); OP::ResourceConstantArgs Args = Program.GetOpArgs(Item.At); int32 ImageIndex = Args.value; FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result.m_format = Program.ConstantImages[ImageIndex].ImageFormat; Result.m_size[0] = Program.ConstantImages[ImageIndex].ImageSizeX; Result.m_size[1] = Program.ConstantImages[ImageIndex].ImageSizeY; Result.m_lods = Program.ConstantImages[ImageIndex].LODCount; int32 LODIndexIndex = Program.ConstantImages[ImageIndex].FirstIndex; { int32 LODIndex = 0; for (; LODIndex < Result.m_lods; ++LODIndex) { const int32 CurrentIndexIndex = LODIndexIndex + LODIndex; const FConstantResourceIndex CurrentIndex = Program.ConstantImageLODIndices[CurrentIndexIndex]; bool bIsLODAvailable = false; if (!CurrentIndex.Streamable) { bIsLODAvailable = true; } else { uint32 RomId = CurrentIndex.Index; bIsLODAvailable = System->StreamInterface->DoesBlockExist(Model.Get(), RomId); } if (bIsLODAvailable) { break; } } Result.FirstLODAvailable = LODIndex; } ImageDescConstantImages.Add(ImageIndex); StoreValidDesc(Item); break; } case EOpType::IM_PARAMETER: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_PARAMETER); check(Item.Stage == 0); OP::ParameterArgs Args = Program.GetOpArgs(Item.At); UTexture* Image = pParams->GetImageValue(Args.variable); FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = GetExternalImageDesc(Image); StoreValidDesc(Item); break; } case EOpType::IM_REFERENCE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_REFERENCE); check(Item.Stage == 0); OP::ResourceReferenceArgs Args = Program.GetOpArgs(Item.At); FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = FExtendedImageDesc{ Args.ImageDesc, 0 }; StoreValidDesc(Item); break; } case EOpType::IM_CONDITIONAL: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_CONDITIONAL); OP::ConditionalArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { // We need to run the full condition result FScheduledOp FullConditionOp(Args.condition, Item); FullConditionOp.Type = FScheduledOp::EType::Full; AddOp(FScheduledOp(Item.At, Item, 1), FullConditionOp); break; } case 1: { bool bValue = LoadBool(FCacheAddress(Args.condition, Item.ExecutionIndex, Item.ExecutionOptions)); OP::ADDRESS ResultAt = bValue ? Args.yes : Args.no; AddOp(FScheduledOp(Item.At, Item, 2, ResultAt), FScheduledOp(ResultAt, Item, 0)); break; } case 2: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Item.CustomState); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_SWITCH: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_SWITCH); const uint8* Data = Program.GetOpArgsPointer(Item.At); OP::ADDRESS VarAddress; FMemory::Memcpy(&VarAddress, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); OP::ADDRESS DefAddress; FMemory::Memcpy(&DefAddress, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); uint32 CaseCount; FMemory::Memcpy(&CaseCount, Data, sizeof(uint32)); Data += sizeof(uint32); switch (Item.Stage) { case 0: { if (VarAddress) { // We need to run the full condition result FScheduledOp FullVariableOp(VarAddress, Item); FullVariableOp.Type = FScheduledOp::EType::Full; AddOp(FScheduledOp(Item.At, Item, 1), FullVariableOp); } else { ImageDescResults.FindOrAdd(Item.At); StoreValidDesc(Item); } break; } case 1: { // Get the variable result int32 Var = LoadInt(FCacheAddress(VarAddress, Item.ExecutionIndex, Item.ExecutionOptions, FScheduledOp::EType::Full)); OP::ADDRESS ValueAt = DefAddress; for (uint32 C = 0; C < CaseCount; ++C) { int32 Condition; FMemory::Memcpy(&Condition, Data, sizeof(int32)); Data += sizeof(int32); OP::ADDRESS At; FMemory::Memcpy(&At, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); if (At && Var == (int32)Condition) { ValueAt = At; break; } } if (ValueAt) { AddOp(FScheduledOp(Item.At, Item, 2, ValueAt), FScheduledOp(ValueAt, Item, 0)); } else { ImageDescResults.FindOrAdd(Item.At); StoreValidDesc(Item); } break; } case 2: { check(Item.CustomState); FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Item.CustomState); StoreValidDesc(Item); break; } default: check(false); break; } break; } case EOpType::IM_LAYERCOLOUR: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_LAYERCOLOUR); OP::ImageLayerColourArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item, 0), FScheduledOp(Args.mask, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.base); if (Args.mask) { const FExtendedImageDesc& MaskResult = ImageDescResults.FindChecked(Args.mask); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, MaskResult.FirstLODAvailable); } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_LAYER: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_LAYER); OP::ImageLayerArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item, 0), FScheduledOp(Args.mask, Item, 0), FScheduledOp(Args.blended, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.base); if (Args.mask) { const FExtendedImageDesc& MaskResult = ImageDescResults.FindChecked(Args.mask); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, MaskResult.FirstLODAvailable); } if (Args.blended) { const FExtendedImageDesc& BlendedResult = ImageDescResults.FindChecked(Args.blended); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, BlendedResult.FirstLODAvailable); } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_MULTILAYER: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_MULTILAYER); OP::ImageMultiLayerArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { //TODO: For now multilayer operations will only check the base to get the descriptor. // but all iterations should be checked for available mips. AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.base); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_NORMALCOMPOSITE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_NORMALCOMPOSITE); OP::ImageNormalCompositeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.base); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_PIXELFORMAT: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_PIXELFORMAT); OP::ImagePixelFormatArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item, 0)); break; case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.source); EImageFormat OldFormat = Result.m_format; EImageFormat NewFormat = Args.format; if (Args.formatIfAlpha != EImageFormat::None && GetImageFormatData(OldFormat).Channels > 3) { NewFormat = Args.formatIfAlpha; } Result.m_format = NewFormat; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_MIPMAP: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_MIPMAP); OP::ImageMipmapArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item, 0)); break; case 1: { // Somewhat synched with Full op execution code. FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.source); int32 LevelCount = Args.levels; int32 MaxLevelCount = FImage::GetMipmapCount(Result.m_size[0], Result.m_size[1]); if (LevelCount == 0) { LevelCount = MaxLevelCount; } else if (LevelCount > MaxLevelCount) { // If code generation is smart enough, this should never happen. // \todo But apparently it does, sometimes. LevelCount = MaxLevelCount; } // At least keep the levels we already have. int32 StartLevel = Result.m_lods; LevelCount = FMath::Max(StartLevel, LevelCount); // Update result. Result.m_lods = LevelCount; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_RESIZE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_RESIZE); OP::ImageResizeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Source); Result.m_size[0] = Args.Size[0]; Result.m_size[1] = Args.Size[1]; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_RESIZELIKE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_RESIZELIKE); OP::ImageResizeLikeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item, 0), FScheduledOp(Args.SizeSource, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Source); if (Args.SizeSource) { const FExtendedImageDesc& SizeSourceResult = ImageDescResults.FindChecked(Args.SizeSource); Result.m_size = SizeSourceResult.m_size; } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_RESIZEREL: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_RESIZEREL); OP::ImageResizeRelArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item, 0)); break; case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Source); FImageSize DestSize( uint16(Result.m_size[0] * Args.Factor[0] + 0.5f), uint16(Result.m_size[1] * Args.Factor[1] + 0.5f)); Result.m_size = DestSize; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_BLANKLAYOUT: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_BLANKLAYOUT); OP::ImageBlankLayoutArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { // We need to run the full layout FScheduledOp FullLayoutOp(Args.Layout, Item); FullLayoutOp.Type = FScheduledOp::EType::Full; AddOp(FScheduledOp(Item.At, Item, 1), FullLayoutOp); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); TSharedPtr Layout = LoadLayout(FCacheAddress(Args.Layout, Item.ExecutionIndex, Item.ExecutionOptions, FScheduledOp::EType::Full)); FIntPoint SizeInBlocks = Layout->GetGridSize(); FIntPoint BlockSizeInPixels(Args.BlockSize[0], Args.BlockSize[1]); FIntPoint ImageSizeInPixels = SizeInBlocks * BlockSizeInPixels; FImageSize DestSize(uint16(ImageSizeInPixels.X), uint16(ImageSizeInPixels.Y)); Result.m_size = DestSize; Result.m_format = Args.Format; if (Args.GenerateMipmaps) { if (Args.MipmapCount == 0) { Result.m_lods = FImage::GetMipmapCount(ImageSizeInPixels.X, ImageSizeInPixels.Y); } else { Result.m_lods = Args.MipmapCount; } } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_COMPOSE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_COMPOSE); OP::ImageComposeArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item, 0), FScheduledOp(Args.blockImage, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.base); if (Args.blockImage) { const FExtendedImageDesc& BlockResult = ImageDescResults.FindChecked(Args.blockImage); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, BlockResult.FirstLODAvailable); } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_INTERPOLATE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_INTERPOLATE); OP::ImageInterpolateArgs Args = Program.GetOpArgs(Item.At); int32 NumImages = 0; for (; NumImages < MUTABLE_OP_MAX_INTERPOLATE_COUNT; ++NumImages) { if (!Args.Targets[NumImages]) { break; } } switch (Item.Stage) { case 0: { TArray> Deps; for (int32 ImageIndex = 0; ImageIndex < NumImages; ++ImageIndex) { Deps.Add(FScheduledOp(Args.Targets[ImageIndex], Item, 0)); } AddOp(FScheduledOp(Item.At, Item, 1), Deps); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); check(Args.Targets[0]); Result = ImageDescResults.FindChecked(Args.Targets[0]); for (int32 ImageIndex = 1; ImageIndex < NumImages; ++ImageIndex) { const FExtendedImageDesc& TargetResult = ImageDescResults.FindChecked(Args.Targets[ImageIndex]); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, TargetResult.FirstLODAvailable); } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_SATURATE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_SATURATE); OP::ImageSaturateArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Base); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_LUMINANCE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_LUMINANCE); OP::ImageLuminanceArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Base); Result.m_format = EImageFormat::L_UByte; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_SWIZZLE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_SWIZZLE); OP::ImageSwizzleArgs Args = Program.GetOpArgs(Item.At); TArray> ValidArgs; for (int32 SourceIndex = 0; SourceIndex < 4; ++SourceIndex) { if (Args.sources[SourceIndex]) { ValidArgs.AddUnique(Args.sources[SourceIndex]); } } switch (Item.Stage) { case 0: { TArray> Deps; for (int32 ArgIndex = 0; ArgIndex < ValidArgs.Num(); ++ArgIndex) { Deps.Add(FScheduledOp(ValidArgs[ArgIndex], Item, 0)); } AddOp(FScheduledOp(Item.At, Item, 1), Deps); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); check(ValidArgs.Num() > 0); Result = ImageDescResults.FindChecked(ValidArgs[0]); Result.m_format = Args.format; for (int32 ArgIndex = 1; ArgIndex < ValidArgs.Num(); ++ArgIndex) { const FExtendedImageDesc& SourceResult = ImageDescResults.FindOrAdd(ValidArgs[ArgIndex]); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, SourceResult.FirstLODAvailable); } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_COLOURMAP: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_COLOURMAP); OP::ImageColourMapArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Base); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_BINARISE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_BINARIZE); OP::ImageBinariseArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result.m_format = EImageFormat::L_UByte; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_INVERT: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_INVERT); OP::ImageInvertArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Base); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_PLAINCOLOUR: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_PLAINCOLOUR); OP::ImagePlainColorArgs Args = Program.GetOpArgs(Item.At); FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result.m_size[0] = Args.Size[0]; Result.m_size[1] = Args.Size[1]; Result.m_lods = Args.LODs; Result.m_format = Args.Format; StoreValidDesc(Item); break; } case EOpType::IM_CROP: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_CROP); OP::ImageCropArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.source, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.source); Result.m_size[0] = Args.sizeX; Result.m_size[1] = Args.sizeY; Result.m_lods = 1; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_PATCH: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_PATCH); OP::ImagePatchArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.base, Item, 0), FScheduledOp(Args.patch, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.base); if (Args.patch) { FExtendedImageDesc& PatchImageDesc = ImageDescResults.FindChecked(Args.patch); Result.FirstLODAvailable = FMath::Max(Result.FirstLODAvailable, PatchImageDesc.FirstLODAvailable); } StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_RASTERMESH: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_RASTERMESH); OP::ImageRasterMeshArgs Args = Program.GetOpArgs(Item.At); FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result.m_size[0] = Args.sizeX; Result.m_size[1] = Args.sizeY; Result.m_lods = 1; Result.m_format = EImageFormat::L_UByte; StoreValidDesc(Item); break; } case EOpType::IM_MAKEGROWMAP: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_MAKEGROWMAP); OP::ImageMakeGrowMapArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.mask, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.mask); Result.m_format = EImageFormat::L_UByte; Result.m_lods = 1; StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_DISPLACE: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_DISPLACE); OP::ImageDisplaceArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Source, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Source); StoreValidDesc(Item); break; } default: check(false); } break; } case EOpType::IM_TRANSFORM: { MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc_IM_TRANSFORM); OP::ImageTransformArgs Args = Program.GetOpArgs(Item.At); switch (Item.Stage) { case 0: { AddOp(FScheduledOp(Item.At, Item, 1), FScheduledOp(Args.Base, Item, 0)); break; } case 1: { FExtendedImageDesc& Result = ImageDescResults.FindOrAdd(Item.At); Result = ImageDescResults.FindChecked(Args.Base); Result.m_lods = 1; Result.m_format = GetUncompressedFormat(Result.m_format); if (!(Args.SizeX == 0 && Args.SizeY == 0)) { Result.m_size[0] = Args.SizeX; Result.m_size[1] = Args.SizeY; } StoreValidDesc(Item); break; } default: check(false); } break; } default: if (Type != EOpType::NONE) { // Operation not implemented check(false); ImageDescResults.FindOrAdd(Item.At); } break; } } FImageOperator MakeImageOperator(CodeRunner* Runner) { return FImageOperator( // Create [Runner](int32 x, int32 y, int32 m, EImageFormat f, EInitializationType i) { return Runner->CreateImage(x, y, m, f, i); }, // Release [Runner](TSharedPtr& Image) { Runner->Release(Image); }, // Clone [Runner](const FImage* Image) { TSharedPtr New = Runner->CreateImage(Image->GetSizeX(), Image->GetSizeY(), Image->GetLODCount(), Image->GetFormat(), EInitializationType::NotInitialized); New->Copy(Image); return New; }, Runner->System->ImagePixelFormatOverride ); } } #if MUTABLE_DEBUG_CODERUNNER_TASK_SCHEDULE_CALLSTACK #undef AddOp #endif