Instancing with ID3DXMESH?

Started by
20 comments, last by Tispe 10 years, 8 months ago

First things first, hello GameDev.net! Long time browser here, love the site, finally registered when I had a question that I couldn't find the answer to while browsing the rest of the internet, so here goes!

I've been working on instancing with DirectX 9 using the following article http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter03.html (3.3.4 Batching with the Geometry Instancing API), however up to this point I've been using ID3DXMESH to load and render my meshes from a .x file. Because of that I never actually will call SetStreamSource() with the stream number at 0.

I tried setting all the others like so


D3DDEV->SetStreamSourceFreq( 0, D3DSTREAMSOURCE_INDEXEDDATA | GetNumInstances() );
D3DDEV->SetStreamSourceFreq( 1, D3DSTREAMSOURCE_INSTANCEDATA | 1 );
D3DDEV->SetStreamSource( 1, InstanceBuffer, 0, sizeof(OBJECT_INSTANCE) );

and then calling DrawSubset on my mesh, to see if it would work, but so far I haven't been too succesfull.

So, does anyone have any ideas on what I should try, do I need to use the get functions of my mesh to render it manually? I still need to debug other parts of the code to see if they're at fault, but any tips in the right direction would be appreciated!

Advertisement

based on what they're doing in the article. looks like you'll want to use the d3dxmesh api to get a pointer to the mesh's vb and ib. that gets your VB and IB for your "geometry packet" as they refer to it. and then use draw indexed primitive once you setup your buffers, source stream, and frequency.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

We once had a discussion about how to instance subsets. To get the parameters for the draw call you probably need to grab the ID3DXBaseMesh::GetAttributeTable

Thanks for the replies! Taking the responses into use, and some meddling with my own, one of the biggest things I was missing was making sure the format of my Mesh matched what I needed to pass to, so the big things I needed was just a call to CloneMesh after loaded it. Also, for anyone running into this topic with a similar issue, the first reply to this topic http://www.gamedev.net/topic/591634-directx-9-hardware-instancing/ has most of the things I needed.

Could you post your source code and HLSL related to this instancing?

I want to try the same thing you are. I have many objects that use the same ID3DXMESH, but sometimes different subsets of it. This could be the "batching" solution I am looking for.

Is your InstanceBuffer VB dynamic, do you reuse it, or do you create one each frame? What pool is it in?

Not sure mine is the ideal approach, but I'll post what I have. I'm using one dynamic vertex buffer, at a size equal to a const UINT MaxInstances.

First here are my vertex declarations


