Custom MPxDeformer node slowness when using keyframes

Custom MPxDeformer node slowness when using keyframes

HTJimenez
Community Visitor Community Visitor
670 Views
3 Replies
Message 1 of 4

Custom MPxDeformer node slowness when using keyframes

HTJimenez
Community Visitor
Community Visitor

Hello, I'm running some basic tests to understand the behaviour and performance of the custom attributes in a MPxDeformerNode. I have created a MFnNumericAttribute that is a float, and then inside the deform method I do nothing (this is for testing purposes only). The new attribute will affect the outputGeometry attribute only.

 

In my scene, I have 1 sphere with 1 million vertices.  And then, I connect my custom deformer node to the sphere to trigger the deform method. So, I realised that if the attribute contains a constant value the scene is running at 600 fps it is normal because the deform is not triggered every frame. Then, if I connect the attribute to a floatConstant node with 1 or more keyframes set, then the performance falls to 60 fps. (Custom_Deformer.gif)

 

I mean, I understand that the attribute gets dirty and the deform method is executed every frame. But then, I took a look to the native BlendShape deformer node and performing a deformation using the same geometry is running at 200 fps with keyframes. (BlendShapeMaya.gif)

 

How can I avoid getting that performance reduction when using keyframes in the input attribute? I was searching in the API documentation but I didn't find anything about this behaviour. Even testing with the given MPxGPUDeformer example, as soon as I connect an attribute with keyframes, the framerate falls too much.

 

Thanks for your attention.

 

This is the code I'm using for this test:

# You have to use maya API 1.0 because MPxDeformerNode is not available in 2.0.
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx


# Set globals to the proper cpp cvars. (compatible from maya 2016)
kInput = OpenMayaMPx.cvar.MPxGeometryFilter_input
kInputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom
kOutputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom
kEnvelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope
kGroupId = OpenMayaMPx.cvar.MPxGeometryFilter_groupId

class templateDeformer(OpenMayaMPx.MPxDeformerNode):
    """Template deformer node."""
    # Replace this with a valid node id for use in production.
    type_id = OpenMaya.MTypeId(0x00001)  
    type_name = "templateDeformer"

    @classmethod
    def initialize(cls):
        """Initialize attributes and dependencies."""
        # Add any input and outputs to the deformer here, also set up 
        # dependencies between the in and outputs. If you want to use another 
        # mesh as an input you can use an MFnGenericAttribute and add 
        # MFnData.kMesh with the addDataAccept method.
        numericAttributeFn = OpenMaya.MFnNumericAttribute()
        cls.sampleInAttribute = numericAttributeFn.create( 'myInputAttribute', 'i', 
                                                          OpenMaya.MFnNumericData.kFloat,
                                                          0.0 )
        cls.addAttribute( cls.sampleInAttribute )
        cls.attributeAffects( cls.sampleInAttribute, kOutputGeom )
        pass

    @classmethod
    def creator(cls):
        """Create instance of this class.

        Returns:
            templateDeformer: New class instance.
        """
        return cls()

    def __init__(self):
        """Construction."""
        OpenMayaMPx.MPxDeformerNode.__init__(self)

    def deform(
        self, 
        data_block, 
        geometry_iterator, 
        local_to_world_matrix, 
        geometry_index
    ):
        """Deform each vertex using the geometry iterator.
        
        Args:
            data_block (MDataBlock): the node's datablock.
            geometry_iterator (MItGeometry): 
                iterator for the geometry being deformed.
            local_to_world_matrix (MMatrix): 
                the geometry's world space transformation matrix.
            geometry_index (int): 
                the index corresponding to the requested output geometry.
        """
        # This is where you can add your deformation logic.

        # you can access the mesh this deformer is applied to either through
        # the given geometry_iterator, or by using the getDeformerInputGeometry 
        # method below.

        # You can access all your defined attributes the way you would in any 
        # other plugin, you can access base deformer attributes like the 
        # envelope using the global variables like so:

        # envelope_attribute = kEnvelope
        # envelope_value = data_block.inputValue( envelope_attribute ).asFloat()

        a = 0

    def getDeformerInputGeometry(self, data_block, geometry_index):
        """Obtain a reference to the input mesh. 
        
        We use MDataBlock.outputArrayValue() to avoid having to recompute the 
        mesh and propagate this recomputation throughout the Dependency Graph.
        
        OpenMayaMPx.cvar.MPxGeometryFilter_input and 
        OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom (Maya 2016) 
        are SWIG-generated variables which respectively contain references to 
        the deformer's 'input' attribute and 'inputGeom' attribute.

        Args:
            data_block (MDataBlock): the node's datablock.
            geometry_index (int): 
                the index corresponding to the requested output geometry.
        """
        inputAttribute = OpenMayaMPx.cvar.MPxGeometryFilter_input
        inputGeometryAttribute = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom
        
        inputHandle = data_block.outputArrayValue( inputAttribute )
        inputHandle.jumpToElement( geometry_index )
        inputGeometryObject = inputHandle.outputValue().child(
            inputGeometryAttribute
        ).asMesh()
        
        return inputGeometryObject


