PDA

View Full Version : API: Slow Defomer Calculations

 JoshM04-13-2009, 05:10 PMI have been developing a deformer on my Intel Core 2 Duo 2.4 GHz laptop using Windows Vista and now when I run the deformer on my Dual Intel Zeon Quad Core 2.8 GHz workstation using Windows XP 64-Bit I am getting a HUGE slowdown. Essentially this is just a pretty simple noise deformer using the PerlinNoise algorithm. My laptop runs the deformer in 30 fps where as my workstation runs it at 15 fps. I have different modes that calculate the geometry in different ways (directional, vertex normal, and spherical), even when I change the mode to a faster method (i.e directional) the frame rate in Maya remains the same 15 fps. The deformer is derived from the MPxDeformerNode class and uses its deform() function. In the deform() function I pretty much just retrieve the interface elements through the provided MDataBlock, check the methods from the interface, and deform the points accordingly in a for loop using the provided MItGeometry. The for loop is pretty simple in its calculations by doing the following operations in pseudo code for(iter.reset(); !iter.isDone(); iter.next()){ //get the point position pt = iter.position(); //transform to correct space pt *= worldMat; //calculate noise for point noiseVal = noise(pt); //undo the transformation pt *= worldMat.inverse() //deform the point pt += directionalVector * env *weight; //set the point iter.setPosition(pt); } I have written this same deformer in Cinema4D's C++ API and the code runs really fast. So I know that the noise() runs pretty quickly. Not sure of the huge slowdown in Maya's API though. Any suggestions as to what I am doing wrong? Or is there more info that I need to provide for a better understanding of the problem? Thanks in advance, Josh
04-13-2009, 05:36 PM
The 2 matrix multiplications + matrix.inverse() caluclation are the most expensive operations in the loop.

Try storing the worldMatrixInverse outside of the loop, instead of recomputing it for each vertex.

Then try to see how fast the "identity" deformer will run.

for(iter.reset(); !iter.isDone(); iter.next()){
//get the point position
pt = iter.position();

//set the point
iter.setPosition(pt);
}

If it still runs at 15 fps then you might as well check the code that is executed before the loop making sure it does not duplicate any Maya's internal data objects.

JoshM
04-13-2009, 06:11 PM

Even after removing all matrix multiplications in the for loop it still runs at the same frame rate.

Which objects are you referring to in regards to 'Maya's internal data objects'? Sorry for being dense but this is the first plug-in that I have developed using the Maya C++ API, so there is still a lot that I don't know about the API.

Josh

04-13-2009, 06:38 PM
The data that goes into your custom deformer is basically pointers to Maya's internal 'data objects'. This is to avoid copying of big data objects such as polygonal meshes flowing from node to the node.

Some of the methods that are used to read the data objects can force Maya to create a copy of the internal data object to return to you instead of the already existing data. This copying is expensive for big data objects.

Although I am skeptical that this is the problem could you post the code that is executed before the loop? This might give us more insight about what is going on.

Also, have you tried compiling the plug-ins from the Devkit to see if they also run at such a miserable speed? If so, it might indeed be an API issue (surprise surprise) which you might want to report to Autodesk.

JoshM
04-13-2009, 08:06 PM
Well I did try and compile one of the examples from the devKit and it ran without this huge slowdown. So its definitely something with my code (not surprising being that its my first plug-in using Maya's API). But what I am doing wrong I can't track it down.

So currently all my code is on a laptop that is not connected to the internet for the time being, but tonight when I have it connected I'll post a code example.

Josh

Keilun
04-14-2009, 05:53 PM
I wonder if this is a driver issue. Can you ensure that your workstation's gfx drivers are up to date (and if they are try using the qualified drivers)? It seems odd to me that this exact code runs slower on a more powerful desktop vs your laptop. In general, I've found XP64 to be rather flaky driver-wise and reverted to XP32 or Vista64.

Just something to try. Not sure which version of Maya you're running. But you can find the HW qual charts per Maya version here:

JoshM
04-14-2009, 09:30 PM
Hi Keilun,
Actually I think I just tracked down the problem by investigating about every aspect of my code =) My deformer is time based, so I have an input for time that is connected to the outTime of the scene's time node. In the deform mode when I was retrieving the time information from the MDataBlock I needed the time to be in seconds and not frames. So when I retrieved this, and set the MTime object to MTime::kSeconds it was being evaluated way more than it needed to be each frame. Thats why on a faster machine the call was evaluated much faster and more frequently which resulted in slower preformance. Getting rid of this seemed to eliminate the speed issue.

But as for driver related issues I have noticed huge slowdowns just while switching applications (Maya to Visual Studio 2005) and then going back into Maya the frame rate was half the speed and the only way to get the speed back was to restart Maya. However, I have only had this happen on my Windows Vista machine. Still testing on my Windows XP 64-bit machine.

Josh

iaiotom
05-06-2009, 08:18 AM
//set the point
iter.setPosition(pt);
}

