Multi-Phase Custom ICENode

 
 
 

The multi-phase processing model allows custom nodes to process the input data in single or multiple phases, where each phase can contain the input ports to process grouped in similar contexts. One of the main goals of the multi-phase evaluation model is to optimize the way input ports are evaluated. For instance, you could pull one port in phase 0 and then decide which ports to pull in subsequent phases based on the data read in phase 0.

The multi-phase evaluation model has the following characteristics:

Restrictions

  • Ports to pull must share the same context type, otherwise an error is raised. You can however mix singleton contexts with the phase context.

  • Ports to pull in the last phase must match the context type of the output port, otherwise an error is raised.

  • All phases but the last cannot be submitted without pulling ports.

  • Ports must be pulled first in order to be accessible from the Evaluate callback.

  • The SubmitEvaluationPhaseInfo callback must be supplied.

Warning

It is crucial to follow these rules. Otherwise, your custom node can become unstable.

How does it work?

The following workflow illustrates the implementation of a typical multi-phase custom ICENode (see for a more detailed example):

  1. Register the node as multi-phase.

  2. Implement the SubmitEvaluationPhaseInfo callback.

  3. Implement the Evaluate callback.

Note

The BeginEvaluate and EndEvaluate callbacks are still optional and are only called during the last evaluation phase.

To register a multi-phase custom node

Use the ICE node definition to set the threading model to multi-phase (siICENodeMultiEvaluationPhase):

CStatus RegisterMultiPhaseSample( PluginRegistrar& in_reg )
{
	ICENodeDef nodeDef;
	nodeDef = Application().GetFactory().CreateICENodeDef(L"MultiPhaseSample",L"Multi Phase Sample");
	
	CStatus st;
	st = nodeDef.PutThreadingModel(XSI::siICENodeMultiEvaluationPhase);
	st.AssertSucceeded( ) ;
	
	// Add input ports and groups.
	st = nodeDef.AddPortGroup(ID_G_100);
	st.AssertSucceeded( ) ;
	
	st = nodeDef.AddPortGroup(ID_G_102);
	st.AssertSucceeded( ) ;
	
	st = nodeDef.AddPortGroup(ID_G_103);
	st.AssertSucceeded( ) ;
	
	st = nodeDef.AddPortGroup(ID_G_114);
	st.AssertSucceeded( ) ;
	
	st = nodeDef.AddInputPort(ID_IN_condition,ID_G_100,siICENodeDataBool,siICENodeStructureSingle,siICENodeContextSingleton,L"condition",L"condition",true); 
	st.AssertSucceeded( ) ;
	st = nodeDef.AddInputPort(ID_IN_true,ID_G_102,siICENodeDataFloat,siICENodeStructureSingle,siICENodeContextSingleton,L"true",L"true",1.0f);
	st.AssertSucceeded( ) ;
	
	st = nodeDef.AddInputPort(ID_IN_false,ID_G_103,siICENodeDataFloat,siICENodeStructureSingle,siICENodeContextSingleton,L"false",L"false",1.0f);
	st.AssertSucceeded( ) ;
	
	st =nodeDef.AddInputPort(ID_IN_points,ID_G_114,siICENodeDataVector3,siICENodeStructureSingle,siICENodeContextComponent0D|siICENodeContextSingleton,L"points",L"points",MATH::CVector3f(1.0,1.0,1.0));
	st.AssertSucceeded( ) ;
	
	// Add output ports.
	st = nodeDef.AddOutputPort(ID_OUT_results,siICENodeDataVector3,siICENodeStructureSingle,siICENodeContextComponent0D|siICENodeContextSingleton,L"results",L"results");
	st.AssertSucceeded( ) ;
	
	PluginItem nodeItem = in_reg.RegisterICENode(nodeDef);
	nodeItem.PutCategories(L"Custom ICENode");
	return CStatus::OK;
}

To implement the SubmitEvaluationPhaseInfo callback

The SubmitEvaluationPhaseInfo callback is required for specifying the ports to pull for the current evaluation phase. The callback is called by Softimage indefinitely until you mark the current phase as the last phase with ICENodeContext::SetLastEvaluationPhase.

XSIPLUGINCALLBACK CStatus MultiPhaseSample_SubmitEvaluationPhaseInfo( ICENodeContext& in_ctxt )
{
	ULONG nPhase = in_ctxt.GetEvaluationPhaseIndex( );
	switch( nPhase )
	{
		case 0:
		{
			in_ctxt.AddEvaluationPhaseInputPort( ID_IN_condition );
		}
		break;
		
		case 1:
		{
			bool bCondition = (bool)in_ctxt.GetUserData();
			ULONG nPortToPull = bCondition ? ID_IN_true : ID_IN_false;
			in_ctxt.PutUserData( nPortToPull );
			in_ctxt.AddEvaluationPhaseInputPort( nPortToPull );
			in_ctxt.AddEvaluationPhaseInputPort( ID_IN_points );
			// This phase is the last one. All ports specified for phase 1 will be evaluated in multi-threaded batches.
			in_ctxt.SetLastEvaluationPhase();
		}
		break;
	}
	return CStatus::OK;
}

To implement the Evaluate callback

The Evaluate callback is where you can process the intermediate phases in single-threading and the last phase in multi-threading.

XSIPLUGINCALLBACK CStatus MultiPhaseSample_Evaluate( ICENodeContext& in_ctxt )
{
	// Read the current phase. 
	// Note: ULONG_MAX is returned if the last phase is being processed.
	ULONG nPhase = in_ctxt.GetEvaluationPhaseIndex( );
	
	switch( nPhase )
	{
		case 0:
		{
			CDataArrayBool conditionData( in_ctxt, ID_IN_condition );
			in_ctxt.PutUserData( conditionData[0] );
			return CStatus::OK;
		}
		break;
	};
	
	// Process the output port being evaluated in multi-threading.
	ULONG out_portID = in_ctxt.GetEvaluatedOutputPortID( );
	
	switch( out_portID )
	{
		case ID_OUT_results:
		{
			ULONG nPortToPull = (ULONG)in_ctxt.GetUserData();
			
			CDataArrayFloat scaleValue( in_ctxt, nPortToPull );
			float fScaleValue = scaleValue[0];
			
			CDataArrayVector3f resultsData( in_ctxt );
			CDataArrayVector3f points( in_ctxt, ID_IN_points );
			
			CIndexSet indexSet( in_ctxt );
			for(CIndexSet::Iterator it = indexSet.Begin(); it.HasNext(); it.Next())
			{
				resultsData[it] = points[it];
				resultsData[it] *= fScaleValue;
			}
		}
		break;
	};
	
	return CStatus::OK;
}