Skip to main content
gamedev.net
🔒 Locked

D3D8 Index buffer problems

Started by Cybertron Aug 2, 2002 at 11:37 AM 5 replies 900+ views
Original Post
Cybertron
Cybertron
Can someone please help me with using Index Buffers in D3D8? the help docs are useless, the word INDEX is thrown around constantly, so you don''t know weather they are refering to the IB or VB!
Evil Bill
Evil Bill
Whats the question?

Heres how to draw an indexed quad using triangle lists (pseudo-code)

      
struct Vertex
{
Vector vPos;
DWORD dwDiffuse;
};
#define FVF_VERTEX (whatever it is)

pDevice->CreateVertexBuffer(4*sizeof(Vertex)) // 4 unique vertices

pDevice->CreateIndexBuffer(6*sizeof(WORD)) // 6 indices


Lock VB
Fill VB with all unique vertices (0,0), (1,0), (0,1), (1,1)
Unlock VB

WORD pIndices[] =
{
0,1,2, // First triangle (Verts 0,1,2)

1,3,2 // Second Triangle (Verts 1,3,2)

}
Lock IB
memcpy() from pIndices into pointer returned by IB
Unlock IB

pDevice->SetStreamSource()
pDevice->SetIndices()

// Clear, Begin Scene, etc.

pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,4,0,2);
// End scene, present, etc.



One confusing part may be the DrawIndexedPrimitve() call:

HRESULT DrawIndexedPrimitive(
D3DPRIMITIVETYPE Type,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount
);

Type: D3DPT_TRIANGLELIST: Specifies we're drawing a list of triangles. each triangle is defined by 3 indices.
MinIndex: 0: The lowest vertex index used in this call
NumVertices: 4: The number of vertices used from MinIndex. Since MinIndex is 0, and we use all 4 vertices, this member is 4.
StartIndex: 0: Used for if you have multiple triangles in the buffer (we do, but we're drawing them both wioth one call so it doesn't count.).
PrimitiveCount: 2: The number of primitives (individual triangles in this case [specified by D3DPT_TRIANGLELIST]) to draw.


Here's another way to do the above drawing:
      
pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,3,0,1);
pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,1,3,3,1);

Looking at the second DIP() call:
MinIndex: 1: Since the second triangle is defined by the indices {1,3,2}, the lowest index in there is 1.
NumVertices: 3: The maximum vertex number used is 4 (index #3, since the vertices are numbered 0,1,2,3). 4-MinIndex = 4-1 = 3.
This is telling D3D that it needs to transform NumVertices (3)vertices starting with MinIndex (1), which means it needs to transform vertices 1, 2 & 3.
StartIndex: 3: Start reading indices from pIndices[StartIndex] = pIndices[3]. This way D3D will read indices {1,3,2}.
PrimitiveCount: 1: Draw 1 primitive (individual triangle). One triangle = 3 vertices, so read 3 indices from the index buffer.

Sorry if thats a bit confusing, but i'm useless at explaining things.

[edit]
Damn format tags...
[/edit]

Steve

Steve
DirectX Programmer
Soon to be the new Bill Gates


[edited by - Evil Bill on August 2, 2002 2:20:51 PM]
Member of the Unban Mindwipe Society (UMWS)
Evil Bill
Evil Bill
Ok, that last post was more than a little confusing. I know how confusing this is to start with (it took me ages to figure out).

Lets have that example again...

0-----1
| /|
| / |
|/ |
2-----3

Here we have a quad draw with 2 triangles. The first triangle made up of vertices {0,1,2} and the second triangle of vertices {1,3,2}.

With DrawPrimitive() if you wanted to use one vertex multiple times (e.g. vertices 1 and 2), you needed to copy that vertex into the stream again. Your vertex buffer would be filled like this: {0,1,2,1,3,2} and you'd just to a DrawPrimitive() to draw 2 triangles. As i'm sure you can see, this is a waste, since vertices 1 and 2 are used twice (this isn't such a good example, but you might have one vertex being used 5 or 6 times).
Vertices take up a fair bit of space (say 32 bytes), and if you have this on a larger scale, you could waste a lot of space with these duplicated vertices. Another problem is that D3D has to transform all the vertices into screen space, D3D doesn't know that the vertices at offset 1 and 3 are the same vertex (vertex number 1), so it goes and transforms it again, doubling the maths it has to do.

What would make more sense is if you could say, "Hey, i've got this shape that uses 4 unique vertices. The first triangle is made up from vertices 0, 1 and 2 and the second triangle is made up of vertices 1, 3 and 2". Thats where indices come in...

When you're using index buffers, you only put in unique vertices in your VB. This way your VB contains vertices {0,1,2,3}; 4 vertices. If you were doing it the previous way your VB would contain {0,1,2,1,3,2}; 6 vertices. So, if your vertex format is 32 bytes, you save 64 bytes.
But then of course you need to tell D3D how to use these vertices to build shapes. You need an index buffer. The index buffer is a buffer of vertex indices (oddly enough). To draw the first triangle, we need to tell D3D to use vertices 0,1,2, and to draw the second triangle, we need to tell D3D to use vertices 1,3,2.
So, we end up with a list of indices looking like this: {0,1,2,1,3,2}. Thats what we stick in the index buffer.
Since an index is only a WORD in size (2 bytes), the index buffer only takes up 12 bytes. Recall that we save 64 bytes by only having 4 vertices as supposed to the 6 that we would need previously. So we have a total saving of 52 bytes, and this is only to draw one quad!

The real problem comes when telling D3D to draw the triangles.

HRESULT DrawIndexedPrimitive(
D3DPRIMITIVETYPE Type,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount
);

The Type is the same as it was in DrawPrimitive. So we can ignore that.
MinIndex and NumVertices are used to optimize D3D performance, so just ignore them for now too.
StartIndex is the equivalent of StartVertex in DrawPrimitive, it defines where to strat reading data in the index buffer.
PrimitiveCount is the same as it was in DrawPrimitive too. So we can ignore that.

Right, back to MinIndex and NumVertices. As i said, these are to optimize D3D performance. If these parameters weren't there, D3D would have to either transform all the vertices (pretty expensive) or find out what vertices are used and only transform those. Now, that sounds like a good idea, but it invilves going thtrough the index buffer, noting the lowest and highest index value, and transforming the range of vertices between the lowest and higheast index. Thats exactly what MinIndex and NumVertices are. They tell D3D what the minimum index number used is, and from that index, how many vertices it has to transform. (Note: It might just be me, but i think that MinIndex and MaxIndex would be a lot less confusing than MinIndex and NumVertices - at least its consistant.).
Looking at our second triangle {1,3,2}, we can see that the minimum index used is 1, and the maximum index used is 3. So, we need to tell D3D that starting from vertex 1, it needs to transform 3 vertices. So, MinIndex is 1 and NumVertices is 3.

[edit]
If you want a quick hack, if you can;t be bothered trying to find out what MinIndex and NumVertices should be, you can (although its a bad idea) set MinIndex to 0, and NumVertices to the number of vertices in your VB (4 in this case). It won't make much of a difference for small objects like this quad, but if you want to render a model of 500 verts, you might need 50 DIP() calls. That means that for each DIP call, D3D will transform all 500 vertices, so instead of transforming 500 vertices, you'll be transforming 25000 verts! Even that shouldn't be a problem if you have hardware TnL HAL (e.g. GeForce2+), but in with normal HAL (e.g. TNT2) or the reference rasterizer, you'll totally murder your frame rate.
[/edit]

*phew* that was a long post.

HTH, Steve

Steve
DirectX Programmer
Soon to be the new Bill Gates

[edited by - Evil Bill on August 2, 2002 2:26:50 PM]
Member of the Unban Mindwipe Society (UMWS)
Cybertron
Cybertron
Wow! thanks alot!

Is this correct then?

HRESULT DrawIndexedPrimitive(
D3DPRIMITIVETYPE Type,
UINT MinIndex, //----- First vertex
UINT NumVertices, //---- Last vertex - first vertex
UINT StartIndex, //---- Start of first index of IB
UINT PrimitiveCount //----Number of shapes to draw from IB
);

I think I get it now

one last question: for textures, I always set the texture stage to NULL after using it, do I need to do the same with SetStreamSource and SetIndicies?

EDIT: Got the test program working! first try too


[edited by - Cybertron on August 2, 2002 4:19:28 PM]
Evil Bill
Evil Bill
quote:

one last question: for textures, I always set the texture stage to NULL after using it, do I need to do the same with SetStreamSource and SetIndicies?


Yep. If you don''t then you might get some stuff dumped out into the debug window about memory leaks. If you don''t then you should set them to NULL anyway just to be safe (its not difficult, as you can see).

Your definition is more or less correct, except MinIndex is the lowest index used - e.g. if your list goes {7,4,6,12,6,4,8}, then MinIndex should be 4.
Also, NumVertices should be MaxIndex-MinIndex+1 = 12-4+1 = 9.

Sorry for my massive ammounts of spraffing

Steve
DirectX Programmer
Soon to be the new Bill Gates
Member of the Unban Mindwipe Society (UMWS)
Cybertron
Cybertron
Thanks! If I set a texture, and then _not_ set it to null it will create a memory leak. But what if I set a new texture, and then to null, will the first texture be released properly?

If so then I would only need to set the texture, stream and index sources to null ONCE just before EndScene() instead of after every single call

You should write an article for GameDev about this if you have enough time to write so much on a forum
Evil Bill
Evil Bill
When you call SetTexture(), SetStreamSource() or SetIndices(), you increment the reference count on the interface you pass. When you set the object to somethign else, D3D decrements the reference count on the old one, and increments the reference coutn on the new one. So, when you pass NULL, it decrements the reference count on the previous object. (as needed).

Every object has a reference count. Lets take textures as an example. When you create the texture, its reference count is set to 1. When you call Release() the reference count is decremented. When it reaches 0, the memory is released. So, if you create a texture and then release it, the ref count is set to 1 (when you create it) and then decremented to 0 (when you Release() it). Because the ref count is 0, the memory is freed.
Now, if you do a SetTexture(), it increments the refernce count to 2. At the next call to SetTexture(), the reference count of the old object is decremented to 1 again. If you pass NULL< you''re saying that you don''t want a new texture.

quote:

You should write an article for GameDev about this if you have enough time to write so much on a forum


Lol, yeah i just don''t have anything better to do. I''m a student, but i don''t really know enough about D3D to write an article. Besides, i gibber way to much

Anyway, i''d better go and sober up.

HTH, Steve

Steve
DirectX Programmer
Soon to be the new Bill Gates
Member of the Unban Mindwipe Society (UMWS)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.