def initializePlugin(plugin):
    """Called when plugin is loaded.

    Args:
        plugin (MObject): The plugin.
    """
    plugin_fn = OpenMayaMPx.MFnPlugin(plugin, "Test", "0.1", "Any")

    try:
        plugin_fn.registerNode(
            templateDeformer.type_name,
            templateDeformer.type_id,
            templateDeformer.creator,
            templateDeformer.initialize,
            OpenMayaMPx.MPxNode.kDeformerNode
        )
    except:
        print("failed to register node {0}".format(templateDeformer.type_name))
        raise


def uninitializePlugin(plugin):
    """Called when plugin is unloaded.

    Args:
        plugin (MObject): The plugin.
    """
    plugin_fn = OpenMayaMPx.MFnPlugin(plugin)

    try:
        plugin_fn.deregisterNode(templateDeformer.type_id)
    except:
        print("failed to deregister node {0}".format(
            templateDeformer.type_name
        ))
        raise

0 Likes
671 Views
3 Replies
Replies (3)
Message 2 of 4

jonah_reinhart
Explorer
Explorer

What fps do you get when changing the "sampleInAttribute" manually (like in the attribute editor)? 

If it's 60 then we can determine that the issue is unrelated to keyframes.

 

One bit of info that might help:

The "deform" function is called by the "compute" function. So even if "deform" does nothing, "compute" still has to spend time creating the inputs for "deform" like the iterator. So re-implimenting "compute" to do nothing (aside from marking plugs as clean) might be a better comparison.

 

 

 

0 Likes
Message 3 of 4

HTJimenez
Community Visitor
Community Visitor

When I select the deformer node and change the value manually, in the right bottom corner of the viewport appears 1.9 fps.

 

Also, as you said, I tested re-implementing the compute() method, but when using heavy geometries as output, it was taking a lot of time too. Setting the dependentsDirty and clean the plugs at the end of the compute method was producing the scene to run at 12-15 fps when using keyframes in the input attribute. Maybe it's possible that using complex geometries are slowing down the performance of the nodes when setting the mesh to the outputGeom plug, because with light geometries the performance slow down was not that evident.

 

But I still cannot understand why the blendShape node works perfectly with heavy geometries as I'm testing, maybe that node contains specific logic to be efficient managing large data.

 

Thanks for the reply.

0 Likes
Message 4 of 4

jonah_reinhart
Explorer
Explorer

1.9fps is quite different from 15fps. You might get a more accurate idea of how long the node is taking to evaluate in both situations by using the profiler. 

The main reason your deformer is so much slower than the blendShape deformer is that it's written in C++. 

 

0 Likes