Home » Community » Forums » DirectX and XNA » Find out what's wrong with my instancing code
  Intel sponsors gamedev.net search:   
[Control Panel] [Register] [Bookmarks] [Who's Online] [Active Topics] [Stats] [FAQ] [Search]

Add Forum to Favorites |  Send Topic To a Friend | View Forum FAQ | Track this topic


 Last Thread Next Thread 
 Find out what's wrong with my instancing code
Post New Topic  Post Reply 
Normally I don't post this much code to focus on a problem, but I've been stumped for days on how to use mesh instancing correctly. The few instancing samples I've seen for DirectX have me still wondering how you should set up your vertex data for instance streams so that they are read correctly according to the vertex declaration. I used NVidia's instancing sample because the code looked clearer than other examples I've seen. Well, this is a bare bones version of the code I'm using but I think I stripped it down too far and probably missing some important things.

This code compiles without errors but I only see one instance of the mesh, and couldn't use Pix or debugging to help me figure out where I could be missing some function calls. My best guess is that the InstanceData vertex declaration isn't configured correctly. No shader files are being used here.

My vertex declarations, and matrix struct used for instancing data:

static D3DVERTEXELEMENT9 vertexN[] = 
{
	{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
	{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
	{0, 24, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
	{0, 28, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
	D3DDECL_END()
};


const D3DVERTEXELEMENT9 InstanceData[] =
{
	{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_COLOR,    0},
	D3DDECL_END()
};

struct MatrixVertex
	{
	MatrixVertex(D3DXVECTOR4& i1, D3DXVECTOR4& i2, D3DXVECTOR4&i3, D3DXVECTOR4&i4, D3DXCOLOR&c)
	: r1(i1),r2(i2),r3(i3),c1(c) {}
	D3DXVECTOR4 r1;     // row 1
	D3DXVECTOR4 r2;     // row 2
	D3DXVECTOR4 r3;	   // row 3
	D3DXCOLOR c1; // color
	};







Setup code

D3DXMATRIX* InstanceMatrices;
LPDIRECT3DVERTEXDECLARATION9 pMeshVertexDecl;
LPDIRECT3DVERTEXBUFFER9 MeshVB, ModelMatrixVB;
LPDIRECT3DINDEXBUFFER9  MeshIB;

// Loading the mesh from a file, it produces the set of vertices 
// to use in the vertex buffer

	TestMesh.loadfromOBJ("Data/test.obj");

	// Set up vertex declaration
	Display.D3Device()->CreateVertexDeclaration(vertexN, &pMeshVertexDecl);
	numInstances = 20;

	// Create the matrix vertex buffer
	Display.D3Device()->CreateVertexBuffer(numInstances * sizeof(MatrixVertex),
		D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &ModelMatrixVB, NULL);

	// Set up the instance matrices
	InstanceMatrices = new D3DXMATRIX[numInstances];

	// Lock the vertex buffer
	MatrixVertex* pVertices;
	ModelMatrixVB->Lock( 0, 0, (void**)&pVertices, D3DLOCK_DISCARD);
	for (unsigned i = 0; i < numInstances; i++) {

		pVertices[i].r1 = D3DXVECTOR4(1.0f, 0.0f, 0.0f, (float)i); // translate along X;
		pVertices[i].r2 = D3DXVECTOR4(0.0f, 1.0f, 0.0f, (float)i); // translate along Y;
		pVertices[i].r3 = D3DXVECTOR4(0.0f, 0.0f, 1.0f, 0.0f);
		pVertices[i].c1 = 0xffffffff;
	}
	ModelMatrixVB->Unlock();

	// Set index buffer indices
	// Allocating the number of vertices

	UINT *g_indices;
	g_indices = new UINT[TestMesh.verts().size()];  

	unsigned totalVertices = TestMesh.verts().size();
	for (unsigned i = 0; i < totalVertices; i++) {
		g_indices[i] = i;
	}

	// Create the mesh vertex buffer and index buffer
	Display.D3Device()->CreateVertexBuffer(totalVertices * sizeof(vertexN),
		D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &MeshVB, NULL );

	Display.D3Device()->CreateIndexBuffer(totalVertices * sizeof(UINT),
		D3DUSAGE_WRITEONLY, D3DFMT_INDEX32, D3DPOOL_MANAGED, &MeshIB, NULL);

	// Lock the mesh vertex buffer
	void *pData;
	MeshVB->Lock( 0, 0, (void**)&pData, 0);
	memcpy(pData, &(TestMesh.verts().at(0)), totalVertices * sizeof(vertexN)); 

	MeshVB->Unlock();

	// Lock the mesh index buffer

	MeshIB->Lock(0, 0, (void**)&pData, 0);
	memcpy(pData, g_indices, totalVertices * sizeof(UINT));

	MeshIB->Unlock();







Rendering code


	// Display setup code to start the scene...

	// Vertex declaration

	Display.D3Device()->SetVertexDeclaration(pMeshVertexDecl);


	// stream sources
	Display.D3Device()->SetStreamSourceFreq(0,(D3DSTREAMSOURCE_INDEXEDDATA | numInstances));
	Display.D3Device()->SetStreamSourceFreq(1,(D3DSTREAMSOURCE_INSTANCEDATA | 1));

	Display.D3Device()->SetStreamSource(0, MeshVB, 0, 
		D3DXGetDeclVertexSize(vertexN, 0));
	Display.D3Device()->SetStreamSource(1, ModelMatrixVB, 0,
		D3DXGetDeclVertexSize(InstanceData, 1));

	// Set index buffer to use
	Display.D3Device()->SetIndices(MeshIB); 

	// Display mesh and clean stream sources
	Display.D3Device()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, totalVertices, 
		0, totalVertices / 3);

	Display.D3Device()->SetStreamSourceFreq(0, 1);
	Display.D3Device()->SetStreamSourceFreq(1, 1);

	Display.D3Device()->EndScene();
	Display.D3Device()->Present(0, 0, 0, 0); 







 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

From what i remember the vertex declaration must be unique and inclusive of the instance data setting the slot parameter to a different value (1 in this case), like :

static D3DVERTEXELEMENT9 vertexN[] =
{
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
{0, 24, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
{0, 28, 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_COLOR, 0},
D3DDECL_END()
};

Otherwise the shader will never know how the instance is composed...

 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

If I put all the instance data into the same declaration, wouldn't that mean that I would have an excess of space allocated for the mesh vertex buffers? Since those buffers would only contain the geometry of the mesh, not its location, orientation, etc. Besides, if I run the code after adjusted for the single large vertex declaration, I get first-chance exception messages and nothing shows up on the screen. All the samples I've looked at had their instance data separate from the mesh vertex data in two separate declarations.

 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

no because even thought you put all in the same vertex declaration you are actually using different slot for trasmitting the data. (and before drawing you set the setstreamfrequency specifing how many times a determined slot is trasferred (per vertex and per instance data basically))

 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Can you give me an example of how to set up vertex buffers for both the instances and the mesh and copy data into them if I used a single, combined vertex declaration?

The reason I wasn't sure in the last post is that with your suggestion, I was creating a vertex buffer of size ((sizeof(vertexN) + sizeof(instanceData)) * totalVertices) when the information I copied was only of size (sizeof(vertexN) * totalVertices). And that resulted in the vertex buffer stream reading unset data and probably why nothing was rendered at all (as opposed to a single mesh as I had originally).

Besides, the suggestion that the vertex declaration must be unique and inclusive of the instance data isn't always true, as Microsoft and NVidia used two separate declarations in their examples.

[Edited by - JustChris on April 10, 2009 3:05:22 PM]

 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

            device.VertexDeclaration = _layout;

            device.SetStreamSource(0, _meshVertexBuffer, 0, 56);
            device.SetStreamSourceFrequency(0, NPrimitives, StreamSource.IndexedData);

            device.SetStreamSource(1, _instancesVertexBuffer, 0, 52);
            device.SetStreamSourceFrequency(1, 1, StreamSource.InstanceData);

            device.Indices = _meshIndexBuffer;

            int passes = _shader.Shader.Begin();
            for (int pass = 0; pass < passes; pass++)
            {
                _shader.Shader.BeginPass(pass);
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, MeshToRender.VertexCount, 0, MeshToRender.IndexCount);
                _shader.Shader.EndPass();
            }
            _shader.Shader.End();

            device.ResetStreamSourceFrequency(0);
            device.ResetStreamSourceFrequency(1);



My code for drawing with instancing in DirectX9 with SlimDX. (you can change this into c++ code alone, its just a wrapper.)
_layout is the vertexdeclaration. It is declared as follow :

            _layout = new VertexDeclaration(Parent.ScreenManager.Device, new VertexElement[] 
                                        {  
                                            new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
                                            new VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0),
                                            new VertexElement(0, 24, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
                                            new VertexElement(0, 32, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Tangent, 0),
                                            new VertexElement(0, 44, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Binormal, 0),
                                            new VertexElement(1, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Color, 0),
                                            new VertexElement(1, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Color, 1),
                                            new VertexElement(1, 24, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Color, 2),
                                            new VertexElement(1, 36, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Color, 3),
                                            VertexElement.VertexDeclarationEnd
                                        });



 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

I see how you set buffer streams, but you still haven't shown how you would initialize the buffers :P And are shader passes optional? I'm not using any shader files.

It seems like instancing is a topic that is treated more of an "expert" level area. Instancing sounds like should be an obvious, elementary consideration for optimizing your scenes. There aren't even many tutorials online I can find on how to do this. I've been thinking of getting a book on DirectX soon anyways, and would be helpful to know which ones do a good job of covering this topic.

 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

How you fill the buffer is highly dependent on how you manage objects. You just have to create 1 vertexbuffer for the mesh, 1 indexbuffer for the mesh (copied from the mesh object EXACTLY as they are (lock-copy-unlock :D)) and 1 vertexbuffer for the instance data filled as you want. There isn't anything difficult in this i think. In my case i load the mesh vertex buffer and indexbuffer when i select the mesh on my object, and regenerate the instance buffer whenever a change is occurred in one of the instances of the mesh right before the draw. (changes like an object has moved or such)

For the shader problem, maybe you can do it even with simply the fixed pipeline, but i've not tested it so i can't tell for sure.

 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Okay, I've gotten the idea now that instancing is a lot like batching. The vertex stream for instances is pretty much treated as arbitrary data until you pass that information to the shader, is this right? I'd like to hear other people's advice as well. Now I know how to use vertex shaders to move the vertices of a single mesh, but still unclear as how to choose which stream to read the data from. The second stream appears to be doing nothing.

 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Put together inside the SINGLE declaration of your vertex shader input data the vertex data and instance data. The GPU will know what you want.

 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Ah i forgot to tell you, but Hardware Instancing is a feature available only for graphic device with Shader Model 3.0 or higher. Will not work for anything lower.
In the MS example Instancing for DirectX9 there are other techniques available for older graphic devices to do instancing (thought they are slower than the hardware instancing).

 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

My hardware supports vs_3_0 so it should work fine. Okay, I now have my vertex declaration as such:

const D3DVERTEXELEMENT9 instanceVertexN[] = 
{
	{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
	{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
	{0, 24, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
	{0, 28, 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()
};





Texcoords 1-4 represent a 4x4 matrix (actually 4 D3DXVECTOR4s) for transforming the instanced mesh. This is how the instance data gets updated on every frame:

	instanceVertex *pVertices;
	pInstanceVB->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD);

	for (unsigned i = 0; i < numInstances; i++) {

		// move the next cube by 1 on the x and y directions
		pVertices[i].row1 = D3DXVECTOR4( 1.0f, 0.0f, 0.0f, (float)i);
		pVertices[i].row2 = D3DXVECTOR4( 0.0f, 1.0f, 0.0f, (float)i);
		pVertices[i].row3 = D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 0.0f);
		pVertices[i].row4 = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f);
	}
	pVertices = pVertices;
	pInstanceVB->Unlock();





Now I think the problem is with the shader. I have a shader function that returns a float4 for the vertex positions. The inputs are 4 separate float4s (I'll change it to a float4x4 when I get it working) which should be the D3DXVECTOR4s above. It's not reading the vertex streams like I thought it would. This is what the shader function looks like now:

float4 Instanced(float4 vPos : POSITION, 
	in float4 vWorld1 : TEXCOORD1, // the four semantics that refer to the data types in the vertex declaration
	in float4 vWorld2 : TEXCOORD2,
	in float4 vWorld3 : TEXCOORD3,
	in float4 vWorld4 : TEXCOORD4)
{
	float4x4 world = float4x4(vWorld1, vWorld2, vWorld3, vWorld4);
	float4 position = mul(vPos, mWorldViewProj); 
// multiplied by the product of the world, view and projection matrices
	
	return position;
}





It's rendering something, but I only see one cube mesh. I want it to show a cube on every new x and y position as x and y go up by one.

 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

with your vertexdeclaration you should do something like :

struct VS_INPUT
{
    float3 Pos : POSITION;
    float3 Norm : NORMAL;
    float color : COLOR;
    float2 Tex : TEXCOORD0;
    float4 MatrixOne : TEXCOORD1;
    float4 MatrixTwo : TEXCOORD2;
    float4 MatrixThree : TEXCOORD3;
    float4 MatrixFour : TEXCOORD4;
};

struct VS_OUTPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD1;
    float4 color : COLOR;
};

VS_OUTPUT VS( VS_INPUT input )
{
   // Your vertex shader code here
}

float4 PS( VS_OUTPUT input ) : SV_Target
{
   // Your pixel shader code here
}

technique Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_3_0, VS() ) );
        SetPixelShader( CompileShader( ps_3_0, PS() ) );
    }
}



In your shader code you didn't specified all the input parameters you passed into the vertex buffer...

 User Rating: 1063   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link


float4x4 world = float4x4(vWorld1, vWorld2, vWorld3, vWorld4);
float4 position = mul(vPos, mWorldViewProj);
// multiplied by the product of the world, view and projection matrices

return position;

.............

obviously this wont work because you don't do anything with the world matrix after you make it; it looks like you just mulitply by the regular worldviewproj.

You cant do it like this...instead pass in the view and projection matrices seperately and do something like this:

float4x4 world = float4x4(vWorld1, vWorld2, vWorld3, vWorld4);
float4 position=mul(vPos,world);
posiiton=mul(position,matView);
posiiton=mul(position,matProj);


 User Rating: 969   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Sorry, that didn't produce any different results.

I know that the vertex buffer has the data loaded correctly...I checked it with Pix. There are just not enough articles out there to show hardware instancing done in a snap :O

 User Rating: 1026   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

Either way you have to do something like what I suggested..just using the worldViewProj matrix wont dowhat you want.. because you are constructing a new world matrix in the shader...


I too have had trouble with hardware instancing with D3DXmeshes...I never did get it working right to be honest.

 User Rating: 969   |  Rate This User  Send Private MessageView Profile Report this Post to a Moderator | Link

All times are ET (US)

Post Reply
 Last Thread Next Thread 
Forum Rules:
You may not post new threads
You may post replies
You may not edit your posts
You may not use HTML in your posts
Jump To:
Administrative Options: