Instancing not working for me (SlimDX, DX9)

Started by
5 comments, last by Mort 12 years, 10 months ago
I'm having problem with getting instancing to work, in my Graphics Engine.

It seems that no matter what I do, I can only get DirectX to render a single instance of my model and I'm not able to apply a transformation matrix to that one instance.

I had a look at the DirectX SDK's instancing sample and documentation, but unfortunately that sample isn't particularly well written (Storing instancing parameters as a color and 4 bytes for position and rotation of the instance). Even when trying to duplicate the results from this sample, I can't get more than one instance.

I've tried to include all my relevant code, in a cut-down form, to keep it as simple as possible to read.

Can anyone please tell me what I'm doing wrong here ?


#region Set up vertex declaration
int vertexSize = 24;
int instanceSize = 64;

VertexElement []vertexElementsArray = 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(1, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
new VertexElement(1, 16, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 1),
new VertexElement(1, 32, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 2),
new VertexElement(1, 48, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 3),
};

// Create a vertex declaration based on the vertex elements.
DrawingDevice.VertexDeclaration = new VertexDeclaration(DrawingDevice, vertexElementsArray);
#endregion

#region Set up vertex buffer for vertices
Vector []vectors = new Vector[]
{
new Vector(-1, 0, 0),
new Vector( 0, 1, 0),
new Vector( 1, 0, 0)
};

Vector normal = new Vector(0, 0, -1);

VertexBuffer vertexBuffer = new VertexBuffer(DrawingDevice, vertexSize*vectors.Length, Usage.None, VertexFormat.None, Pool.Managed);
DataStream vertexBufferStream = vertexBuffer.Lock(0, vertexSize*vectors.Length, LockFlags.None);

// Copy the vertex data to the Vertex Buffer memory block.
for(int nCount=0; nCount<vectors.Length; nCount++)
{
vertexBufferStream.Write<float>(vectors[nCount].X);
vertexBufferStream.Write<float>(vectors[nCount].Y);
vertexBufferStream.Write<float>(vectors[nCount].Z);

vertexBufferStream.Write<float>(normal.X);
vertexBufferStream.Write<float>(normal.Y);
vertexBufferStream.Write<float>(normal.Z);
}

// Unlock the Vertex Buffer again, to allow rendering of the Vertex Buffer data.
vertexBuffer.Unlock();
#endregion


#region Set up vertex buffer for instances
int numberOfObjects = 10;

// Create the interleaved Vertex Buffer.
VertexBuffer instanceBuffer = new VertexBuffer(DrawingDevice, instanceSize*numberOfObjects, Usage.None, VertexFormat.None, Pool.Managed);

DataStream instanceBufferStream = instanceBuffer.Lock(0, instanceSize*numberOfObjects, LockFlags.None);

// Create identity matrix.
Math3D.Matrix matrix = new Math3D.Matrix();

// Copy the matrix data to the Vertex Buffer memory block.
for(int count=0; count<numberOfObjects; count++)
{
// Translate the matrix along the X axis.
matrix._41 = count;

instanceBufferStream.Write<float>(matrix[0, 0]);
instanceBufferStream.Write<float>(matrix[0, 1]);
instanceBufferStream.Write<float>(matrix[0, 2]);
instanceBufferStream.Write<float>(matrix[0, 3]);

instanceBufferStream.Write<float>(matrix[1, 0]);
instanceBufferStream.Write<float>(matrix[1, 1]);
instanceBufferStream.Write<float>(matrix[1, 2]);
instanceBufferStream.Write<float>(matrix[1, 3]);

instanceBufferStream.Write<float>(matrix[2, 0]);
instanceBufferStream.Write<float>(matrix[2, 1]);
instanceBufferStream.Write<float>(matrix[2, 2]);
instanceBufferStream.Write<float>(matrix[2, 3]);

instanceBufferStream.Write<float>(matrix[3, 0]);
instanceBufferStream.Write<float>(matrix[3, 1]);
instanceBufferStream.Write<float>(matrix[3, 2]);
instanceBufferStream.Write<float>(matrix[3, 3]);
}

// Unlock the Vertex Buffer again, to allow rendering of the Vertex Buffer data.
instanceBuffer.Unlock();
#endregion


#region Set up index buffer
int numberOfSurfaces = 1;

IndexBuffer indexBuffer = new IndexBuffer(DrawingDevice, numberOfSurfaces*sizeof(uint)*3, Usage.None, Pool.Default, false);

// Lock the buffer, so that we can access the data.
DataStream indexBufferStream = indexBuffer.Lock(0, numberOfSurfaces*sizeof(uint)*3, LockFlags.None);

indexBufferStream.Write<uint>(0);
indexBufferStream.Write<uint>(1);
indexBufferStream.Write<uint>(2);

// Unlock the stream again, committing all changes.
indexBuffer.Unlock();

Device.Indices = indexBuffer;
#endregion

