2184 lines
80 KiB
C++
2184 lines
80 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MuT/AST.h"
|
|
#include "MuT/ASTOpAddLOD.h"
|
|
#include "MuT/ASTOpConditional.h"
|
|
#include "MuT/ASTOpImageCompose.h"
|
|
#include "MuT/ASTOpImageMipmap.h"
|
|
#include "MuT/ASTOpImagePixelFormat.h"
|
|
#include "MuT/ASTOpImageLayer.h"
|
|
#include "MuT/ASTOpImageLayerColor.h"
|
|
#include "MuT/ASTOpImageResizeLike.h"
|
|
#include "MuT/ASTOpImagePlainColor.h"
|
|
#include "MuT/ASTOpImageDisplace.h"
|
|
#include "MuT/ASTOpMeshMorph.h"
|
|
#include "MuT/ASTOpInstanceAdd.h"
|
|
#include "MuT/ASTOpLayoutFromMesh.h"
|
|
#include "MuT/ASTOpParameter.h"
|
|
#include "MuT/ASTOpConstantColor.h"
|
|
#include "MuT/CodeOptimiser.h"
|
|
#include "MuT/Compiler.h"
|
|
#include "MuT/CompilerPrivate.h"
|
|
#include "MuT/DataPacker.h"
|
|
#include "MuR/Image.h"
|
|
#include "MuR/ImagePrivate.h"
|
|
#include "MuR/MutableTrace.h"
|
|
#include "MuR/Operations.h"
|
|
#include "MuR/ParametersPrivate.h"
|
|
#include "MuR/Ptr.h"
|
|
#include "MuR/MutableRuntimeModule.h"
|
|
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "HAL/PlatformMath.h"
|
|
#include "Logging/LogCategory.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Trace/Detail/Channel.h"
|
|
|
|
#include <array>
|
|
|
|
|
|
namespace mu
|
|
{
|
|
|
|
RuntimeParameterVisitorAST::RuntimeParameterVisitorAST(const FStateCompilationData* InState)
|
|
: State(InState)
|
|
{
|
|
}
|
|
|
|
|
|
bool RuntimeParameterVisitorAST::HasAny( const Ptr<ASTOp>& Root )
|
|
{
|
|
if (!State->nodeState.RuntimeParams.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Shortcut flag: if true we already found a runtime parameter, don't process new ops,
|
|
// but still store the results of processed ops.
|
|
bool bRuntimeFound = false;
|
|
|
|
Pending.Reset();
|
|
|
|
FPendingItem Start;
|
|
Start.Op = Root;
|
|
Start.ItemType = 0;
|
|
Start.OnlyLayoutsRelevant = 0;
|
|
Pending.Add(Start);
|
|
|
|
// Don't early out to be able to complete parent op cached flags
|
|
while (!Pending.IsEmpty())
|
|
{
|
|
FPendingItem Item = Pending.Pop(EAllowShrinking::No);
|
|
Ptr<ASTOp> Op = Item.Op;
|
|
|
|
if (!Op)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Not cached?
|
|
EOpState* FoundCached = Visited.Find(Op);
|
|
if (
|
|
!FoundCached
|
|
||
|
|
(
|
|
*FoundCached !=EOpState::VisitedHasRuntime
|
|
&&
|
|
*FoundCached != EOpState::VisitedFullDoesntHaveRuntime
|
|
)
|
|
)
|
|
{
|
|
if (Item.ItemType)
|
|
{
|
|
// Item indicating we finished with all the children of a parent
|
|
check(*FoundCached ==EOpState::ChildrenPendingFull
|
|
||
|
|
*FoundCached ==EOpState::ChildrenPendingPartial
|
|
||
|
|
*FoundCached ==EOpState::VisitedPartialDoesntHaveRuntime );
|
|
|
|
bool bSubtreeFound = false;
|
|
Op->ForEachChild( [&](ASTChild& ref)
|
|
{
|
|
EOpState* FoundChild = Visited.Find(ref.child());
|
|
if (FoundChild && *FoundChild == EOpState::VisitedHasRuntime)
|
|
{
|
|
bSubtreeFound = true;
|
|
}
|
|
});
|
|
|
|
if (bSubtreeFound)
|
|
{
|
|
*FoundCached = EOpState::VisitedHasRuntime;
|
|
}
|
|
else
|
|
{
|
|
*FoundCached = Item.OnlyLayoutsRelevant
|
|
? EOpState::VisitedPartialDoesntHaveRuntime
|
|
: EOpState::VisitedFullDoesntHaveRuntime;
|
|
}
|
|
}
|
|
|
|
else if (!bRuntimeFound)
|
|
{
|
|
// We need to process the subtree
|
|
check(!FoundCached
|
|
||
|
|
*FoundCached ==EOpState::NotVisited
|
|
||
|
|
(*FoundCached == EOpState::VisitedPartialDoesntHaveRuntime
|
|
&&
|
|
Item.OnlyLayoutsRelevant==0 )
|
|
);
|
|
|
|
// Request the processing of the end of this instruction
|
|
FPendingItem endItem = Item;
|
|
endItem.ItemType = 1;
|
|
Pending.Add( endItem );
|
|
Visited.Add(Op, Item.OnlyLayoutsRelevant
|
|
? EOpState::ChildrenPendingPartial
|
|
: EOpState::ChildrenPendingFull);
|
|
|
|
// Is it a special op type?
|
|
switch ( Op->GetOpType() )
|
|
{
|
|
|
|
case EOpType::BO_PARAMETER:
|
|
case EOpType::NU_PARAMETER:
|
|
case EOpType::SC_PARAMETER:
|
|
case EOpType::CO_PARAMETER:
|
|
case EOpType::PR_PARAMETER:
|
|
case EOpType::IM_PARAMETER:
|
|
case EOpType::ME_PARAMETER:
|
|
case EOpType::MA_PARAMETER:
|
|
{
|
|
const ASTOpParameter* Typed = static_cast<const ASTOpParameter*>(Op.get());
|
|
const TArray<FString>& Params = State->nodeState.RuntimeParams;
|
|
if (Params.Find(Typed->Parameter.Name)
|
|
!=
|
|
INDEX_NONE )
|
|
{
|
|
bRuntimeFound = true;
|
|
Visited.Add(Op,EOpState::VisitedHasRuntime);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
Op->ForEachChild([&](ASTChild& ref)
|
|
{
|
|
FPendingItem ChildItem;
|
|
ChildItem.ItemType = 0;
|
|
ChildItem.Op = ref.child();
|
|
ChildItem.OnlyLayoutsRelevant = Item.OnlyLayoutsRelevant;
|
|
AddIfNeeded(ChildItem);
|
|
});
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
// We won't process it.
|
|
Visited.Add(Op,EOpState::NotVisited);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Visited[Root]==EOpState::VisitedHasRuntime;
|
|
}
|
|
|
|
|
|
void RuntimeParameterVisitorAST::AddIfNeeded( const FPendingItem& Item )
|
|
{
|
|
if (Item.Op)
|
|
{
|
|
const EOpState* Found = Visited.Find(Item.Op);
|
|
if (!Found || *Found==EOpState::NotVisited)
|
|
{
|
|
Pending.Add(Item);
|
|
}
|
|
else if (*Found ==EOpState::VisitedPartialDoesntHaveRuntime
|
|
&&
|
|
Item.OnlyLayoutsRelevant==0)
|
|
{
|
|
Pending.Add(Item);
|
|
}
|
|
else if (*Found ==EOpState::ChildrenPendingPartial
|
|
&&
|
|
Item.OnlyLayoutsRelevant==0)
|
|
{
|
|
Pending.Add(Item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
Ptr<ASTOp> EnsureValidMask( Ptr<ASTOp> Mask, Ptr<ASTOp> Base )
|
|
{
|
|
if ( !Mask)
|
|
{
|
|
Ptr<ASTOpConstantColor> whiteOp = new ASTOpConstantColor;
|
|
whiteOp->Value = FVector4f(1,1,1,1);
|
|
|
|
Ptr<ASTOpImagePlainColor> wplainOp = new ASTOpImagePlainColor;
|
|
wplainOp->Color = whiteOp;
|
|
wplainOp->Format = EImageFormat::L_UByte;
|
|
wplainOp->Size[0] = 4;
|
|
wplainOp->Size[1] = 4;
|
|
wplainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> ResizeOp = new ASTOpImageResizeLike;
|
|
ResizeOp->Source = wplainOp;
|
|
ResizeOp->SizeSource = Base;
|
|
|
|
Mask = ResizeOp;
|
|
}
|
|
|
|
return Mask;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
ParameterOptimiserAST::ParameterOptimiserAST(
|
|
FStateCompilationData& s,
|
|
const FModelOptimizationOptions& optimisationOptions
|
|
)
|
|
: StateProps(s)
|
|
, bModified(false)
|
|
, OptimisationOptions(optimisationOptions)
|
|
, HasRuntimeParamVisitor(&s)
|
|
{
|
|
}
|
|
|
|
|
|
bool ParameterOptimiserAST::Apply()
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(ParameterOptimiserAST);
|
|
|
|
bModified = false;
|
|
|
|
// Optimise the cloned tree
|
|
Traverse( StateProps.root );
|
|
|
|
return bModified;
|
|
}
|
|
|
|
|
|
Ptr<ASTOp> ParameterOptimiserAST::Visit( Ptr<ASTOp> At, bool& processChildren )
|
|
{
|
|
// Only process children if there are runtime parameters in the subtree
|
|
processChildren = HasRuntimeParamVisitor.HasAny(At);
|
|
|
|
EOpType type = At->GetOpType();
|
|
switch ( type )
|
|
{
|
|
//-------------------------------------------------------------------------------------
|
|
// Be careful with changing merge options and "mergesurfaces" flags
|
|
// case EOpType::ME_MERGE:
|
|
// {
|
|
// OP::MeshMergeArgs mergeArgs = program.m_code[At].args.MeshMerge;
|
|
|
|
// RuntimeParameterVisitor paramVis;
|
|
|
|
// switch ( program.m_code[ mergeArgs.base ].type )
|
|
// {
|
|
// case EOpType::ME_CONDITIONAL:
|
|
// {
|
|
// OP::ADDRESS conditionAt =
|
|
// program.m_code[ mergeArgs.base ].args.Conditional.condition;
|
|
// bool conditionConst = !paramVis.HasAny( Model.get(),
|
|
// m_state,
|
|
// conditionAt );
|
|
// if ( conditionConst )
|
|
// {
|
|
// // TODO: this may unfold mesh combinations of some models increasing the size of
|
|
// // the model data. Make this optimisation optional.
|
|
// bModified = true;
|
|
|
|
// OP yesOp = program.m_code[At];
|
|
// yesOp.args.MeshMerge.base = program.m_code[ mergeArgs.base ].args.Conditional.yes;
|
|
|
|
// OP noOp = program.m_code[At];
|
|
// noOp.args.MeshMerge.base = program.m_code[ mergeArgs.base ].args.Conditional.no;
|
|
|
|
// OP op = program.m_code[ mergeArgs.base ];
|
|
// op.args.Conditional.yes = program.AddOp( yesOp );
|
|
// op.args.Conditional.no = program.AddOp( noOp );
|
|
// At = program.AddOp( op );
|
|
// }
|
|
|
|
// break;
|
|
// }
|
|
|
|
// case EOpType::ME_MERGE:
|
|
// {
|
|
// OP::ADDRESS childBaseAt = program.m_code[ mergeArgs.base ].args.MeshMerge.base;
|
|
// bool childBaseConst = !paramVis.HasAny( Model.get(),
|
|
// m_state,
|
|
// childBaseAt );
|
|
|
|
// OP::ADDRESS childAddAt = program.m_code[ mergeArgs.base ].args.MeshMerge.added;
|
|
// bool childAddConst = !paramVis.HasAny( Model.get(),
|
|
// m_state,
|
|
// childAddAt );
|
|
|
|
// bool addConst = !paramVis.HasAny( Model.get(),
|
|
// m_state,
|
|
// mergeArgs.added );
|
|
|
|
// if ( !childBaseConst && childAddConst && addConst )
|
|
// {
|
|
// bModified = true;
|
|
|
|
// OP bottom = program.m_code[At];
|
|
// bottom.args.MeshMerge.base = childAddAt;
|
|
|
|
// OP top = program.m_code[ mergeArgs.base ];
|
|
// top.args.MeshMerge.added = program.AddOp( bottom );
|
|
// At = program.AddOp( top );
|
|
// }
|
|
// break;
|
|
// }
|
|
|
|
// default:
|
|
// break;
|
|
// }
|
|
|
|
// break;
|
|
// }
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------------------
|
|
//-------------------------------------------------------------------------------------
|
|
case EOpType::IM_CONDITIONAL:
|
|
{
|
|
const ASTOpConditional* TypedOp = static_cast<const ASTOpConditional*>(At.get());
|
|
|
|
// If the condition is not runtime, but the branches are, try to move the
|
|
// conditional down
|
|
bool optimised = false;
|
|
|
|
if ( !HasRuntimeParamVisitor.HasAny( TypedOp->condition.child() ) )
|
|
{
|
|
EOpType yesType = TypedOp->yes->GetOpType();
|
|
EOpType noType = TypedOp->no->GetOpType();
|
|
|
|
bool yesHasAny = HasRuntimeParamVisitor.HasAny( TypedOp->yes.child() );
|
|
bool noHasAny = HasRuntimeParamVisitor.HasAny( TypedOp->no.child() );
|
|
|
|
if ( !optimised && yesHasAny && noHasAny && yesType==noType)
|
|
{
|
|
switch (yesType)
|
|
{
|
|
case EOpType::IM_COMPOSE:
|
|
{
|
|
const ASTOpImageCompose* typedYes = static_cast<const ASTOpImageCompose*>(TypedOp->yes.child().get());
|
|
const ASTOpImageCompose* typedNo = static_cast<const ASTOpImageCompose*>(TypedOp->no.child().get());
|
|
if ( typedYes->BlockId == typedNo->BlockId
|
|
&&
|
|
(
|
|
(typedYes->Mask.child().get() != nullptr)
|
|
==
|
|
(typedNo->Mask.child().get() != nullptr)
|
|
)
|
|
)
|
|
{
|
|
// Move the conditional down
|
|
Ptr<ASTOpImageCompose> compOp = mu::Clone<ASTOpImageCompose>(typedYes);
|
|
|
|
Ptr<ASTOpConditional> baseCond = mu::Clone<ASTOpConditional>(TypedOp);
|
|
baseCond->yes = typedYes->Base.child();
|
|
baseCond->no = typedNo->Base.child();
|
|
compOp->Base = baseCond;
|
|
|
|
Ptr<ASTOpConditional> blockCond = mu::Clone<ASTOpConditional>(TypedOp);
|
|
blockCond->yes = typedYes->BlockImage.child();
|
|
blockCond->no = typedNo->BlockImage.child();
|
|
compOp->BlockImage = blockCond;
|
|
|
|
if (typedYes->Mask)
|
|
{
|
|
Ptr<ASTOpConditional> maskCond = mu::Clone<ASTOpConditional>(TypedOp);
|
|
maskCond->yes = typedYes->Mask.child();
|
|
maskCond->no = typedNo->Mask.child();
|
|
compOp->Mask = maskCond;
|
|
}
|
|
|
|
Ptr<ASTOpConditional> layCond = mu::Clone<ASTOpConditional>(TypedOp);
|
|
layCond->type = EOpType::LA_CONDITIONAL;
|
|
layCond->yes = typedYes->Layout.child();
|
|
layCond->no = typedNo->Layout.child();
|
|
compOp->Layout = layCond;
|
|
|
|
|
|
At = compOp;
|
|
optimised = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if ( !optimised && yesHasAny )
|
|
{
|
|
switch (yesType)
|
|
{
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayerColor* typedYes = static_cast<const ASTOpImageLayerColor*>(TypedOp->yes.child().get());
|
|
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0,0,0,1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
plainOp->Format = EImageFormat::L_UByte;
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> ResizeOp = new ASTOpImageResizeLike;
|
|
ResizeOp->Source = plainOp;
|
|
ResizeOp->SizeSource = typedYes->base.child();
|
|
|
|
Ptr<ASTOpConditional> maskOp = mu::Clone<ASTOpConditional>(TypedOp);
|
|
maskOp->no = ResizeOp;
|
|
|
|
// If there is no mask (because it is optional), we need to make a
|
|
// white plain image
|
|
maskOp->yes = EnsureValidMask( typedYes->mask.child(), typedYes->base.child() );
|
|
|
|
Ptr<ASTOpConditional> baseOp = mu::Clone<ASTOpConditional>(TypedOp);
|
|
baseOp->yes = typedYes->base.child();
|
|
|
|
Ptr<ASTOpImageLayerColor> softOp = mu::Clone<ASTOpImageLayerColor>(typedYes);
|
|
softOp->base = baseOp;
|
|
softOp->mask = maskOp;
|
|
|
|
At = softOp;
|
|
break;
|
|
}
|
|
|
|
// TODO
|
|
// It seems this is not worth since it replaces a conditional by a compose
|
|
// (but only At build time, not update?) and it introduces the use of masks
|
|
// and resize likes... plus masks can't always be used if BC formats.
|
|
// case EOpType::IM_COMPOSE:
|
|
// {
|
|
// optimised = true;
|
|
|
|
// OP blackOp;
|
|
// blackOp.type = EOpType::CO_CONSTANT;
|
|
// blackOp.args.ColourConstant.value[0] = 0;
|
|
// blackOp.args.ColourConstant.value[1] = 0;
|
|
// blackOp.args.ColourConstant.value[2] = 0;
|
|
// blackOp.args.ColourConstant.value[3] = 1;
|
|
|
|
// OP plainOp;
|
|
// plainOp.type = EOpType::IM_PLAINCOLOUR;
|
|
// plainOp.args.ImagePlainColour.colour = program.AddOp( blackOp );
|
|
// plainOp.args.ImagePlainColour.format = L_UByte;
|
|
// plainOp.args.ImagePlainColour.size = ;
|
|
// plainOp.args.ImagePlainColour.LODs = ;
|
|
|
|
// OP resizeOp;
|
|
// resizeOp.type = EOpType::IM_RESIZELIKE;
|
|
// resizeOp.args.ImageResizeLike.source = program.AddOp( plainOp );
|
|
// resizeOp.args.ImageResizeLike.sizeSource =
|
|
// program.m_code[args.yes].args.ImageCompose.blockImage;
|
|
|
|
// OP maskOp = program.m_code[At];
|
|
// maskOp.args.Conditional.no = program.AddOp( resizeOp );
|
|
|
|
// // If there is no mask (because it is optional), we need to make a
|
|
// // white plain image
|
|
// maskOp.args.Conditional.yes = EnsureValidMask
|
|
// ( program.m_code[args.yes].args.ImageCompose.mask,
|
|
// program.m_code[args.yes].args.ImageCompose.base,
|
|
// program );
|
|
|
|
// OP baseOp = program.m_code[At];
|
|
// baseOp.args.Conditional.yes =
|
|
// program.m_code[args.yes].args.ImageCompose.base;
|
|
|
|
// OP composeOp = program.m_code[args.yes];
|
|
// composeOp.args.ImageCompose.base = program.AddOp( baseOp );
|
|
// composeOp.args.ImageCompose.mask = program.AddOp( maskOp );
|
|
|
|
// At = program.AddOp( composeOp );
|
|
|
|
// // Process the new children
|
|
// At = Recurse( At, program );
|
|
// break;
|
|
// }
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
else if ( !optimised && noHasAny )
|
|
{
|
|
switch (noType)
|
|
{
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayerColor* typedNo = static_cast<const ASTOpImageLayerColor*>(TypedOp->no.child().get());
|
|
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0,0,0,1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
plainOp->Format = EImageFormat::L_UByte;
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> ResizeOp = new ASTOpImageResizeLike;
|
|
ResizeOp->Source = plainOp;
|
|
ResizeOp->SizeSource = typedNo->base.child();
|
|
|
|
Ptr<ASTOpConditional> maskOp = mu::Clone<ASTOpConditional>(TypedOp);
|
|
maskOp->no = ResizeOp;
|
|
|
|
// If there is no mask (because it is optional), we need to make a
|
|
// white plain image
|
|
maskOp->no = EnsureValidMask( typedNo->mask.child(), typedNo->base.child() );
|
|
|
|
Ptr<ASTOpConditional> baseOp = mu::Clone<ASTOpConditional>(TypedOp);
|
|
baseOp->no = typedNo->base.child();
|
|
|
|
Ptr<ASTOpImageLayerColor> softOp = mu::Clone<ASTOpImageLayerColor>(typedNo);
|
|
softOp->base = baseOp;
|
|
softOp->mask = maskOp;
|
|
|
|
At = softOp;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
bModified |= optimised;
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
case EOpType::IM_SWITCH:
|
|
{
|
|
// If the switch is not runtime, but the branches are, try to move the
|
|
// switch down
|
|
// bool optimised = false;
|
|
|
|
// OP::ADDRESS variable = program.m_code[At].args.Switch.variable;
|
|
|
|
// if ( !HasRuntimeParamVisitor.HasAny( variable, program ) )
|
|
// {
|
|
// SWITCH_CHAIN chain = GetSwitchChain( program, At );
|
|
|
|
// bool branchHasAny = false;
|
|
// EOpType branchType = (EOpType)program.m_code[program.m_code[At].args.Switch.values[0]].type;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// ++it )
|
|
// {
|
|
// if ( program.m_code[it->second].type != branchType )
|
|
// {
|
|
// branchType = EOpType::NONE;
|
|
// }
|
|
// else
|
|
// {
|
|
// if (!branchHasAny)
|
|
// {
|
|
// branchHasAny = HasRuntimeParamVisitor.HasAny( it->second, program );
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// if ( chain.def && program.m_code[chain.def].type != branchType )
|
|
// {
|
|
// branchType = EOpType::NONE;
|
|
// }
|
|
|
|
// // Some branch in runtime
|
|
// if ( branchHasAny )
|
|
// {
|
|
// switch ( branchType )
|
|
// {
|
|
|
|
// // TODO: Other operations
|
|
// case EOpType::IM_BLEND:
|
|
// case EOpType::IM_MULTIPLY:
|
|
// {
|
|
// // Move the switch down the base
|
|
// OP::ADDRESS baseAt = 0;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// )
|
|
// {
|
|
// OP bsw;
|
|
// bsw.type = EOpType::IM_SWITCH;
|
|
// bsw.args.Switch.variable = variable;
|
|
|
|
// for ( int b=0;
|
|
// it != chain.cases.end() && b<MUTABLE_OP_MAX_SWITCH_OPTIONS;
|
|
// ++b )
|
|
// {
|
|
// bsw.args.Switch.conditions[b] = it->first;
|
|
// bsw.args.Switch.values[b] =
|
|
// program.m_code[it->second].args.ImageLayer.base;
|
|
// ++it;
|
|
// }
|
|
|
|
// bsw.args.Switch.def = baseAt;
|
|
// baseAt = program.AddOp( bsw );
|
|
// }
|
|
|
|
// // Move the switch down the mask
|
|
// OP::ADDRESS maskAt = 0;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// )
|
|
// {
|
|
// OP bsw;
|
|
// bsw.type = EOpType::IM_SWITCH;
|
|
// bsw.args.Switch.variable = variable;
|
|
|
|
// for ( int b=0;
|
|
// it != chain.cases.end() && b<MUTABLE_OP_MAX_SWITCH_OPTIONS;
|
|
// ++b )
|
|
// {
|
|
// bsw.args.Switch.conditions[b] = it->first;
|
|
// bsw.args.Switch.values[b] =
|
|
// program.m_code[it->second].args.ImageLayer.mask;
|
|
// ++it;
|
|
// }
|
|
|
|
// bsw.args.Switch.def = maskAt;
|
|
// maskAt = program.AddOp( bsw );
|
|
// }
|
|
|
|
// // Move the switch down the blended
|
|
// OP::ADDRESS blendedAt = 0;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// )
|
|
// {
|
|
// OP bsw;
|
|
// bsw.type = EOpType::IM_SWITCH;
|
|
// bsw.args.Switch.variable = variable;
|
|
|
|
// for ( int b=0;
|
|
// it != chain.cases.end() && b<MUTABLE_OP_MAX_SWITCH_OPTIONS;
|
|
// ++b )
|
|
// {
|
|
// bsw.args.Switch.conditions[b] = it->first;
|
|
// bsw.args.Switch.values[b] =
|
|
// program.m_code[it->second].args.ImageLayer.blended;
|
|
// ++it;
|
|
// }
|
|
|
|
// bsw.args.Switch.def = blendedAt;
|
|
// blendedAt = program.AddOp( bsw );
|
|
// }
|
|
|
|
// OP top;
|
|
// top.type = branchType;
|
|
// top.args.ImageLayer.base = baseAt;
|
|
// top.args.ImageLayer.mask = maskAt;
|
|
// top.args.ImageLayer.blended = blendedAt;
|
|
// At = program.AddOp( top );
|
|
// optimised = true;
|
|
// break;
|
|
// }
|
|
|
|
|
|
// // TODO: Other operations
|
|
// case EOpType::IM_SOFTLIGHTCOLOUR:
|
|
// {
|
|
// // Move the switch down the base
|
|
// OP::ADDRESS baseAt = 0;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// )
|
|
// {
|
|
// OP bsw;
|
|
// bsw.type = EOpType::IM_SWITCH;
|
|
// bsw.args.Switch.variable = variable;
|
|
|
|
// for ( int b=0;
|
|
// it != chain.cases.end() && b<MUTABLE_OP_MAX_SWITCH_OPTIONS;
|
|
// ++b )
|
|
// {
|
|
// bsw.args.Switch.conditions[b] = it->first;
|
|
// bsw.args.Switch.values[b] =
|
|
// program.m_code[it->second].args.ImageLayerColour.base;
|
|
// ++it;
|
|
// }
|
|
|
|
// bsw.args.Switch.def = baseAt;
|
|
// baseAt = program.AddOp( bsw );
|
|
// }
|
|
|
|
// // Move the switch down the mask
|
|
// OP::ADDRESS maskAt = 0;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// )
|
|
// {
|
|
// OP bsw;
|
|
// bsw.type = EOpType::IM_SWITCH;
|
|
// bsw.args.Switch.variable = variable;
|
|
|
|
// for ( int b=0;
|
|
// it != chain.cases.end() && b<MUTABLE_OP_MAX_SWITCH_OPTIONS;
|
|
// ++b )
|
|
// {
|
|
// bsw.args.Switch.conditions[b] = it->first;
|
|
// bsw.args.Switch.values[b] =
|
|
// program.m_code[it->second].args.ImageLayerColour.mask;
|
|
// ++it;
|
|
// }
|
|
|
|
// bsw.args.Switch.def = maskAt;
|
|
// maskAt = program.AddOp( bsw );
|
|
// }
|
|
|
|
// // Move the switch down the colour
|
|
// OP::ADDRESS colourAt = 0;
|
|
// for ( map<uint16,OP::ADDRESS>::const_iterator it=chain.cases.begin();
|
|
// it != chain.cases.end();
|
|
// )
|
|
// {
|
|
// OP bsw;
|
|
// bsw.type = EOpType::CO_SWITCH;
|
|
// bsw.args.Switch.variable = variable;
|
|
|
|
// for ( int b=0;
|
|
// it != chain.cases.end() && b<MUTABLE_OP_MAX_SWITCH_OPTIONS;
|
|
// ++b )
|
|
// {
|
|
// bsw.args.Switch.conditions[b] = it->first;
|
|
// bsw.args.Switch.values[b] =
|
|
// program.m_code[it->second].args.ImageLayerColour.colour;
|
|
// ++it;
|
|
// }
|
|
|
|
// bsw.args.Switch.def = colourAt;
|
|
// colourAt = program.AddOp( bsw );
|
|
// }
|
|
|
|
// OP top;
|
|
// top.type = branchType;
|
|
// top.args.ImageLayerColour.base = baseAt;
|
|
// top.args.ImageLayerColour.mask = maskAt;
|
|
// top.args.ImageLayerColour.colour = colourAt;
|
|
// At = program.AddOp( top );
|
|
// optimised = true;
|
|
// break;
|
|
// }
|
|
|
|
|
|
// default:
|
|
// break;
|
|
|
|
// }
|
|
// }
|
|
|
|
// }
|
|
|
|
// bModified |= optimised;
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
case EOpType::IM_COMPOSE:
|
|
{
|
|
const ASTOpImageCompose* TypedOp = static_cast<const ASTOpImageCompose*>(At.get());
|
|
|
|
Ptr<ASTOp> blockAt = TypedOp->BlockImage.child();
|
|
Ptr<ASTOp> baseAt = TypedOp->Base.child();
|
|
Ptr<ASTOp> layoutAt = TypedOp->Layout.child();
|
|
|
|
if (!blockAt)
|
|
{
|
|
At = baseAt;
|
|
break;
|
|
}
|
|
|
|
EOpType blockType = blockAt->GetOpType();
|
|
EOpType baseType = baseAt->GetOpType();
|
|
|
|
bool baseHasRuntime = HasRuntimeParamVisitor.HasAny( baseAt );
|
|
bool blockHasRuntime = HasRuntimeParamVisitor.HasAny( blockAt );
|
|
bool layoutHasRuntime = HasRuntimeParamVisitor.HasAny( layoutAt );
|
|
|
|
bool optimised = false;
|
|
|
|
// Try to optimise base and block together, if possible
|
|
if ( blockHasRuntime && baseHasRuntime && !layoutHasRuntime )
|
|
{
|
|
if ( baseType == blockType )
|
|
{
|
|
switch ( blockType )
|
|
{
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayerColor* typedBaseAt = static_cast<const ASTOpImageLayerColor*>(baseAt.get());
|
|
const ASTOpImageLayerColor* typedBlockAt = static_cast<const ASTOpImageLayerColor*>(blockAt.get());
|
|
|
|
// The mask is a compose of the block mask on the base mask, but if none has
|
|
// a mask we don't need to make one.
|
|
Ptr<ASTOp> baseImage = typedBaseAt->base.child();
|
|
Ptr<ASTOp> baseMask = typedBaseAt->mask.child();
|
|
Ptr<ASTOp> blockImage = typedBlockAt->base.child();
|
|
Ptr<ASTOp> blockMask = typedBlockAt->mask.child();
|
|
|
|
Ptr<ASTOpImageCompose> maskOp;
|
|
if (baseMask || blockMask)
|
|
{
|
|
// This may create a discrepancy of number of mips between the base image and the mask This is for now solved with emergy fix
|
|
Ptr<ASTOp> newBaseMask = EnsureValidMask(baseMask, baseImage);
|
|
Ptr<ASTOp> newBlockMask = EnsureValidMask(blockMask, blockImage);
|
|
|
|
maskOp = mu::Clone<ASTOpImageCompose>(TypedOp);
|
|
maskOp->Base = newBaseMask;
|
|
maskOp->BlockImage = newBlockMask;
|
|
}
|
|
|
|
// The base is composition of the bases of both layer effect
|
|
Ptr<ASTOpImageCompose> baseOp = mu::Clone<ASTOpImageCompose>(TypedOp);
|
|
baseOp->Base = baseImage;
|
|
baseOp->BlockImage = blockImage;
|
|
|
|
Ptr<ASTOpImageLayerColor> nop = mu::Clone<ASTOpImageLayerColor>(blockAt);
|
|
nop->mask = maskOp;
|
|
nop->base = baseOp;
|
|
|
|
// Done
|
|
At = nop;
|
|
break;
|
|
}
|
|
|
|
case EOpType::IM_LAYER:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayer* typedBaseAt = static_cast<const ASTOpImageLayer*>(baseAt.get());
|
|
const ASTOpImageLayer* typedBlockAt = static_cast<const ASTOpImageLayer*>(blockAt.get());
|
|
|
|
// The mask is a compose of the block mask on the base mask, but if none has
|
|
// a mask we don't need to make one.
|
|
Ptr<ASTOp> baseImage = typedBaseAt->base.child();
|
|
Ptr<ASTOp> baseBlended = typedBaseAt->blend.child();
|
|
Ptr<ASTOp> baseMask = typedBaseAt->mask.child();
|
|
Ptr<ASTOp> blockImage = typedBlockAt->base.child();
|
|
Ptr<ASTOp> blockBlended = typedBlockAt->blend.child();
|
|
Ptr<ASTOp> blockMask = typedBlockAt->mask.child();
|
|
|
|
Ptr<ASTOpImageCompose> maskOp;
|
|
if (baseMask || blockMask)
|
|
{
|
|
// This may create a discrepancy of number of mips between the base image and the mask This is for now solved with emergy fix
|
|
Ptr<ASTOp> newBaseMask = EnsureValidMask(baseMask, baseImage);
|
|
Ptr<ASTOp> newBlockMask = EnsureValidMask(blockMask, blockImage);
|
|
|
|
maskOp = mu::Clone<ASTOpImageCompose>(TypedOp);
|
|
maskOp->Base = newBaseMask;
|
|
maskOp->BlockImage = newBlockMask;
|
|
}
|
|
|
|
// The base is composition of the bases of both layer effect
|
|
Ptr<ASTOpImageCompose> baseOp = mu::Clone<ASTOpImageCompose>(TypedOp);
|
|
baseOp->Base = baseImage;
|
|
baseOp->BlockImage = blockImage;
|
|
|
|
// The base is composition of the bases of both layer effect
|
|
Ptr<ASTOpImageCompose> blendedOp = mu::Clone<ASTOpImageCompose>(TypedOp);
|
|
blendedOp->Base = baseBlended;
|
|
blendedOp->BlockImage = blockBlended;
|
|
|
|
Ptr<ASTOpImageLayer> nop = mu::Clone<ASTOpImageLayer>(blockAt);
|
|
nop->mask = maskOp;
|
|
nop->base = baseOp;
|
|
nop->blend = blendedOp;
|
|
|
|
// Done
|
|
At = nop;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Swap two composes
|
|
if ( !optimised && baseHasRuntime && !blockHasRuntime
|
|
&&
|
|
baseType == EOpType::IM_COMPOSE )
|
|
{
|
|
const ASTOpImageCompose* typedBaseAt = static_cast<const ASTOpImageCompose*>(baseAt.get());
|
|
|
|
Ptr<ASTOp> baseBlockAt = typedBaseAt->BlockImage.child();
|
|
bool baseBlockHasAny = HasRuntimeParamVisitor.HasAny( baseBlockAt );
|
|
if ( baseBlockHasAny )
|
|
{
|
|
optimised = true;
|
|
|
|
// Swap
|
|
Ptr<ASTOpImageCompose> childCompose = mu::Clone<ASTOpImageCompose>(At);
|
|
childCompose->Base = typedBaseAt->Base.child();
|
|
|
|
Ptr<ASTOpImageCompose> parentCompose = mu::Clone<ASTOpImageCompose>(baseAt);
|
|
parentCompose->Base = childCompose;
|
|
|
|
At = parentCompose;
|
|
}
|
|
}
|
|
|
|
|
|
// Try to optimise the block
|
|
// This optimisation requires a lot of memory for every target. Use only if
|
|
// we are optimising for GPU processing.
|
|
if ( !optimised && blockHasRuntime && !baseHasRuntime
|
|
//&& StateProps.m_gpu.m_external
|
|
// TODO BLEH
|
|
// Only worth in case of more than one block using the same operation. Move this
|
|
// optimisation to that test.
|
|
//&& false
|
|
)
|
|
{
|
|
switch ( blockType )
|
|
{
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayerColor* typedBlockAt = static_cast<const ASTOpImageLayerColor*>(blockAt.get());
|
|
|
|
Ptr<ASTOp> blockImage = typedBlockAt->base.child();
|
|
Ptr<ASTOp> blockMask = typedBlockAt->mask.child();
|
|
|
|
// The mask is a compose of the layer mask on a black image, however if there is
|
|
// no mask and the base of the layer opertation is a blanklayout, we can skip
|
|
// generating a mask.
|
|
Ptr<ASTOpImageCompose> maskOp;
|
|
if (blockMask || baseType!=EOpType::IM_BLANKLAYOUT)
|
|
{
|
|
maskOp = mu::Clone<ASTOpImageCompose>(At);
|
|
Ptr<ASTOp> newMaskBlock = EnsureValidMask(blockMask, blockImage);
|
|
maskOp->BlockImage = newMaskBlock;
|
|
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0,0,0,1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
plainOp->Format = EImageFormat::L_UByte;
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> BaseResizeOp = new ASTOpImageResizeLike;
|
|
BaseResizeOp->SizeSource = baseAt;
|
|
BaseResizeOp->Source = plainOp;
|
|
|
|
maskOp->Base = BaseResizeOp;
|
|
}
|
|
|
|
// The base is composition of the layer base on the compose base
|
|
Ptr<ASTOpImageCompose> baseOp = mu::Clone<ASTOpImageCompose>(At);
|
|
baseOp->BlockImage = typedBlockAt->base.child();
|
|
|
|
Ptr<ASTOpImageLayerColor> nop = mu::Clone<ASTOpImageLayerColor>(blockAt);
|
|
nop->mask = maskOp;
|
|
nop->base = baseOp;
|
|
|
|
// Done
|
|
At = nop;
|
|
break;
|
|
}
|
|
|
|
case EOpType::IM_LAYER:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayer* typedBlockAt = static_cast<const ASTOpImageLayer*>(blockAt.get());
|
|
|
|
Ptr<ASTOp> blockImage = typedBlockAt->base.child();
|
|
Ptr<ASTOp> blockBlended = typedBlockAt->blend.child();
|
|
Ptr<ASTOp> blockMask = typedBlockAt->mask.child();
|
|
|
|
// The mask is a compose of the layer mask on a black image, however if there is
|
|
// no mask and the base of the layer opertation is a blanklayout, we can skip
|
|
// generating a mask.
|
|
Ptr<ASTOpImageCompose> maskOp;
|
|
if (blockMask || baseType != EOpType::IM_BLANKLAYOUT)
|
|
{
|
|
maskOp = mu::Clone<ASTOpImageCompose>(At);
|
|
Ptr<ASTOp> newMaskBlock = EnsureValidMask(blockMask, blockImage);
|
|
maskOp->BlockImage = newMaskBlock;
|
|
|
|
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0,0,0,1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
plainOp->Format = EImageFormat::L_UByte;
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> BaseResizeOp = new ASTOpImageResizeLike;
|
|
BaseResizeOp->SizeSource = baseAt;
|
|
BaseResizeOp->Source = plainOp;
|
|
|
|
maskOp->Base = BaseResizeOp;
|
|
}
|
|
|
|
// The blended is a compose of the blended image on a blank image
|
|
Ptr<ASTOpImageCompose> blendedOp = mu::Clone<ASTOpImageCompose>(At);
|
|
{
|
|
blendedOp->BlockImage = blockBlended;
|
|
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0, 0, 0, 1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
FImageDesc blendedDesc = baseAt->GetImageDesc();
|
|
plainOp->Format = blendedDesc.m_format;
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> ResizeOp = new ASTOpImageResizeLike;
|
|
ResizeOp->SizeSource = baseAt;
|
|
ResizeOp->Source = plainOp;
|
|
|
|
blendedOp->Base = ResizeOp;
|
|
}
|
|
|
|
// The base is composition of the softlight base on the compose base
|
|
Ptr<ASTOpImageCompose> baseOp = mu::Clone<ASTOpImageCompose>(At);
|
|
baseOp->BlockImage = typedBlockAt->base.child();
|
|
|
|
Ptr<ASTOpImageLayer> nop = mu::Clone<ASTOpImageLayer>(blockAt);
|
|
nop->base = baseOp;
|
|
nop->mask = maskOp;
|
|
nop->blend = blendedOp;
|
|
|
|
// Done
|
|
At = nop;
|
|
break;
|
|
}
|
|
|
|
|
|
// case EOpType::IM_INTERPOLATE:
|
|
// {
|
|
// optimised = true;
|
|
// OP op = program.m_code[blockAt];
|
|
|
|
// // The targets are composition of the block targets on the compose base
|
|
// for ( int t=0; t<MUTABLE_OP_MAX_INTERPOLATE_COUNT; ++t )
|
|
// {
|
|
// if ( op.args.ImageInterpolate.targets[t] )
|
|
// {
|
|
// OP targetOp = program.m_code[At];
|
|
// targetOp.args.ImageCompose.blockImage =
|
|
// op.args.ImageInterpolate.targets[t];
|
|
// op.args.ImageInterpolate.targets[t] = program.AddOp( targetOp );
|
|
// }
|
|
// }
|
|
|
|
// // Done
|
|
// At = program.AddOp( op );
|
|
|
|
// // Reprocess the new children
|
|
// At = Recurse( At, program );
|
|
|
|
// break;
|
|
// }
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// Try to optimise the base
|
|
if ( !optimised && baseHasRuntime /*&& StateProps.nodeState.m_optimisation.m_gpu.m_external*/ )
|
|
{
|
|
switch ( baseType )
|
|
{
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayerColor* typedBaseAt = static_cast<const ASTOpImageLayerColor*>(baseAt.get());
|
|
|
|
Ptr<ASTOpImageCompose> maskOp = mu::Clone<ASTOpImageCompose>(At);
|
|
{
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0,0,0,1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
plainOp->Format = EImageFormat::L_UByte; //TODO: FORMAT_LIKE
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> BlockResizeOp = new ASTOpImageResizeLike;
|
|
BlockResizeOp->SizeSource = blockAt;
|
|
BlockResizeOp->Source = plainOp;
|
|
|
|
// Blank out the block from the mask
|
|
Ptr<ASTOp> newMaskBase = EnsureValidMask( typedBaseAt->mask.child(), baseAt );
|
|
maskOp->Base = newMaskBase;
|
|
maskOp->BlockImage = BlockResizeOp;
|
|
}
|
|
|
|
// The base is composition of the softlight base on the compose base
|
|
Ptr<ASTOpImageCompose> baseOp = mu::Clone<ASTOpImageCompose>(At);
|
|
baseOp->Base = typedBaseAt->base.child();
|
|
|
|
Ptr<ASTOpImageLayerColor> nop = mu::Clone<ASTOpImageLayerColor>(baseAt);
|
|
nop->base = baseOp;
|
|
nop->mask = maskOp;
|
|
|
|
// Done
|
|
At = nop;
|
|
break;
|
|
}
|
|
|
|
case EOpType::IM_LAYER:
|
|
{
|
|
optimised = true;
|
|
|
|
const ASTOpImageLayer* typedBaseAt = static_cast<const ASTOpImageLayer*>(baseAt.get());
|
|
|
|
Ptr<ASTOpImageCompose> maskOp = mu::Clone<ASTOpImageCompose>(At);
|
|
{
|
|
Ptr<ASTOpConstantColor> blackOp = new ASTOpConstantColor;
|
|
blackOp->Value = FVector4f(0, 0, 0, 1);
|
|
|
|
Ptr<ASTOpImagePlainColor> plainOp = new ASTOpImagePlainColor;
|
|
plainOp->Color = blackOp;
|
|
plainOp->Format = EImageFormat::L_UByte; //TODO: FORMAT_LIKE
|
|
plainOp->Size[0] = 4;
|
|
plainOp->Size[1] = 4;
|
|
plainOp->LODs = 1;
|
|
|
|
Ptr<ASTOpImageResizeLike> BlockResizeOp = new ASTOpImageResizeLike;
|
|
BlockResizeOp->SizeSource = blockAt;
|
|
BlockResizeOp->Source = plainOp;
|
|
|
|
// Blank out the block from the mask
|
|
Ptr<ASTOp> newMaskBase = EnsureValidMask( typedBaseAt->mask.child(), baseAt );
|
|
maskOp->Base = newMaskBase;
|
|
maskOp->BlockImage = BlockResizeOp;
|
|
}
|
|
|
|
// The base is composition of the effect base on the compose base
|
|
Ptr<ASTOpImageCompose> baseOp = mu::Clone<ASTOpImageCompose>(At);
|
|
baseOp->Base = typedBaseAt->base.child();
|
|
|
|
Ptr<ASTOpImageLayer> nop = mu::Clone<ASTOpImageLayer>(baseAt);
|
|
nop->base = baseOp;
|
|
nop->mask = maskOp;
|
|
|
|
// Done
|
|
At = nop;
|
|
break;
|
|
}
|
|
|
|
|
|
// case EOpType::IM_INTERPOLATE:
|
|
// {
|
|
// optimised = true;
|
|
// OP op = program.m_code[baseAt];
|
|
|
|
// // The targets are composition of the blocks on the compose base targets
|
|
// for ( int t=0; t<MUTABLE_OP_MAX_INTERPOLATE_COUNT; ++t )
|
|
// {
|
|
// if ( op.args.ImageInterpolate.targets[t] )
|
|
// {
|
|
// OP targetOp = program.m_code[At];
|
|
// targetOp.args.ImageCompose.base =
|
|
// op.args.ImageInterpolate.targets[t];
|
|
// op.args.ImageInterpolate.targets[t] = program.AddOp( targetOp );
|
|
// }
|
|
// }
|
|
|
|
// // Done
|
|
// At = program.AddOp( op );
|
|
|
|
// // Reprocess the new children
|
|
// At = Recurse( At, program );
|
|
// break;
|
|
// }
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
bModified = bModified || optimised;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
//-----------------------------------------------------------------------------------------
|
|
// TODO: Other ops?
|
|
case EOpType::IM_BLEND:
|
|
{
|
|
OP op = program.m_code[At];
|
|
|
|
if ( !HasRuntimeParamVisitor.HasAny( op.args.ImageLayer.mask, program ) )
|
|
{
|
|
// If both the base and the blended have the same image layer operation with
|
|
// similar parameters, we can move that operation up.
|
|
EOpType baseType = program.m_code[ op.args.ImageLayer.base ].type;
|
|
EOpType blendedType = program.m_code[ op.args.ImageLayer.blended ].type;
|
|
if ( baseType == blendedType )
|
|
{
|
|
switch ( baseType )
|
|
{
|
|
case EOpType::IM_BLENDCOLOUR:
|
|
case EOpType::IM_SOFTLIGHTCOLOUR:
|
|
case EOpType::IM_HARDLIGHTCOLOUR:
|
|
case EOpType::IM_BURNCOLOUR:
|
|
case EOpType::IM_SCREENCOLOUR:
|
|
case EOpType::IM_OVERLAYCOLOUR:
|
|
case EOpType::IM_DODGECOLOUR:
|
|
case EOpType::IM_MULTIPLYCOLOUR:
|
|
{
|
|
if ( OptimisationOptions.m_optimiseOverlappedMasks )
|
|
{
|
|
OP::ADDRESS maskAt = op.args.ImageLayer.mask;
|
|
OP::ADDRESS baseMaskAt =
|
|
program.m_code[ op.args.ImageLayer.base ].args.ImageLayerColour.mask;
|
|
OP::ADDRESS blendedMaskAt =
|
|
program.m_code[ op.args.ImageLayer.blended ].args.ImageLayerColour.mask;
|
|
OP::ADDRESS baseColourAt =
|
|
program.m_code[ op.args.ImageLayer.base ].args.ImageLayerColour.colour;
|
|
OP::ADDRESS blendedColourAt =
|
|
program.m_code[ op.args.ImageLayer.blended ].args.ImageLayerColour.colour;
|
|
|
|
// Check extra conditions on the masks
|
|
if ( maskAt && baseMaskAt && blendedMaskAt
|
|
&& (baseColourAt==blendedColourAt)
|
|
&& !AreMasksOverlapping( program, baseMaskAt, blendedMaskAt )
|
|
&& !AreMasksOverlapping( program, baseMaskAt, maskAt ) )
|
|
{
|
|
// We can apply the transform
|
|
bModified = true;
|
|
|
|
OP baseOp;
|
|
baseOp.type = EOpType::IM_BLEND;
|
|
baseOp.args.ImageLayer.base =
|
|
program.m_code[ op.args.ImageLayer.base ].args.ImageLayerColour.base;
|
|
baseOp.args.ImageLayer.blended =
|
|
program.m_code[ op.args.ImageLayer.blended ].args.ImageLayerColour.base;
|
|
baseOp.args.ImageLayer.mask = maskAt;
|
|
|
|
// TODO: Find out why these are not equivalent
|
|
// OP maskOp;
|
|
// maskOp.type = EOpType::IM_SCREEN;
|
|
// maskOp.args.ImageLayer.base = baseMaskAt;
|
|
// maskOp.args.ImageLayer.blended = blendedMaskAt;
|
|
OP maskOp;
|
|
maskOp.type = EOpType::IM_BLEND;
|
|
maskOp.args.ImageLayer.base = baseMaskAt;
|
|
maskOp.args.ImageLayer.blended = blendedMaskAt;
|
|
maskOp.args.ImageLayer.mask = blendedMaskAt;
|
|
maskOp.args.ImageLayer.flags = OP::ImageLayerArgs::F_BINARY_MASK;
|
|
|
|
OP top;
|
|
top.type = baseType;
|
|
top.args.ImageLayerColour.base = program.AddOp( baseOp );
|
|
top.args.ImageLayerColour.mask = program.AddOp( maskOp );
|
|
top.args.ImageLayerColour.colour = baseColourAt;
|
|
At = program.AddOp( top );
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
// Sink the mipmap if worth it.
|
|
case EOpType::IM_MIPMAP:
|
|
{
|
|
const ASTOpImageMipmap* TypedOp = static_cast<const ASTOpImageMipmap*>(At.get());
|
|
|
|
Ptr<ASTOp> sourceOp = TypedOp->Source.child();
|
|
|
|
switch ( sourceOp->GetOpType() )
|
|
{
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
const ASTOpImageLayerColor* typedSource = static_cast<const ASTOpImageLayerColor*>(sourceOp.get());
|
|
|
|
bool colourHasRuntime = HasRuntimeParamVisitor.HasAny( typedSource->color.child() );
|
|
|
|
if (colourHasRuntime)
|
|
{
|
|
bModified = true;
|
|
|
|
Ptr<ASTOpImageLayerColor> top = mu::Clone<ASTOpImageLayerColor>(sourceOp);
|
|
|
|
Ptr<ASTOpImageMipmap> baseOp = mu::Clone<ASTOpImageMipmap>(At);
|
|
baseOp->Source = typedSource->base.child();
|
|
top->base = baseOp;
|
|
|
|
Ptr<ASTOp> sourceMaskOp = typedSource->mask.child();
|
|
if (sourceMaskOp)
|
|
{
|
|
Ptr<ASTOpImageMipmap> maskOp = mu::Clone<ASTOpImageMipmap>(At);
|
|
maskOp->Source = sourceMaskOp;
|
|
top->mask = maskOp;
|
|
}
|
|
|
|
At = top;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
return At;
|
|
}
|
|
|
|
|
|
class AccumulateAllImageFormatsOpAST
|
|
: public Visitor_TopDown_Unique_Const< std::array<uint8_t, size_t(EImageFormat::Count)> >
|
|
{
|
|
public:
|
|
|
|
void Run( const ASTOpList& roots )
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(AccumulateAllImageFormatsOpAST);
|
|
|
|
// Initially, all formats are supported
|
|
AllSupported.fill(1);
|
|
|
|
// The initial traversal state is no format supported
|
|
InitialState.fill(0);
|
|
Traverse( roots, InitialState );
|
|
}
|
|
|
|
|
|
bool Visit( const Ptr<ASTOp>& At ) override
|
|
{
|
|
bool recurse = false;
|
|
|
|
const std::array<uint8, SIZE_T(EImageFormat::Count)>& CurrentFormats = GetCurrentState();
|
|
|
|
// Remove unsupported formats
|
|
if (GetOpDataType( At->GetOpType() )==EDataType::Image)
|
|
{
|
|
std::array<uint8, SIZE_T(EImageFormat::Count)>* it = SupportedFormats.Find(At);
|
|
if (!it)
|
|
{
|
|
// Default to all supported
|
|
SupportedFormats.Add(At, AllSupported);
|
|
it = SupportedFormats.Find(At);
|
|
}
|
|
|
|
for ( int32 f=0; f< int32(EImageFormat::Count); ++f )
|
|
{
|
|
if ( !CurrentFormats[f] )
|
|
{
|
|
(*it)[f] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch ( At->GetOpType() )
|
|
{
|
|
// TODO: Code shared with the constant data format optimisation visitor
|
|
case EOpType::IM_LAYERCOLOUR:
|
|
{
|
|
const ASTOpImageLayerColor* TypedOp = static_cast<const ASTOpImageLayerColor*>(At.get());
|
|
|
|
RecurseWithCurrentState( TypedOp->base.child() );
|
|
RecurseWithCurrentState( TypedOp->color.child() );
|
|
|
|
if ( TypedOp->mask )
|
|
{
|
|
std::array<uint8, SIZE_T(EImageFormat::Count)> NewState;
|
|
NewState.fill(0);
|
|
NewState[SIZE_T(EImageFormat::L_UByte) ] = 1;
|
|
NewState[SIZE_T(EImageFormat::L_UByteRLE) ] = 1;
|
|
|
|
RecurseWithState( TypedOp->mask.child(), NewState );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EOpType::IM_LAYER:
|
|
{
|
|
const ASTOpImageLayer* TypedOp = static_cast<const ASTOpImageLayer*>(At.get());
|
|
|
|
RecurseWithCurrentState( TypedOp->base.child() );
|
|
RecurseWithCurrentState( TypedOp->blend.child() );
|
|
|
|
std::array<uint8, SIZE_T(EImageFormat::Count)> NewState;
|
|
NewState.fill(0);
|
|
// TODO
|
|
//NewState[ L_UByte ] = 1;
|
|
//NewState[ L_UByteRLE ] = 1;
|
|
|
|
if ( TypedOp->mask )
|
|
{
|
|
RecurseWithState( TypedOp->mask.child(), NewState );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EOpType::IM_DISPLACE:
|
|
{
|
|
const ASTOpImageDisplace* TypedOp = static_cast<const ASTOpImageDisplace*>(At.get());
|
|
|
|
RecurseWithCurrentState( TypedOp->Source.child() );
|
|
|
|
std::array<uint8, SIZE_T(EImageFormat::Count)> NewState;
|
|
NewState.fill(0);
|
|
NewState[SIZE_T(EImageFormat::L_UByte) ] = 1;
|
|
NewState[SIZE_T(EImageFormat::L_UByteRLE) ] = 1;
|
|
RecurseWithState( TypedOp->DisplacementMap.child(), NewState );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
SetCurrentState( InitialState );
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
return recurse;
|
|
}
|
|
|
|
|
|
bool IsSupportedFormat( Ptr<ASTOp> Op, EImageFormat Format ) const
|
|
{
|
|
const std::array<uint8, SIZE_T(EImageFormat::Count)>* it = SupportedFormats.Find(Op);
|
|
if (!it)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (*it)[SIZE_T(Format)]!=0;
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
//! Formats known to be supported for every instruction.
|
|
//! Count*code.size() entries
|
|
TMap< Ptr<ASTOp>, std::array<uint8, SIZE_T(EImageFormat::Count)> > SupportedFormats;
|
|
|
|
//! Constant convenience initial value
|
|
std::array<uint8, SIZE_T(EImageFormat::Count)> InitialState;
|
|
|
|
//! Constant convenience initial value
|
|
std::array<uint8, SIZE_T(EImageFormat::Count)> AllSupported;
|
|
|
|
};
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void SubtreeRelevantParametersVisitorAST::Run( Ptr<ASTOp> root )
|
|
{
|
|
// Cached?
|
|
TSet<FString>* it = ResultCache.Find( FState(root,false) );
|
|
if (it)
|
|
{
|
|
Parameters = *it;
|
|
return;
|
|
}
|
|
|
|
// Not cached
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(SubtreeRelevantParametersVisitorAST);
|
|
|
|
Parameters.Empty();
|
|
|
|
// The state is the onlyLayoutRelevant flag
|
|
ASTOp::Traverse_TopDown_Unique_Imprecise_WithState<bool>( root, false,
|
|
[&]( Ptr<ASTOp>& At, bool& state, TArray<TPair<Ptr<ASTOp>,bool>>& Pending )
|
|
{
|
|
(void)state;
|
|
|
|
switch ( At->GetOpType() )
|
|
{
|
|
case EOpType::NU_PARAMETER:
|
|
case EOpType::SC_PARAMETER:
|
|
case EOpType::BO_PARAMETER:
|
|
case EOpType::CO_PARAMETER:
|
|
case EOpType::PR_PARAMETER:
|
|
case EOpType::IM_PARAMETER:
|
|
case EOpType::ME_PARAMETER:
|
|
case EOpType::MA_PARAMETER:
|
|
{
|
|
const ASTOpParameter* TypedOp = static_cast<const ASTOpParameter*>(At.get());
|
|
Parameters.Add(TypedOp->Parameter.Name);
|
|
|
|
// Not interested in the parameters from the parameters decorators.
|
|
return false;
|
|
}
|
|
|
|
case EOpType::LA_FROMMESH:
|
|
{
|
|
// Manually choose how to recurse this op
|
|
const ASTOpLayoutFromMesh* TypedOp = static_cast<const ASTOpLayoutFromMesh*>(At.get());
|
|
|
|
// For that mesh we only want to know about the layouts
|
|
if (const ASTChild& Mesh = TypedOp->Mesh)
|
|
{
|
|
Pending.Add({ Mesh.Child, true });
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
case EOpType::ME_MORPH:
|
|
{
|
|
// Manually choose how to recurse this op
|
|
const ASTOpMeshMorph* TypedOp = static_cast<const ASTOpMeshMorph*>( At.get() );
|
|
|
|
if ( TypedOp->Base )
|
|
{
|
|
Pending.Add({ TypedOp->Base.Child, state });
|
|
}
|
|
|
|
// Mesh morphs don't modify the layouts, so we can ignore the factor and morphs
|
|
if (!state)
|
|
{
|
|
if ( TypedOp->Factor )
|
|
{
|
|
Pending.Add({ TypedOp->Factor.Child, state });
|
|
}
|
|
|
|
if ( TypedOp->Target )
|
|
{
|
|
Pending.Add({ TypedOp->Target.Child, state });
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
ResultCache.Add( FState(root,false), Parameters );
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//! Mark all the instructions that don't depend on runtime parameters but are below
|
|
//! instructions that do.
|
|
//! Also detect which instructions are the root of a resource that is dynamic in this state.
|
|
//! Visitor state is:
|
|
//! .first IsResourceRoot
|
|
//! .second ParentIsRuntime
|
|
//---------------------------------------------------------------------------------------------
|
|
class StateCacheDetectorAST : public Visitor_TopDown_Unique_Const< TPair<bool,bool> >
|
|
{
|
|
public:
|
|
|
|
StateCacheDetectorAST(FStateCompilationData* State )
|
|
: HasRuntimeParamVisitor( State )
|
|
{
|
|
ASTOpList roots;
|
|
roots.Add(State->root);
|
|
Traverse(roots, { false,false });
|
|
|
|
State->m_updateCache.Empty();
|
|
State->m_dynamicResources.Empty();
|
|
|
|
for( const TPair<Ptr<ASTOp>, bool>& i : Cache )
|
|
{
|
|
if ( i.Value )
|
|
{
|
|
State->m_updateCache.Add( i.Key );
|
|
}
|
|
}
|
|
|
|
for(const TPair<Ptr<ASTOp>, bool>& i : DynamicResourceRoot )
|
|
{
|
|
if ( i.Value )
|
|
{
|
|
// Generate the list of relevant parameters
|
|
SubtreeRelevantParametersVisitorAST subtreeParams;
|
|
subtreeParams.Run( i.Key );
|
|
|
|
// Temp copy
|
|
TArray<FString> ParamCopy;
|
|
for ( const FString& e: subtreeParams.Parameters )
|
|
{
|
|
ParamCopy.Add(e);
|
|
}
|
|
|
|
State->m_dynamicResources.Emplace( i.Key, MoveTemp(ParamCopy) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Visit( const Ptr<ASTOp>& At ) override
|
|
{
|
|
bool bThisIsRuntime = HasRuntimeParamVisitor.HasAny( At );
|
|
|
|
bool bResourceRoot = GetCurrentState().Key;
|
|
bool bParentIsRuntime = GetCurrentState().Value;
|
|
|
|
Cache.FindOrAdd(At, false);
|
|
|
|
EOpType Type = At->GetOpType();
|
|
if ( GetOpToolsDesc( Type ).bCached )
|
|
{
|
|
// If parent is runtime, but we are not
|
|
if ( (!bThisIsRuntime) && bParentIsRuntime
|
|
&&
|
|
// Resource roots are special, and they don't need to be marked as updateCache
|
|
// since the dynamicResource flag takes care of everything.
|
|
!bResourceRoot )
|
|
{
|
|
// We want to cache this result to update the instances.
|
|
// Mark this as update cache
|
|
Cache.Add(At, true);
|
|
}
|
|
}
|
|
|
|
if ( !Cache[At] && bResourceRoot && bThisIsRuntime )
|
|
{
|
|
DynamicResourceRoot.Add(At, true);
|
|
}
|
|
|
|
if ( !Cache[At] && bThisIsRuntime )
|
|
{
|
|
switch(Type)
|
|
{
|
|
case EOpType::IN_ADDIMAGE:
|
|
case EOpType::IN_ADDMESH:
|
|
case EOpType::IN_ADDVECTOR:
|
|
case EOpType::IN_ADDSCALAR:
|
|
case EOpType::IN_ADDSTRING:
|
|
{
|
|
const ASTOpInstanceAdd* TypedOp = static_cast<const ASTOpInstanceAdd*>(At.get());
|
|
|
|
TPair<bool,bool> NewState;
|
|
NewState.Key = false; //resource root
|
|
NewState.Value = bThisIsRuntime;
|
|
RecurseWithState( TypedOp->instance.child(), NewState );
|
|
|
|
if ( TypedOp->value )
|
|
{
|
|
NewState.Key = true; //resource root
|
|
NewState.Value = bThisIsRuntime;
|
|
RecurseWithState( TypedOp->value.child(), NewState );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
{
|
|
TPair<bool,bool> NewState;
|
|
NewState.Key = false; //resource root
|
|
NewState.Value = bThisIsRuntime;
|
|
SetCurrentState(NewState);
|
|
return true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
|
|
//!
|
|
TMap<Ptr<ASTOp>,bool> Cache;
|
|
TMap<Ptr<ASTOp>,bool> DynamicResourceRoot;
|
|
|
|
RuntimeParameterVisitorAST HasRuntimeParamVisitor;
|
|
};
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//! Find out what images can be compressed during build phase of an instance so that the update
|
|
//! cache can be smaller (and some update operations faster)
|
|
//---------------------------------------------------------------------------------------------
|
|
class StateCacheFormatOptimiserAST : public Visitor_TopDown_Unique_Cloning
|
|
{
|
|
public:
|
|
|
|
StateCacheFormatOptimiserAST(FStateCompilationData& state,
|
|
const AccumulateAllImageFormatsOpAST& opFormats )
|
|
: m_state(state)
|
|
, m_opFormats(opFormats)
|
|
{
|
|
Traverse( state.root );
|
|
}
|
|
|
|
|
|
protected:
|
|
|
|
Ptr<ASTOp> Visit( Ptr<ASTOp> At, bool& processChildren ) override
|
|
{
|
|
processChildren = true;
|
|
|
|
bool isUpdateCache = m_state.m_updateCache.Contains(At);
|
|
|
|
if ( isUpdateCache )
|
|
{
|
|
// Its children cannot be update-cache, so no need to process them.
|
|
processChildren = false;
|
|
|
|
// See if we can convert it to a more efficient format
|
|
if ( GetOpDataType( At->GetOpType() )== EDataType::Image )
|
|
{
|
|
FImageDesc desc = At->GetImageDesc();
|
|
|
|
if ( desc.m_format!=EImageFormat::L_UByteRLE
|
|
&&
|
|
m_opFormats.IsSupportedFormat(At, EImageFormat::L_UByteRLE) )
|
|
{
|
|
Ptr<ASTOpImagePixelFormat> op = new ASTOpImagePixelFormat;
|
|
op->Format = EImageFormat::L_UByteRLE;
|
|
// Note: we have to clone here, to avoid a loop with the visitor system
|
|
// that updates visited children before processing a node.
|
|
ASTOp::MapChildFunc Identity = [](const Ptr<ASTOp>& o) {return o;};
|
|
op->Source = At->Clone(Identity);
|
|
|
|
At = op;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return At;
|
|
}
|
|
|
|
private:
|
|
|
|
FStateCompilationData& m_state;
|
|
|
|
const AccumulateAllImageFormatsOpAST& m_opFormats;
|
|
};
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
RuntimeTextureCompressionRemoverAST::RuntimeTextureCompressionRemoverAST(
|
|
FStateCompilationData* State,
|
|
bool bInAlwaysUncompress
|
|
)
|
|
: HasRuntimeParamVisitor(State)
|
|
, bAlwaysUncompress(bInAlwaysUncompress)
|
|
{
|
|
Traverse( State->root );
|
|
}
|
|
|
|
|
|
Ptr<ASTOp> RuntimeTextureCompressionRemoverAST::Visit( Ptr<ASTOp> At, bool& processChildren )
|
|
{
|
|
EOpType type = At->GetOpType();
|
|
processChildren = GetOpDataType(type)== EDataType::Instance;
|
|
|
|
// TODO: Finer grained: what if the runtime parameter just selects between compressed
|
|
// textures? We don't want them uncompressed.
|
|
if( type==EOpType::IN_ADDIMAGE )
|
|
{
|
|
ASTOpInstanceAdd* TypedOp = static_cast<ASTOpInstanceAdd*>(At.get());
|
|
|
|
if (TypedOp && TypedOp->value)
|
|
{
|
|
Ptr<ASTOp> ImageOp = TypedOp->value.child();
|
|
|
|
// Does it have a runtime parameter in its subtree?
|
|
bool hasRuntimeParameter = HasRuntimeParamVisitor.HasAny(ImageOp);
|
|
|
|
if (bAlwaysUncompress || hasRuntimeParameter)
|
|
{
|
|
FImageDesc imageDesc = ImageOp->GetImageDesc( true );
|
|
|
|
// Is it a compressed format?
|
|
EImageFormat format = imageDesc.m_format;
|
|
EImageFormat uncompressedFormat = GetUncompressedFormat( format );
|
|
bool isCompressedFormat = (uncompressedFormat != format);
|
|
|
|
if (isCompressedFormat)
|
|
{
|
|
Ptr<ASTOpInstanceAdd> NewOp = mu::Clone<ASTOpInstanceAdd>(At);
|
|
|
|
// Add a new format operation to uncompress the image
|
|
Ptr<ASTOpImagePixelFormat> fop = new ASTOpImagePixelFormat;
|
|
fop->Format = uncompressedFormat;
|
|
fop->FormatIfAlpha = uncompressedFormat;
|
|
fop->Source = ImageOp;
|
|
|
|
NewOp->value = fop;
|
|
At = NewOp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return At;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
|
|
/** Recursively search for the first operation of the given type. */
|
|
class FFindMesh : public Visitor_TopDown_Unique_Const<uint8_t>
|
|
{
|
|
public:
|
|
Ptr<ASTOpInstanceAdd> Result;
|
|
|
|
FFindMesh(const ASTOpList& Roots)
|
|
{
|
|
Traverse(Roots, false);
|
|
}
|
|
|
|
private:
|
|
virtual bool Visit(const Ptr<ASTOp>& Node) override
|
|
{
|
|
if (Result)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const EOpType OpType = Node->GetOpType();
|
|
|
|
if (OpType == EOpType::IN_ADDMESH)
|
|
{
|
|
Result = static_cast<ASTOpInstanceAdd*>(Node.get());
|
|
return false;
|
|
}
|
|
|
|
if (GetOpDataType(OpType) != EDataType::Instance)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
|
|
LODCountReducerAST::LODCountReducerAST( Ptr<ASTOp>& root, uint8 NumExtraLODsToBuildAfterFirstLOD )
|
|
{
|
|
NumExtraLODs = NumExtraLODsToBuildAfterFirstLOD;
|
|
Traverse( root );
|
|
}
|
|
|
|
|
|
Ptr<ASTOp> LODCountReducerAST::Visit( Ptr<ASTOp> At, bool& processChildren )
|
|
{
|
|
processChildren = true;
|
|
|
|
if( At->GetOpType()==EOpType::IN_ADDLOD )
|
|
{
|
|
ASTOpAddLOD* TypedOp = static_cast<ASTOpAddLOD*>(At.get());
|
|
|
|
// Search for the first LOD that has a valid mesh.
|
|
const int32 FirstLOD = TypedOp->lods.IndexOfByPredicate([](const ASTChild& Element)
|
|
{
|
|
TArray<mu::Ptr<ASTOp>> Roots;
|
|
Roots.Add(Element.child());
|
|
|
|
FFindMesh SearchMesh(Roots);
|
|
|
|
return SearchMesh.Result.get() && SearchMesh.Result->value.child().get();
|
|
});
|
|
|
|
const int32 NumLODs = FirstLOD + NumExtraLODs + 1;
|
|
|
|
if (TypedOp->lods.Num() > NumLODs)
|
|
{
|
|
Ptr<ASTOpAddLOD> NewOp = mu::Clone<ASTOpAddLOD>(At);
|
|
while (NewOp->lods.Num() > NumLODs)
|
|
{
|
|
NewOp->lods.Pop();
|
|
}
|
|
At = NewOp;
|
|
}
|
|
|
|
processChildren = false;
|
|
}
|
|
|
|
return At;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
//---------------------------------------------------------------------------------------------
|
|
void CodeOptimiser::OptimiseStatesAST()
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(OptimiseStatesAST);
|
|
|
|
for ( int32 s=0; s<States.Num(); ++s )
|
|
{
|
|
// Remove the unnecessary lods
|
|
FStateOptimizationOptions StateOptimization = States[s].nodeState.Optimisation;
|
|
if (StateOptimization.bOnlyFirstLOD)
|
|
{
|
|
LODCountReducerAST(States[s].root, StateOptimization.NumExtraLODsToBuildAfterFirstLOD);
|
|
}
|
|
|
|
// Apply texture compression strategy
|
|
bool bModified = false;
|
|
switch (StateOptimization.TextureCompressionStrategy)
|
|
{
|
|
case ETextureCompressionStrategy::DontCompressRuntime:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RuntimeTextureCompressionRemover);
|
|
RuntimeTextureCompressionRemoverAST r(&States[s], false);
|
|
bModified = true;
|
|
break;
|
|
}
|
|
|
|
case ETextureCompressionStrategy::NeverCompress:
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(RuntimeTextureCompressionRemover);
|
|
RuntimeTextureCompressionRemoverAST r(&States[s], true);
|
|
bModified = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// If a state has no runtime parameters, skip its optimisation alltogether
|
|
if (bModified || States[s].nodeState.RuntimeParams.Num())
|
|
{
|
|
// Promote the intructions that depend on runtime parameters, and sink new
|
|
// format instructions.
|
|
bModified = true;
|
|
int32 NumIterations = 0;
|
|
while (bModified && ( OptimizeIterationsLeft>0 || !NumIterations))
|
|
{
|
|
bModified = false;
|
|
|
|
++NumIterations;
|
|
--OptimizeIterationsLeft;
|
|
UE_LOG(LogMutableCore, Verbose, TEXT("State optimise iteration %d, left %d"), NumIterations, OptimizeIterationsLeft);
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - before parameter optimiser"));
|
|
|
|
ParameterOptimiserAST param( States[s], Options->GetPrivate()->OptimisationOptions );
|
|
bModified = param.Apply();
|
|
|
|
TArray<Ptr<ASTOp>> Roots;
|
|
Roots.Add(States[s].root);
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - after parameter optimiser"));
|
|
|
|
// All kind of optimisations that depend on the meaning of each operation
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - semantic optimiser"));
|
|
bModified |= SemanticOptimiserAST(Roots, Options->GetPrivate()->OptimisationOptions, 1 );
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - sink optimiser"));
|
|
bModified |= SinkOptimiserAST(Roots, Options->GetPrivate()->OptimisationOptions );
|
|
|
|
// Image size operations are treated separately
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - size optimiser"));
|
|
bModified |= SizeOptimiserAST(Roots);
|
|
|
|
// Some sink optimizations can only be applied after some constant reductions
|
|
for (Ptr<ASTOp>& Root : Roots)
|
|
{
|
|
bModified |= ConstantGenerator(Options->GetPrivate(), Root, 1);
|
|
}
|
|
|
|
}
|
|
|
|
TArray<Ptr<ASTOp>> Roots;
|
|
Roots.Add(States[s].root);
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated data remover"));
|
|
bModified |= DuplicatedDataRemoverAST(Roots);
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated code remover"));
|
|
bModified |= DuplicatedCodeRemoverAST(Roots);
|
|
|
|
States[s].root = Roots[0];
|
|
}
|
|
}
|
|
|
|
|
|
TArray<Ptr<ASTOp>> Roots;
|
|
for (const FStateCompilationData& s : States)
|
|
{
|
|
Roots.Add(s.root);
|
|
}
|
|
|
|
// Mark the instructions that don't depend on runtime parameters to be cached. This is
|
|
// necessary At this stage before GPU optimisation.
|
|
{
|
|
AccumulateAllImageFormatsOpAST opFormats;
|
|
opFormats.Run(Roots);
|
|
|
|
// Reset the state root operations in case they have changed due to optimization
|
|
for (int32 RootIndex = 0; RootIndex < States.Num(); ++RootIndex)
|
|
{
|
|
States[RootIndex].root = Roots[RootIndex];
|
|
}
|
|
|
|
for (FStateCompilationData& s: States )
|
|
{
|
|
{
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - state cache"));
|
|
MUTABLE_CPUPROFILER_SCOPE(StateCache);
|
|
StateCacheDetectorAST c( &s );
|
|
}
|
|
|
|
{
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - state cache format"));
|
|
MUTABLE_CPUPROFILER_SCOPE(StateCacheFormat);
|
|
StateCacheFormatOptimiserAST f( s, opFormats );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reoptimise because of state cache reformats
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(Reoptimise);
|
|
bool bModified = true;
|
|
int32 NumIterations = 0;
|
|
int32 Pass = 1;
|
|
while (bModified && (OptimizeIterationsLeft>0 || !NumIterations))
|
|
{
|
|
++NumIterations;
|
|
--OptimizeIterationsLeft;
|
|
UE_LOG(LogMutableCore, Verbose, TEXT("State reoptimise iteration %d, left %d"), NumIterations, OptimizeIterationsLeft);
|
|
|
|
bModified = false;
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - semantic optimiser"));
|
|
bModified |= SemanticOptimiserAST( Roots, Options->GetPrivate()->OptimisationOptions, Pass );
|
|
|
|
// Image size operations are treated separately
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - size optimiser"));
|
|
bModified |= SizeOptimiserAST( Roots );
|
|
}
|
|
|
|
for(Ptr<ASTOp>& Root : Roots)
|
|
{
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - constant optimiser"));
|
|
bModified = ConstantGenerator( Options->GetPrivate(), Root, Pass );
|
|
}
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated data remover"));
|
|
DuplicatedDataRemoverAST( Roots );
|
|
|
|
UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated code remover"));
|
|
DuplicatedCodeRemoverAST( Roots );
|
|
}
|
|
|
|
// Reset the state root operations in case they have changed due to optimization
|
|
for (int32 RootIndex = 0; RootIndex < States.Num(); ++RootIndex)
|
|
{
|
|
States[RootIndex].root = Roots[RootIndex];
|
|
}
|
|
|
|
// Optimise the data formats
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(DataFormats);
|
|
|
|
DataOptimise( Options.get(), Roots);
|
|
|
|
// After optimising the data formats, we may remove more constants
|
|
DuplicatedDataRemoverAST( Roots );
|
|
DuplicatedCodeRemoverAST( Roots );
|
|
|
|
// Update the marks for the instructions that don't depend on runtime parameters to be cached.
|
|
for (FStateCompilationData& s:States)
|
|
{
|
|
StateCacheDetectorAST c( &s );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|