Skinned meshes with low values for MaxVertexBlendMatrixIndex

Started by
8 comments, last by remigius 15 years, 11 months ago
Hi all, I've run into a little problem, and I thought maybe I'm not the first... I have a reasonably functional MDX rendering engine. It works great for all meshes on the machine I used to create it, but skinned meshes get completely screwed when I switch over to machines with ATI graphics cards. I've determined that the problem involves the Caps.MaxVertexBlendMatrixIndex... which is 255 on my usual machine, but only 8 for every ATI card I have at my disposal. I'm fairly certain that value exposes the limit to the number of matrices I am able to set using the Device.Transform.SetWorldMatrixByIndex() method, which in turn limits the number of bone transforms I can feasibly associate with a BoneAttributeCombination. If I'm wrong about any of this so far, please correct me. My question is, then, how do I resolve this in the general case? Should I break apart the BoneAttributeCombinations and create a bunch of attributes in the attribute table when I create the mesh (at least when the value of MaxVertexBlendMatrixIndex warrants it)? Or is there some trick I can use when I render the mesh? As it stands, my rendering code is similar to the SDK examples where an entry in the attribute table is only necessary to specify which material gets used for a subset of the mesh data. I have a feeling I'm going to have to extend this to compensate for any limitations on the number of matrices the graphics card is capable of using at any one time, but I thought it would be worthwhile to ask for advice. Thanks.
David Hooks
Advertisement
I haven't done much skeletal animation myself, let alone using the fixed function pipeline, but IIRC the max number of bone transforms should depend on the shader model your card supports. Unless you're working with very old ATI cards (pre Radeon 9600?), the card should comfortably allow at least some 50 bones. So I doubt you're looking at hardware limitations here.

I pulled out the good old MDX Kickstart book and according to that, the MaxVertexBlendMatrixIndex actually represents the maximum number of matrices that can be blended to influence a single vertex at any given time. So it doesn't say anything about the max number of bones. Most animations typically work with 4 blend indices per vertex (that is, 4 indices into a matrix array containing the bones transforms), that have a weighted influence on the vertex's skinning transformation.

If anything, I think the hardware on your machine is either being overly optimistic returning 255 for this cap, or it's running in software vertex processing mode (typical for Intel cards) which might indeed support this huge number of simultaneous blend indices. Anyway, I think you could safely set up your bones on the ATI cards and see if it works.
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Thanks for replying remigius.