#region Render the scene
DrawingDevice.SetStreamSource(0, vertexBuffer, 0, vertexSize);
DrawingDevice.SetStreamSource(1, instanceBuffer, 0, instanceSize);

// Specify how many times the vertex stream source and the instance stream source should be rendered.
DrawingDevice.SetStreamSourceFrequency(0, 10, StreamSource.IndexedData);
DrawingDevice.SetStreamSourceFrequency(1, 1, StreamSource.InstanceData);

DrawingDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 3, 0, 1);

// Reset the stream source frequence to it's default values, before exiting.
DrawingDevice.ResetStreamSourceFrequency(0);
DrawingDevice.ResetStreamSourceFrequency(1);
#endregion
- Mort
Advertisement
It's hard to say from just the code you've posted. Try removing things until you have a minimal complete example that demonstrates the problem, and then uploading the code so that someone else can see it all in context.
Mike Popoloski | Journal | SlimDX
The code I have posted should contain everything necessary to draw one polygon and render it 10 times using instancing.

I have cut away textures, lighting and more complex polygons, since they don't serve to demonstrate the problem.The code that is ommitted sets up the Device (DrawingDevice), handles windows, matrices, models, vectors and a lot of other stuff that shouldn't be relevant to the problem.

I'm able to render my models successfully without instancing and everything works fine if doing so. My problem is that when I try to improve performance by using instancing instead of rendering each vertex buffer individually, I don't get the result I expect and the transformation matrix I tried to apply using instancing seems to be ignored.
- Mort
Have you tried using PIX? You can inspect the device state at the time of the draw call so that you can ensure it's what you expect it to be, as well as view the vertex data before and after your vertex shader. You can even step into the vertex shader to debug it, if you want.
Yes I tried using PIX. It showed me the scene as it was being rendered, but didn't tell me why the scene wasn't rendering more than one instance of the model.
- Mort
That is definitely not everything necessary to do any sort of drawing. For all we know, your pixel shader could be set up to return a solid color, or your vertex shader could be transforming everything on top of itself, or any other of a billion different possibilities. I'll reiterate; if you want more help than some random guessing, you need to post actual code.
Mike Popoloski | Journal | SlimDX
I found the solution to the problem, myself.

I found out that to use hardware instancing, you also need to include a bit of high level shader language (HLSL) code, that will be sent to GPU. This was the bit of information that I failed to extract from the DirectX sample and other articles regarding hardware instancing.

Fortunately I found a great set of articles describing how to use HLSL on this website: http://www.riemers.n...arp/series3.php. The HLSL tutorial found there doesn't describe how to do instancing, but it provides the set of information needed to get started on HLSL and using it to perform a simple rendering.This information combined with the Instancing sample from the DirectX SDK allowed me to create a simple shader which worked with the sample of code that I previously posted.

The reason the missing piece of HLSL eluded me was that even though I didn't have any HLSL code myself, the default implementation used by DirectX would still render a single instance of the model provided.

To allow others to learn from my mistakes, here is the final bit of code that I needed to get instancing to work:


#region Set up vertex declaration
int vertexSize = 24;
int instanceSize = 64;

VertexElement []vertexElementsArray = 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(1, 0, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
new VertexElement(1, 16, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 1),
new VertexElement(1, 32, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 2),
new VertexElement(1, 48, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 3),
};

// Create a vertex declaration based on the vertex elements.
device.VertexDeclaration = new VertexDeclaration(device, vertexElementsArray);
#endregion

#region Set up vertex buffer for vertices
Vector []vectors = new Vector[]
{
new Vector(-1, 0, 0),
new Vector( 0, 1, 0),
new Vector( 1, 0, 0)
};

Vector normal = new Vector(0, 0, -1);

VertexBuffer vertexBuffer = new VertexBuffer(device, vertexSize*vectors.Length, Usage.None, VertexFormat.None, Direct3D.ResourcePool);
DataStream vertexBufferStream = vertexBuffer.Lock(0, vertexSize*vectors.Length, LockFlags.None);

// Copy the vertex data to the Vertex Buffer memory block.
for(int nCount=0; nCount<vectors.Length; nCount++)
{
vertexBufferStream.Write<float>(vectors[nCount].X);
vertexBufferStream.Write<float>(vectors[nCount].Y);
vertexBufferStream.Write<float>(vectors[nCount].Z);

vertexBufferStream.Write<float>(normal.X);
vertexBufferStream.Write<float>(normal.Y);
vertexBufferStream.Write<float>(normal.Z);
}

// Unlock the Vertex Buffer again, to allow rendering of the Vertex Buffer data.
vertexBuffer.Unlock();
#endregion

#region Set up vertex buffer for instances
int numberOfObjects = 10;

// Create the interleaved Vertex Buffer.
VertexBuffer instanceBuffer = new VertexBuffer(device, instanceSize*numberOfObjects, Usage.None, VertexFormat.None, Direct3D.ResourcePool);

