// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "HAL/PlatformMath.h" #include "HAL/UnrealMemory.h" #include "Misc/AssertionMacros.h" #include "MuR/Model.h" #include "MuR/ModelPrivate.h" #include "MuR/Operations.h" #include "MuR/ParametersPrivate.h" #include "MuR/Ptr.h" #include "MuR/System.h" #include "MuR/SystemPrivate.h" namespace mu { /** Decide what operations are an "add resource" since they are handled differently sometimes. */ inline bool VisitorIsAddResource(const EOpType& type) { return type == EOpType::IN_ADDIMAGE || type == EOpType::IN_ADDMESH; } /** Code visitor that: * - is top-down * - cannot change the visited instructions. * - will not visit twice the same instruction with the same state. * - Its iterative */ template class UniqueConstCodeVisitorIterative { public: UniqueConstCodeVisitorIterative( bool bInSkipResources=false ) { // Default state States.Add(STATE()); CurrentState = 0; bSkipResources = bInSkipResources; } //! Ensure virtual destruction virtual ~UniqueConstCodeVisitorIterative() {} protected: //! void SetDefaultState(const STATE& State) { States[0] = State; } //! const STATE& GetDefaultState() const { return States[0]; } //! Use this from visit to access the state at the time of processing the current //! instruction. STATE GetCurrentState() const { return States[CurrentState]; } //! For manual recursion that changes the state for a specific path. void RecurseWithState(OP::ADDRESS Address, const STATE& NewState) { int32 StateIndex = States.Find(NewState); if (StateIndex==INDEX_NONE) { StateIndex = States.Add(NewState); } Pending.Emplace( Address, StateIndex ); } //! For manual recursion that doesn't change the state for a specific path. void RecurseWithCurrentState(OP::ADDRESS Address) { Pending.Emplace( Address, CurrentState ); } //! Can be called from visit to set the state to visit all children ops void SetCurrentState(const STATE& NewState) { CurrentState = States.Find(NewState); if (CurrentState ==INDEX_NONE) { CurrentState = States.Add(NewState); } } void Traverse( OP::ADDRESS Root, FProgram& Program ) { Pending.Reserve( Program.OpAddress.Num() ); // Visit the given root Pending.Emplace(Root, 0); Recurse( Program ); } void FullTraverse( FProgram& Program ) { // Visit all the state roots for ( int32 StateIndex=0; StateIndex Pending; //! States found so far TArray States; //! Index of the current state, from the States array. int32 CurrentState; //! If true, operations adding resources (meshes or images) will only //! have the base operation recursed, but not the resources. bool bSkipResources; //! Array of states visited for each operation. //! Empty array means operation not visited at all. TArray> Visited; //! Process all the pending operations and visit all children if necessary void Recurse( FProgram& Program ) { Visited.Empty(); Visited.SetNum(Program.OpAddress.Num()); while ( Pending.Num() ) { OP::ADDRESS Address = Pending.Last().Address; CurrentState = Pending.Last().StateIndex; Pending.Pop(); bool bRecurse = false; bool bVisitedInThisState = Visited[Address].Contains(CurrentState); if (!bVisitedInThisState) { Visited[Address].Add(CurrentState); // Visit may change current state bRecurse = Visit(Address, Program ); } if (bRecurse) { ForEachReference( Program, Address, [&](OP::ADDRESS Ref) { if (Ref) { Pending.Emplace(Ref, CurrentState); } }); } } } }; //--------------------------------------------------------------------------------------------- //! Code visitor template for visitors that: //! - only traverses the operations that are relevant for a given set of parameter values. It //! only considers the discrete parameters like integers and booleans. In the case of forks //! caused by continuous parameters like float weights for interpolation, all the branches are //! traversed. //! - cannot change the instructinos //--------------------------------------------------------------------------------------------- struct COVERED_CODE_VISITOR_STATE { uint16 m_underResourceCount = 0; bool operator==(const COVERED_CODE_VISITOR_STATE& o) const { return m_underResourceCount==o.m_underResourceCount; } }; template class DiscreteCoveredCodeVisitorBase : public PARENT { public: DiscreteCoveredCodeVisitorBase ( FSystem::Private* InSystem, const TSharedPtr& InModel, const TSharedPtr& InParams, unsigned InLodMask, bool bSkipResources=false ) : PARENT(bSkipResources) { System = InSystem; Model = InModel; Params = InParams.Get(); LODMask = InLodMask; // Visiting state PARENT::SetDefaultState( STATE() ); } void Run( OP::ADDRESS at ) { PARENT::SetDefaultState( STATE() ); PARENT::Traverse( at, Model->GetPrivate()->Program ); } protected: virtual bool Visit( OP::ADDRESS Address, FProgram& Program ) { bool bRecurse = true; EOpType Type = Program.GetOpType(Address); switch ( Type ) { case EOpType::NU_CONDITIONAL: case EOpType::SC_CONDITIONAL: case EOpType::CO_CONDITIONAL: case EOpType::IM_CONDITIONAL: case EOpType::ME_CONDITIONAL: case EOpType::LA_CONDITIONAL: case EOpType::IN_CONDITIONAL: case EOpType::ED_CONDITIONAL: { OP::ConditionalArgs Args = Program.GetOpArgs(Address); bRecurse = false; PARENT::RecurseWithCurrentState( Args.condition ); // If there is no expression, we'll assume true. bool bValue = true; if (Args.condition) { bValue = System->BuildBool(Model, Params, Args.condition); } if (bValue) { PARENT::RecurseWithCurrentState( Args.yes ); } else { PARENT::RecurseWithCurrentState( Args.no ); } break; } case EOpType::NU_SWITCH: case EOpType::SC_SWITCH: case EOpType::CO_SWITCH: case EOpType::IM_SWITCH: case EOpType::ME_SWITCH: case EOpType::LA_SWITCH: case EOpType::IN_SWITCH: case EOpType::ED_SWITCH: { bRecurse = false; const uint8* Data = Program.GetOpArgsPointer(Address); OP::ADDRESS VarAddress; FMemory::Memcpy( &VarAddress, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); if (VarAddress) { OP::ADDRESS DefAddress; FMemory::Memcpy( &DefAddress, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); uint32 CaseCount; FMemory::Memcpy( &CaseCount, Data, sizeof(uint32)); Data += sizeof(uint32); PARENT::RecurseWithCurrentState( VarAddress ); int32 var = System->BuildInt( Model, Params, VarAddress ); OP::ADDRESS valueAt = DefAddress; for (uint32 CaseIndex = 0; CaseIndex < CaseCount; ++CaseIndex) { int32 Condition; FMemory::Memcpy( &Condition, Data, sizeof(int32)); Data += sizeof(int32); OP::ADDRESS At; FMemory::Memcpy( &At, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); if (At && var == (int32)Condition) { valueAt = At; break; } } PARENT::RecurseWithCurrentState( valueAt ); } break; } case EOpType::IN_ADDLOD: { bRecurse = false; const uint8* Data = Program.GetOpArgsPointer(Address); uint8 LODCount; FMemory::Memcpy(&LODCount, Data, sizeof(uint8)); Data += sizeof(uint8); STATE NewState = PARENT::GetCurrentState(); for (int8 LODIndex=0; LODIndex < LODCount;++LODIndex) { OP::ADDRESS LODAddress; FMemory::Memcpy(&LODAddress, Data, sizeof(OP::ADDRESS)); Data += sizeof(OP::ADDRESS); if (LODAddress) { bool bSelected = ( (1<< LODIndex) & LODMask ) != 0; if (bSelected) { PARENT::RecurseWithState(LODAddress, NewState); } } } break; } case EOpType::IN_ADDMESH: { OP::InstanceAddArgs Args = Program.GetOpArgs(Address); bRecurse = false; PARENT::RecurseWithCurrentState(Args.instance); STATE NewState = PARENT::GetCurrentState(); NewState.m_underResourceCount=1; OP::ADDRESS MeshAddress = Args.value; if (MeshAddress) { PARENT::RecurseWithState(MeshAddress, NewState); } break; } case EOpType::IN_ADDIMAGE: { OP::InstanceAddArgs Args = Program.GetOpArgs(Address); bRecurse = false; PARENT::RecurseWithCurrentState(Args.instance); STATE NewState = PARENT::GetCurrentState(); NewState.m_underResourceCount=1; OP::ADDRESS ImageAddress = Args.value; if (ImageAddress) { PARENT::RecurseWithState(ImageAddress, NewState); } break; } default: break; } return bRecurse; } protected: FSystem::Private* System = nullptr; TSharedPtr Model; const FParameters* Params = nullptr; uint32 LODMask = 0; }; /** Code visitor that : * - only traverses the operations that are relevant for a given set of parameter values. It * only considers the discrete parameters like integers and booleans. In the case of forks * caused by continuous parameters like float weights for interpolation, all the branches are * traversed. * - cannot change the instructions * - will not repeat visits to instructions with the same state * - the state has to be a compatible with COVERED_CODE_VISITOR_STATE */ template class UniqueDiscreteCoveredCodeVisitor : public DiscreteCoveredCodeVisitorBase < UniqueConstCodeVisitorIterative, COVERED_STATE > { using PARENT=DiscreteCoveredCodeVisitorBase, COVERED_STATE>; public: UniqueDiscreteCoveredCodeVisitor ( FSystem::Private* InSystem, const TSharedPtr& InModel, const TSharedPtr& InParams, uint32 InLodMask ) : PARENT(InSystem, InModel, InParams, InLodMask ) { } }; }