// 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/ASTOpMeshMorph.h" #include "MuT/ASTOpInstanceAdd.h" #include "MuT/ASTOpLayoutFromMesh.h" #include "MuT/ASTOpParameter.h" #include "MuT/CodeOptimiser.h" #include "MuT/Compiler.h" #include "MuT/CompilerPrivate.h" #include "MuT/DataPacker.h" #include "MuT/Platform.h" #include "MuR/Image.h" #include "MuR/ImagePrivate.h" #include "MuR/MutableTrace.h" #include "MuR/Operations.h" #include "MuR/ParametersPrivate.h" #include "MuR/Platform.h" #include "MuR/Ptr.h" #include "MuR/RefCounted.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 namespace mu { //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- RuntimeParameterVisitorAST::RuntimeParameterVisitorAST(const FStateCompilationData* pState) : m_pState(pState) { } bool RuntimeParameterVisitorAST::HasAny( const Ptr& root ) { if (!m_pState->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 found = false; m_pending.clear(); PENDING_ITEM start; start.at = root; start.itemType = 0; start.onlyLayoutsRelevant = 0; m_pending.push_back( start ); // Don't early out to be able to complete parent op cached flags while ( m_pending.size() ) { PENDING_ITEM item = m_pending.back(); m_pending.pop_back(); Ptr at = item.at; if (!at) { continue; } // Not cached? if ( m_visited[at]!=OP_STATE::VISITED_HASRUNTIME && m_visited[at]!=OP_STATE::VISITED_FULL_DOESNTHAVERUNTIME ) { if (item.itemType) { // Item indicating we finished with all the children of a parent check( m_visited[at]==OP_STATE::CHILDREN_PENDING_FULL || m_visited[at]==OP_STATE::CHILDREN_PENDING_PARTIAL || m_visited[at]==OP_STATE::VISITED_PARTIAL_DOESNTHAVERUNTIME ); bool subtreeFound = false; at->ForEachChild( [&](ASTChild& ref) { subtreeFound = subtreeFound || m_visited[ref.child()]==OP_STATE::VISITED_HASRUNTIME; }); if (subtreeFound) { m_visited[at] = OP_STATE::VISITED_HASRUNTIME; } else { m_visited[at] = item.onlyLayoutsRelevant ? OP_STATE::VISITED_PARTIAL_DOESNTHAVERUNTIME : OP_STATE::VISITED_FULL_DOESNTHAVERUNTIME; } } else if (!found) { // We need to process the subtree check( m_visited[at]==OP_STATE::NOT_VISITED || ( m_visited[at]==OP_STATE::VISITED_PARTIAL_DOESNTHAVERUNTIME && item.onlyLayoutsRelevant==0 ) ); // Request the processing of the end of this instruction PENDING_ITEM endItem = item; endItem.itemType = 1; m_pending.push_back( endItem ); m_visited[at] = item.onlyLayoutsRelevant ? OP_STATE::CHILDREN_PENDING_PARTIAL : OP_STATE::CHILDREN_PENDING_FULL; // Is it a special op type? switch ( at->GetOpType() ) { case OP_TYPE::BO_PARAMETER: case OP_TYPE::NU_PARAMETER: case OP_TYPE::SC_PARAMETER: case OP_TYPE::CO_PARAMETER: case OP_TYPE::PR_PARAMETER: case OP_TYPE::IM_PARAMETER: { const ASTOpParameter* typed = static_cast(at.get()); const TArray& params = m_pState->nodeState.RuntimeParams; if ( params.Find( typed->parameter.m_name) != INDEX_NONE ) { found = true; m_visited[at] = OP_STATE::VISITED_HASRUNTIME; } break; } case OP_TYPE::ME_INTERPOLATE: { const ASTOpFixed* typed = static_cast(at.get()); PENDING_ITEM childItem; childItem.itemType = 0; childItem.onlyLayoutsRelevant = item.onlyLayoutsRelevant; if ( item.onlyLayoutsRelevant==0 ) { childItem.at = typed->children[typed->op.args.MeshInterpolate.factor].child(); AddIfNeeded(childItem); } childItem.at = typed->children[typed->op.args.MeshInterpolate.base].child(); AddIfNeeded(childItem); for (int32 t=0;tchildren[typed->op.args.MeshInterpolate.targets[t]].child(); AddIfNeeded(childItem); } break; } default: { at->ForEachChild([&](ASTChild& ref) { PENDING_ITEM childItem; childItem.itemType = 0; childItem.at = ref.child(); childItem.onlyLayoutsRelevant = item.onlyLayoutsRelevant; AddIfNeeded(childItem); }); break; } } } else { // We won't process it. m_visited[at] = OP_STATE::NOT_VISITED; } } } return m_visited[root]==OP_STATE::VISITED_HASRUNTIME; } void RuntimeParameterVisitorAST::AddIfNeeded( const PENDING_ITEM& item ) { if (item.at) { if (m_visited[item.at]==OP_STATE::NOT_VISITED) { m_pending.push_back( item ); } else if (m_visited[item.at]==OP_STATE::VISITED_PARTIAL_DOESNTHAVERUNTIME && item.onlyLayoutsRelevant==0) { m_pending.push_back( item ); } else if (m_visited[item.at]==OP_STATE::CHILDREN_PENDING_PARTIAL && item.onlyLayoutsRelevant==0) { m_pending.push_back( item ); } } } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- Ptr EnsureValidMask( Ptr mask, Ptr base ) { if ( !mask ) { Ptr whiteOp = new ASTOpFixed; whiteOp->op.type = OP_TYPE::CO_CONSTANT; whiteOp->op.args.ColourConstant.value[0] = 1; whiteOp->op.args.ColourConstant.value[1] = 1; whiteOp->op.args.ColourConstant.value[2] = 1; whiteOp->op.args.ColourConstant.value[3] = 1; Ptr wplainOp = new ASTOpFixed; wplainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; wplainOp->SetChild( wplainOp->op.args.ImagePlainColour.colour, whiteOp ); wplainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; wplainOp->op.args.ImagePlainColour.size[0] = 4; wplainOp->op.args.ImagePlainColour.size[1] = 4; wplainOp->op.args.ImagePlainColour.LODs = 1; Ptr wresizeOp = new ASTOpFixed; wresizeOp->op.type = OP_TYPE::IM_RESIZELIKE; wresizeOp->SetChild( wresizeOp->op.args.ImageResizeLike.source, wplainOp ); wresizeOp->SetChild( wresizeOp->op.args.ImageResizeLike.sizeSource, base ); mask = wresizeOp; } return mask; } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- ParameterOptimiserAST::ParameterOptimiserAST( FStateCompilationData& s, const FModelOptimizationOptions& optimisationOptions ) : m_stateProps(s) , m_modified(false) , OptimisationOptions(optimisationOptions) , m_hasRuntimeParamVisitor(&s) { } bool ParameterOptimiserAST::Apply() { MUTABLE_CPUPROFILER_SCOPE(ParameterOptimiserAST); m_modified = false; // Optimise the cloned tree Traverse( m_stateProps.root ); return m_modified; } Ptr ParameterOptimiserAST::Visit( Ptr at, bool& processChildren ) { // Only process children if there are runtime parameters in the subtree processChildren = m_hasRuntimeParamVisitor.HasAny(at); OP_TYPE type = at->GetOpType(); switch ( type ) { //------------------------------------------------------------------------------------- // Be careful with changing merge options and "mergesurfaces" flags // case OP_TYPE::ME_MERGE: // { // OP::MeshMergeArgs mergeArgs = program.m_code[at].args.MeshMerge; // RuntimeParameterVisitor paramVis; // switch ( program.m_code[ mergeArgs.base ].type ) // { // case OP_TYPE::ME_CONDITIONAL: // { // OP::ADDRESS conditionAt = // program.m_code[ mergeArgs.base ].args.Conditional.condition; // bool conditionConst = !paramVis.HasAny( m_pModel.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. // m_modified = 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 OP_TYPE::ME_MERGE: // { // OP::ADDRESS childBaseAt = program.m_code[ mergeArgs.base ].args.MeshMerge.base; // bool childBaseConst = !paramVis.HasAny( m_pModel.get(), // m_state, // childBaseAt ); // OP::ADDRESS childAddAt = program.m_code[ mergeArgs.base ].args.MeshMerge.added; // bool childAddConst = !paramVis.HasAny( m_pModel.get(), // m_state, // childAddAt ); // bool addConst = !paramVis.HasAny( m_pModel.get(), // m_state, // mergeArgs.added ); // if ( !childBaseConst && childAddConst && addConst ) // { // m_modified = 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 OP_TYPE::IM_CONDITIONAL: { const ASTOpConditional* typedAt = static_cast(at.get()); // If the condition is not runtime, but the branches are, try to move the // conditional down bool optimised = false; if ( !m_hasRuntimeParamVisitor.HasAny( typedAt->condition.child() ) ) { OP_TYPE yesType = typedAt->yes->GetOpType(); OP_TYPE noType = typedAt->no->GetOpType(); bool yesHasAny = m_hasRuntimeParamVisitor.HasAny( typedAt->yes.child() ); bool noHasAny = m_hasRuntimeParamVisitor.HasAny( typedAt->no.child() ); if ( !optimised && yesHasAny && noHasAny && yesType==noType) { switch (yesType) { case OP_TYPE::IM_COMPOSE: { const ASTOpImageCompose* typedYes = static_cast(typedAt->yes.child().get()); const ASTOpImageCompose* typedNo = static_cast(typedAt->no.child().get()); if ( typedYes->BlockId == typedNo->BlockId && ( (typedYes->Mask.child().get() != nullptr) == (typedNo->Mask.child().get() != nullptr) ) ) { // Move the conditional down Ptr compOp = mu::Clone(typedYes); Ptr baseCond = mu::Clone(typedAt); baseCond->yes = typedYes->Base.child(); baseCond->no = typedNo->Base.child(); compOp->Base = baseCond; Ptr blockCond = mu::Clone(typedAt); blockCond->yes = typedYes->BlockImage.child(); blockCond->no = typedNo->BlockImage.child(); compOp->BlockImage = blockCond; if (typedYes->Mask) { Ptr maskCond = mu::Clone(typedAt); maskCond->yes = typedYes->Mask.child(); maskCond->no = typedNo->Mask.child(); compOp->Mask = maskCond; } Ptr layCond = mu::Clone(typedAt); layCond->type = OP_TYPE::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 OP_TYPE::IM_LAYERCOLOUR: { optimised = true; const ASTOpImageLayerColor* typedYes = static_cast(typedAt->yes.child().get()); Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); plainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr resizeOp = new ASTOpFixed; resizeOp->op.type = OP_TYPE::IM_RESIZELIKE; resizeOp->SetChild( resizeOp->op.args.ImageResizeLike.source, plainOp ); resizeOp->SetChild( resizeOp->op.args.ImageResizeLike.sizeSource, typedYes->base ); Ptr maskOp = mu::Clone(typedAt); 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 baseOp = mu::Clone(typedAt); baseOp->yes = typedYes->base.child(); Ptr softOp = mu::Clone(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 OP_TYPE::IM_COMPOSE: // { // optimised = true; // OP blackOp; // blackOp.type = OP_TYPE::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 = OP_TYPE::IM_PLAINCOLOUR; // plainOp.args.ImagePlainColour.colour = program.AddOp( blackOp ); // plainOp.args.ImagePlainColour.format = IF_L_UBYTE; // plainOp.args.ImagePlainColour.size = ; // plainOp.args.ImagePlainColour.LODs = ; // OP resizeOp; // resizeOp.type = OP_TYPE::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 OP_TYPE::IM_LAYERCOLOUR: { optimised = true; const ASTOpImageLayerColor* typedNo = static_cast(typedAt->no.child().get()); Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); plainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr resizeOp = new ASTOpFixed; resizeOp->op.type = OP_TYPE::IM_RESIZELIKE; resizeOp->SetChild( resizeOp->op.args.ImageResizeLike.source, plainOp ); resizeOp->SetChild( resizeOp->op.args.ImageResizeLike.sizeSource, typedNo->base ); Ptr maskOp = mu::Clone(typedAt); 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 baseOp = mu::Clone(typedAt); baseOp->no = typedNo->base.child(); Ptr softOp = mu::Clone(typedNo); softOp->base = baseOp; softOp->mask = maskOp; at = softOp; break; } default: break; } } } m_modified |= optimised; break; } //------------------------------------------------------------------------------------- case OP_TYPE::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 ( !m_hasRuntimeParamVisitor.HasAny( variable, program ) ) // { // SWITCH_CHAIN chain = GetSwitchChain( program, at ); // bool branchHasAny = false; // OP_TYPE branchType = (OP_TYPE)program.m_code[program.m_code[at].args.Switch.values[0]].type; // for ( map::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ++it ) // { // if ( program.m_code[it->second].type != branchType ) // { // branchType = OP_TYPE::NONE; // } // else // { // if (!branchHasAny) // { // branchHasAny = m_hasRuntimeParamVisitor.HasAny( it->second, program ); // } // } // } // if ( chain.def && program.m_code[chain.def].type != branchType ) // { // branchType = OP_TYPE::NONE; // } // // Some branch in runtime // if ( branchHasAny ) // { // switch ( branchType ) // { // // TODO: Other operations // case OP_TYPE::IM_BLEND: // case OP_TYPE::IM_MULTIPLY: // { // // Move the switch down the base // OP::ADDRESS baseAt = 0; // for ( map::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ) // { // OP bsw; // bsw.type = OP_TYPE::IM_SWITCH; // bsw.args.Switch.variable = variable; // for ( int b=0; // it != chain.cases.end() && bfirst; // 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::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ) // { // OP bsw; // bsw.type = OP_TYPE::IM_SWITCH; // bsw.args.Switch.variable = variable; // for ( int b=0; // it != chain.cases.end() && bfirst; // 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::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ) // { // OP bsw; // bsw.type = OP_TYPE::IM_SWITCH; // bsw.args.Switch.variable = variable; // for ( int b=0; // it != chain.cases.end() && bfirst; // 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 OP_TYPE::IM_SOFTLIGHTCOLOUR: // { // // Move the switch down the base // OP::ADDRESS baseAt = 0; // for ( map::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ) // { // OP bsw; // bsw.type = OP_TYPE::IM_SWITCH; // bsw.args.Switch.variable = variable; // for ( int b=0; // it != chain.cases.end() && bfirst; // 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::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ) // { // OP bsw; // bsw.type = OP_TYPE::IM_SWITCH; // bsw.args.Switch.variable = variable; // for ( int b=0; // it != chain.cases.end() && bfirst; // 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::const_iterator it=chain.cases.begin(); // it != chain.cases.end(); // ) // { // OP bsw; // bsw.type = OP_TYPE::CO_SWITCH; // bsw.args.Switch.variable = variable; // for ( int b=0; // it != chain.cases.end() && bfirst; // 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; // } // } // } // m_modified |= optimised; break; } //----------------------------------------------------------------------------------------- case OP_TYPE::IM_COMPOSE: { const ASTOpImageCompose* typedAt = static_cast(at.get()); Ptr blockAt = typedAt->BlockImage.child(); Ptr baseAt = typedAt->Base.child(); Ptr layoutAt = typedAt->Layout.child(); if (!blockAt) { at = baseAt; break; } OP_TYPE blockType = blockAt->GetOpType(); OP_TYPE baseType = baseAt->GetOpType(); bool baseHasRuntime = m_hasRuntimeParamVisitor.HasAny( baseAt ); bool blockHasRuntime = m_hasRuntimeParamVisitor.HasAny( blockAt ); bool layoutHasRuntime = m_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 OP_TYPE::IM_LAYERCOLOUR: { optimised = true; const ASTOpImageLayerColor* typedBaseAt = static_cast(baseAt.get()); const ASTOpImageLayerColor* typedBlockAt = static_cast(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 baseImage = typedBaseAt->base.child(); Ptr baseMask = typedBaseAt->mask.child(); Ptr blockImage = typedBlockAt->base.child(); Ptr blockMask = typedBlockAt->mask.child(); Ptr maskOp; if (baseMask || blockMask) { // \TODO: BLEH! This may create a discrepancy of number of mips between // the base image and the mask This is for now solved with emergy fix // c36adf47-e40d-490f-b709-41142bafad78 Ptr newBaseMask = EnsureValidMask(baseMask, baseImage); Ptr newBlockMask = EnsureValidMask(blockMask, blockImage); maskOp = mu::Clone(typedAt); maskOp->Base = newBaseMask; maskOp->BlockImage = newBlockMask; } // The base is composition of the bases of both layer effect Ptr baseOp = mu::Clone(typedAt); baseOp->Base = baseImage; baseOp->BlockImage = blockImage; Ptr nop = mu::Clone(blockAt); nop->mask = maskOp; nop->base = baseOp; // Done at = nop; break; } case OP_TYPE::IM_LAYER: { optimised = true; const ASTOpImageLayer* typedBaseAt = static_cast(baseAt.get()); const ASTOpImageLayer* typedBlockAt = static_cast(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 baseImage = typedBaseAt->base.child(); Ptr baseBlended = typedBaseAt->blend.child(); Ptr baseMask = typedBaseAt->mask.child(); Ptr blockImage = typedBlockAt->base.child(); Ptr blockBlended = typedBlockAt->blend.child(); Ptr blockMask = typedBlockAt->mask.child(); Ptr maskOp; if (baseMask || blockMask) { // \TODO: BLEH! This may create a discrepancy of number of mips between // the base image and the mask This is for now solved with emergy fix // c36adf47-e40d-490f-b709-41142bafad78 Ptr newBaseMask = EnsureValidMask(baseMask, baseImage); Ptr newBlockMask = EnsureValidMask(blockMask, blockImage); maskOp = mu::Clone(typedAt); maskOp->Base = newBaseMask; maskOp->BlockImage = newBlockMask; } // The base is composition of the bases of both layer effect Ptr baseOp = mu::Clone(typedAt); baseOp->Base = baseImage; baseOp->BlockImage = blockImage; // The base is composition of the bases of both layer effect Ptr blendedOp = mu::Clone(typedAt); blendedOp->Base = baseBlended; blendedOp->BlockImage = blockBlended; Ptr nop = mu::Clone(blockAt); nop->mask = maskOp; nop->base = baseOp; nop->blend = blendedOp; // Done at = nop; break; } default: break; } } } // Swap two composes if ( !optimised && baseHasRuntime && !blockHasRuntime && baseType == OP_TYPE::IM_COMPOSE ) { const ASTOpImageCompose* typedBaseAt = static_cast(baseAt.get()); Ptr baseBlockAt = typedBaseAt->BlockImage.child(); bool baseBlockHasAny = m_hasRuntimeParamVisitor.HasAny( baseBlockAt ); if ( baseBlockHasAny ) { optimised = true; // Swap Ptr childCompose = mu::Clone(at); childCompose->Base = typedBaseAt->Base.child(); Ptr parentCompose = mu::Clone(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 //&& m_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 OP_TYPE::IM_LAYERCOLOUR: { optimised = true; const ASTOpImageLayerColor* typedBlockAt = static_cast(blockAt.get()); Ptr blockImage = typedBlockAt->base.child(); Ptr 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 maskOp; if (blockMask || baseType!=OP_TYPE::IM_BLANKLAYOUT) { maskOp = mu::Clone(at); Ptr newMaskBlock = EnsureValidMask(blockMask, blockImage); maskOp->BlockImage = newMaskBlock; Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); plainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr baseResizeOp = new ASTOpFixed; baseResizeOp->op.type = OP_TYPE::IM_RESIZELIKE; baseResizeOp->SetChild( baseResizeOp->op.args.ImageResizeLike.sizeSource, baseAt ); baseResizeOp->SetChild( baseResizeOp->op.args.ImageResizeLike.source, plainOp ); maskOp->Base = baseResizeOp; } // The base is composition of the layer base on the compose base Ptr baseOp = mu::Clone(at); baseOp->BlockImage = typedBlockAt->base.child(); Ptr nop = mu::Clone(blockAt); nop->mask = maskOp; nop->base = baseOp; // Done at = nop; break; } case OP_TYPE::IM_LAYER: { optimised = true; const ASTOpImageLayer* typedBlockAt = static_cast(blockAt.get()); Ptr blockImage = typedBlockAt->base.child(); Ptr blockBlended = typedBlockAt->blend.child(); Ptr 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 maskOp; if (blockMask || baseType != OP_TYPE::IM_BLANKLAYOUT) { maskOp = mu::Clone(at); Ptr newMaskBlock = EnsureValidMask(blockMask, blockImage); maskOp->BlockImage = newMaskBlock; Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); plainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr baseResizeOp = new ASTOpFixed; baseResizeOp->op.type = OP_TYPE::IM_RESIZELIKE; baseResizeOp->SetChild( baseResizeOp->op.args.ImageResizeLike.sizeSource, baseAt ); baseResizeOp->SetChild( baseResizeOp->op.args.ImageResizeLike.source, plainOp ); maskOp->Base = baseResizeOp; } // The blended is a compose of the blended image on a blank image Ptr blendedOp = mu::Clone(at); { blendedOp->BlockImage = blockBlended; Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); FImageDesc blendedDesc = baseAt->GetImageDesc(); plainOp->op.args.ImagePlainColour.format = blendedDesc.m_format; plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr resizeOp = new ASTOpFixed; resizeOp->op.type = OP_TYPE::IM_RESIZELIKE; resizeOp->SetChild( resizeOp->op.args.ImageResizeLike.sizeSource, baseAt ); resizeOp->SetChild( resizeOp->op.args.ImageResizeLike.source, plainOp ); blendedOp->Base = resizeOp; } // The base is composition of the softlight base on the compose base Ptr baseOp = mu::Clone(at); baseOp->BlockImage = typedBlockAt->base.child(); Ptr nop = mu::Clone(blockAt); nop->base = baseOp; nop->mask = maskOp; nop->blend = blendedOp; // Done at = nop; break; } // case OP_TYPE::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(baseAt.get()); Ptr maskOp = mu::Clone(at); { Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); plainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; //TODO: FORMAT_LIKE plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr blockResizeOp = new ASTOpFixed; blockResizeOp->op.type = OP_TYPE::IM_RESIZELIKE; blockResizeOp->SetChild( blockResizeOp->op.args.ImageResizeLike.sizeSource, blockAt ); blockResizeOp->SetChild( blockResizeOp->op.args.ImageResizeLike.source, plainOp ); // Blank out the block from the mask Ptr 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 baseOp = mu::Clone(at); baseOp->Base = typedBaseAt->base.child(); Ptr nop = mu::Clone(baseAt); nop->base = baseOp; nop->mask = maskOp; // Done at = nop; break; } case OP_TYPE::IM_LAYER: { optimised = true; const ASTOpImageLayer* typedBaseAt = static_cast(baseAt.get()); Ptr maskOp = mu::Clone(at); { Ptr blackOp = new ASTOpFixed; blackOp->op.type = OP_TYPE::CO_CONSTANT; blackOp->op.args.ColourConstant.value[0] = 0; blackOp->op.args.ColourConstant.value[1] = 0; blackOp->op.args.ColourConstant.value[2] = 0; blackOp->op.args.ColourConstant.value[3] = 1; Ptr plainOp = new ASTOpFixed; plainOp->op.type = OP_TYPE::IM_PLAINCOLOUR; plainOp->SetChild( plainOp->op.args.ImagePlainColour.colour, blackOp ); plainOp->op.args.ImagePlainColour.format = EImageFormat::IF_L_UBYTE; //TODO: FORMAT_LIKE plainOp->op.args.ImagePlainColour.size[0] = 4; plainOp->op.args.ImagePlainColour.size[1] = 4; plainOp->op.args.ImagePlainColour.LODs = 1; Ptr blockResizeOp = new ASTOpFixed; blockResizeOp->op.type = OP_TYPE::IM_RESIZELIKE; blockResizeOp->SetChild( blockResizeOp->op.args.ImageResizeLike.sizeSource, blockAt ); blockResizeOp->SetChild( blockResizeOp->op.args.ImageResizeLike.source, plainOp ); // Blank out the block from the mask Ptr 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 baseOp = mu::Clone(at); baseOp->Base = typedBaseAt->base.child(); Ptr nop = mu::Clone(baseAt); nop->base = baseOp; nop->mask = maskOp; // Done at = nop; break; } // case OP_TYPE::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(at.get()); Ptr sourceOp = typedAt->Source.child(); switch ( sourceOp->GetOpType() ) { case OP_TYPE::IM_LAYERCOLOUR: { const ASTOpImageLayerColor* typedSource = static_cast(sourceOp.get()); bool colourHasRuntime = m_hasRuntimeParamVisitor.HasAny( typedSource->color.child() ); if (colourHasRuntime) { m_modified = true; Ptr top = mu::Clone(sourceOp); Ptr baseOp = mu::Clone(at); baseOp->Source = typedSource->base.child(); top->base = baseOp; Ptr sourceMaskOp = typedSource->mask.child(); if (sourceMaskOp) { Ptr maskOp = mu::Clone(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 > { public: void Run( const ASTOpList& roots ) { MUTABLE_CPUPROFILER_SCOPE(AccumulateAllImageFormatsOpAST); // Initially, all formats are supported m_allSupported.fill(1); // The initial traversal state is no format supported m_initialState.fill(0); Traverse( roots, m_initialState ); } bool Visit( const Ptr& at ) override { bool recurse = false; const std::array& currentFormats = GetCurrentState(); // Remove unsupported formats if (GetOpDataType( at->GetOpType() )==DT_IMAGE) { std::array* it = m_supportedFormats.Find(at); if (!it) { // Default to all supported m_supportedFormats.Add(at, m_allSupported); it = m_supportedFormats.Find(at); } for ( unsigned f=0; f< (unsigned)EImageFormat::IF_COUNT; ++f ) { if ( !currentFormats[f] ) { (*it)[f] = 0; } } } switch ( at->GetOpType() ) { // TODO: Code shared with the constant data format optimisation visitor case OP_TYPE::IM_LAYERCOLOUR: { const ASTOpImageLayerColor* typedAt = static_cast(at.get()); RecurseWithCurrentState( typedAt->base.child() ); RecurseWithCurrentState( typedAt->color.child() ); if ( typedAt->mask ) { std::array newState; newState.fill(0); newState[(size_t)EImageFormat::IF_L_UBYTE ] = 1; newState[(size_t)EImageFormat::IF_L_UBYTE_RLE ] = 1; RecurseWithState( typedAt->mask.child(), newState ); } break; } case OP_TYPE::IM_LAYER: { const ASTOpImageLayer* typedAt = static_cast(at.get()); RecurseWithCurrentState( typedAt->base.child() ); RecurseWithCurrentState( typedAt->blend.child() ); std::array newState; newState.fill(0); // TODO //newState[ IF_L_UBYTE ] = 1; //newState[ IF_L_UBYTE_RLE ] = 1; if ( typedAt->mask ) { RecurseWithState( typedAt->mask.child(), newState ); } break; } case OP_TYPE::IM_DISPLACE: { const ASTOpFixed* typedAt = static_cast(at.get()); RecurseWithCurrentState( typedAt->children[typedAt->op.args.ImageDisplace.source].child() ); std::array newState; newState.fill(0); newState[(size_t)EImageFormat::IF_L_UBYTE ] = 1; newState[(size_t)EImageFormat::IF_L_UBYTE_RLE ] = 1; RecurseWithState( typedAt->children[typedAt->op.args.ImageDisplace.displacementMap].child(), newState ); break; } default: SetCurrentState( m_initialState ); recurse = true; break; } return recurse; } bool IsSupportedFormat( Ptr at, EImageFormat format ) const { const std::array* it = m_supportedFormats.Find(at); if (!it) { return false; } return (*it)[(size_t)format]!=0; } private: //! Formats known to be supported for every instruction. //! IF_COUNT*code.size() entries TMap< Ptr, std::array > m_supportedFormats; //! Constant convenience initial value std::array m_initialState; //! Constant convenience initial value std::array m_allSupported; }; //--------------------------------------------------------------------------------------------- void SubtreeRelevantParametersVisitorAST::Run( Ptr root ) { // Cached? TSet* it = m_resultCache.Find( FState(root,false) ); if (it) { m_params = *it; return; } // Not cached { MUTABLE_CPUPROFILER_SCOPE(SubtreeRelevantParametersVisitorAST); m_params.Empty(); // The state is the onlyLayoutRelevant flag ASTOp::Traverse_TopDown_Unique_Imprecise_WithState( root, false, [&]( Ptr& at, bool& state, TArray,bool>>& pending ) { (void)state; switch ( at->GetOpType() ) { case OP_TYPE::NU_PARAMETER: case OP_TYPE::SC_PARAMETER: case OP_TYPE::BO_PARAMETER: case OP_TYPE::CO_PARAMETER: case OP_TYPE::PR_PARAMETER: case OP_TYPE::IM_PARAMETER: { const ASTOpParameter* typedAt = static_cast(at.get()); m_params.Add(typedAt->parameter.m_name); // Not interested in the parameters from the parameters decorators. return false; break; } case OP_TYPE::LA_FROMMESH: { // Manually choose how to recurse this op const ASTOpLayoutFromMesh* pTyped = static_cast(at.get()); // For that mesh we only want to know about the layouts if (const ASTChild& Mesh = pTyped->Mesh) { pending.Add({ Mesh.m_child, true }); } return false; } case OP_TYPE::ME_MORPH: { // Manually choose how to recurse this op const ASTOpMeshMorph* pTyped = static_cast( at.get() ); if ( pTyped->Base ) { pending.Add({ pTyped->Base.m_child, state }); } // Mesh morphs don't modify the layouts, so we can ignore the factor and morphs if (!state) { if ( pTyped->Factor ) { pending.Add({ pTyped->Factor.m_child, state }); } if ( pTyped->Target ) { pending.Add({ pTyped->Target.m_child, state }); } } return false; } default: break; } return true; }); m_resultCache.Add( FState(root,false), m_params ); } } //--------------------------------------------------------------------------------------------- //! 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 > { public: StateCacheDetectorAST(FStateCompilationData* pState ) : m_hasRuntimeParamVisitor( pState ) { ASTOpList roots; roots.Add(pState->root); Traverse(roots, { false,false }); pState->m_updateCache.Empty(); pState->m_dynamicResources.Empty(); for( const TPair, bool>& i : m_cache ) { if ( i.Value ) { pState->m_updateCache.Add( i.Key ); } } for(const TPair, bool>& i : m_dynamicResourceRoot ) { if ( i.Value ) { // Generate the list of relevant parameters SubtreeRelevantParametersVisitorAST subtreeParams; subtreeParams.Run( i.Key ); // Temp copy TArray ParamCopy; for ( const FString& e: subtreeParams.m_params ) { ParamCopy.Add(e); } pState->m_dynamicResources.Emplace( i.Key, MoveTemp(ParamCopy) ); } } } bool Visit( const Ptr& at ) override { bool thisIsRuntime = m_hasRuntimeParamVisitor.HasAny( at ); bool resourceRoot = GetCurrentState().Key; bool parentIsRuntime = GetCurrentState().Value; m_cache.FindOrAdd(at, false); OP_TYPE type = at->GetOpType(); if ( GetOpDesc( type ).cached ) { // If parent is runtime, but we are not if ( (!thisIsRuntime) && parentIsRuntime && // Resource roots are special, and they don't need to be marked as updateCache // since the dynamicResource flag takes care of everything. !resourceRoot ) { // We want to cache this result to update the instances. // Mark this as update cache m_cache.Add(at, true); } } if ( !m_cache[at] && resourceRoot && thisIsRuntime ) { m_dynamicResourceRoot.Add(at, true); } if ( !m_cache[at] && thisIsRuntime ) { switch( type ) { case OP_TYPE::IN_ADDIMAGE: case OP_TYPE::IN_ADDMESH: case OP_TYPE::IN_ADDVECTOR: case OP_TYPE::IN_ADDSCALAR: case OP_TYPE::IN_ADDSTRING: { const ASTOpInstanceAdd* typedAt = static_cast(at.get()); TPair newState; newState.Key = false; //resource root newState.Value = thisIsRuntime; RecurseWithState( typedAt->instance.child(), newState ); if ( typedAt->value ) { newState.Key = true; //resource root newState.Value = thisIsRuntime; RecurseWithState( typedAt->value.child(), newState ); } return false; } default: { TPair newState; newState.Key = false; //resource root newState.Value = thisIsRuntime; SetCurrentState(newState); return true; } } } return false; } private: //! TMap,bool> m_cache; TMap,bool> m_dynamicResourceRoot; RuntimeParameterVisitorAST m_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 Visit( Ptr 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() )==DT_IMAGE ) { FImageDesc desc = at->GetImageDesc(); if ( desc.m_format!=EImageFormat::IF_L_UBYTE_RLE && m_opFormats.IsSupportedFormat(at, EImageFormat::IF_L_UBYTE_RLE) ) { Ptr op = new ASTOpImagePixelFormat; op->Format = EImageFormat::IF_L_UBYTE_RLE; // 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& o) {return o;}; op->Source = at->Clone(Identity); at = op; } } } return at; } private: FStateCompilationData& m_state; const AccumulateAllImageFormatsOpAST& m_opFormats; }; //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- RuntimeTextureCompressionRemoverAST::RuntimeTextureCompressionRemoverAST( FStateCompilationData* pState, bool bInAlwaysUncompress ) : m_hasRuntimeParamVisitor(pState) , bAlwaysUncompress(bInAlwaysUncompress) { Traverse( pState->root ); } Ptr RuntimeTextureCompressionRemoverAST::Visit( Ptr at, bool& processChildren ) { OP_TYPE type = at->GetOpType(); processChildren = GetOpDataType(type)==DT_INSTANCE; // TODO: Finer grained: what if the runtime parameter just selects between compressed // textures? We don't want them uncompressed. if( type==OP_TYPE::IN_ADDIMAGE ) { ASTOpInstanceAdd* typedAt = static_cast(at.get()); Ptr imageAt = typedAt->value.child(); // Does it have a runtime parameter in its subtree? bool hasRuntimeParameter = m_hasRuntimeParamVisitor.HasAny( imageAt ); if (bAlwaysUncompress || hasRuntimeParameter) { FImageDesc imageDesc = imageAt->GetImageDesc( true ); // Is it a compressed format? EImageFormat format = imageDesc.m_format; EImageFormat uncompressedFormat = GetUncompressedFormat( format ); bool isCompressedFormat = (uncompressedFormat != format); if (isCompressedFormat) { Ptr newAt = mu::Clone(at); // Add a new format operation to uncompress the image Ptr fop = new ASTOpImagePixelFormat; fop->Format = uncompressedFormat; fop->FormatIfAlpha = uncompressedFormat; fop->Source = imageAt; newAt->value = fop; at = newAt; } } } return at; } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- LODCountReducerAST::LODCountReducerAST( Ptr& root, int32 lodCount ) { m_lodCount = lodCount; Traverse( root ); } Ptr LODCountReducerAST::Visit( Ptr at, bool& processChildren ) { processChildren = true; if( at->GetOpType()==OP_TYPE::IN_ADDLOD ) { ASTOpAddLOD* typedAt = static_cast(at.get()); if (typedAt->lods.Num()>(size_t)m_lodCount) { Ptr newAt = mu::Clone(at); while( newAt->lods.Num()>(size_t)m_lodCount ) { newAt->lods.Pop(); } at = newAt; } processChildren = false; } return at; } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- void CodeOptimiser::OptimiseStatesAST() { MUTABLE_CPUPROFILER_SCOPE(OptimiseStatesAST); for ( int32 s=0; s0 || !numIterations )) { modified = false; ++numIterations; --m_optimizeIterationsLeft; UE_LOG(LogMutableCore, Verbose, TEXT("State optimise iteration %d, max %d, left %d"), numIterations, m_optimizeIterationsMax, m_optimizeIterationsLeft); UE_LOG(LogMutableCore, Verbose, TEXT(" - before parameter optimiser")); ParameterOptimiserAST param( m_states[s], m_options->GetPrivate()->OptimisationOptions ); modified = param.Apply(); TArray> roots; roots.Add(m_states[s].root); UE_LOG(LogMutableCore, Verbose, TEXT(" - after parameter optimiser")); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); // All kind of optimisations that depend on the meaning of each operation UE_LOG(LogMutableCore, Verbose, TEXT(" - semantic optimiser")); modified |= SemanticOptimiserAST( roots, m_options->GetPrivate()->OptimisationOptions, 1 ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); //ASTOp::LogHistogram(roots); UE_LOG(LogMutableCore, Verbose, TEXT(" - sink optimiser")); modified |= SinkOptimiserAST( roots, m_options->GetPrivate()->OptimisationOptions ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); //ASTOp::LogHistogram(roots); // Image size operations are treated separately UE_LOG(LogMutableCore, Verbose, TEXT(" - size optimiser")); modified |= SizeOptimiserAST( roots ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); //LogicOptimiser log; //modified |= log.Apply( program, s ); } TArray> roots; roots.Add(m_states[s].root); UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated data remover")); DuplicatedDataRemoverAST( roots ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated code remover")); DuplicatedCodeRemoverAST( roots ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); m_states[s].root = roots[0]; } } TArray> roots; for (const FStateCompilationData& s : m_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 < m_states.Num(); ++RootIndex) { m_states[RootIndex].root = roots[RootIndex]; } for (FStateCompilationData& s: m_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 and gpuization { MUTABLE_CPUPROFILER_SCOPE(Reoptimise); bool modified = true; int32 numIterations = 0; int32 Pass = 1; while (modified && (!m_optimizeIterationsMax || m_optimizeIterationsLeft>0 || !numIterations )) { ++numIterations; --m_optimizeIterationsLeft; UE_LOG(LogMutableCore, Verbose, TEXT("State reoptimise iteration %d, max %d, left %d"), numIterations, m_optimizeIterationsMax, m_optimizeIterationsLeft); modified = false; UE_LOG(LogMutableCore, Verbose, TEXT(" - semantic optimiser")); modified |= SemanticOptimiserAST( roots, m_options->GetPrivate()->OptimisationOptions, Pass ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); // Image size operations are treated separately UE_LOG(LogMutableCore, Verbose, TEXT(" - size optimiser")); modified |= SizeOptimiserAST( roots ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); } for(Ptr& Root : roots) { UE_LOG(LogMutableCore, Verbose, TEXT(" - constant optimiser")); modified = ConstantGeneratorAST( m_options->GetPrivate(), Root, Pass ); } //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated data remover")); DuplicatedDataRemoverAST( roots ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); UE_LOG(LogMutableCore, Verbose, TEXT(" - duplicated code remover")); DuplicatedCodeRemoverAST( roots ); //UE_LOG(LogMutableCore, Verbose, TEXT("(int) %s : %ld"), TEXT("ast size"), int64(ASTOp::CountNodes(roots))); } // Reset the state root operations in case they have changed due to optimization for (int32 RootIndex = 0; RootIndex < m_states.Num(); ++RootIndex) { m_states[RootIndex].root = roots[RootIndex]; } // Optimise the data formats { MUTABLE_CPUPROFILER_SCOPE(DataFormats); DataOptimise( m_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:m_states) { StateCacheDetectorAST c( &s ); } } } }