DataStream instanceBufferStream = instanceBuffer.Lock(0, instanceSize*numberOfObjects, LockFlags.None);

// Create identity matrix.
Math3D.Matrix matrix = new Math3D.Matrix();

// Copy the matrix data to the Vertex Buffer memory block.
for(int count=0; count<numberOfObjects; count++)
{
// Translate the matrix along the X axis.
matrix._41 = (float) count;
matrix._42 = (float) 0;
matrix._43 = (float) 10;

instanceBufferStream.Write<float>(matrix[0, 0]);
instanceBufferStream.Write<float>(matrix[0, 1]);
instanceBufferStream.Write<float>(matrix[0, 2]);
instanceBufferStream.Write<float>(matrix[0, 3]);

instanceBufferStream.Write<float>(matrix[1, 0]);
instanceBufferStream.Write<float>(matrix[1, 1]);
instanceBufferStream.Write<float>(matrix[1, 2]);
instanceBufferStream.Write<float>(matrix[1, 3]);

instanceBufferStream.Write<float>(matrix[2, 0]);
instanceBufferStream.Write<float>(matrix[2, 1]);
instanceBufferStream.Write<float>(matrix[2, 2]);
instanceBufferStream.Write<float>(matrix[2, 3]);

instanceBufferStream.Write<float>(matrix[3, 0]);
instanceBufferStream.Write<float>(matrix[3, 1]);
instanceBufferStream.Write<float>(matrix[3, 2]);
instanceBufferStream.Write<float>(matrix[3, 3]);
}

// Unlock the Vertex Buffer again, to allow rendering of the Vertex Buffer data.
instanceBuffer.Unlock();
#endregion

#region Set up index buffer
int numberOfSurfaces = 1;

IndexBuffer indexBuffer = new IndexBuffer(device, numberOfSurfaces*sizeof(uint)*3, Usage.None, Direct3D.ResourcePool, false);

// Lock the buffer, so that we can access the data.
DataStream indexBufferStream = indexBuffer.Lock(0, numberOfSurfaces*sizeof(uint)*3, LockFlags.None);

indexBufferStream.Write<uint>(0);
indexBufferStream.Write<uint>(1);
indexBufferStream.Write<uint>(2);

// Unlock the stream again, committing all changes.
indexBuffer.Unlock();

device.Indices = indexBuffer;
#endregion

// Read a text file from an included resource, called "Instancing.fx"
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream stream = assembly.GetManifestResourceStream("Instancing.fx");
byte[] instancingEffects = new byte[stream.Length];
string compilationErrors;

// Multiply projection and view matrix here to provide a view-projection matrix. Right here, the view is an identity matrix, so only projection matrix is used.
SlimDX.Matrix viewProjectionMatrix = MyProjectionMatrix;
Effect effect = null;

stream.Read(instancingEffects, 0, instancingEffects.Length);

try
{
// Compile the bit of HLSL.
effect = Effect.FromMemory(device, instancingEffects, null, null, null, ShaderFlags.None, null, out compilationErrors);
}
catch(Exception exception)
{
string message = exception.ToString();
}

effect.Technique = "Instance0Textures";
effect.SetValue("xViewProjection", viewProjectionMatrix);

#region Render the scene
device.SetStreamSource(0, vertexBuffer, 0, vertexSize);
device.SetStreamSource(1, instanceBuffer, 0, instanceSize);

// Specify how many times the vertex stream source and the instance stream source should be rendered.
device.SetStreamSourceFrequency(0, 10, StreamSource.IndexedData);
device.SetStreamSourceFrequency(1, 1, StreamSource.InstanceData);

int numberOfPasses = effect.Begin(0);
for(int pass=0; pass<numberOfPasses; pass++)
{
effect.BeginPass(pass);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 3, 0, 1);
effect.EndPass();
}
effect.End();

// Reset the stream source frequence to it's default values, before exiting.
device.ResetStreamSourceFrequency(0);
device.ResetStreamSourceFrequency(1);
#endregion



And here is the bit of HLSL that made it work (The "instancing.fx" text file, included in the project):

float4x4 xViewProjection;

void InstancingWith0Textures(float4 position : POSITION, float4 tex0 : TEXCOORD0, float4 tex1 : TEXCOORD1, float4 tex2 : TEXCOORD2, float4 tex3 : TEXCOORD3, out float4 transformedPosition : POSITION)
{
// Use the values from the 4 texture coordinates to compose a transformation matrix.
float4x4 transformation = {tex0, tex1, tex2, tex3};

// Transform the vertex into world coordinates.
transformedPosition = mul(position, transformation);

// Transform the vertex from world coordinates into screen coordinates.
transformedPosition = mul(transformedPosition, xViewProjection);
}

technique Instance0Textures
{
pass Pass0
{
VertexShader = compile vs_2_0 InstancingWith0Textures();
PixelShader = NULL;
}
}
- Mort

This topic is closed to new replies.

Advertisement