With last post we talked about the Best practices while migrating your plugin to Parallel Evaluation, in this post, we will talk about the following 2 new methods, and how to make use of them to improve the performance:
- MPxNode::preEvaluation
- MPxNode::postEvaluation
Within the old DG system, because MPxNode::setDependentsDirty function is called during dirty propagation, it can be used to manage attribute dirtying, and sometimes, we can cache the computed data in this method to avoid expensive calculations in MPxNode::compute. Let’s take the sample plugin simpleEvaluationNode as an example:
Every time when the input and aTimeInput got dirty, set cachedValueIsValid to false to make it be re-calculated.
- MStatus simpleEvaluationNode::setDependentsDirty( const MPlug& plug, MPlugArray& plugArray)
- {
- if (plug == input || plug == aTimeInput )
- {
- cachedValueIsValid = false;
- }
- return MPxNode::setDependentsDirty(plug, plugArray);
- }
Within compute() method, only do the expensive calculation when cachedValueIsValid is false.
- MStatus simpleEvaluationNode::compute( const MPlug& plug, MDataBlock& data )
- {
- if( plug == output )
- {
- ... ...
- if ( ! cachedValueIsValid )
- {
- MTime time = inputTimeData.asTime();
- cachedValue = doExpensiveCalculation( inputData.asFloat() , (float) time.value() );
- cachedValueIsValid = true;
- }
- MDataHandle outputHandle = data.outputValue( simpleEvaluationNode::output );
- outputHandle.set( cachedValue );
- data.setClean(plug);
- }
- return MS::kSuccess;
- }
This works well within old DG, but the EM evaluation does not propagate dirty messages, the cached data in MPxNode::setDependentsDirty will no longer applies during EM evaluating. So how to do the same thing with EM? One of the solution is MPxNode::preEvaluation.
As discussed previously, once the EG has been built, dirty propagation is disabled and the EG is re-used. Therefore, any custom logic in your plug-in that depends on setDependentsDirty no longer applies. MPxNode::preEvaluation allows your plug-in to determine which plugs/attributes are dirty and if any action is needed, you can use the new MEvaluationNode class to determine what has been dirtied.
- MStatus simpleEvaluationNode::preEvaluation( constMDGContext& context, const MEvaluationNode& evaluationNode )
- {
- MStatus status;
- // we use m_CachedValueIsValid only for normal context
- if( !context.isNormal() )
- return MStatus::kFailure;
- if( ( evaluationNode.dirtyPlugExists(input, &status) && status ) ||
- ( evaluationNode.dirtyPlugExists(aTimeInput, &status) && status ) )
- {
- cachedValueIsValid = false;
- }
- return MS::kSuccess;
- }
If you want to check the whole project, please look at simpleEvaluationNode sample under devkit.
Another new method, MPxNode::postEvaluation, is called once all computations have been performed on a specific node instance. Since this method is called from a worker thread, it performs calculations for downstream graph operations without blocking other Maya processing tasks of non-dependent nodes.
Within the simpleEvaluationDraw devkit example, we can use postEvaluation method to performance heavy calculations for rendering, if this plugin is running in regular evaluation, Maya will slow down because evaluation is blocked whenever expensive calculations in postEvaluation are performed. But if you run it in Parallel Evaluation Mode, a worker thread calls the postEvaluation method and prepares data for subsequent drawing operations, so it will be much faster than in regular DG evaluation, and when testing, you will see higher frame rates in Parallel evaluation versus regular or serial evaluation. But please make sure that code in postEvaluation should be thread-safe.
- MStatus simpleEvaluationDraw::postEvaluation( constMDGContext& context, const MEvaluationNode& evaluationNode, PostEvaluationType evalType )
- {
- if( !context.isNormal() )
- return MStatus::kFailure;
- MStatus status;
- if(evalType == kLeaveDirty)
- {
- scaleUpToDate = false;
- }
- else if ( (evaluationNode.dirtyPlugExists(aCopies, &status) && status ) ||
- ( evaluationNode.dirtyPlugExists(aTimeInput, &status) && status ) )
- {
- MDataBlock block = forceCache();
- MDataHandle inputTimeData = block.inputValue( aTimeInput, &status );
- if ( status )
- {
- MDataHandle copiesData = block.inputValue( aCopies, &status );
- if ( status )
- {
- // A made up calculation to slow down processing
- //
- MTime time = inputTimeData.asTime();
- int copies = copiesData.asInt();
- double t = time.value();
- if ( ! scaleUpToDate )
- {
- scaleXBy = doExpensiveCalculation( copies, t );
- // Mark the scale as up to date so that draw does not
- // have to recompute it
- scaleUpToDate = true;
- }
- }
- }
- }
- return MStatus::kSuccess;
- }
Please also check the simpleEvaluationDraw devkit example for the whole project.
Comments
You can follow this conversation by subscribing to the comment feed for this post.