You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Operations where the source image can be requested to load a higher mip (IM_TRANSFORM and IM_RASTERMESH with minfilter method set) now resize the image if the source expected size does not match the loaded source size. #jira UE-225298 #rb jordi.rovira #rnx [CL 36761644 by genis sole in 5.5 branch]
6964 lines
204 KiB
C++
6964 lines
204 KiB
C++
// 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/UnrealMathSSE.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/OpImageGradient.h"
|
|
#include "MuR/OpImageInterpolate.h"
|
|
#include "MuR/OpImageInvert.h"
|
|
#include "MuR/OpImageLuminance.h"
|
|
#include "MuR/OpImageNormalCombine.h"
|
|
#include "MuR/OpImageProject.h"
|
|
#include "MuR/OpImageRasterMesh.h"
|
|
#include "MuR/OpImageSaturate.h"
|
|
#include "MuR/OpImageTransform.h"
|
|
#include "MuR/OpLayoutPack.h"
|
|
#include "MuR/OpLayoutRemoveBlocks.h"
|
|
#include "MuR/OpMeshApplyLayout.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/Platform.h"
|
|
#include "MuR/Skeleton.h"
|
|
#include "MuR/SystemPrivate.h"
|
|
#include "Templates/Tuple.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
|
|
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);
|
|
}
|
|
|
|
namespace mu
|
|
{
|
|
|
|
TSharedRef<CodeRunner> CodeRunner::Create(
|
|
const Ptr<const Settings>& InSettings,
|
|
class System::Private* InSystem,
|
|
EExecutionStrategy InExecutionStrategy,
|
|
const TSharedPtr<const Model>& InModel,
|
|
const Parameters* InParams,
|
|
OP::ADDRESS At,
|
|
uint32 InLODMask, uint8 ExecutionOptions, int32 InImageLOD, FScheduledOp::EType Type)
|
|
{
|
|
return MakeShared<CodeRunner>(FPrivateToken {},
|
|
InSettings, InSystem, InExecutionStrategy, InModel, InParams, At, InLODMask, ExecutionOptions, InImageLOD, Type);
|
|
}
|
|
|
|
CodeRunner::CodeRunner(FPrivateToken PrivateToken,
|
|
const Ptr<const Settings>& InSettings,
|
|
class System::Private* InSystem,
|
|
EExecutionStrategy InExecutionStrategy,
|
|
const TSharedPtr<const Model>& InModel,
|
|
const Parameters* InParams,
|
|
OP::ADDRESS at,
|
|
uint32 InLodMask, uint8 executionOptions, int32 InImageLOD, FScheduledOp::EType Type )
|
|
: m_pSettings(InSettings)
|
|
, RunnerCompletionEvent(TEXT("CodeRunnerCompletioneEventInit"))
|
|
, ExecutionStrategy(InExecutionStrategy)
|
|
, m_pSystem(InSystem)
|
|
, m_pModel(InModel)
|
|
, m_pParams(InParams)
|
|
, m_lodMask(InLodMask)
|
|
{
|
|
const FProgram& program = m_pModel->GetPrivate()->m_program;
|
|
ScheduledStagePerOp.resize(program.m_opAddress.Num());
|
|
|
|
// 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 *m_pSystem->WorkingMemoryManager.CurrentInstanceCache;
|
|
}
|
|
|
|
|
|
TTuple<UE::Tasks::FTask, TFunction<void()>> CodeRunner::LoadExternalImageAsync(FExternalResourceId Id, uint8 MipmapsToSkip, TFunction<void(Ptr<Image>)>& ResultCallback)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(LoadExternalImageAsync);
|
|
|
|
check(m_pSystem);
|
|
|
|
if (m_pSystem->ExternalResourceProvider)
|
|
{
|
|
if (Id.ReferenceResourceId < 0)
|
|
{
|
|
// It's a parameter image
|
|
return m_pSystem->ExternalResourceProvider->GetImageAsync(Id.ParameterId, MipmapsToSkip, ResultCallback);
|
|
}
|
|
else
|
|
{
|
|
// It's an image reference
|
|
return m_pSystem->ExternalResourceProvider->GetReferencedImageAsync(m_pModel.Get(), Id.ReferenceResourceId, MipmapsToSkip, ResultCallback);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not found and there is no generator!
|
|
check(false);
|
|
}
|
|
|
|
return MakeTuple(UE::Tasks::MakeCompletedTask<void>(), []() -> void {});
|
|
}
|
|
|
|
|
|
TTuple<UE::Tasks::FTask, TFunction<void()>> CodeRunner::LoadExternalMeshAsync(FExternalResourceId Id, TFunction<void(Ptr<Mesh>)>& ResultCallback)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(LoadExternalImageAsync);
|
|
|
|
check(m_pSystem);
|
|
|
|
if (m_pSystem->ExternalResourceProvider)
|
|
{
|
|
if (Id.ReferenceResourceId < 0)
|
|
{
|
|
// It's a parameter mesh
|
|
return m_pSystem->ExternalResourceProvider->GetMeshAsync(Id.ParameterId, ResultCallback);
|
|
}
|
|
else
|
|
{
|
|
// It's a mesh reference
|
|
return m_pSystem->ExternalResourceProvider->GetReferencedMeshAsync(m_pModel.Get(), Id.ReferenceResourceId, ResultCallback);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not found and there is no generator!
|
|
check(false);
|
|
}
|
|
|
|
return MakeTuple(UE::Tasks::MakeCompletedTask<void>(), []() -> void {});
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
FImageDesc CodeRunner::GetExternalImageDesc(FName Id, uint8 MipmapsToSkip)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(GetExternalImageDesc);
|
|
|
|
check(m_pSystem);
|
|
|
|
if (m_pSystem->ExternalResourceProvider)
|
|
{
|
|
return m_pSystem->ExternalResourceProvider->GetImageDesc(Id, MipmapsToSkip);
|
|
}
|
|
else
|
|
{
|
|
// Not found and there is no generator!
|
|
check(false);
|
|
}
|
|
|
|
return FImageDesc();
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Conditional( const FScheduledOp& item, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Conditional);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
OP::ConditionalArgs args = Program.GetOpArgs<OP::ConditionalArgs>(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 DT_BOOL: StoreBool( cat, LoadBool(rat) ); break;
|
|
case DT_INT: StoreInt( cat, LoadInt(rat) ); break;
|
|
case DT_SCALAR: StoreScalar( cat, LoadScalar(rat) ); break;
|
|
case DT_STRING: StoreString( cat, LoadString( rat ) ); break;
|
|
case DT_COLOUR: StoreColor( cat, LoadColor( rat ) ); break;
|
|
case DT_PROJECTOR: StoreProjector( cat, LoadProjector(rat) ); break;
|
|
case DT_MESH: StoreMesh( cat, LoadMesh(rat) ); break;
|
|
case DT_IMAGE: StoreImage( cat, LoadImage(rat) ); break;
|
|
case DT_LAYOUT: StoreLayout( cat, LoadLayout(rat) ); break;
|
|
case DT_INSTANCE: StoreInstance( cat, LoadInstance(rat) ); break;
|
|
case DT_EXTENSION_DATA: StoreExtensionData( cat, LoadExtensionData(rat) ); break;
|
|
default:
|
|
// Not implemented
|
|
check( false );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Switch(const FScheduledOp& item, const Model* pModel )
|
|
{
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE 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 DT_BOOL: StoreBool(item, false); break;
|
|
case DT_INT: StoreInt(item, 0); break;
|
|
case DT_SCALAR: StoreScalar(item, 0.0f); break;
|
|
case DT_STRING: StoreString(item, nullptr); break;
|
|
case DT_COLOUR: StoreColor(item, FVector4f()); break;
|
|
case DT_PROJECTOR: StoreProjector(item, FProjector()); break;
|
|
case DT_MESH: StoreMesh(item, nullptr); break;
|
|
case DT_IMAGE: StoreImage(item, nullptr); break;
|
|
case DT_LAYOUT: StoreLayout(item, nullptr); break;
|
|
case DT_INSTANCE: StoreInstance(item, nullptr); break;
|
|
case DT_EXTENSION_DATA: StoreExtensionData(item, new ExtensionData); break;
|
|
default:
|
|
// Not implemented
|
|
check(false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Get the variable result
|
|
int 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 == (int)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 DT_BOOL: StoreBool( cat, LoadBool(rat) ); break;
|
|
case DT_INT: StoreInt( cat, LoadInt(rat) ); break;
|
|
case DT_SCALAR: StoreScalar( cat, LoadScalar(rat) ); break;
|
|
case DT_STRING: StoreString( cat, LoadString( rat ) ); break;
|
|
case DT_COLOUR: StoreColor( cat, LoadColor( rat ) ); break;
|
|
case DT_PROJECTOR: StoreProjector( cat, LoadProjector(rat) ); break;
|
|
case DT_MESH: StoreMesh( cat, LoadMesh(rat) ); break;
|
|
case DT_IMAGE: StoreImage( cat, LoadImage(rat) ); break;
|
|
case DT_LAYOUT: StoreLayout( cat, LoadLayout(rat) ); break;
|
|
case DT_INSTANCE: StoreInstance( cat, LoadInstance(rat) ); break;
|
|
case DT_EXTENSION_DATA: StoreExtensionData( cat, LoadExtensionData(rat) ); break;
|
|
default:
|
|
// Not implemented
|
|
check( false );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Instance(const FScheduledOp& item, const Model* pModel, uint32 lodMask )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Instance);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::IN_ADDVECTOR:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>(item.At);
|
|
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.instance, item),
|
|
FScheduledOp( args.value, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
InstancePtrConst pBase = LoadInstance( FCacheAddress(args.instance,item) );
|
|
InstancePtr pResult;
|
|
if (!pBase)
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
else
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
|
|
if ( args.value )
|
|
{
|
|
FVector4f value = LoadColor( FCacheAddress(args.value,item) );
|
|
|
|
OP::ADDRESS nameAd = args.name;
|
|
check( nameAd < (uint32)Program.m_constantStrings.Num() );
|
|
const FString& Name = Program.m_constantStrings[ nameAd ];
|
|
|
|
pResult->GetPrivate()->AddVector( 0, 0, 0, value, FName(Name) );
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IN_ADDSCALAR:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.instance, item),
|
|
FScheduledOp( args.value, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
InstancePtrConst pBase = LoadInstance( FCacheAddress(args.instance,item) );
|
|
InstancePtr pResult;
|
|
if (!pBase)
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
else
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
|
|
if ( args.value )
|
|
{
|
|
float value = LoadScalar( FCacheAddress(args.value,item) );
|
|
|
|
OP::ADDRESS nameAd = args.name;
|
|
check( nameAd < (uint32)Program.m_constantStrings.Num() );
|
|
const FString& Name = Program.m_constantStrings[ nameAd ];
|
|
|
|
pResult->GetPrivate()->AddScalar( 0, 0, 0, value, FName(Name));
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IN_ADDSTRING:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>( item.At );
|
|
switch ( item.Stage )
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1 ), FScheduledOp( args.instance, item ),
|
|
FScheduledOp( args.value, item ) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
InstancePtrConst pBase =
|
|
LoadInstance( FCacheAddress( args.instance, item ) );
|
|
InstancePtr pResult;
|
|
if ( !pBase )
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
else
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
|
|
if ( args.value )
|
|
{
|
|
Ptr<const String> value = LoadString( FCacheAddress( args.value, item ) );
|
|
|
|
OP::ADDRESS nameAd = args.name;
|
|
check( nameAd < (uint32)Program.m_constantStrings.Num() );
|
|
const FString& Name = Program.m_constantStrings[nameAd];
|
|
|
|
pResult->GetPrivate()->AddString( 0, 0, 0, value->GetValue(), FName(Name) );
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check( false );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IN_ADDCOMPONENT:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.instance, item),
|
|
FScheduledOp( args.value, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
Ptr<const Instance> pBase = LoadInstance( FCacheAddress(args.instance,item) );
|
|
Ptr<Instance> pResult;
|
|
if (!pBase)
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
else
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
|
|
if ( args.value )
|
|
{
|
|
Ptr<const Instance> pComp = LoadInstance( FCacheAddress(args.value,item) );
|
|
|
|
int32 NewComponentIndex = pResult->GetPrivate()->AddComponent();
|
|
|
|
if ( !pComp->GetPrivate()->Components.IsEmpty() )
|
|
{
|
|
pResult->GetPrivate()->Components[NewComponentIndex] = pComp->GetPrivate()->Components[0];
|
|
|
|
// Id
|
|
pResult->GetPrivate()->Components[NewComponentIndex].Id = args.id;
|
|
}
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IN_ADDSURFACE:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.instance, item),
|
|
FScheduledOp( args.value, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
Ptr<const Instance> pBase = LoadInstance( FCacheAddress(args.instance,item) );
|
|
|
|
Ptr<Instance> pResult;
|
|
if (pBase)
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
else
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
|
|
// Empty surfaces are ok, they still need to be created, because they may contain
|
|
// additional information like internal or external IDs
|
|
//if ( args.value )
|
|
{
|
|
Ptr<const Instance> pSurf = LoadInstance( FCacheAddress(args.value,item) );
|
|
|
|
int sindex = pResult->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())
|
|
{
|
|
pResult->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.m_constantStrings.Num() );
|
|
const FString& Name = Program.m_constantStrings[ nameAd ];
|
|
pResult->GetPrivate()->SetSurfaceName( 0, 0, sindex, FName(Name) );
|
|
|
|
// IDs
|
|
pResult->GetPrivate()->Components[0].LODs[0].Surfaces[sindex].InternalId = args.id;
|
|
pResult->GetPrivate()->Components[0].LODs[0].Surfaces[sindex].ExternalId = args.ExternalId;
|
|
pResult->GetPrivate()->Components[0].LODs[0].Surfaces[sindex].SharedId = args.SharedSurfaceId;
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::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<FScheduledOp> deps;
|
|
for (uint8 LODIndex=0; LODIndex <LODCount; ++LODIndex)
|
|
{
|
|
OP::ADDRESS LODAddress;
|
|
FMemory::Memcpy(&LODAddress, Data, sizeof(OP::ADDRESS));
|
|
Data += sizeof(OP::ADDRESS);
|
|
|
|
if (LODAddress)
|
|
{
|
|
bool bSelectedLod = ( (1<< LODIndex) & lodMask ) != 0;
|
|
|
|
if (bSelectedLod)
|
|
{
|
|
deps.Emplace(LODAddress, item);
|
|
}
|
|
}
|
|
}
|
|
|
|
AddOp( FScheduledOp( item.At, item, 1), deps );
|
|
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Assemble result
|
|
Ptr<Instance> pResult = new Instance();
|
|
int32 ComponentIndex = pResult->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<<LODIndex) & lodMask ) != 0;
|
|
|
|
// Add an empty LOD even if not selected.
|
|
int32 InstanceLODIndex = pResult->GetPrivate()->AddLOD(ComponentIndex);
|
|
|
|
if (bIsSelectedLod)
|
|
{
|
|
Ptr<const Instance> 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())
|
|
{
|
|
pResult->GetPrivate()->Components[ComponentIndex].LODs[InstanceLODIndex] = pLOD->GetPrivate()->Components[0].LODs[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IN_ADDEXTENSIONDATA:
|
|
{
|
|
OP::InstanceAddExtensionDataArgs Args = Program.GetOpArgs<OP::InstanceAddExtensionDataArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
// Must pass in an Instance op and ExtensionData op
|
|
check(Args.Instance);
|
|
check(Args.ExtensionData);
|
|
|
|
TArray<FScheduledOp> Dependencies;
|
|
Dependencies.Emplace(Args.Instance, item);
|
|
Dependencies.Emplace(Args.ExtensionData, item);
|
|
|
|
AddOp(FScheduledOp(item.At, item, 1), Dependencies);
|
|
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Assemble result
|
|
InstancePtrConst InstanceOpResult = LoadInstance(FCacheAddress(Args.Instance, item));
|
|
|
|
InstancePtr Result = mu::CloneOrTakeOver<Instance>(InstanceOpResult.get());
|
|
|
|
if (ExtensionDataPtrConst ExtensionData = LoadExtensionData(FCacheAddress(Args.ExtensionData, item)))
|
|
{
|
|
const OP::ADDRESS NameAddress = Args.ExtensionDataName;
|
|
check(NameAddress < (uint32)Program.m_constantStrings.Num());
|
|
const FString& NameString = Program.m_constantStrings[NameAddress];
|
|
|
|
Result->GetPrivate()->AddExtensionData(ExtensionData, FName(NameString) );
|
|
}
|
|
|
|
StoreInstance(item, Result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_InstanceAddResource(const FScheduledOp& item, const TSharedPtr<const Model>& InModel, const Parameters* InParams )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_InstanceAddResource);
|
|
|
|
if (!InModel || !m_pSystem)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::IN_ADDMESH:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>(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:
|
|
{
|
|
InstancePtrConst pBase = LoadInstance( FCacheAddress(args.instance,item) );
|
|
InstancePtr pResult;
|
|
if (!pBase)
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
else
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
|
|
if ( args.value )
|
|
{
|
|
FResourceID MeshId = m_pSystem->WorkingMemoryManager.GetResourceKey(InModel,InParams,args.relevantParametersListIndex, args.value);
|
|
OP::ADDRESS NameAd = args.name;
|
|
check(NameAd < (uint32)Program.m_constantStrings.Num());
|
|
const FString& Name = Program.m_constantStrings[NameAd];
|
|
pResult->GetPrivate()->SetMesh(0, 0, MeshId, FName(Name));
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IN_ADDIMAGE:
|
|
{
|
|
OP::InstanceAddArgs args = Program.GetOpArgs<OP::InstanceAddArgs>(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:
|
|
{
|
|
InstancePtrConst pBase = LoadInstance( FCacheAddress(args.instance,item) );
|
|
InstancePtr pResult;
|
|
if (!pBase)
|
|
{
|
|
pResult = new Instance();
|
|
}
|
|
else
|
|
{
|
|
pResult = mu::CloneOrTakeOver<Instance>(pBase.get());
|
|
}
|
|
|
|
if ( args.value )
|
|
{
|
|
FResourceID ImageId = m_pSystem->WorkingMemoryManager.GetResourceKey(InModel, InParams, args.relevantParametersListIndex, args.value);
|
|
OP::ADDRESS NameAd = args.name;
|
|
check(NameAd < (uint32)Program.m_constantStrings.Num());
|
|
const FString& Name = Program.m_constantStrings[NameAd];
|
|
pResult->GetPrivate()->AddImage(0, 0, 0, ImageId, FName(Name) );
|
|
}
|
|
StoreInstance( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
bool CodeRunner::RunCode_ConstantResource(const FScheduledOp& item, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Constant);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::ME_CONSTANT:
|
|
{
|
|
OP::MeshConstantArgs args = Program.GetOpArgs<OP::MeshConstantArgs>(item.At);
|
|
|
|
OP::ADDRESS cat = args.value;
|
|
|
|
// Assume the ROM has been loaded previously
|
|
check(Program.ConstantMeshes[cat].Value)
|
|
|
|
Ptr<const Mesh> SourceConst;
|
|
Program.GetConstant(cat, SourceConst);
|
|
|
|
check(SourceConst);
|
|
Ptr<Mesh> Source = CreateMesh(SourceConst->GetDataSize());
|
|
Source->CopyFrom(*SourceConst);
|
|
|
|
// Set the separate skeleton if necessary
|
|
if (args.skeleton >= 0)
|
|
{
|
|
check(Program.m_constantSkeletons.Num() > size_t(args.skeleton));
|
|
Ptr<const Skeleton> pSkeleton = Program.m_constantSkeletons[args.skeleton];
|
|
Source->SetSkeleton(pSkeleton);
|
|
}
|
|
|
|
if (args.physicsBody >= 0)
|
|
{
|
|
check(Program.m_constantPhysicsBodies.Num() > size_t(args.physicsBody));
|
|
Ptr<const PhysicsBody> pPhysicsBody = Program.m_constantPhysicsBodies[args.physicsBody];
|
|
Source->SetPhysicsBody(pPhysicsBody);
|
|
}
|
|
|
|
StoreMesh(item, Source);
|
|
//UE_LOG(LogMutableCore, Log, TEXT("Set mesh constant %d."), item.At);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_CONSTANT:
|
|
{
|
|
OP::ResourceConstantArgs args = Program.GetOpArgs<OP::ResourceConstantArgs>(item.At);
|
|
OP::ADDRESS cat = args.value;
|
|
|
|
int32 MipsToSkip = item.ExecutionOptions;
|
|
Ptr<const Image> 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 OP_TYPE::ED_CONSTANT:
|
|
{
|
|
OP::ResourceConstantArgs Args = Program.GetOpArgs<OP::ResourceConstantArgs>(item.At);
|
|
|
|
// Assume the ROM has been loaded previously
|
|
ExtensionDataPtrConst SourceConst;
|
|
Program.GetExtensionDataConstant(Args.value, SourceConst);
|
|
|
|
check(SourceConst);
|
|
|
|
StoreExtensionData(item, SourceConst);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (type!=OP_TYPE::NONE)
|
|
{
|
|
// Operation not implemented
|
|
check( false );
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Success
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Mesh(const FScheduledOp& item, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Mesh);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::ME_REFERENCE:
|
|
{
|
|
OP::ResourceReferenceArgs Args = Program.GetOpArgs<OP::ResourceReferenceArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
Ptr<Mesh> Result;
|
|
if (Args.ForceLoad)
|
|
{
|
|
// This should never be reached because it should have been caught as a Task in IssueOp
|
|
check(false);
|
|
}
|
|
else
|
|
{
|
|
Result = Mesh::CreateAsReference(Args.ID, false);
|
|
}
|
|
StoreMesh(item, Result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_APPLYLAYOUT:
|
|
{
|
|
OP::MeshApplyLayoutArgs args = Program.GetOpArgs<OP::MeshApplyLayoutArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(args.mesh, item),
|
|
FScheduledOp(args.layout, item));
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_APPLYLAYOUT)
|
|
|
|
Ptr<const Mesh> pBase = LoadMesh(FCacheAddress(args.mesh, item));
|
|
|
|
if (pBase)
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(pBase);
|
|
|
|
Ptr<const Layout> pLayout = LoadLayout(FCacheAddress(args.layout, item));
|
|
int texCoordsSet = args.channel;
|
|
|
|
MeshApplyLayout(Result.get(), pLayout.get(), texCoordsSet);
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::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 (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)
|
|
|
|
Ptr<const Mesh> pBase = LoadMesh(FCacheAddress(BaseAt,item));
|
|
Ptr<const Mesh> pTarget = LoadMesh(FCacheAddress(TargetAt,item));
|
|
|
|
TArray<EMeshBufferSemantic, TInlineAllocator<8>> Semantics;
|
|
TArray<int32, TInlineAllocator<8>> 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);
|
|
}
|
|
|
|
Ptr<Mesh> Result = CreateMesh();
|
|
bool bOutSuccess = false;
|
|
MeshDifference(Result.get(), pBase.get(), pTarget.get(),
|
|
NumChannels, Semantics.GetData(), SemanticIndices.GetData(),
|
|
bIgnoreTextureCoords != 0, bOutSuccess);
|
|
Release(pBase);
|
|
Release(pTarget);
|
|
|
|
StoreMesh(item, Result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::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)
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(FactorAt, item));
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_MORPH_1)
|
|
|
|
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 HeapData;
|
|
HeapData.Interpolate.Bifactor = Factor;
|
|
uint32 DataAddress = uint32(m_heapData.Add(HeapData));
|
|
|
|
// 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)
|
|
|
|
Ptr<const Mesh> BaseMesh = LoadMesh(FCacheAddress(BaseAt, item));
|
|
|
|
// Factor from 0 to 1 between the two targets
|
|
const FScheduledOpData& HeapData = m_heapData[item.CustomState];
|
|
float Factor = HeapData.Interpolate.Bifactor;
|
|
|
|
if (BaseMesh)
|
|
{
|
|
// No morph
|
|
if (FMath::IsNearlyZero(Factor))
|
|
{
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
// The Morph, partial or full
|
|
else
|
|
{
|
|
Ptr<const Mesh> MorphMesh = LoadMesh(FCacheAddress(TargetAt, item));
|
|
|
|
if (MorphMesh)
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_MERGE:
|
|
{
|
|
OP::MeshMergeArgs args = Program.GetOpArgs<OP::MeshMergeArgs>(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)
|
|
|
|
Ptr<const Mesh> pA = LoadMesh(FCacheAddress(args.base, item));
|
|
Ptr<const Mesh> 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();
|
|
|
|
Ptr<Mesh> 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()))
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_INTERPOLATE:
|
|
{
|
|
OP::MeshInterpolateArgs Args = Program.GetOpArgs<OP::MeshInterpolateArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (Args.base)
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(Args.base, item),
|
|
FScheduledOp(Args.factor, item));
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_INTERPOLATE_1)
|
|
|
|
int32 Count = 1;
|
|
for (int32 TargetIndex = 0; TargetIndex < MUTABLE_OP_MAX_INTERPOLATE_COUNT-1 && Args.targets[TargetIndex]; ++TargetIndex)
|
|
{
|
|
++Count;
|
|
}
|
|
|
|
// Factor from 0 to 1 across all targets
|
|
float Factor = LoadScalar(FCacheAddress(Args.factor, item));
|
|
|
|
float Delta = 1.0f/(Count-1);
|
|
int32 Min = FMath::FloorToInt(Factor/Delta);
|
|
int32 Max = FMath::CeilToInt(Factor/Delta);
|
|
|
|
// Factor from 0 to 1 between the two targets
|
|
float Bifactor = Factor/Delta - Min;
|
|
|
|
FScheduledOpData Data;
|
|
Data.Interpolate.Bifactor = Bifactor;
|
|
Data.Interpolate.Min = FMath::Clamp(Min, 0, Count - 1);
|
|
Data.Interpolate.Max = FMath::Clamp(Max, 0, Count - 1);
|
|
uint32 DataAddress = uint32(m_heapData.Num());
|
|
|
|
// Just the first of the targets
|
|
if (Bifactor < UE_SMALL_NUMBER)
|
|
{
|
|
if (Min == 0)
|
|
{
|
|
// Just the base
|
|
Ptr<const Mesh> BaseMesh = LoadMesh(FCacheAddress(Args.base, item));
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
else
|
|
{
|
|
// Base with one full morph
|
|
m_heapData.Add(Data);
|
|
AddOp(FScheduledOp(item.At, item, 2, DataAddress),
|
|
FScheduledOp(Args.base, item),
|
|
FScheduledOp(Args.targets[Min-1], item));
|
|
}
|
|
}
|
|
// Just the second of the targets
|
|
else if (Bifactor > 1.0f-UE_SMALL_NUMBER)
|
|
{
|
|
m_heapData.Add(Data);
|
|
AddOp(FScheduledOp(item.At, item, 2, DataAddress),
|
|
FScheduledOp(Args.base, item),
|
|
FScheduledOp(Args.targets[Max-1], item));
|
|
}
|
|
// Mix the first target on the base
|
|
else if (Min == 0)
|
|
{
|
|
m_heapData.Add(Data);
|
|
AddOp(FScheduledOp(item.At, item, 2, DataAddress),
|
|
FScheduledOp(Args.base, item),
|
|
FScheduledOp(Args.targets[0], item));
|
|
}
|
|
// Mix two targets on the base
|
|
else
|
|
{
|
|
m_heapData.Add(Data);
|
|
AddOp(FScheduledOp(item.At, item, 2, DataAddress),
|
|
FScheduledOp(Args.base, item),
|
|
FScheduledOp(Args.targets[Min-1], item),
|
|
FScheduledOp(Args.targets[Max-1], item));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_INTERPOLATE_2)
|
|
|
|
int32 Count = 1;
|
|
for (int32 TargetIndex = 0; TargetIndex < MUTABLE_OP_MAX_INTERPOLATE_COUNT-1 && Args.targets[TargetIndex]; ++TargetIndex)
|
|
{
|
|
++Count;
|
|
}
|
|
|
|
const FScheduledOpData& Data = m_heapData[ (size_t)item.CustomState ];
|
|
|
|
// Factor from 0 to 1 between the two targets
|
|
float Bifactor = Data.Interpolate.Bifactor;
|
|
int32 Min = Data.Interpolate.Min;
|
|
int32 Max = Data.Interpolate.Max;
|
|
|
|
Ptr<const Mesh> BaseMesh = LoadMesh(FCacheAddress(Args.base, item));
|
|
|
|
if (BaseMesh)
|
|
{
|
|
// Just the first of the targets
|
|
if (Bifactor < UE_SMALL_NUMBER)
|
|
{
|
|
if (Min == 0)
|
|
{
|
|
// Just the base. It should have been dealt with in the previous stage.
|
|
check(false);
|
|
}
|
|
else
|
|
{
|
|
// Base with one full morph
|
|
Ptr<const Mesh> MorphMesh = LoadMesh(FCacheAddress(Args.targets[Min-1], item));
|
|
|
|
if (MorphMesh)
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(BaseMesh);
|
|
|
|
MeshMorph(Result.get(), MorphMesh.get());
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
|
|
Release(MorphMesh);
|
|
}
|
|
}
|
|
// Just the second of the targets
|
|
else if (Bifactor > 1.0f-UE_SMALL_NUMBER)
|
|
{
|
|
check(Max > 0);
|
|
Ptr<const Mesh> MorphMesh = LoadMesh(FCacheAddress(Args.targets[Max-1], item));
|
|
|
|
if (MorphMesh)
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(BaseMesh);
|
|
|
|
MeshMorph(Result.get(), MorphMesh.get());
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
|
|
Release(MorphMesh);
|
|
}
|
|
// Mix the first target on the base
|
|
else if (Min == 0)
|
|
{
|
|
Ptr<const Mesh> MorphMesh = LoadMesh(FCacheAddress(Args.targets[0], item));
|
|
if (MorphMesh)
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(BaseMesh);
|
|
|
|
MeshMorph(Result.get(), MorphMesh.get(), Bifactor);
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
|
|
Release(MorphMesh);
|
|
}
|
|
// Mix two targets on the base
|
|
else
|
|
{
|
|
Ptr<const Mesh> MinMesh = LoadMesh(FCacheAddress(Args.targets[Min-1], item));
|
|
Ptr<const Mesh> MaxMesh = LoadMesh(FCacheAddress(Args.targets[Max-1], item));
|
|
|
|
if (MinMesh && MaxMesh)
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(BaseMesh);
|
|
|
|
MeshMorph2(Result.get(), MinMesh.get(), MaxMesh.get(), Bifactor);
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
|
|
Release(MinMesh);
|
|
Release(MaxMesh);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, BaseMesh);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_MASKCLIPMESH:
|
|
{
|
|
OP::MeshMaskClipMeshArgs args = Program.GetOpArgs<OP::MeshMaskClipMeshArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(args.source, item),
|
|
FScheduledOp(args.clip, item));
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_MASKCLIPMESH_1)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source, item));
|
|
Ptr<const Mesh> pClip = LoadMesh(FCacheAddress(args.clip, item));
|
|
|
|
// Only if both are valid.
|
|
if (Source.get() && pClip.get())
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_MASKCLIPUVMASK:
|
|
{
|
|
OP::MeshMaskClipUVMaskArgs args = Program.GetOpArgs<OP::MeshMaskClipUVMaskArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
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);
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.Source, item));
|
|
Ptr<const Mesh> UVSource = LoadMesh(FCacheAddress(args.UVSource, item));
|
|
Ptr<const Image> MaskImage = LoadImage(FCacheAddress(args.MaskImage, item));
|
|
Ptr<const Layout> MaskLayout = LoadLayout(FCacheAddress(args.MaskLayout, item));
|
|
|
|
// Only if both are valid.
|
|
if (Source.get() && MaskImage.get())
|
|
{
|
|
Ptr<Mesh> 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())
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_MASKDIFF:
|
|
{
|
|
OP::MeshMaskDiffArgs args = Program.GetOpArgs<OP::MeshMaskDiffArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(args.source, item),
|
|
FScheduledOp(args.fragment, item));
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_MASKDIFF_1)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source, item));
|
|
Ptr<const Mesh> pClip = LoadMesh(FCacheAddress(args.fragment, item));
|
|
|
|
// Only if both are valid.
|
|
if (Source.get() && pClip.get())
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_FORMAT:
|
|
{
|
|
OP::MeshFormatArgs args = Program.GetOpArgs<OP::MeshFormatArgs>(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)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source,item));
|
|
Ptr<const Mesh> 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)
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(Source);
|
|
|
|
if (Flags & OP::MeshFormatArgs::ResetBufferIndices)
|
|
{
|
|
Result->ResetBufferIndices();
|
|
}
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_EXTRACTLAYOUTBLOCK:
|
|
{
|
|
const uint8* data = Program.GetOpArgsPointer(item.At);
|
|
|
|
OP::ADDRESS source;
|
|
FMemory::Memcpy( &source, data, sizeof(OP::ADDRESS) );
|
|
data += sizeof(OP::ADDRESS);
|
|
|
|
uint16 layout;
|
|
FMemory::Memcpy( &layout, 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)
|
|
|
|
Ptr<const Mesh> 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)
|
|
{
|
|
Ptr<Mesh> Result = CreateMesh();
|
|
bool bOutSuccess;
|
|
|
|
if (blockCount > 0)
|
|
{
|
|
MeshExtractLayoutBlock(Result.get(), Source.get(), layout, blockCount, blocks, bOutSuccess);
|
|
}
|
|
else
|
|
{
|
|
MeshExtractLayoutBlock(Result.get(), Source.get(), layout, bOutSuccess);
|
|
}
|
|
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(Result);
|
|
StoreMesh(item, Source);
|
|
}
|
|
else
|
|
{
|
|
Release(Source);
|
|
StoreMesh(item, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_TRANSFORM:
|
|
{
|
|
OP::MeshTransformArgs args = Program.GetOpArgs<OP::MeshTransformArgs>(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)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source,item));
|
|
|
|
const FMatrix44f& mat = Program.m_constantMatrices[args.matrix];
|
|
|
|
Ptr<Mesh> 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 OP_TYPE::ME_CLIPMORPHPLANE:
|
|
{
|
|
OP::MeshClipMorphPlaneArgs args = Program.GetOpArgs<OP::MeshClipMorphPlaneArgs>(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)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source, item));
|
|
|
|
check(args.morphShape < (uint32)pModel->GetPrivate()->m_program.m_constantShapes.Num());
|
|
|
|
// Should be an ellipse
|
|
const FShape& morphShape = Program.m_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)pModel->GetPrivate()->m_program.m_constantShapes.Num());
|
|
|
|
// Should be None or an axis aligned box
|
|
const FShape& selectionShape = Program.m_constantShapes[args.vertexSelectionShapeOrBone];
|
|
|
|
Ptr<Mesh> 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;
|
|
|
|
Ptr<Mesh> 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;
|
|
|
|
Ptr<Mesh> 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 OP_TYPE::ME_CLIPWITHMESH:
|
|
{
|
|
OP::MeshClipWithMeshArgs args = Program.GetOpArgs<OP::MeshClipWithMeshArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (args.source)
|
|
{
|
|
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)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source, item));
|
|
Ptr<const Mesh> pClip = LoadMesh(FCacheAddress(args.clipMesh, item));
|
|
|
|
// Only if both are valid.
|
|
if (Source && pClip)
|
|
{
|
|
Ptr<Mesh> Result = CreateMesh(Source->GetDataSize());
|
|
|
|
bool bOutSuccess = false;
|
|
MeshClipWithMesh(Result.get(), Source.get(), pClip.get(), bOutSuccess);
|
|
|
|
Release(pClip);
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(Result);
|
|
StoreMesh(item, Source);
|
|
}
|
|
else
|
|
{
|
|
Release(Source);
|
|
StoreMesh(item, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Release(pClip);
|
|
StoreMesh(item, Source);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case OP_TYPE::ME_CLIPDEFORM:
|
|
{
|
|
OP::MeshClipDeformArgs args = Program.GetOpArgs<OP::MeshClipDeformArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (args.mesh)
|
|
{
|
|
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)
|
|
|
|
Ptr<const Mesh> BaseMesh = LoadMesh(FCacheAddress(args.mesh, item));
|
|
Ptr<const Mesh> ClipShape = LoadMesh(FCacheAddress(args.clipShape, item));
|
|
|
|
if (BaseMesh && ClipShape)
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_APPLYPOSE:
|
|
{
|
|
OP::MeshApplyPoseArgs args = Program.GetOpArgs<OP::MeshApplyPoseArgs>(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)
|
|
|
|
Ptr<const Mesh> pBase = LoadMesh(FCacheAddress(args.base, item));
|
|
Ptr<const Mesh> pPose = LoadMesh(FCacheAddress(args.pose, item));
|
|
|
|
// Only if both are valid.
|
|
if (pBase && pPose)
|
|
{
|
|
Ptr<Mesh> Result = CreateMesh(pBase->GetSkeleton() ? pBase->GetDataSize() : 0);
|
|
|
|
bool bOutSuccess = false;
|
|
MeshApplyPose(Result.get(), pBase.get(), pPose.get(), bOutSuccess);
|
|
|
|
Release(pPose);
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(Result);
|
|
StoreMesh(item, pBase);
|
|
}
|
|
else
|
|
{
|
|
Release(pBase);
|
|
StoreMesh(item, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Release(pPose);
|
|
StoreMesh(item, pBase);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case OP_TYPE::ME_GEOMETRYOPERATION:
|
|
{
|
|
OP::MeshGeometryOperationArgs args = Program.GetOpArgs<OP::MeshGeometryOperationArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (args.meshA)
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(args.meshA, item),
|
|
FScheduledOp(args.meshB, item),
|
|
FScheduledOp(args.scalarA, item),
|
|
FScheduledOp(args.scalarB, item));
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_GEOMETRYOPERATION_1)
|
|
|
|
Ptr<const Mesh> MeshA = LoadMesh(FCacheAddress(args.meshA, item));
|
|
Ptr<const Mesh> MeshB = LoadMesh(FCacheAddress(args.meshB, item));
|
|
float ScalarA = LoadScalar(FCacheAddress(args.scalarA, item));
|
|
float ScalarB = LoadScalar(FCacheAddress(args.scalarB, item));
|
|
|
|
Ptr<Mesh> Result = CreateMesh(MeshA ? MeshA->GetDataSize() : 0);
|
|
|
|
bool bOutSuccess = false;
|
|
MeshGeometryOperation(Result.get(), MeshA.get(), MeshB.get(), ScalarA, ScalarB, bOutSuccess);
|
|
|
|
Release(MeshA);
|
|
Release(MeshB);
|
|
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(Result);
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, Result);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case OP_TYPE::ME_BINDSHAPE:
|
|
{
|
|
OP::MeshBindShapeArgs Args = Program.GetOpArgs<OP::MeshBindShapeArgs>(item.At);
|
|
const uint8* Data = Program.GetOpArgsPointer(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_BINDSHAPE_1)
|
|
Ptr<const Mesh> BaseMesh = LoadMesh(FCacheAddress(Args.mesh, item));
|
|
Ptr<const Mesh> Shape = LoadMesh(FCacheAddress(Args.shape, item));
|
|
|
|
EShapeBindingMethod BindingMethod = static_cast<EShapeBindingMethod>(Args.bindingMethod);
|
|
|
|
if (BindingMethod == EShapeBindingMethod::ReshapeClosestProject)
|
|
{
|
|
// 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<FBoneName> 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<FBoneName> PhysicsToDeform;
|
|
PhysicsToDeform.SetNumUninitialized(NumPhysicsBodies);
|
|
FMemory::Memcpy(PhysicsToDeform.GetData(), Data, NumPhysicsBodies * sizeof(FBoneName));
|
|
Data += NumPhysicsBodies * sizeof(FBoneName);
|
|
|
|
const EMeshBindShapeFlags BindFlags = static_cast<EMeshBindShapeFlags>(Args.flags);
|
|
|
|
FMeshBindColorChannelUsages ColorChannelUsages;
|
|
FMemory::Memcpy(&ColorChannelUsages, &Args.ColorUsage, sizeof(ColorChannelUsages));
|
|
static_assert(sizeof(ColorChannelUsages) == sizeof(Args.ColorUsage));
|
|
|
|
Ptr<Mesh> 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))
|
|
{
|
|
Ptr<Mesh> BindMeshNoVertsResult = CloneOrTakeOver(BaseMesh);
|
|
BindMeshNoVertsResult->AdditionalBuffers = MoveTemp(BindMeshResult->AdditionalBuffers);
|
|
|
|
Release(BaseMesh);
|
|
Release(BindMeshResult);
|
|
StoreMesh(item, BindMeshNoVertsResult);
|
|
}
|
|
else
|
|
{
|
|
Release(BaseMesh);
|
|
StoreMesh(item, BindMeshResult);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ptr<Mesh> 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 OP_TYPE::ME_APPLYSHAPE:
|
|
{
|
|
OP::MeshApplyShapeArgs args = Program.GetOpArgs<OP::MeshApplyShapeArgs>(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)
|
|
|
|
Ptr<const Mesh> BaseMesh = LoadMesh(FCacheAddress(args.mesh, item));
|
|
Ptr<const Mesh> Shape = LoadMesh(FCacheAddress(args.shape, item));
|
|
|
|
const EMeshBindShapeFlags ReshapeFlags = static_cast<EMeshBindShapeFlags>(args.flags);
|
|
const bool bReshapeVertices = EnumHasAnyFlags(ReshapeFlags, EMeshBindShapeFlags::ReshapeVertices);
|
|
|
|
Ptr<Mesh> 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);
|
|
|
|
Ptr<Mesh> NoVerticesReshpedMesh = CloneOrTakeOver(BaseMesh);
|
|
|
|
NoVerticesReshpedMesh->SetSkeleton(ReshapedMeshResult->GetSkeleton().get());
|
|
NoVerticesReshpedMesh->SetPhysicsBody(ReshapedMeshResult->GetPhysicsBody().get());
|
|
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 OP_TYPE::ME_MORPHRESHAPE:
|
|
{
|
|
OP::MeshMorphReshapeArgs Args = Program.GetOpArgs<OP::MeshMorphReshapeArgs>(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)
|
|
|
|
Ptr<const Mesh> MorphedMesh = LoadMesh(FCacheAddress(Args.Morph, item));
|
|
Ptr<const Mesh> ReshapeMesh = LoadMesh(FCacheAddress(Args.Reshape, item));
|
|
|
|
if (ReshapeMesh && MorphedMesh)
|
|
{
|
|
// Copy without Skeleton, Physics or Poses
|
|
EMeshCopyFlags CopyFlags = ~(
|
|
EMeshCopyFlags::WithSkeleton |
|
|
EMeshCopyFlags::WithPhysicsBody |
|
|
EMeshCopyFlags::WithPoses);
|
|
|
|
Ptr<Mesh> Result = CreateMesh(MorphedMesh->GetDataSize());
|
|
Result->CopyFrom(*MorphedMesh, CopyFlags);
|
|
|
|
Result->SetSkeleton(ReshapeMesh->GetSkeleton().get());
|
|
Result->SetPhysicsBody(ReshapeMesh->GetPhysicsBody().get());
|
|
Result->BonePoses = ReshapeMesh->BonePoses;
|
|
|
|
Release(MorphedMesh);
|
|
Release(ReshapeMesh);
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, MorphedMesh);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_SETSKELETON:
|
|
{
|
|
OP::MeshSetSkeletonArgs args = Program.GetOpArgs<OP::MeshSetSkeletonArgs>(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)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source, item));
|
|
Ptr<const Mesh> pSkeleton = LoadMesh(FCacheAddress(args.skeleton, item));
|
|
|
|
// Only if both are valid.
|
|
if (Source && pSkeleton)
|
|
{
|
|
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."));
|
|
|
|
Ptr<Mesh> Result = CreateMesh(Source->GetDataSize());
|
|
|
|
bool bOutSuccess = false;
|
|
MeshRemapSkeleton(Result.get(), Source.get(), pSkeleton->GetSkeleton().get(), bOutSuccess);
|
|
|
|
Release(pSkeleton);
|
|
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(Result);
|
|
StoreMesh(item, Source);
|
|
}
|
|
else
|
|
{
|
|
//Result->GetPrivate()->CheckIntegrity();
|
|
Release(Source);
|
|
StoreMesh(item, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(Source);
|
|
|
|
Result->SetSkeleton(pSkeleton->GetSkeleton().get());
|
|
|
|
//Result->GetPrivate()->CheckIntegrity();
|
|
Release(pSkeleton);
|
|
StoreMesh(item, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Release(pSkeleton);
|
|
StoreMesh(item, Source);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::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<FScheduledOp> conditions;
|
|
TArray<OP::ADDRESS> masks;
|
|
|
|
uint16 removes;
|
|
FMemory::Memcpy(&removes,data,sizeof(uint16));
|
|
data += sizeof(uint16);
|
|
|
|
for( uint16 r=0; r<removes; ++r)
|
|
{
|
|
OP::ADDRESS condition;
|
|
FMemory::Memcpy(&condition,data,sizeof(OP::ADDRESS));
|
|
data += sizeof(OP::ADDRESS);
|
|
|
|
conditions.Emplace(condition, item);
|
|
|
|
OP::ADDRESS mask;
|
|
FMemory::Memcpy(&mask,data,sizeof(OP::ADDRESS));
|
|
data += sizeof(OP::ADDRESS);
|
|
|
|
masks.Add(mask);
|
|
}
|
|
|
|
|
|
// Schedule next stages
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (source)
|
|
{
|
|
// Request the conditions
|
|
AddOp(FScheduledOp(item.At, item, 1), conditions);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_REMOVEMASK_1)
|
|
|
|
// Request the source and the necessary masks
|
|
// \todo: store condition values in heap?
|
|
TArray<FScheduledOp> deps;
|
|
deps.Emplace( source, item );
|
|
for( size_t r=0; source && 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)
|
|
{
|
|
deps.Emplace(masks[r], item);
|
|
}
|
|
}
|
|
|
|
if (source)
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 2), deps);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_REMOVEMASK_2)
|
|
|
|
// \todo: single remove operation with all masks?
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(source, item));
|
|
|
|
if (Source)
|
|
{
|
|
Ptr<Mesh> Result = CreateMesh(Source->GetDataSize());
|
|
Result->CopyFrom(*Source);
|
|
|
|
Release(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)
|
|
{
|
|
Ptr<const Mesh> Mask = LoadMesh(FCacheAddress(masks[r], item));
|
|
if (Mask)
|
|
{
|
|
//MeshRemoveMask will make a copy of Result, try to make room for it.
|
|
Ptr<Mesh> IterResult = CreateMesh(Result->GetDataSize());
|
|
|
|
bool bOutSuccess = false;
|
|
bool bRemoveIfAllVerticesCulled = FaceCullStrategy == EFaceCullStrategy::AllVerticesCulled;
|
|
MeshRemoveMask(IterResult.get(), Result.get(), Mask.get(), bRemoveIfAllVerticesCulled, bOutSuccess);
|
|
|
|
Release(Mask);
|
|
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(IterResult);
|
|
}
|
|
else
|
|
{
|
|
Swap(Result, IterResult);
|
|
Release(IterResult);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_ADDTAGS:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_ADDTAGS)
|
|
|
|
// 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);
|
|
|
|
// Schedule next stages
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (Source)
|
|
{
|
|
// Request the source
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(Source, item));
|
|
}
|
|
else
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ME_ADDTAGS_2)
|
|
|
|
Ptr<const Mesh> SourceMesh = LoadMesh(FCacheAddress(Source, item));
|
|
|
|
if (!SourceMesh)
|
|
{
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
else
|
|
{
|
|
Ptr<Mesh> Result = CloneOrTakeOver(SourceMesh);
|
|
|
|
// Decode the tags
|
|
uint16 TagCount;
|
|
FMemory::Memcpy(&TagCount, Data, sizeof(uint16));
|
|
Data += sizeof(uint16);
|
|
|
|
int32 FirstMeshTagIndex = Result->Tags.Num();
|
|
Result->Tags.SetNum(FirstMeshTagIndex+TagCount);
|
|
for (uint16 TagIndex = 0; TagIndex < TagCount; ++TagIndex)
|
|
{
|
|
OP::ADDRESS TagConstant;
|
|
FMemory::Memcpy(&TagConstant, Data, sizeof(OP::ADDRESS));
|
|
Data += sizeof(OP::ADDRESS);
|
|
|
|
check(TagConstant < (uint32)pModel->GetPrivate()->m_program.m_constantStrings.Num());
|
|
const FString& Name = Program.m_constantStrings[TagConstant];
|
|
Result->Tags[FirstMeshTagIndex+TagIndex] = Name;
|
|
}
|
|
|
|
StoreMesh(item, Result);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_PROJECT:
|
|
{
|
|
OP::MeshProjectArgs args = Program.GetOpArgs<OP::MeshProjectArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (args.mesh)
|
|
{
|
|
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)
|
|
|
|
Ptr<const Mesh> pMesh = LoadMesh(FCacheAddress(args.mesh,item));
|
|
const FProjector Projector = LoadProjector(FCacheAddress(args.projector, item));
|
|
|
|
// Only if both are valid.
|
|
if (pMesh && pMesh->GetVertexBuffers().GetBufferCount() > 0)
|
|
{
|
|
Ptr<Mesh> Result = CreateMesh();
|
|
|
|
bool bOutSuccess = false;
|
|
MeshProject(Result.get(), pMesh.get(), Projector, bOutSuccess);
|
|
|
|
if (!bOutSuccess)
|
|
{
|
|
Release(Result);
|
|
StoreMesh(item, pMesh);
|
|
}
|
|
else
|
|
{
|
|
// Result->GetPrivate()->CheckIntegrity();
|
|
Release(pMesh);
|
|
StoreMesh(item, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Release(pMesh);
|
|
StoreMesh(item, nullptr);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ME_OPTIMIZESKINNING:
|
|
{
|
|
OP::MeshOptimizeSkinningArgs args = Program.GetOpArgs<OP::MeshOptimizeSkinningArgs>(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)
|
|
|
|
Ptr<const Mesh> Source = LoadMesh(FCacheAddress(args.source, item));
|
|
|
|
if (Source && Source->IsReference())
|
|
{
|
|
StoreMesh(item, Source);
|
|
}
|
|
|
|
Ptr<Mesh> 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 OP_TYPE::ME_TRANSFORMWITHMESH:
|
|
{
|
|
OP::MeshTransformWithinMeshArgs args = Program.GetOpArgs<OP::MeshTransformWithinMeshArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
if (args.sourceMesh)
|
|
{
|
|
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)
|
|
|
|
Ptr<const Mesh> SourceMesh = LoadMesh(FCacheAddress(args.sourceMesh,item));
|
|
Ptr<const Mesh> BoundingMesh = LoadMesh(FCacheAddress(args.boundingMesh, item));
|
|
const FMatrix44f& Transform = LoadMatrix(FCacheAddress(args.matrix, item));
|
|
|
|
if (SourceMesh)
|
|
{
|
|
Ptr<Mesh> 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!=OP_TYPE::NONE)
|
|
{
|
|
// Operation not implemented
|
|
check( false );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Image(const FScheduledOp& item, const Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Image);
|
|
|
|
FImageOperator ImOp = MakeImageOperator(this);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::IM_LAYERCOLOUR:
|
|
{
|
|
OP::ImageLayerColourArgs args = Program.GetOpArgs<OP::ImageLayerColourArgs>(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 OP_TYPE::IM_LAYER:
|
|
{
|
|
OP::ImageLayerArgs args = Program.GetOpArgs<OP::ImageLayerArgs>(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 OP_TYPE::IM_MULTILAYER:
|
|
{
|
|
OP::ImageMultiLayerArgs args = Program.GetOpArgs<OP::ImageMultiLayerArgs>(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.
|
|
DATATYPE RangeSizeType = GetOpDataType( pModel->GetPrivate()->m_program.GetOpType(args.rangeSize) );
|
|
if (RangeSizeType == DT_INT)
|
|
{
|
|
Iterations = LoadInt(RangeAddress);
|
|
}
|
|
else if (RangeSizeType == DT_SCALAR)
|
|
{
|
|
Iterations = int32( LoadScalar(RangeAddress) );
|
|
}
|
|
}
|
|
|
|
Ptr<const Image> Base = LoadImage(FCacheAddress(args.base, item));
|
|
|
|
if (Iterations <= 0)
|
|
{
|
|
// There are no layers: return the base
|
|
StoreImage(item, Base);
|
|
}
|
|
else
|
|
{
|
|
// Store the base
|
|
Ptr<Image> New = CloneOrTakeOver(Base);
|
|
EImageFormat InitialBaseFormat = New->GetFormat();
|
|
|
|
// Reset relevancy map.
|
|
New->m_flags &= ~Image::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)
|
|
{
|
|
Ptr<Image> Formatted = CreateImage( New->GetSizeX(), New->GetSizeY(), New->GetLODCount(), BaseFormat, EInitializationType::NotInitialized );
|
|
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->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 = m_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 = m_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
|
|
|
|
Ptr<Image> Base = static_cast<Image*>(Data.Resource.get());
|
|
|
|
FScheduledOp itemCopy = item;
|
|
ExecutionIndex index = GetMemory().GetRangeIndex( item.ExecutionIndex );
|
|
|
|
{
|
|
index.SetFromModelRangeIndex( args.rangeId, CurrentIteration);
|
|
itemCopy.ExecutionIndex = GetMemory().GetRangeIndexIndex(index);
|
|
itemCopy.CustomState = 0;
|
|
|
|
Ptr<const Image> 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);
|
|
|
|
Ptr<Image> Formatted = CreateImage(Blended->GetSizeX(), Blended->GetSizeY(), Blended->GetLODCount(), Base->GetFormat(), EInitializationType::NotInitialized);
|
|
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->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);
|
|
|
|
Ptr<Image> 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<VectorBlendChannelMasked, VectorLightenChannel, false>(Base.get(), Blended.get(), bBlendOnlyOneMip, args.BlendAlphaSourceChannel);
|
|
}
|
|
else
|
|
{
|
|
BufferLayerComposite<BlendChannelMasked, LightenChannel, false>(Base.get(), Blended.get(), bBlendOnlyOneMip, args.BlendAlphaSourceChannel);
|
|
}
|
|
}
|
|
|
|
if (!bDone && args.mask)
|
|
{
|
|
Ptr<const Image> Mask = LoadImage( FCacheAddress(args.mask,itemCopy) );
|
|
|
|
// TODO: This shouldn't happen, but be defensive.
|
|
if (Mask && Mask->GetSize() != ResultSize)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ImageResize_MaskFixForMultilayer);
|
|
|
|
Ptr<Image> 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<SoftLightChannelMasked, SoftLightChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_HARDLIGHT: BufferLayer<HardLightChannelMasked, HardLightChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_BURN: BufferLayer<BurnChannelMasked, BurnChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_DODGE: BufferLayer<DodgeChannelMasked, DodgeChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_SCREEN: BufferLayer<ScreenChannelMasked, ScreenChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_OVERLAY: BufferLayer<OverlayChannelMasked, OverlayChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_LIGHTEN: BufferLayer<LightenChannelMasked, LightenChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_MULTIPLY: BufferLayer<MultiplyChannelMasked, MultiplyChannel, false>(Base.get(), Base.get(), Mask.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_BLEND: BufferLayer<BlendChannelMasked, BlendChannel, false>(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<SoftLightChannelMasked, SoftLightChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_HARDLIGHT: BufferLayerEmbeddedMask<HardLightChannelMasked, HardLightChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_BURN: BufferLayerEmbeddedMask<BurnChannelMasked, BurnChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_DODGE: BufferLayerEmbeddedMask<DodgeChannelMasked, DodgeChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_SCREEN: BufferLayerEmbeddedMask<ScreenChannelMasked, ScreenChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_OVERLAY: BufferLayerEmbeddedMask<OverlayChannelMasked, OverlayChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_LIGHTEN: BufferLayerEmbeddedMask<LightenChannelMasked, LightenChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_MULTIPLY: BufferLayerEmbeddedMask<MultiplyChannelMasked, MultiplyChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip); break;
|
|
case EBlendType::BT_BLEND: BufferLayerEmbeddedMask<BlendChannelMasked, BlendChannel, false>(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<SoftLightChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_HARDLIGHT: BufferLayer<HardLightChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_BURN: BufferLayer<BurnChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_DODGE: BufferLayer<DodgeChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_SCREEN: BufferLayer<ScreenChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_OVERLAY: BufferLayer<OverlayChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_LIGHTEN: BufferLayer<LightenChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_MULTIPLY: BufferLayer<MultiplyChannel, false>(Base.get(), Base.get(), Blended.get(), bApplyColorBlendToAlpha, bBlendOnlyOneMip, bUseBlendSourceFromBlendAlpha); break;
|
|
case EBlendType::BT_BLEND: BufferLayer<BlendChannel, false>(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<SoftLightChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_HARDLIGHT: BufferLayerInPlace<HardLightChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_BURN: BufferLayerInPlace<BurnChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_DODGE: BufferLayerInPlace<DodgeChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_SCREEN: BufferLayerInPlace<ScreenChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_OVERLAY: BufferLayerInPlace<OverlayChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_LIGHTEN: BufferLayerInPlace<LightenChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_MULTIPLY: BufferLayerInPlace<MultiplyChannel, false, 1>(Base.get(), Blended.get(), bBlendOnlyOneMip, 3, args.BlendAlphaSourceChannel); break;
|
|
case EBlendType::BT_BLEND: BufferLayerInPlace<BlendChannel, false, 1>(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(m_pSettings->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 OP_TYPE::IM_NORMALCOMPOSITE:
|
|
{
|
|
OP::ImageNormalCompositeArgs args = Program.GetOpArgs<OP::ImageNormalCompositeArgs>(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)
|
|
|
|
Ptr<const Image> Base = LoadImage(FCacheAddress(args.base, item));
|
|
Ptr<const Image> 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();
|
|
|
|
Ptr<Image> NormalFix = CloneOrTakeOver(Normal);
|
|
|
|
FMipmapGenerationSettings MipSettings{};
|
|
ImOp.ImageMipmap(m_pSettings->ImageCompressionQuality, NormalFix.get(), NormalFix.get(), StartLevel, LevelCount, MipSettings);
|
|
|
|
Normal = NormalFix;
|
|
}
|
|
|
|
|
|
Ptr<Image> 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 OP_TYPE::IM_PIXELFORMAT:
|
|
{
|
|
OP::ImagePixelFormatArgs args = Program.GetOpArgs<OP::ImagePixelFormatArgs>(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 OP_TYPE::IM_MIPMAP:
|
|
{
|
|
OP::ImageMipmapArgs args = Program.GetOpArgs<OP::ImageMipmapArgs>(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 OP_TYPE::IM_RESIZE:
|
|
{
|
|
OP::ImageResizeArgs args = Program.GetOpArgs<OP::ImageResizeArgs>(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 OP_TYPE::IM_RESIZELIKE:
|
|
{
|
|
OP::ImageResizeLikeArgs args = Program.GetOpArgs<OP::ImageResizeLikeArgs>(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)
|
|
|
|
Ptr<const Image> Base = LoadImage( FCacheAddress(args.source,item) );
|
|
Ptr<const Image> SizeBase = LoadImage( FCacheAddress(args.sizeSource,item) );
|
|
FImageSize DestSize = SizeBase->GetSize();
|
|
Release(SizeBase);
|
|
|
|
if (Base->GetSize() != DestSize)
|
|
{
|
|
int32 BaseLODCount = Base->GetLODCount();
|
|
Ptr<Image> Result = CreateImage(DestSize[0], DestSize[1], BaseLODCount, Base->GetFormat(), EInitializationType::NotInitialized);
|
|
ImOp.ImageResizeLinear(Result.get(), m_pSettings->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 = Image::GetMipmapCount(Result->GetSizeX(), Result->GetSizeY());
|
|
Result->DataStorage.SetNumLODs(LevelCount);
|
|
|
|
FMipmapGenerationSettings MipSettings{};
|
|
ImOp.ImageMipmap(m_pSettings->ImageCompressionQuality, Result.get(), Result.get(), 0, LevelCount, MipSettings);
|
|
}
|
|
|
|
StoreImage(item, Result);
|
|
}
|
|
else
|
|
{
|
|
StoreImage(item, Base);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_RESIZEREL:
|
|
{
|
|
OP::ImageResizeRelArgs args = Program.GetOpArgs<OP::ImageResizeRelArgs>(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 OP_TYPE::IM_BLANKLAYOUT:
|
|
{
|
|
OP::ImageBlankLayoutArgs Args = Program.GetOpArgs<OP::ImageBlankLayoutArgs>(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)
|
|
|
|
Ptr<const Layout> 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 = Image::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);
|
|
//int MinBlockSize = FMath::Max(FormatData.PixelsPerBlockX, FormatData.PixelsPerBlockY);
|
|
//ReducedBlockSizeInPixels.X = FMath::Max<int32>(ReducedBlockSizeInPixels.X, FormatData.PixelsPerBlockX);
|
|
//ReducedBlockSizeInPixels.Y = FMath::Max<int32>(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 = Image::GetMipmapCount(ImageSizeInPixels.X, ImageSizeInPixels.Y);
|
|
}
|
|
else
|
|
{
|
|
MipsToGenerate = FMath::Max(Args.mipmapCount - MipsToSkip, 1);
|
|
}
|
|
}
|
|
|
|
// It needs to be initialized in case it has gaps.
|
|
Ptr<Image> New = CreateImage(ImageSizeInPixels.X, ImageSizeInPixels.Y, MipsToGenerate, EImageFormat(Args.format), EInitializationType::Black );
|
|
StoreImage(item, New);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_COMPOSE:
|
|
{
|
|
OP::ImageComposeArgs Args = Program.GetOpArgs<OP::ImageComposeArgs>(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:
|
|
{
|
|
Ptr<const Layout> ComposeLayout =
|
|
LoadLayout(FCacheAddress(Args.layout, FScheduledOp::FromOpAndOptions(Args.layout, item, 0)));
|
|
|
|
FScheduledOpData Data;
|
|
Data.Resource = const_cast<Layout*>(ComposeLayout.get());
|
|
int32 DataPos = m_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:
|
|
{
|
|
Ptr<const Layout> ComposeLayout =
|
|
LoadLayout(FCacheAddress(Args.layout, FScheduledOp::FromOpAndOptions(Args.layout, item, 0)));
|
|
|
|
FScheduledOpData Data;
|
|
Data.Resource = const_cast<Layout*>(ComposeLayout.get());
|
|
int32 DataPos = m_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 OP_TYPE::IM_INTERPOLATE:
|
|
{
|
|
OP::ImageInterpolateArgs args = Program.GetOpArgs<OP::ImageInterpolateArgs>(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
|
|
int count = 0;
|
|
for ( int i=0
|
|
; i<MUTABLE_OP_MAX_INTERPOLATE_COUNT && args.targets[i]
|
|
; ++i )
|
|
{
|
|
count++;
|
|
}
|
|
|
|
float factor = LoadScalar( FCacheAddress(args.factor,item) );
|
|
|
|
float delta = 1.0f/(count-1);
|
|
int min = (int)floorf( factor/delta );
|
|
int max = (int)ceilf( factor/delta );
|
|
|
|
float bifactor = factor/delta - min;
|
|
|
|
FScheduledOpData data;
|
|
data.Interpolate.Bifactor = bifactor;
|
|
data.Interpolate.Min = FMath::Clamp(min, 0, count - 1);
|
|
data.Interpolate.Max = FMath::Clamp(max, 0, count - 1);
|
|
uint32 dataPos = uint32(m_heapData.Add(data));
|
|
|
|
if ( bifactor < UE_SMALL_NUMBER )
|
|
{
|
|
AddOp( FScheduledOp( item.At, item, 2, dataPos),
|
|
FScheduledOp( args.targets[min], item) );
|
|
}
|
|
else if ( bifactor > 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
|
|
int count = 0;
|
|
for ( int i=0
|
|
; i<MUTABLE_OP_MAX_INTERPOLATE_COUNT && args.targets[i]
|
|
; ++i )
|
|
{
|
|
count++;
|
|
}
|
|
|
|
// Factor from 0 to 1 between the two targets
|
|
const FScheduledOpData& Data = m_heapData[(size_t)item.CustomState];
|
|
float Bifactor = Data.Interpolate.Bifactor;
|
|
int32 Min = Data.Interpolate.Min;
|
|
int32 Max = Data.Interpolate.Max;
|
|
|
|
if (Bifactor < UE_SMALL_NUMBER)
|
|
{
|
|
Ptr<const Image> Source = LoadImage(FCacheAddress(args.targets[Min], item));
|
|
StoreImage(item, Source);
|
|
}
|
|
else if (Bifactor > 1.0f - UE_SMALL_NUMBER)
|
|
{
|
|
Ptr<const Image> Source = LoadImage(FCacheAddress(args.targets[Max], item));
|
|
StoreImage(item, Source);
|
|
}
|
|
else
|
|
{
|
|
Ptr<const Image> pMin = LoadImage(FCacheAddress(args.targets[Min], item));
|
|
Ptr<const Image> pMax = LoadImage(FCacheAddress(args.targets[Max], item));
|
|
|
|
if (pMin && pMax)
|
|
{
|
|
Ptr<Image> pNew = CloneOrTakeOver(pMin);
|
|
|
|
// Be defensive: ensure image sizes match.
|
|
if (pNew->GetSize() != pMax->GetSize())
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ImageResize_ForInterpolate);
|
|
Ptr<Image> 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);
|
|
|
|
Ptr<Image> Formatted = CreateImage(pMax->GetSizeX(), pMax->GetSizeY(), pMax->GetLODCount(), pNew->GetFormat(), EInitializationType::NotInitialized);
|
|
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->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 Settings{};
|
|
ImOp.ImageMipmap(m_pSettings->ImageCompressionQuality, pNew.get(), pNew.get(), StartLevel, LevelCount, Settings);
|
|
|
|
}
|
|
|
|
if (pMax->GetLODCount() != LevelCount)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(Mipmap_ForInterpolate);
|
|
|
|
int32 StartLevel = pMax->GetLODCount() - 1;
|
|
|
|
Ptr<Image> MaxFix = CloneOrTakeOver(pMax);
|
|
MaxFix->DataStorage.SetNumLODs(LevelCount);
|
|
|
|
FMipmapGenerationSettings Settings{};
|
|
ImOp.ImageMipmap(m_pSettings->ImageCompressionQuality, MaxFix.get(), MaxFix.get(), StartLevel, LevelCount, Settings);
|
|
|
|
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 OP_TYPE::IM_SATURATE:
|
|
{
|
|
OP::ImageSaturateArgs args = Program.GetOpArgs<OP::ImageSaturateArgs>(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 OP_TYPE::IM_LUMINANCE:
|
|
{
|
|
OP::ImageLuminanceArgs args = Program.GetOpArgs<OP::ImageLuminanceArgs>(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)
|
|
|
|
Ptr<const Image> Base = LoadImage( FCacheAddress(args.base,item) );
|
|
|
|
Ptr<Image> Result = CreateImage(Base->GetSizeX(), Base->GetSizeY(), Base->GetLODCount(), EImageFormat::IF_L_UBYTE, EInitializationType::NotInitialized);
|
|
ImageLuminance( Result.get(),Base.get() );
|
|
|
|
Release(Base);
|
|
StoreImage( item, Result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_SWIZZLE:
|
|
{
|
|
OP::ImageSwizzleArgs args = Program.GetOpArgs<OP::ImageSwizzleArgs>(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 OP_TYPE::IM_COLOURMAP:
|
|
{
|
|
OP::ImageColourMapArgs args = Program.GetOpArgs<OP::ImageColourMapArgs>(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)
|
|
|
|
Ptr<const Image> Source = LoadImage( FCacheAddress(args.base,item) );
|
|
Ptr<const Image> Mask = LoadImage( FCacheAddress(args.mask,item) );
|
|
Ptr<const Image> 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);
|
|
Ptr<Image> Resized = CreateImage(Source->GetSizeX(), Source->GetSizeY(), 1, Mask->GetFormat(), EInitializationType::NotInitialized);
|
|
ImOp.ImageResizeLinear(Resized.get(), 0, Mask.get());
|
|
Release(Mask);
|
|
Mask = Resized;
|
|
}
|
|
|
|
Ptr<Image> 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(m_pSettings->ImageCompressionQuality, Result.get(), DummyMipSettings);
|
|
}
|
|
|
|
Release(Source);
|
|
Release(Mask);
|
|
Release(Map);
|
|
StoreImage( item, Result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_GRADIENT:
|
|
{
|
|
OP::ImageGradientArgs args = Program.GetOpArgs<OP::ImageGradientArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp::FromOpAndOptions( args.colour0, item, 0 ),
|
|
FScheduledOp::FromOpAndOptions( args.colour1, item, 0 ) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(IM_GRADIENT_1)
|
|
|
|
FVector4f colour0 = LoadColor(FScheduledOp::FromOpAndOptions(args.colour0, item, 0));
|
|
FVector4f colour1 = LoadColor(FScheduledOp::FromOpAndOptions(args.colour1, item, 0));
|
|
|
|
ImagePtr pResult = CreateImage(args.size[0], args.size[1], 1, EImageFormat::IF_RGB_UBYTE, EInitializationType::NotInitialized);
|
|
ImageGradient( pResult.get(), colour0, colour1 );
|
|
|
|
StoreImage( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_BINARISE:
|
|
{
|
|
OP::ImageBinariseArgs args = Program.GetOpArgs<OP::ImageBinariseArgs>(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)
|
|
|
|
Ptr<const Image> pA = LoadImage( FCacheAddress(args.base,item) );
|
|
|
|
float c = LoadScalar(FScheduledOp::FromOpAndOptions(args.threshold, item, 0));
|
|
|
|
Ptr<Image> Result = CreateImage(pA->GetSizeX(), pA->GetSizeY(), pA->GetLODCount(), EImageFormat::IF_L_UBYTE, EInitializationType::NotInitialized);
|
|
ImageBinarise( Result.get(), pA.get(), c );
|
|
|
|
Release(pA);
|
|
StoreImage( item, Result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_INVERT:
|
|
{
|
|
OP::ImageInvertArgs args = Program.GetOpArgs<OP::ImageInvertArgs>(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 OP_TYPE::IM_PLAINCOLOUR:
|
|
{
|
|
OP::ImagePlainColourArgs args = Program.GetOpArgs<OP::ImagePlainColourArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1), FScheduledOp::FromOpAndOptions( args.colour, item, 0 ) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(IM_PLAINCOLOUR_1)
|
|
|
|
FVector4f c = LoadColor(FScheduledOp::FromOpAndOptions(args.colour, 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 (int l=0; l<item.ExecutionOptions; ++l)
|
|
{
|
|
SizeX = FMath::Max(uint16(1), FMath::DivideAndRoundUp(SizeX, uint16(2)));
|
|
SizeY = FMath::Max(uint16(1), FMath::DivideAndRoundUp(SizeY, uint16(2)));
|
|
--LODs;
|
|
}
|
|
|
|
ImagePtr 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 OP_TYPE::IM_REFERENCE:
|
|
{
|
|
OP::ResourceReferenceArgs Args = Program.GetOpArgs<OP::ResourceReferenceArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
Ptr<Image> Result;
|
|
if (Args.ForceLoad)
|
|
{
|
|
// This should never be reached because it should have been caught as a Task in IssueOp
|
|
check(false);
|
|
}
|
|
else
|
|
{
|
|
Result = Image::CreateAsReference(Args.ID, Args.ImageDesc, false);
|
|
}
|
|
StoreImage(item, Result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_CROP:
|
|
{
|
|
OP::ImageCropArgs args = Program.GetOpArgs<OP::ImageCropArgs>(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)
|
|
|
|
Ptr<const Image> pA = LoadImage( FCacheAddress(args.source,item) );
|
|
|
|
box< UE::Math::TIntVector2<int32> > 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--;
|
|
}
|
|
|
|
ImagePtr pResult;
|
|
if (!rect.IsEmpty())
|
|
{
|
|
pResult = CreateImage( rect.size[0], rect.size[1], 1, pA->GetFormat(), EInitializationType::NotInitialized);
|
|
ImOp.ImageCrop(pResult.get(), m_pSettings->ImageCompressionQuality, pA.get(), rect);
|
|
}
|
|
|
|
Release(pA);
|
|
StoreImage( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::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<OP::ImagePatchArgs>(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)
|
|
|
|
Ptr<const Image> pA = LoadImage( FCacheAddress(args.base,item) );
|
|
Ptr<const Image> 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<FIntVector2> 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();
|
|
|
|
ImagePtr pResult = 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 (pResult->GetFormat() != pB->GetFormat())
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ImagPatchReformat);
|
|
|
|
EImageFormat format = GetMostGenericFormat(pResult->GetFormat(), pB->GetFormat());
|
|
|
|
const FImageFormatData& finfo = GetImageFormatData(format);
|
|
if (finfo.PixelsPerBlockX == 0)
|
|
{
|
|
format = GetUncompressedFormat(format);
|
|
}
|
|
|
|
if (pResult->GetFormat() != format)
|
|
{
|
|
Ptr<Image> Formatted = CreateImage(pResult->GetSizeX(), pResult->GetSizeY(), pResult->GetLODCount(), format, EInitializationType::NotInitialized);
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->ImageCompressionQuality, Formatted.get(), pResult.get());
|
|
check(bSuccess);
|
|
Release(pResult);
|
|
pResult = Formatted;
|
|
}
|
|
if (pB->GetFormat() != format)
|
|
{
|
|
Ptr<Image> Formatted = CreateImage(pB->GetSizeX(), pB->GetSizeY(), pB->GetLODCount(), format, EInitializationType::NotInitialized);
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->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(pResult->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]) <= pResult->GetSizeX() &&
|
|
(rect.min[1] + rect.size[1]) <= pResult->GetSizeY()
|
|
;
|
|
}
|
|
|
|
if (bApplyPatch)
|
|
{
|
|
ImOp.ImageCompose(pResult.get(), pB.get(), rect);
|
|
pResult->m_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], pResult->GetSizeX(), pResult->GetSizeY());
|
|
}
|
|
|
|
Release(pB);
|
|
StoreImage( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_RASTERMESH:
|
|
{
|
|
OP::ImageRasterMeshArgs args = Program.GetOpArgs<OP::ImageRasterMeshArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
if (args.image)
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp::FromOpAndOptions(args.mesh, item, 0),
|
|
FScheduledOp::FromOpAndOptions(args.projector, item, 0));
|
|
}
|
|
else
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp::FromOpAndOptions(args.mesh, item, 0));
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(IM_RASTERMESH_1)
|
|
|
|
Ptr<const Mesh> pMesh = LoadMesh(FScheduledOp::FromOpAndOptions(args.mesh, item, 0));
|
|
|
|
// 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<uint16> CropMin(args.CropMinX, args.CropMinY);
|
|
UE::Math::TIntVector2<uint16> 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
|
|
Ptr<Image> ResultImage = CreateImage(SizeX, SizeY, 1, EImageFormat::IF_L_UBYTE, EInitializationType::Black);
|
|
if (pMesh)
|
|
{
|
|
ImageRasterMesh(pMesh.get(), ResultImage.get(), args.LayoutIndex, args.BlockId, CropMin, UncroppedSize);
|
|
Release(pMesh);
|
|
}
|
|
|
|
// 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<float>(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<EMinFilterMethod>(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 (pMesh)
|
|
{
|
|
const float ComputedMip = ComputeProjectedFootprintBestMip(pMesh.get(), Projector, TargetImageSizeF, SourceImageSizeF);
|
|
|
|
Data.RasterMesh.MipValue = FMath::Max(0.0f, ComputedMip + GlobalProjectionLodBias);
|
|
Data.RasterMesh.Mip = static_cast<uint8>(FMath::FloorToInt32(Data.RasterMesh.MipValue));
|
|
}
|
|
}
|
|
|
|
const int32 DataHeapAddress = m_heapData.Add(Data);
|
|
|
|
// pMesh is need again in the next stage, store it in the heap.
|
|
m_heapData[DataHeapAddress].Resource = const_cast<Mesh*>(pMesh.get());
|
|
|
|
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 = m_heapData[item.CustomState];
|
|
|
|
// Unsafe downcast, should be fine as it is known to be a Mesh.
|
|
Ptr<const Mesh> pMesh = static_cast<Mesh*>(Data.Resource.get());
|
|
Data.Resource = nullptr;
|
|
|
|
if (!pMesh)
|
|
{
|
|
check(false);
|
|
StoreImage(item, nullptr);
|
|
break;
|
|
}
|
|
|
|
uint16 SizeX = args.sizeX;
|
|
uint16 SizeY = args.sizeY;
|
|
UE::Math::TIntVector2<uint16> CropMin(args.CropMinX, args.CropMinY);
|
|
UE::Math::TIntVector2<uint16> 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
|
|
Ptr<const Image> Source = LoadImage(FCacheAddress(args.image, item.ExecutionIndex, Data.RasterMesh.Mip));
|
|
|
|
Ptr<const Image> 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);
|
|
|
|
Ptr<Image> 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::IF_L_UBYTE;
|
|
|
|
if (Source && Source->GetFormat()!=Format)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_RasterMesh_ReformatSource);
|
|
Ptr<Image> Formatted = CreateImage(Source->GetSizeX(), Source->GetSizeY(), Source->GetLODCount(), Format, EInitializationType::NotInitialized);
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->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<EMinFilterMethod>(args.MinFilterMethod);
|
|
});
|
|
|
|
if (MinFilterMethod == EMinFilterMethod::TotalAreaHeuristic)
|
|
{
|
|
const uint16 Mip = Data.RasterMesh.Mip;
|
|
const FImageSize ExpectedSourceSize = FImageSize(
|
|
FMath::Max<uint16>(args.SourceSizeX >> Mip, 1),
|
|
FMath::Max<uint16>(args.SourceSizeY >> Mip, 1));
|
|
|
|
if (Source->GetSize() != ExpectedSourceSize)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageRasterMesh_SizeFixup);
|
|
|
|
Ptr<Image> Resized = CreateImage(ExpectedSourceSize.X, ExpectedSourceSize.Y, 1, Format, EInitializationType::NotInitialized);
|
|
ImOp.ImageResizeLinear(Resized.get(), m_pSettings->ImageCompressionQuality, Source.get());
|
|
|
|
Release(Source);
|
|
Source = Resized;
|
|
}
|
|
}
|
|
|
|
// Allocate memory for the temporary buffers
|
|
FScratchImageProject Scratch;
|
|
Scratch.Vertices.SetNum(pMesh->GetVertexCount());
|
|
Scratch.CulledVertex.SetNum(pMesh->GetVertexCount());
|
|
|
|
ESamplingMethod SamplingMethod = Invoke([&]() -> ESamplingMethod
|
|
{
|
|
if (ForcedProjectionMode == 0)
|
|
{
|
|
return ESamplingMethod::Point;
|
|
}
|
|
else if (ForcedProjectionMode == 1)
|
|
{
|
|
return ESamplingMethod::BiLinear;
|
|
}
|
|
|
|
return static_cast<ESamplingMethod>(args.SamplingMethod);
|
|
});
|
|
|
|
if (SamplingMethod == ESamplingMethod::BiLinear)
|
|
{
|
|
if (Source->GetLODCount() < 2 && Source->GetSizeX() > 1 && Source->GetSizeY() > 1)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_RasterMesh_BilinearMipGen);
|
|
|
|
Ptr<Image> 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.
|
|
Ptr<Image> 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 PROJECTOR_TYPE::PLANAR:
|
|
ImageRasterProjectedPlanar(pMesh.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 PROJECTOR_TYPE::WRAPPING:
|
|
ImageRasterProjectedWrapping(pMesh.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 PROJECTOR_TYPE::CYLINDRICAL:
|
|
ImageRasterProjectedCylindrical(pMesh.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(pMesh);
|
|
Release(Source);
|
|
Release(Mask);
|
|
StoreImage(item, New);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_MAKEGROWMAP:
|
|
{
|
|
OP::ImageMakeGrowMapArgs args = Program.GetOpArgs<OP::ImageMakeGrowMapArgs>(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)
|
|
|
|
Ptr<const Image> Mask = LoadImage( FCacheAddress(args.mask,item) );
|
|
|
|
Ptr<Image> Result = CreateImage( Mask->GetSizeX(), Mask->GetSizeY(), Mask->GetLODCount(), EImageFormat::IF_L_UBYTE, EInitializationType::NotInitialized);
|
|
|
|
ImageMakeGrowMap(Result.get(), Mask.get(), args.border );
|
|
Result->m_flags |= Image::IF_CANNOT_BE_SCALED;
|
|
|
|
Release(Mask);
|
|
StoreImage( item, Result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_DISPLACE:
|
|
{
|
|
OP::ImageDisplaceArgs args = Program.GetOpArgs<OP::ImageDisplaceArgs>(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)
|
|
|
|
Ptr<const Image> Source = LoadImage( FCacheAddress(args.source,item) );
|
|
Ptr<const Image> 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);
|
|
|
|
Ptr<Image> 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.
|
|
Ptr<Image> 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);
|
|
Ptr<Image> 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 OP_TYPE::IM_TRANSFORM:
|
|
{
|
|
const OP::ImageTransformArgs Args = Program.GetOpArgs<OP::ImageTransformArgs>(item.At);
|
|
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
const TArray<FScheduledOp, TFixedAllocator<2>> 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<uint16>;
|
|
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 HeapData;
|
|
HeapData.ImageTransform.SizeX = DestSizeI.X;
|
|
HeapData.ImageTransform.SizeY = DestSizeI.Y;
|
|
FPlatformMath::StoreHalf(&HeapData.ImageTransform.ScaleXEncodedHalf, Scale.X);
|
|
FPlatformMath::StoreHalf(&HeapData.ImageTransform.ScaleYEncodedHalf, Scale.Y);
|
|
HeapData.ImageTransform.MipValue = BestMip + GlobalImageTransformLodBias;
|
|
|
|
const int32 HeapDataAddress = m_heapData.Add(HeapData);
|
|
|
|
const uint8 Mip = static_cast<uint8>(FMath::Max(0, FMath::FloorToInt(HeapData.ImageTransform.MipValue)));
|
|
const TArray<FScheduledOp, TFixedAllocator<4>> 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 HeapData = m_heapData[item.CustomState];
|
|
|
|
const uint8 Mip = static_cast<uint8>(FMath::Max(0, FMath::FloorToInt(HeapData.ImageTransform.MipValue)));
|
|
Ptr<const Image> 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(&HeapData.ImageTransform.ScaleXEncodedHalf),
|
|
FPlatformMath::LoadHalf(&HeapData.ImageTransform.ScaleYEncodedHalf));
|
|
|
|
FVector2f AspectCorrectionScale = FVector2f(1.0f, 1.0f);
|
|
if (Args.bKeepAspectRatio)
|
|
{
|
|
const FVector2f DestSize = FVector2f(HeapData.ImageTransform.SizeX, HeapData.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);
|
|
Ptr<Image> Formatted = CreateImage(Source->GetSizeX(), Source->GetSizeY(), Source->GetLODCount(), Format, EInitializationType::NotInitialized);
|
|
bool bSuccess = false;
|
|
ImOp.ImagePixelFormat(bSuccess, m_pSettings->ImageCompressionQuality, Formatted.get(), Source.get());
|
|
check(bSuccess);
|
|
|
|
Release(Source);
|
|
Source = Formatted;
|
|
}
|
|
|
|
const FImageSize ExpectedSourceSize = FImageSize(
|
|
FMath::Max<uint16>(Args.SourceSizeX >> (uint16)Mip, 1),
|
|
FMath::Max<uint16>(Args.SourceSizeY >> (uint16)Mip, 1));
|
|
if (Source->GetSize() != ExpectedSourceSize)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageTransform_SizeFixup);
|
|
|
|
Ptr<Image> Resized = CreateImage(ExpectedSourceSize.X, ExpectedSourceSize.Y, 1, Format, EInitializationType::NotInitialized);
|
|
ImOp.ImageResizeLinear(Resized.get(), m_pSettings->ImageCompressionQuality, Source.get());
|
|
|
|
Release(Source);
|
|
Source = Resized;
|
|
}
|
|
|
|
if (Source->GetLODCount() < 2 && Source->GetSizeX() > 1 && Source->GetSizeY() > 1)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_ImageTransform_BilinearMipGen);
|
|
|
|
Ptr<Image> 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<EAddressMode>(Args.AddressMode);
|
|
|
|
const EInitializationType InitType = AddressMode == EAddressMode::ClampToBlack
|
|
? EInitializationType::Black
|
|
: EInitializationType::NotInitialized;
|
|
|
|
Ptr<Image> Result = CreateImage(
|
|
HeapData.ImageTransform.SizeX, HeapData.ImageTransform.SizeY, 1, Format, InitType);
|
|
|
|
const float MipFactor = FMath::Frac(FMath::Max(0.0f, HeapData.ImageTransform.MipValue));
|
|
ImageTransform(Result.get(), Source.get(), Transform, MipFactor, AddressMode, bUseImageTransformVectorImpl);
|
|
|
|
Release(Source);
|
|
StoreImage(item, Result);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (type!=OP_TYPE::NONE)
|
|
{
|
|
// Operation not implemented
|
|
check( false );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
Ptr<RangeIndex> CodeRunner::BuildCurrentOpRangeIndex( const FScheduledOp& item, const Parameters* pParams, const Model* pModel, int32 parameterIndex )
|
|
{
|
|
if (!item.ExecutionIndex)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// \todo: optimise to avoid allocating the index here, we could access internal
|
|
// data directly.
|
|
Ptr<RangeIndex> index = pParams->NewRangeIndex( parameterIndex );
|
|
if (!index)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
const FParameterDesc& paramDesc = Program.m_parameters[ parameterIndex ];
|
|
for( size_t rangeIndexInParam=0;
|
|
rangeIndexInParam<paramDesc.m_ranges.Num();
|
|
++rangeIndexInParam )
|
|
{
|
|
uint32 rangeIndexInModel = paramDesc.m_ranges[rangeIndexInParam];
|
|
const ExecutionIndex& currentIndex = GetMemory().GetRangeIndex( item.ExecutionIndex );
|
|
int position = currentIndex.GetFromModelRangeIndex(rangeIndexInModel);
|
|
index->GetPrivate()->m_values[rangeIndexInParam] = position;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Bool(const FScheduledOp& item, const Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Bool);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::BO_CONSTANT:
|
|
{
|
|
OP::BoolConstantArgs args = Program.GetOpArgs<OP::BoolConstantArgs>(item.At);
|
|
bool result = args.value;
|
|
StoreBool( item, result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::BO_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
bool result = false;
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
result = pParams->GetBoolValue( args.variable, index );
|
|
StoreBool( item, result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::BO_LESS:
|
|
{
|
|
OP::BoolLessArgs args = Program.GetOpArgs<OP::BoolLessArgs>(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) );
|
|
bool result = a<b;
|
|
StoreBool( item, result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::BO_AND:
|
|
{
|
|
OP::BoolBinaryArgs args = Program.GetOpArgs<OP::BoolBinaryArgs>(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 OP_TYPE::BO_OR:
|
|
{
|
|
OP::BoolBinaryArgs args = Program.GetOpArgs<OP::BoolBinaryArgs>(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 OP_TYPE::BO_NOT:
|
|
{
|
|
OP::BoolNotArgs args = Program.GetOpArgs<OP::BoolNotArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.source, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
bool result = !LoadBool( FCacheAddress(args.source,item) );
|
|
StoreBool( item, result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::BO_EQUAL_INT_CONST:
|
|
{
|
|
OP::BoolEqualScalarConstArgs args = Program.GetOpArgs<OP::BoolEqualScalarConstArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.value, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
int 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 Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Int);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::NU_CONSTANT:
|
|
{
|
|
OP::IntConstantArgs args = Program.GetOpArgs<OP::IntConstantArgs>(item.At);
|
|
int result = args.value;
|
|
StoreInt( item, result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::NU_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
int result = pParams->GetIntValue( args.variable, index );
|
|
|
|
// Check that the value is actually valid. Otherwise set the default.
|
|
if ( pParams->GetIntPossibleValueCount( args.variable ) )
|
|
{
|
|
bool valid = false;
|
|
for ( int i=0;
|
|
(!valid) && i<pParams->GetIntPossibleValueCount( 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 Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Scalar);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::SC_CONSTANT:
|
|
{
|
|
OP::ScalarConstantArgs args = Program.GetOpArgs<OP::ScalarConstantArgs>(item.At);
|
|
float result = args.value;
|
|
StoreScalar( item, result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::SC_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
float result = pParams->GetFloatValue( args.variable, index );
|
|
StoreScalar( item, result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::SC_CURVE:
|
|
{
|
|
OP::ScalarCurveArgs args = Program.GetOpArgs<OP::ScalarCurveArgs>(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 OP_TYPE::SC_MULTIPLYADD:
|
|
// \TODO
|
|
check( false );
|
|
break;
|
|
|
|
case OP_TYPE::SC_ARITHMETIC:
|
|
{
|
|
OP::ArithmeticArgs args = Program.GetOpArgs<OP::ArithmeticArgs>(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 Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_String );
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType( item.At );
|
|
switch ( type )
|
|
{
|
|
|
|
case OP_TYPE::ST_CONSTANT:
|
|
{
|
|
OP::ResourceConstantArgs args = Program.GetOpArgs<OP::ResourceConstantArgs>( item.At );
|
|
check( args.value < (uint32)pModel->GetPrivate()->m_program.m_constantStrings.Num() );
|
|
|
|
const FString& result = Program.m_constantStrings[args.value];
|
|
StoreString( item, new String(result) );
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::ST_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>( item.At );
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
FString result;
|
|
pParams->GetStringValue(args.variable, result, index);
|
|
StoreString( item, new String(result) );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check( false );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Colour(const FScheduledOp& item, const Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Colour);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
|
|
switch ( type )
|
|
{
|
|
|
|
case OP_TYPE::CO_CONSTANT:
|
|
{
|
|
OP::ColourConstantArgs args = Program.GetOpArgs<OP::ColourConstantArgs>(item.At);
|
|
FVector4f result;
|
|
result[0] = args.value[0];
|
|
result[1] = args.value[1];
|
|
result[2] = args.value[2];
|
|
result[3] = args.value[3];
|
|
StoreColor( item, result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::CO_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
float R = 0.f;
|
|
float G = 0.f;
|
|
float B = 0.f;
|
|
float A = 0.f;
|
|
pParams->GetColourValue( args.variable, &R, &G, &B, &A, index );
|
|
StoreColor( item, FVector4f(R, G, B, A) );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::CO_SAMPLEIMAGE:
|
|
{
|
|
OP::ColourSampleImageArgs args = Program.GetOpArgs<OP::ColourSampleImageArgs>(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;
|
|
|
|
Ptr<const Image> 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 OP_TYPE::CO_SWIZZLE:
|
|
{
|
|
OP::ColourSwizzleArgs args = Program.GetOpArgs<OP::ColourSwizzleArgs>(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 (int t=0;t<MUTABLE_OP_MAX_SWIZZLE_CHANNELS;++t)
|
|
{
|
|
if ( args.sources[t] )
|
|
{
|
|
FVector4f p = LoadColor( FCacheAddress(args.sources[t],item) );
|
|
result[t] = p[ args.sourceChannels[t] ];
|
|
}
|
|
}
|
|
|
|
StoreColor( item, result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::CO_FROMSCALARS:
|
|
{
|
|
OP::ColourFromScalarsArgs args = Program.GetOpArgs<OP::ColourFromScalarsArgs>(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 OP_TYPE::CO_ARITHMETIC:
|
|
{
|
|
OP::ArithmeticArgs args = Program.GetOpArgs<OP::ArithmeticArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.a, item),
|
|
FScheduledOp( args.b, item));
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
OP_TYPE otype = Program.GetOpType( args.a );
|
|
DATATYPE dtype = GetOpDataType( otype );
|
|
check( dtype == DT_COLOUR );
|
|
otype = Program.GetOpType( args.b );
|
|
dtype = GetOpDataType( otype );
|
|
check( dtype == DT_COLOUR );
|
|
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 Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Projector);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::PR_CONSTANT:
|
|
{
|
|
OP::ResourceConstantArgs args = Program.GetOpArgs<OP::ResourceConstantArgs>(item.At);
|
|
FProjector Result = Program.m_constantProjectors[args.value];
|
|
StoreProjector( item, Result );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::PR_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
FProjector Result = pParams->GetPrivate()->GetProjectorValue(args.variable,index);
|
|
|
|
// The type cannot be changed, take it from the default value
|
|
const FProjector& def = Program.m_parameters[args.variable].m_defaultValue.Get<ParamProjectorType>();
|
|
Result.type = def.type;
|
|
|
|
StoreProjector( item, Result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check( false );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Matrix(const FScheduledOp& item, const Parameters* pParams, const Model* pModel )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCode_Transform);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
|
|
switch ( type )
|
|
{
|
|
case OP_TYPE::MA_CONSTANT:
|
|
{
|
|
OP::MatrixConstantArgs args = Program.GetOpArgs<OP::MatrixConstantArgs>(item.At);
|
|
StoreMatrix( item, Program.m_constantMatrices[args.value] );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::MA_PARAMETER:
|
|
{
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
Ptr<RangeIndex> index = BuildCurrentOpRangeIndex( item, pParams, pModel, args.variable );
|
|
FMatrix44f Value;
|
|
pParams->GetMatrixValue( args.variable, Value, index );
|
|
StoreMatrix( item, Value );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode_Layout(const FScheduledOp& item, const Model* pModel )
|
|
{
|
|
//MUTABLE_CPUPROFILER_SCOPE(RunCode_Layout);
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::LA_CONSTANT:
|
|
{
|
|
OP::ResourceConstantArgs args = Program.GetOpArgs<OP::ResourceConstantArgs>(item.At);
|
|
check( args.value < (uint32)pModel->GetPrivate()->m_program.m_constantLayouts.Num() );
|
|
|
|
Ptr<const Layout> pResult = Program.m_constantLayouts
|
|
[ args.value ];
|
|
StoreLayout( item, pResult );
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::LA_MERGE:
|
|
{
|
|
OP::LayoutMergeArgs args = Program.GetOpArgs<OP::LayoutMergeArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.Base, item),
|
|
FScheduledOp( args.Added, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
Ptr<const Layout> pA = LoadLayout( FCacheAddress(args.Base,item) );
|
|
Ptr<const Layout> pB = LoadLayout( FCacheAddress(args.Added,item) );
|
|
|
|
Ptr<const Layout> pResult;
|
|
|
|
if (pA && pB)
|
|
{
|
|
pResult = LayoutMerge(pA.get(),pB.get());
|
|
}
|
|
else if (pA)
|
|
{
|
|
pResult = pA->Clone();
|
|
}
|
|
else if (pB)
|
|
{
|
|
pResult = pB->Clone();
|
|
}
|
|
|
|
StoreLayout( item, pResult );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::LA_PACK:
|
|
{
|
|
OP::LayoutPackArgs args = Program.GetOpArgs<OP::LayoutPackArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp( FScheduledOp( item.At, item, 1),
|
|
FScheduledOp( args.Source, item) );
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
Ptr<const Layout> Source = LoadLayout( FCacheAddress(args.Source,item) );
|
|
|
|
Ptr<Layout> Result;
|
|
|
|
if (Source)
|
|
{
|
|
Result = Source->Clone();
|
|
|
|
LayoutPack3(Result.get(), Source.get() );
|
|
}
|
|
|
|
StoreLayout( item, Result );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::LA_FROMMESH:
|
|
{
|
|
OP::LayoutFromMeshArgs args = Program.GetOpArgs<OP::LayoutFromMeshArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(args.Mesh, item));
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
Ptr<const Mesh> Mesh = LoadMesh(FCacheAddress(args.Mesh, item));
|
|
|
|
Ptr<const Layout> Result = LayoutFromMesh_RemoveBlocks(Mesh.get(), args.LayoutIndex);
|
|
|
|
Release(Mesh);
|
|
StoreLayout(item, Result);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::LA_REMOVEBLOCKS:
|
|
{
|
|
OP::LayoutRemoveBlocksArgs args = Program.GetOpArgs<OP::LayoutRemoveBlocksArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1),
|
|
FScheduledOp(args.Source, item),
|
|
FScheduledOp(args.ReferenceLayout, item));
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
Ptr<const Layout> Source = LoadLayout(FCacheAddress(args.Source, item));
|
|
Ptr<const Layout> ReferenceLayout = LoadLayout(FCacheAddress(args.ReferenceLayout, item));
|
|
|
|
Ptr<const Layout> pResult;
|
|
|
|
if (Source && ReferenceLayout)
|
|
{
|
|
pResult = LayoutRemoveBlocks(Source.get(), ReferenceLayout.get());
|
|
}
|
|
else if (Source)
|
|
{
|
|
pResult = Source;
|
|
}
|
|
|
|
StoreLayout(item, pResult);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// Operation not implemented
|
|
check( false );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCode( const FScheduledOp& item, const Parameters* pParams, const TSharedPtr<const Model>& InModel, uint32 lodMask)
|
|
{
|
|
//UE_LOG( LogMutableCore, Log, TEXT("Running :%5d , %d "), item.At, item.Stage );
|
|
check( item.Type == FScheduledOp::EType::Full );
|
|
|
|
const Model* pModel = InModel.Get();
|
|
|
|
const FProgram& Program = pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE 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 (m_pSystem)
|
|
//{
|
|
// m_pSystem->WorkingMemoryManager.LogWorkingMemory( this );
|
|
//}
|
|
|
|
switch ( type )
|
|
{
|
|
case OP_TYPE::NONE:
|
|
break;
|
|
|
|
case OP_TYPE::NU_CONDITIONAL:
|
|
case OP_TYPE::SC_CONDITIONAL:
|
|
case OP_TYPE::CO_CONDITIONAL:
|
|
case OP_TYPE::IM_CONDITIONAL:
|
|
case OP_TYPE::ME_CONDITIONAL:
|
|
case OP_TYPE::LA_CONDITIONAL:
|
|
case OP_TYPE::IN_CONDITIONAL:
|
|
case OP_TYPE::ED_CONDITIONAL:
|
|
RunCode_Conditional(item, pModel);
|
|
break;
|
|
|
|
case OP_TYPE::ME_CONSTANT:
|
|
case OP_TYPE::IM_CONSTANT:
|
|
case OP_TYPE::ED_CONSTANT:
|
|
RunCode_ConstantResource(item, pModel);
|
|
break;
|
|
|
|
case OP_TYPE::NU_SWITCH:
|
|
case OP_TYPE::SC_SWITCH:
|
|
case OP_TYPE::CO_SWITCH:
|
|
case OP_TYPE::IM_SWITCH:
|
|
case OP_TYPE::ME_SWITCH:
|
|
case OP_TYPE::LA_SWITCH:
|
|
case OP_TYPE::IN_SWITCH:
|
|
case OP_TYPE::ED_SWITCH:
|
|
RunCode_Switch(item, pModel);
|
|
break;
|
|
|
|
case OP_TYPE::IN_ADDMESH:
|
|
case OP_TYPE::IN_ADDIMAGE:
|
|
RunCode_InstanceAddResource(item, InModel, pParams);
|
|
break;
|
|
|
|
default:
|
|
{
|
|
DATATYPE DataType = GetOpDataType(type);
|
|
switch (DataType)
|
|
{
|
|
case DT_INSTANCE:
|
|
RunCode_Instance(item, pModel, lodMask);
|
|
break;
|
|
|
|
case DT_MESH:
|
|
RunCode_Mesh(item, pModel);
|
|
break;
|
|
|
|
case DT_IMAGE:
|
|
RunCode_Image(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_LAYOUT:
|
|
RunCode_Layout(item, pModel);
|
|
break;
|
|
|
|
case DT_BOOL:
|
|
RunCode_Bool(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_SCALAR:
|
|
RunCode_Scalar(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_STRING:
|
|
RunCode_String(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_INT:
|
|
RunCode_Int(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_PROJECTOR:
|
|
RunCode_Projector(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_COLOUR:
|
|
RunCode_Colour(item, pParams, pModel);
|
|
break;
|
|
|
|
case DT_MATRIX:
|
|
RunCode_Matrix(item, pParams, pModel);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeRunner::RunCodeImageDesc(const FScheduledOp& item, const Parameters* pParams, const Model* pModel, uint32 lodMask )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RunCodeImageDesc);
|
|
|
|
check(item.Type == FScheduledOp::EType::ImageDesc);
|
|
|
|
// Ensure there is room for the result in the heap.
|
|
if (item.CustomState >= uint32(m_heapData.Num()))
|
|
{
|
|
m_heapImageDesc.SetNum(item.CustomState+1);
|
|
}
|
|
|
|
|
|
const FProgram& Program = m_pModel->GetPrivate()->m_program;
|
|
|
|
OP_TYPE type = Program.GetOpType(item.At);
|
|
switch (type)
|
|
{
|
|
|
|
case OP_TYPE::IM_CONSTANT:
|
|
{
|
|
check(item.Stage == 0);
|
|
OP::ResourceConstantArgs args = Program.GetOpArgs<OP::ResourceConstantArgs>(item.At);
|
|
int32 ImageIndex = args.value;
|
|
|
|
FImageDesc& Result = m_heapImageDesc[item.CustomState];
|
|
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;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_PARAMETER:
|
|
{
|
|
check(item.Stage == 0);
|
|
OP::ParameterArgs args = Program.GetOpArgs<OP::ParameterArgs>(item.At);
|
|
FName Id = pParams->GetImageValue(args.variable);
|
|
uint8 MipsToSkip = item.ExecutionOptions;
|
|
m_heapImageDesc[item.CustomState] = GetExternalImageDesc(Id, MipsToSkip);
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_REFERENCE:
|
|
{
|
|
check(item.Stage == 0);
|
|
OP::ResourceReferenceArgs Args = Program.GetOpArgs<OP::ResourceReferenceArgs>(item.At);
|
|
FImageDesc& Result = m_heapImageDesc[item.CustomState];
|
|
Result = Args.ImageDesc;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_CONDITIONAL:
|
|
{
|
|
OP::ConditionalArgs args = Program.GetOpArgs<OP::ConditionalArgs>(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 value = LoadBool(FCacheAddress(args.condition, item.ExecutionIndex, item.ExecutionOptions));
|
|
OP::ADDRESS resultAt = value ? args.yes : args.no;
|
|
AddOp(FScheduledOp(item.At, item, 2), FScheduledOp(resultAt, item));
|
|
break;
|
|
}
|
|
|
|
case 2: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::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
|
|
{
|
|
StoreValidDesc(item);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
// Get the variable result
|
|
int 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 == (int)Condition)
|
|
{
|
|
valueAt = At;
|
|
break;
|
|
}
|
|
}
|
|
|
|
AddOp(FScheduledOp(item.At, item, 2, valueAt),
|
|
FScheduledOp(valueAt, item));
|
|
|
|
break;
|
|
}
|
|
|
|
case 2: StoreValidDesc(item); break;
|
|
default: check(false); break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_LAYERCOLOUR:
|
|
{
|
|
OP::ImageLayerColourArgs args = Program.GetOpArgs<OP::ImageLayerColourArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_LAYER:
|
|
{
|
|
OP::ImageLayerArgs args = Program.GetOpArgs<OP::ImageLayerArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_MULTILAYER:
|
|
{
|
|
OP::ImageMultiLayerArgs args = Program.GetOpArgs<OP::ImageMultiLayerArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_NORMALCOMPOSITE:
|
|
{
|
|
OP::ImageNormalCompositeArgs args = Program.GetOpArgs<OP::ImageNormalCompositeArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_PIXELFORMAT:
|
|
{
|
|
OP::ImagePixelFormatArgs args = Program.GetOpArgs<OP::ImagePixelFormatArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.source, item));
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
// Update directly in the heap
|
|
EImageFormat OldFormat = m_heapImageDesc[item.CustomState].m_format;
|
|
EImageFormat NewFormat = args.format;
|
|
if (args.formatIfAlpha != EImageFormat::IF_NONE
|
|
&&
|
|
GetImageFormatData(OldFormat).Channels > 3)
|
|
{
|
|
NewFormat = args.formatIfAlpha;
|
|
}
|
|
m_heapImageDesc[item.CustomState].m_format = NewFormat;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_MIPMAP:
|
|
{
|
|
OP::ImageMipmapArgs args = Program.GetOpArgs<OP::ImageMipmapArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.source, item));
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
// Somewhat synched with Full op execution code.
|
|
FImageDesc BaseDesc = m_heapImageDesc[item.CustomState];
|
|
int32 LevelCount = args.levels;
|
|
int32 MaxLevelCount = Image::GetMipmapCount(BaseDesc.m_size[0], BaseDesc.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 = BaseDesc.m_lods;
|
|
LevelCount = FMath::Max(StartLevel, LevelCount);
|
|
|
|
// Update result.
|
|
m_heapImageDesc[item.CustomState].m_lods = LevelCount;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_RESIZE:
|
|
{
|
|
OP::ImageResizeArgs args = Program.GetOpArgs<OP::ImageResizeArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.source, item));
|
|
break;
|
|
|
|
case 1:
|
|
m_heapImageDesc[item.CustomState].m_size[0] = args.size[0];
|
|
m_heapImageDesc[item.CustomState].m_size[1] = args.size[1];
|
|
StoreValidDesc(item);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_RESIZELIKE:
|
|
{
|
|
OP::ImageResizeLikeArgs args = Program.GetOpArgs<OP::ImageResizeLikeArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
int32 ResultAndBaseDesc = item.CustomState;
|
|
int32 SourceDescAddress = m_heapImageDesc.Add({});
|
|
FScheduledOpData Data;
|
|
Data.ResizeLike.ResultDescAt = ResultAndBaseDesc;
|
|
Data.ResizeLike.SourceDescAt = SourceDescAddress;
|
|
int32 SecondStageData = m_heapData.Add(Data);
|
|
AddOp(FScheduledOp(item.At, item, 1, SecondStageData),
|
|
FScheduledOp(args.source, item, 0, ResultAndBaseDesc),
|
|
FScheduledOp(args.sizeSource, item, 0, SourceDescAddress));
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
const FScheduledOpData& SecondStageData = m_heapData[ item.CustomState ];
|
|
FImageDesc& ResultAndBaseDesc = m_heapImageDesc[SecondStageData.ResizeLike.ResultDescAt];
|
|
const FImageDesc& SourceDesc = m_heapImageDesc[SecondStageData.ResizeLike.SourceDescAt];
|
|
ResultAndBaseDesc.m_size = SourceDesc.m_size;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_RESIZEREL:
|
|
{
|
|
OP::ImageResizeRelArgs args = Program.GetOpArgs<OP::ImageResizeRelArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.source, item));
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
FImageDesc& ResultAndBaseDesc = m_heapImageDesc[item.CustomState];
|
|
FImageSize destSize(
|
|
uint16(ResultAndBaseDesc.m_size[0] * args.factor[0] + 0.5f),
|
|
uint16(ResultAndBaseDesc.m_size[1] * args.factor[1] + 0.5f));
|
|
ResultAndBaseDesc.m_size = destSize;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_BLANKLAYOUT:
|
|
{
|
|
OP::ImageBlankLayoutArgs args = Program.GetOpArgs<OP::ImageBlankLayoutArgs>(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:
|
|
{
|
|
Ptr<const Layout> pLayout = LoadLayout(FCacheAddress(args.layout, item));
|
|
|
|
FIntPoint SizeInBlocks = pLayout->GetGridSize();
|
|
FIntPoint BlockSizeInPixels(args.blockSize[0], args.blockSize[1]);
|
|
FIntPoint ImageSizeInPixels = SizeInBlocks * BlockSizeInPixels;
|
|
|
|
FImageDesc& ResultAndBaseDesc = m_heapImageDesc[item.CustomState];
|
|
FImageSize destSize(uint16(ImageSizeInPixels.X), uint16(ImageSizeInPixels.Y));
|
|
ResultAndBaseDesc.m_size = destSize;
|
|
ResultAndBaseDesc.m_format = args.format;
|
|
|
|
if (args.generateMipmaps)
|
|
{
|
|
if (args.mipmapCount == 0)
|
|
{
|
|
ResultAndBaseDesc.m_lods = Image::GetMipmapCount(ImageSizeInPixels.X, ImageSizeInPixels.Y);
|
|
}
|
|
else
|
|
{
|
|
ResultAndBaseDesc.m_lods = args.mipmapCount;
|
|
}
|
|
}
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_COMPOSE:
|
|
{
|
|
OP::ImageComposeArgs args = Program.GetOpArgs<OP::ImageComposeArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_INTERPOLATE:
|
|
{
|
|
OP::ImageInterpolateArgs args = Program.GetOpArgs<OP::ImageInterpolateArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.targets[0], item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_SATURATE:
|
|
{
|
|
OP::ImageSaturateArgs args = Program.GetOpArgs<OP::ImageSaturateArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_LUMINANCE:
|
|
{
|
|
OP::ImageLuminanceArgs args = Program.GetOpArgs<OP::ImageLuminanceArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item));
|
|
break;
|
|
|
|
case 1:
|
|
m_heapImageDesc[item.CustomState].m_format = EImageFormat::IF_L_UBYTE;
|
|
StoreValidDesc(item);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_SWIZZLE:
|
|
{
|
|
OP::ImageSwizzleArgs args = Program.GetOpArgs<OP::ImageSwizzleArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.sources[0], item));
|
|
break;
|
|
|
|
case 1:
|
|
m_heapImageDesc[item.CustomState].m_format = args.format;
|
|
StoreValidDesc(item);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_COLOURMAP:
|
|
{
|
|
OP::ImageColourMapArgs args = Program.GetOpArgs<OP::ImageColourMapArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_GRADIENT:
|
|
{
|
|
OP::ImageGradientArgs args = Program.GetOpArgs<OP::ImageGradientArgs>(item.At);
|
|
m_heapImageDesc[item.CustomState].m_size[0] = args.size[0];
|
|
m_heapImageDesc[item.CustomState].m_size[1] = args.size[1];
|
|
m_heapImageDesc[item.CustomState].m_lods = 1;
|
|
m_heapImageDesc[item.CustomState].m_format = EImageFormat::IF_RGB_UBYTE;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_BINARISE:
|
|
{
|
|
OP::ImageBinariseArgs args = Program.GetOpArgs<OP::ImageBinariseArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item));
|
|
break;
|
|
|
|
case 1:
|
|
m_heapImageDesc[item.CustomState].m_format = EImageFormat::IF_L_UBYTE;
|
|
StoreValidDesc(item);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_INVERT:
|
|
{
|
|
OP::ImageInvertArgs args = Program.GetOpArgs<OP::ImageInvertArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_PLAINCOLOUR:
|
|
{
|
|
OP::ImagePlainColourArgs args = Program.GetOpArgs<OP::ImagePlainColourArgs>(item.At);
|
|
m_heapImageDesc[item.CustomState].m_size[0] = args.size[0];
|
|
m_heapImageDesc[item.CustomState].m_size[1] = args.size[1];
|
|
m_heapImageDesc[item.CustomState].m_lods = args.LODs;
|
|
m_heapImageDesc[item.CustomState].m_format = args.format;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_CROP:
|
|
{
|
|
OP::ImageCropArgs args = Program.GetOpArgs<OP::ImageCropArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.source, item));
|
|
break;
|
|
|
|
case 1:
|
|
m_heapImageDesc[item.CustomState].m_size[0] = args.sizeX;
|
|
m_heapImageDesc[item.CustomState].m_size[1] = args.sizeY;
|
|
m_heapImageDesc[item.CustomState].m_lods = 1;
|
|
StoreValidDesc(item);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_PATCH:
|
|
{
|
|
OP::ImagePatchArgs args = Program.GetOpArgs<OP::ImagePatchArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.base, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_RASTERMESH:
|
|
{
|
|
OP::ImageRasterMeshArgs args = Program.GetOpArgs<OP::ImageRasterMeshArgs>(item.At);
|
|
m_heapImageDesc[item.CustomState].m_size[0] = args.sizeX;
|
|
m_heapImageDesc[item.CustomState].m_size[1] = args.sizeY;
|
|
m_heapImageDesc[item.CustomState].m_lods = 1;
|
|
m_heapImageDesc[item.CustomState].m_format = EImageFormat::IF_L_UBYTE;
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_MAKEGROWMAP:
|
|
{
|
|
OP::ImageMakeGrowMapArgs args = Program.GetOpArgs<OP::ImageMakeGrowMapArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.mask, item));
|
|
break;
|
|
|
|
case 1:
|
|
m_heapImageDesc[item.CustomState].m_format = EImageFormat::IF_L_UBYTE;
|
|
m_heapImageDesc[item.CustomState].m_lods = 1;
|
|
StoreValidDesc(item);
|
|
break;
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_DISPLACE:
|
|
{
|
|
OP::ImageDisplaceArgs args = Program.GetOpArgs<OP::ImageDisplaceArgs>(item.At);
|
|
switch (item.Stage)
|
|
{
|
|
case 0: AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(args.source, item)); break;
|
|
case 1: StoreValidDesc(item); break;
|
|
default: check(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_TYPE::IM_TRANSFORM:
|
|
{
|
|
|
|
OP::ImageTransformArgs Args = Program.GetOpArgs<OP::ImageTransformArgs>(item.At);
|
|
|
|
switch (item.Stage)
|
|
{
|
|
case 0:
|
|
{
|
|
AddOp(FScheduledOp(item.At, item, 1), FScheduledOp(Args.Base, item));
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
m_heapImageDesc[item.CustomState].m_lods = 1;
|
|
m_heapImageDesc[item.CustomState].m_format = GetUncompressedFormat(m_heapImageDesc[item.CustomState].m_format);
|
|
|
|
if (!(Args.SizeX == 0 && Args.SizeY == 0))
|
|
{
|
|
m_heapImageDesc[item.CustomState].m_size[0] = Args.SizeX;
|
|
m_heapImageDesc[item.CustomState].m_size[1] = Args.SizeY;
|
|
}
|
|
|
|
StoreValidDesc(item);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (type != OP_TYPE::NONE)
|
|
{
|
|
// Operation not implemented
|
|
check(false);
|
|
m_heapImageDesc[item.CustomState] = FImageDesc();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|