Maybe I didn't convey the problem correctly. I don't think there's a hardware limitation as to the number of bones allowed at all. My concern is that, when I render the mesh, on my usual machine (which has an NVIDIA Quadro FX 1400 card), I'm allowed to set up to 256 world matrices for the device (or at least 47, which is the most I'm setting in the current application)... at any rate, it's plenty to handle all of the bones for an entire mesh. On ATI cards, I'm only allowed to set 9 matrices (why 9, first of all?... one of the machines is a laptop with an ATI Mobility Radeon HD 2300 card... I'm not in the lab right now, so I don't have access to the other machines). I want to say that there are no more than 2 matrices affecting any one vertex, but there are lots of matrices affecting the vertices in a subset of the mesh. So, when I call DrawSubset for the mesh, I have no idea how to handle setting the world matrices when I'm limited to 9. My initial reaction is that I need to make significant changes to the attribute table so that I can group vertices into subsets that only involve a limited number of bone transforms. I was just wondering if there were any other solutions that might save me re-writing the code to set up the attribute table.

Thanks again.
David Hooks
I'm not at all an expert with animated meshes but I'm running an ATI Radeon X500 series with max index of 37 and 4 matrices and I use attribute groups.

I don't do anything special to setup the attribute tables. I grab the attribute table after the ConvertToIndexedBlendedMesh call. You'll just have to limit the palette size in your call.

With the code I use, if the max index is 8, you would be limited to 2 influences per vertex (6 influences for the face), which is minimally useable.

Here's the routine I use for rendering the attribute groups, pretty much copied from the SDK. You'll have to translate to C#, but it might help.

The matrix multiplication can obviously be combined but I was doing some testing.

	if (mesh->pSkinInfo != NULL)	{		if (mesh->numInfluences == 1)			device->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_0WEIGHTS);		else			device->SetRenderState(D3DRS_VERTEXBLEND,(mesh->numInfluences - 1));				if (mesh->numInfluences > 0) device->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE,TRUE);				LPD3DXBONECOMBINATION bones = mesh->bones;		for(DWORD iAttrib = 0; iAttrib < mesh->numAttributes; iAttrib++)		{			// first, get world matrices			for (int iPaletteEntry = 0; iPaletteEntry < mesh->numPalette; ++iPaletteEntry)			{				int iMatrixIndex = bones[iAttrib].BoneId[iPaletteEntry];				if (iMatrixIndex != -1)				{					D3DXMATRIX mat = *(mesh->offsetMatrices[iMatrixIndex]) * (mesh->frameMatrices[iMatrixIndex])->combined;					device->SetTransform(D3DTS_WORLDMATRIX(iPaletteEntry),&mat);				}			}			// Setup the material			D3DXMATERIAL matl = mesh->pMaterials[bones[iAttrib].AttribId];			hr = device->SetMaterial(&mesh->pMaterials[bones[iAttrib].AttribId].MatD3D);			hr = device->SetTexture(0,mesh->textures[bones[iAttrib].AttribId]);			// Finally draw the subset			hr = mesh->MeshData.pMesh->DrawSubset(iAttrib);		}	}

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Quote:Original post by deh0512
Maybe I didn't convey the problem correctly...


No, I probably didn't convey my answer correctly. I shouldn't be typing in the middle of the night [smile]

I was trying to say that, according to the MDX reference book, MaxVertexBlendMatrixIndex does not indicate the maximum number of bones you can set up on your card. Rather, it specifies the maximum number of bones that can affect one single vertex simultaneously. Your card could accept say 100 bones in total, but your model would need to be limited to using only MaxVertexBlendMatrixIndex bone influences per vertex.

As for the hardware discussion, I was trying to point out that you shouldn't have to worry much about the maximum total number of bones on semi modern hardware. So, just try setting up your bones on the device without even looking at the MaxVertexBlendMatrixIndex. If the book is correct on the real meaning of MaxVertexBlendMatrixIndex, it should just work.

Obviously you'll need to find a more robust way of checking the max total number of bones the cards support eventually, but at least you won't need to go messing around with adding subsets to your mesh. As far as I can tell, that simply shouldn't be needed in the first place on modern hardware and rendering more subsets does add more overhead on the CPU side since you need more draw calls.

Hope this makes more sense [wink]
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Hello again...

Buckeye,

My rendering code is effectively the same as yours (I got it from the SDK also [smile]). I've tried to limit the palette size when I call ConvertToIndexedBlendedMesh, but if I provide a value that is anything less than the number of bones, the call fails. It gives me a D3DX exception that just says "E_FAIL". Are you able to call this method with arbitrary values for the palette size?

remigius,

Quote:So, just try setting up your bones on the device without even looking at the MaxVertexBlendMatrixIndex


That's what I have done all along. On the ATI cards (maybe not all, but the one's I have access to), parts of the mesh get drawn correctly and other parts seem to get sucked into the origin (the vertices are being influenced by identity matrices). As best as I am able to discern so far, the calls to SetWorldMatrixByIndex (in Buckeye's code snippet, the C version of this is device->SetTransform(...)) become ineffectual for values over 8. Perhaps it's not the MaxVertexBlendMatrixIndex that describes this limitation, but the limitation is still making my code not work.

Quote:Obviously you'll need to find a more robust way of checking the max total number of bones the cards support


Any idea how to go about doing that? That seems like something I should like to do [smile].

I appreciate you guys trying to help me figure this out.
David Hooks
Quote:Original post by deh0512

That's what I have done all along. On the ATI cards (maybe not all, but the one's I have access to), parts of the mesh get drawn correctly and other parts seem to get sucked into the origin (the vertices are being influenced by identity matrices). As best as I am able to discern so far, the calls to SetWorldMatrixByIndex (in Buckeye's code snippet, the C version of this is device->SetTransform(...)) become ineffectual for values over 8. Perhaps it's not the MaxVertexBlendMatrixIndex that describes this limitation, but the limitation is still making my code not work.


Very odd... The book might be wrong here then, but I find the caps values utterly confusing, so I can't be sure. The caps viewer shows two 'blend' caps, MaxVertexBlendMatrices and MaxVertexBlendMatrixIndex with the following values for my machine:

                                       Reference device     8600GT   MaxVertexBlendMatrices                       4                4MaxVertexBlendMatrixIndex                   255               0


From the values for the reference device I gather the 1st cap would indicate the max number of bone influences per vertex, while the 2nd indicates the max number of bones. Looking at the caps for my 8600GT though, it seems that idea is pretty much out the window, since 0 bones is obviously wrong. And yes, I realize this isn't helping you solve the problem [smile]

Are you sure you aren't using the MaxVertexBlendMatrixIndex somewhere else in your code, so that it could be limitting the number of matrices you set indirectly? When setting the bone matrices, have you tried outputting the index used for SetWorldMatrixByIndex() to see if your code actually tries to set indices higher than MaxVertexBlendMatrixIndex?

Another option might be to enable the debug runtimes and check if the output from those can tell you more. If you are indeed setting up matrices on higher indices than the card supports, the debug runtimes probably will spam you with warnings. You also might want to try running the (managed) skeletal animation sample from the SDK on the ATI machines to make sure it's not a hardware issue. The C++ sample does work for my 8600GT with its 0 MaxVertexBlendMatrixIndex.

Quote:Any idea how to go about doing that? That seems like something I should like to do [smile].


Hehe, me and my big mouth. I can't tell you how to do it exactly for the fixed function pipeline (FFP). For shader based skinning, it's a matter of figuring out the number of constants ('inputs') the card supports, subtracting the number of constrants you need for lighting etc and finally dividing the number of remaining contant slots by the number of constants you need for a single bone, which is typically 4 for a matrix per bone.

Anyway, this typically allows you to use some 50-60 bones for a model on SM2 hardware, depending on how much additional info you need to set for your lighting etc. Since the FFP is widely rumored to be emulated using shaders, this max bone value should also be a good indication of what the FFP supports.

Obviously that's not too robust, so if anyone can chip in a better solution that'd be great [smile]
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!
Quote:if I provide a value that is anything less than the number of bones, the call fails...Are you able to call this method with arbitrary values for the palette size?

Not sure what you mean by "arbitrary." However, except for the very simplest meshes I've tested, the call is always with a palettesize less than the number of bones. I successfully animate meshes with >60 bones and >30,000 vertices. That particular mesh ends up with 5 or 6 attribute groups.

Do you generate the adjacency table for the unskinned mesh to pass to the CreateIndexed.. call?

By the way, as you surmise, the max number of bones doesn't matter. It's the number of bone influences per face that determine the number of attribute groups.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Hmmm....

Buckeye,

Quote:Do you generate the adjacency table for the unskinned mesh to pass to the CreateIndexed.. call?

Yes.

Quote:the call is always with a palettesize less than the number of bones

That makes me think I've done something wrong earlier in my code, which is quite possible. (Read on to see why...)

I'm becoming more convinced that the palette size issue is my problem, though...

remigius (& Buckeye),

Quote:see if your code actually tries to set indices higher than MaxVertexBlendMatrixIndex

I know it tries. It just doesn't work.

Quote:Are you sure you aren't using the MaxVertexBlendMatrixIndex somewhere else in your code

No I wasn't using MaxVertex... anywhere in my code, but now I think I should be. The SDK samples use it to determine the palette size if that value is less than the number of bones (reference the GenerateSkinnedMesh method in the SimpleAnimation example). Apparently, I took that out of my code at some point (probably because it never made a difference on my machine). I'm going to do some experiments, because I have a feeling this is the root of my problem.

I'll let you guys know if I get it all figured out, but thanks a ton for pointing me in the right direction. You've both been extremely helpful.
David Hooks

I'd only wish our comments were also more useful [smile]

I some more digging to figure this one out and came across this thread which might help. Taking a look at CardCaps.xls (do a search in your DX SDK folder) confirms Muhammad's last remark, that many NVidia cards indeed return 0 for this Cap.

I'm sorry I can't be of more help, but FFP skinning is starting to look like plain Voodoo to me. As a last resort, you might want to check out if shader based skinning is a viable alternative (MDX sample).
Rim van Wersch [ MDXInfo ] [ XNAInfo ] [ YouTube ] - Do yourself a favor and bookmark this excellent free online D3D/shader book!

This topic is closed to new replies.

Advertisement