//used to clone the mesh
const D3DVERTEXELEMENT9 VBD_GEOMETRYDATA[] =
{
	{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
	{0, 12,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
	{0, 24,  D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
	D3DDECL_END()
};

//this declaration is actually used for rendering
extern LPDIRECT3DVERTEXDECLARATION9 DECL_GEOMETRYPACKET;
const D3DVERTEXELEMENT9 VBD_GEOMETRYPACKET[] = 
{
	{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
	{0, 12,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0},
	{0, 24,  D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
	{1, 0,  D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
	{1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2},
	{1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3},
	{1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4},
	D3DDECL_END()
};

//class describing a single element of the instance buffer
struct OBJECT_INSTANCE
{
	//the four rows of the world matrix of an instance
	float Row1[4];
	float Row2[4];
	float Row3[4];
	float Row4[4];
};

Here the function to create the instance buffer (should only be called once, but outside of performance wont break anything if its called multiple times)

??


void CreateInstanceBuffer()
{
        //checks if the buffer already exists and releases it if it does
	ReleaseInstanceBuffer();

        BufferSize = MaxInstances*sizeof(OBJECT_INSTANCE);
	D3DDEV->CreateVertexBuffer(BufferSize, D3DUSAGE_DYNAMIC, NULL, D3DPOOL_DEFAULT, &InstanceBuffer, NULL);
	D3DDEV->CreateVertexDeclaration(VBD_GEOMETRYPACKET, &DECL_GEOMETRYPACKET);
}

Then after loaded the mesh, clone it with your geometry declaration


//when you load the tempmesh is the mesh you wish to instance
D3DXMESH newmesh;
tempmesh->CloneMesh(D3DXMESH_MANAGED, VBD_GEOMETRYDATA, D3DDEV, &_newmesh)

//store the vertex and index buffers once
newmesh->GetVertexBuffer(&MeshVB);
newmesh->GetIndexBuffer(&MeshIB);

//if you no longer need the old mesh release it and replace it with the new once
tempmesh->Release();
tempmesh = newmesh;

Here is the code I use to lock the instance buffer, note that AddIndex keeps track of where to add the next instance (and hence will also keep track of how many instance you have currently added to the buffer) when I'm done rendering one mesh I just set AddIndex to 0, and it overwrites whatever data is already there the next time I start adding instances (also note that the instance buffer should be locked and the data stored in OBJECT_INSTANCE *LockedBuffer)


bool AddInstanceToLockedBuffer(D3DXMATRIX World)
{ 
	//the buffer is not locked or you have reached the max instances
	if (!BufferLocked) return false;
	if (AddIndex >= MaxInstances) return false;

	D3DXMATRIX InverseWorld;
	D3DXMatrixInverse(&InverseWorld, 0, World);

        /*----------------------------------------------
        fill LockedBuffer[AddIndex] with World
        ----------------------------------------------*/

	//increment the instance buffers current index
	AddIndex++;

	return true;
}

UINT GetNumInstances()
{
	return AddIndex;
}

void ResetInstanceBuffer()
{
	AddIndex = 0;
}

Lastly is rendering


//set the instancing parameters
D3DDEV->SetVertexDeclaration(DECL_GEOMETRYPACKET);
D3DDEV->SetIndices(MeshIB);

D3DDEV->SetStreamSourceFreq( 0, D3DSTREAMSOURCE_INDEXEDDATA | GetNumInstances());
D3DDEV->SetStreamSource(0, _vb, 0, D3DXGetDeclVertexSize(VBD_GEOMETRYPACKET, 0));

D3DDEV->SetStreamSourceFreq( 1, D3DSTREAMSOURCE_INSTANCEDATA | 1 );
D3DDEV->SetStreamSource(1, InstanceBuffer, 0, D3DXGetDeclVertexSize
                               (VBD_GEOMETRYPACKET, 1));

//set all the properties of your effect

//render the mesh
UINT numpasses;
Effect->Begin(&numpasses, 0);

for (int i=0; i<numpasses; i++)
{
        Effect->BeginPass(i);

	D3DDEV->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, _Mesh->GetNumVertices(),
                                      0, Mesh->GetNumFaces());

	Effect->EndPass();
}

Effect->End();

//reset instancing parameters
D3DDEV->SetStreamSourceFreq(0, 1);
D3DDEV->SetStreamSourceFreq(1, 1);

Pretty long, but hope that helps someone out. Also if anyone has any suggestions that would be cool too!

Edit: Probably noticed I haven't gotten around to rendering multiple subsets of the mesh yet, I will be doing that next. One of the links posted in one of the above replies has some stuff on that though.

Thanks.


D3DDEV->CreateVertexBuffer(BufferSize, D3DUSAGE_DYNAMIC, NULL, D3DPOOL_DEFAULT, &InstanceBuffer, NULL);

You can probably add the D3DUSAGE_WRITEONLY flag next to dynamic.

What about using an std::vector<OBJECT_INSTANCE> InstancesArray and add all instances to it, then memcpy &InstancesArray[0] to InstanceBuffer with InstancesArray.size()?

I wanted to post this in my previous post but at the time I was not quite sure if it was relevant. Now after looking at some code at msdn I want to ask some questions.


D3DDEV->SetStreamSourceFreq( 0, D3DSTREAMSOURCE_INDEXEDDATA | GetNumInstances());
D3DDEV->SetStreamSource(0, _vb, 0, D3DXGetDeclVertexSize(VBD_GEOMETRYPACKET, 0));

D3DDEV->SetStreamSourceFreq( 1, D3DSTREAMSOURCE_INSTANCEDATA | 1 );
D3DDEV->SetStreamSource(1, InstanceBuffer, 0, D3DXGetDeclVertexSize(VBD_GEOMETRYPACKET, 1));

Why are you using VBD_GEOMETRYPACKET for both Stream sources? The example from http://msdn.microsoft.com/en-us/library/windows/desktop/bb173349(v=vs.85).aspx has two declarations, one for vertex data and one for instance data:


// Set up the geometry data stream
pd3dDevice->SetStreamSourceFreq(0, (D3DSTREAMSOURCE_INDEXEDDATA | g_numInstancesToDraw));
pd3dDevice->SetStreamSource(0, g_VB_Geometry, 0, D3DXGetDeclVertexSize( g_VBDecl_Geometry, 0 ));

// Set up the instance data stream
pd3dDevice->SetStreamSourceFreq(1, (D3DSTREAMSOURCE_INSTANCEDATA | 1));
pd3dDevice->SetStreamSource(1, g_VB_InstanceData, 0, D3DXGetDeclVertexSize( g_VBDecl_InstanceData, 1 ));

The vertex data and instance data would then automatically merge to form the input for the vertex shader:


struct vsInput
{
  // stream 0
  float4 position : POSITION;
  float3 normal   : NORMAL;

  // stream 1
  float4 model_matrix0 : TEXCOORD0;
  float4 model_matrix1 : TEXCOORD1;
  float4 model_matrix2 : TEXCOORD2;
  float4 model_matrix3 : TEXCOORD3;
  float4 instance_color: D3DCOLOR;
};

Right?

There's not reason in particular to use one or two streams, VBD_GEOMETRYPACKET (which contains both geometry and instance data) is needed for D3DDEV->SetDecleration() msdn had one more vertex declaration for instances that I don't.

If you look at the two calls to this function

D3DXGetDeclVertexSize(VBD_GEOMETRYPACKET, 0)

D3DXGetDeclVertexSize(VBD_GEOMETRYPACKET, 1)

I have 0 set for the second parameter in the first call and 1 in the second call, this just refers to the stream, the function will return the vertex size of that stream, rather than the entire declaration, so in essence its functioning as two different declarations merged into one. The only reason I have the vertex declaration for the mesh is for cloning it, if you didn't need to do that, you would only technically need the one vertex declaration.

Regardless its a matter of preference, if you want another declaration for instancing like msdn it won't make any difference, though may be useful for you elsewhere in your code.


The vertex data and instance data would then automatically merge to form the input for the vertex shader:

Not automatically, no. Ximsu got it right and the MSDN page is misleading (and there's also a community contribution at the bottom pointing it out): You can only set one declaration at a time.

This topic is closed to new replies.

Advertisement