This is the main problem!
Don't set point for each cicle of for loop. Store all position in an Array and then set all position outside foor loop with only a 1 call.
There there are another triocks to improve the performance of a deformer node.
1) Try to use C array and not Maya Array or structure, this give you a speed up of 5-10X
2) Declare all variable like MPoint, MVector or M...Array outside for loop, because if you declare this tipe of variable inside the foor loop, for each cicle the constructor fo this type of variable is called.
3) Set all point position outside the for loop with MItGeometry::setAllPosition()
4) if you are using the MFnMesh class don't use the member function getNormals(), because there is a bug in this function that return the normals in the wrong order

Bye
Alan

JoshM
05-06-2009, 02:28 PM
This is the main problem!
Don't set point for each cicle of for loop. Store all position in an Array and then set all position outside foor loop with only a 1 call.
There there are another triocks to improve the performance of a deformer node.
1) Try to use C array and not Maya Array or structure, this give you a speed up of 5-10X
2) Declare all variable like MPoint, MVector or M...Array outside for loop, because if you declare this tipe of variable inside the foor loop, for each cicle the constructor fo this type of variable is called.
3) Set all point position outside the for loop with MItGeometry::setAllPosition()
4) if you are using the MFnMesh class don't use the member function getNormals(), because there is a bug in this function that return the normals in the wrong order

Bye
Alan

Awesome! Thanks for these great tips Alan. I will definitely modify my code to reflect these speed up tips.

Josh

JoshM
05-06-2009, 02:39 PM
3) Set all point position outside the for loop with MItGeometry::setAllPosition()

Actually, I am looking for this function in the API for Maya 2008 and I don't see it. However, I do see it in the API for 2009. So I take it that this is only possible in 2009?

Josh

iaiotom
05-06-2009, 02:52 PM
Actually, I am looking for this function in the API for Maya 2008 and I don't see it. However, I do see it in the API for 2009. So I take it that this is only possible in 2009?

Josh

No you can use the MNFnMesh class. But before you need to get the MObject of input mesh with a plug. Try do this:

MObject thisNode = thisMObject();
MFnDependencyNode nodeFn(thisNode);
MPlug inGeomPlug = nodeFn.findPlug(inputGeom,true,&returnStatus);
MObject inputGeoObj;
inGeomPlug.getvalue(inputGeoObj);
MFnMesh meshFn(inputGeoObj);
...
...
meshFn.setPoints( );

Use this if you are using deform() function, while if you are using compute() function, get the input Object with the datablock function.

bye
Alan

edit:
sorry, you can get the mobject of the input mesh directly with the datablock like this:

MDataHandle inObjHandle = data.inputValue(inputGeom,&returnstatus);
MObject inputGeomObj = inObjHandel.asMesh();
MFnMesh meshFn(inputGeoObj);
...
...
meshFn.setPoints( );

JoshM
05-06-2009, 03:16 PM
I can't thank you enough Alan! This is really good info to know. So once again, Thanks!

Josh

iaiotom
05-06-2009, 04:15 PM
I can't thank you enough Alan! This is really good info to know. So once again, Thanks!

Josh

eheheh You are welcome :)

JoshM
05-06-2009, 10:05 PM
So I am trying to implement the MFnMesh::setPoints() method but everything works but the actual function setPoints( ). I have my own C array of double[pointCount][4] which is populated with the correct positions, but it will not modify the geometry in the scene. Here is some of the code that I am using:

MyDeformNode::deform(MDataBlock &data, MIterGeometry iter, const MMatrix &mat, unsigned int multiIndex){
...
...
//get the object from the plug
MObject thisNode = thisMOBject();
MFnDependencyNode nodeFn(thisNode);
MPlug inGeomPlug = nodeFn.findPlug(inputGeom, true, &stat);
MObject inputObject;
inGeomPlug.getValue(inputObject);

//make sure its valid
if(stat != MS::kSuccess) return stat;

//create the function set
MFnMesh meshFn(inputObject, &stat);
if(stat != MS::kSuccess) return stat;

//create a double array to store the modified points
int pointCount = meshFn.numVertices(&stat);
double (*modifiedPositions)[4] = new double[pointCount][4]; //pointer to array of 4 double

...
...
for(iter.reset(); !iter.isDone(); iter.next()){
pt = iter.position();

//modify the point
...
...

//store the modified positions in the array
if(modifiedPositions){
modifiedPositions[iter.index()][0] = pt.x;
modifiedPositions[iter.index()][1] = pt.y;
modifiedPositions[iter.index()][2] = pt.z;
modifiedPositions[iter.index()][3] = pt.w;
}
}//end for loop

//set all the positions of the mesh
stat = meshFn.setPoints(MPointArray(modifiedPositions, pointCount));
if(stat != MS::kSuccess) return stat;

//delete the array for memory
delete []modifiedPositions;

I have even checked all of the positions within the array and they contain the correct point positions. But the geometry will not change with the setPoints() function. Any ideas on what I am doing wrong?

Josh

iaiotom
05-06-2009, 10:16 PM
Sorry my mistake, you must copy the input mesh to the output mesh before use the NFnMesh.

MDataHandle hGeom = dataBlock.inputValue(inputGeom);
MDataHandle hOutput = dataBlock.outputValue(outputGeom);
hOutput.copy(hGeom);
MObject meshObj = hOutput.asMesh();
MFnMesh meshFn(meshObj);

JoshM
05-06-2009, 10:45 PM
Thanks for the quick response! However, this still doesn't seem to modify the points. Actually it looks like when I try to attach the MFnMesh it errors out:

MFnMesh meshFn(inputObject, &stat)

Does this anything to do with that the input of the MPxDeformerNode can be an array of attributes?

Josh

iaiotom
05-06-2009, 11:33 PM
Thanks for the quick response! However, this still doesn't seem to modify the points. Actually it looks like when I try to attach the MFnMesh it errors out:

MFnMesh meshFn(inputObject, &stat)

Does this anything to do with that the input of the MPxDeformerNode can be an array of attributes?

Josh

This is an exemple. I tested it and it works perfeclty, just modify the code and optimaze it :)

MStatus
testDeform::compute(const MPlug& plug, MDataBlock& dataBlock)
{
MStatus status = MS::kUnknownParameter;
if (plug.attribute() == outputGeom) {
// get the input corresponding to this output
//
unsigned int index = plug.logicalIndex();
MObject thisNode = this->thisMObject();
MPlug inPlug(thisNode,input);
inPlug.selectAncestorLogicalIndex(index,input);
MDataHandle hInput = dataBlock.inputValue(inPlug);

// get the input geometry and input groupId
//
MDataHandle hGeom = hInput.child(inputGeom);
MDataHandle hGroup = hInput.child(groupId);
unsigned int groupId = hGroup.asLong();
MDataHandle hOutput = dataBlock.outputValue(plug);
hOutput.copy(hGeom);

MObject inMeshObj = hGeom.asMesh();

MFnMeshData MeshData;
MFnMesh meshFn;
MObject outMeshObj = MeshData.create();
meshFn.copy(inMeshObj, outMeshObj, &status);

MPointArray points, outPoints;
meshFn.getPoints(points);

MPoint pt;
for(unsigned int i=0;i<points.length();i++){
pt = points[i];

pt.x += (rand()/(static_cast<double>(RAND_MAX) + 1.0))*2.0 -1.0;
pt.y += (rand()/(static_cast<double>(RAND_MAX) + 1.0))*2.0 -1.0;
pt.z += (rand()/(static_cast<double>(RAND_MAX) + 1.0))*2.0 -1.0;

outPoints.append(pt);
}

status = meshFn.setPoints(outPoints);
if(status != MS::kSuccess) return status;

hOutput.set(outMeshObj);
dataBlock.setClean(plug);
}

return status;
}

CGTalk Moderation
05-06-2009, 11:33 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.