Input Layout - It's all Greek to me!
Data in a vertex buffer is just a long string of bits. For the GPU to work with this data, it must be able to interpret the bits in a meaningful way. This is accomplished via an input layout.
Let's first examine the anatomy of a vertex. A vertex can have many properties. In addition to position, it can have a normal, color(s), texture coordinates, etc. For our purposes, a vertex will just have a position. We will describe the vertex using a struct with one property.
struct SimpleVertex
{
D3DXVector3 pos;// Position
// other possible attributes
};
The data for the vertices will be stored in the vertex buffer. An input layout is what allows the GPU to interpret the string of bits in the vertex buffer as data for individual vertices.
Like we have seen before, before a resource can be created and set, a decription must be filled out. In this case, the description will not be for the input layout as a whole; rather a description will be filled out for each property in the vertex structure.
These descriptions will be in the form of a D3D10_INPUT_ELEMENT_DESC structure. An application must define an array of one more of these structures. In our case, because our vertex only has one property, our array will consist one description.
// Define the input layout
D3D10_INPUT_ELEMENT_DESC layout[] =
{
{ L"POSITION", // SemanticName
0, // SemanticIndex
DXGI_FORMAT_R32G32B32_FLOAT, // Format
0, // InputSlot
0, // AlignedByteOffset
D3D10_INPUT_PER_VERTEX_DATA, // InputSlotClass
0 }, // InstanceDataStepRate
// {...}, another description if needed
// {...}, another description if needed
};
UINT numElements = sizeof(layout)/sizeof(layout[0]);
In addition to the description(s) of the property in the vertex, an input signature of the associated vertex shader is required to create the input layout. With the required information in hand, the input layout is created and set on the device.
// Create the input layout
D3D10_PASS_DESC PassDesc;
g_pTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );
if( FAILED (g_pd3dDevice->CreateInputLayout(
layout,
numElements,
PassDesc.pIAInputSignature,
PassDesc.IAInputSignatureSize,
&g_pVertexLayout ) ) )
return FALSE;
// Set the input layout
g_pd3dDevice->IASetInputLayout( g_pVertexLayout );
Searches in the SDK for D3D10_PASS_DESC and CreateInputLayout will shed some light on the specifics. A more indepth explanation of "the technique object and the associated shaders" will be found in the next tutorial.
For now, notice the hint as to the coupled relationship between the layout and the shaders in the code:
From the code for the description of the vertex in Tutorial02.cpp
{ L"POSITION", // SemanticName
From the code for the vertex shader in Tutorial02.fx
float4 VS( float4 Pos : POSITION ) : SV_POSITION
Notice the use of POSITION in both pieces of code. It appears as though the use of POSITION as the SemanticName in the layout description is indicating to the vertex shader that this is the data it will be performing its processing on.
Vertex Buffer - Show me the vertices!
We can now turn to creating the vertex data, creating a vertex buffer that stores that data, and setting the vertex buffer on the device.
The vertices for our triangle will be stored in a SimpleVertex array with 3 elements. Following the usual protocol, before the vertex buffer can be created, descriptions must be filled out.
The D3D10_BUFFER_DESC provides a description for the vertex buffer.
The D3D10_SUBRESOURCE_DATA provides a description for the data that will be in the vertex buffer.
ID3D10Device::CreateBuffer() is then called to create the buffer. This is followed by a call to ID3D10Device::IASetVertexBuffers() to bind the vertex buffer to the input-assembler of the pipeline.
// Define the vertices to be stored in the vertex buffer
SimpleVertex vertices[] =
{ // ( X, Y, Z )
D3DXVECTOR3( 0.0f, 0.5f, 0.5f ),
D3DXVECTOR3( 0.5f, -0.5f, 0.5f ),
D3DXVECTOR3( -0.5f, -0.5f, 0.5f ),
};
// Vertex buffer description
D3D10_BUFFERDESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
bd.ByteWidth = sizeof( SimpleVertex ) * 3;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
// Create vertex buffer
if( FAILED( g_pd3dDevice->CreateBuffer (
bd, // buffer description
&InitData, // data to be stored in the vertex buffer
&g_pVertexBuffer ) ) ) // pointer to the newly created buffer
return false;
// Set vertex Buffer
UINT stride = sizeof( SimpleVertex );
UINT offset = 0;
g_pd3dDevice->IASetVertexBuffer(
0,
1,
&g_pVertexBuffer,
&stride,
&offset );
Primitive Topology - What do I do with these vertices?
Before the GPU can render the triangle, it must know how to retrieve the vertices from the vertex buffer. Setting the Primitive Topology does this. The Primitive Topology tells the GPU what to render and the order in which it needs to access the vertices in the buffer. In our case of 3 vertices for 1 triangle, the TRIANGLELIST topology will suffice.
// Set primitive topology
g_pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
Render the triangle!
The final step in the process involves using the technique obtained through the effect system to setup the GPU for drawing. First a D3D10FX_TECHNIQUE_DESC description of the technique is obtained via the ID3D10EffectTechnique::GetDesc() function call. The application will then loop through all the passes in the technique.
During each loop, the shaders and render states for that particular pass are bound to the graphics pipeline. This step will be further detailed in the next tutorial. Finally a call to ID3D10Device::Draw() is made. This instructs the GPU to draw using the current shaders, vertex buffer, vertex layout, and primitive topology bound to the pipeline.
// Render the triangle
D3D10_TECHNIQUE_DESC techDesc;
g_pTechnique->GetDesc( &techDesc );
for( UINT p = 0; p <>
{
g_pTechnique->GetPassByIndex( p )->Apply(0);
g_pd3dDevice->Draw( 3, 0 );
}
Cleanup
The following resources were created and need to be released.
if( g_pVertexBuffer ) g_pVertexBuffer->Release();
if( g_pVertexLayout ) g_pVertexLayout->Release();
if( g_pEffect) g_pEffect->Release();
Experiments
-Try different values for the X, Y, and Z positions of the vertices. What does this highlight about the min/max values used for the render window as a whole? What about camera orientation?
-Add more vertices to the vertices[] array and modify the vertex buffer description and the Draw() call to include them.
-Search the SDK for D3D10_PRIMITIVE_TOPOLOGY. Try setting different topologies and observe the results.
-Combine the different topologies with different number of vertices.
Conclusion
There you have it! A triangle drawn onto your screen.
Let's quickly recap what we did.
-Determined what information each of our vertices would contain and the format to represent them.
-Created an input layout to instruct the GPU how to interpret the data in a vertex buffer as vertices.
-Created our vertex data, stored it in a vertex buffer, and bound the buffer to the pipeline.
-Instructed the GPU how to use the vertices in the vertex buffer to render the chosen topology.
-Rendered the triangle using the technique obtained from the effect system.
One thing I wanted to mention was the use of 'input layout' and 'vertex layout' in the SDK tutorials. While each was used as a heading for a specific section, it appeared to me as though their meanings were being used in the text interchangably. I chose to treat them as referring to the same concept. If this is incorrect and they are in fact distinct, please let me know and give me an explanation.
As usual, questions, comments, criticisms, and correction are welcomed!
~Elmer
DirectX10 SDK Location: DirectX Graphics->Tutorials and Samples ->Tutorials->Basic Tutorials
Source Code Location: (SDK root)\Samples\C++\Direct3D10\Tutorials\Tutorial02
------