Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
101 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Implementing the Functions

The most complex of the functions will be d3dVertex3f() and d3dEnd(). We'll go through those two last. Let's start off with d3dBegin():

void d3dBegin( D3DPRIMITIVETYPE Type, IDirect3DDevice8* pDevice )
{
  //Initialize all our vars
  if( pDevice == NULL )
    return;
  pD3DDevice = pDevice;
  pD3DDevice->AddRef();  
  
  PrimitiveType = Type;

  pVertList = new ListVertex[MIN_VERTEX_LIST_SIZE];
  NumVerts = 0;
  MaxArraySize = MIN_VERTEX_LIST_SIZE;
  
  bRenderBegun = TRUE;
}

Most of the lines are simply initializing the variables to their default values. Notice that the color, texture coordinates, and normals are not initialized; this is so that you can make calls to these functions before calling d3dBegin(). We create a new list with the starting size as well. I haven't checked for a possible memory out error for brevity, but you can add it if you wish. Another detail is that I haven't zeroed the newly allocated memory. This is not necessary because the memory used will not contain junk data by the time we actually read it. The memory that is not set to a meaningful value will never be used either. We then AddRef() on the device pointer to make sure that we don't lose the device between now and d3dEnd(). Lastly, we set bRenderBegun to TRUE so that the other functions know that we're ready. Next, we'll look at d3dNormal3f(), d3dColor4f(), and d3dTexCoord2f():

void d3dColor4f( float a, float r, float g, float b )
{
  CurDiffuse = D3DCOLOR_COLORVALUE( r, g, b, a );
}

void d3dNormal3f( float nx, float ny, float nz )
{
  CurNX = nx;
  CurNY = ny;
  CurNZ = nz;
}

void d3dTexCoord2f( float tu, float tv )
{
  CurTU = tu;
  CurTV = tv;
}

The reason I'm putting up all three functions at the same time is because they are all basically the same. We don't modify any vertices because these functions are not supposed to modify the vertices. Remember that the settings have to be applied to all of the vertex calls that follow. The values don't change unless these functions are called again. Instead of modifying the current vertex, we simply store these values. They'll be applied to a vertex in d3dVertex3f(), which we'll look at now:

void d3dVertex3f( float x, float y, float z )
{
  //If we haven't begun, then there's no array to add to! :o
  if( !bRenderBegun )
    return;

  //If we're out of space in the array, add more space
  if( NumVerts == MaxArraySize )
  {
    //Expand the array by MIN_VERTEX_LIST_SIZE
    MaxArraySize += MIN_VERTEX_LIST_SIZE;
    ListVertex* pTemp = new ListVertex[MaxArraySize];

    memcpy( pTemp, pVertList, NumVerts * sizeof(ListVertex) );

    delete[] pVertList;
    pVertList = pTemp;
  }

  //Alias the current vertex to spare a bit of typing
  ListVertex* CurVert = &(pVertList[NumVerts]);
  
  CurVert->x = x;
  CurVert->y = y;
  CurVert->z = z;

  CurVert->nx = CurNX;
  CurVert->ny = CurNY;
  CurVert->nz = CurNZ;

  CurVert->Diffuse = CurDiffuse;
  
  CurVert->tu = CurTU;
  CurVert->tu = CurTV;

  NumVerts++;
}

This function has two parts to it. The first one checks if we are out of space in the array. Remember that NumVerts is 1 for the first vertex, but that vertex's index in the array is 0. This means that when d3dVertex3f() is called, pVertList[NumVerts] is the next vertex. We don't increment NumVerts until the very end. Back to the point, if we are out of space, we create a new array with more space, copy the old list to the new one, and delete the old list. Finally, we change the pointer to the new list, since the old one doesn't exist. The second part of the function simply sets the values for the vertex from the parameters, and also from the current values that we stored from d3dColor4f(), d3dNormal3f(), and d3dTexCoord2f(). After some calls to d3dVertex3f(), the client will want to render with d3dEnd():

void d3dEnd()
{
  HRESULT r = 0;

  if( !bRenderBegun )
    return;

  int NumPrimitives = 0;

  switch( PrimitiveType )
  {
  case D3DPT_POINTLIST:
    NumPrimitives = NumVerts;
    break;
  case D3DPT_LINELIST:
    NumPrimitives = NumVerts / 2;
    break;
  case D3DPT_LINESTRIP:
    NumPrimitives = NumVerts - 1;
    break;
  case D3DPT_TRIANGLELIST:
    NumPrimitives = NumVerts / 3;
    break;
  case D3DPT_TRIANGLESTRIP:
    NumPrimitives = NumVerts - 2;
    break;
  case D3DPT_TRIANGLEFAN:
    NumPrimitives = NumVerts - 2;
  }


  //Create a vertex buffer and fill it
  IDirect3DVertexBuffer8* pVB = NULL;

  r = pD3DDevice->CreateVertexBuffer( sizeof(ListVertex) * NumVerts, D3DUSAGE_WRITEONLY, 
                    FVF_LISTVERTEX, D3DPOOL_DEFAULT, &pVB );
  if( FAILED( r ) )
  {
    //Don't forget that there are things to do before bailing!
    pD3DDevice->Release();
    delete[] pVertList;
    bRenderBegun = FALSE;

    return;
  }

  void* pVertexData = NULL;

  r = pVB->Lock( 0, 0, (BYTE**)&pVertexData, 0 );
  if( FAILED( r ) )
  {
    pVB->Release();
    pD3DDevice->Release();
    delete[] pVertList;
    bRenderBegun = FALSE;

    return;
  }

  memcpy( pVertexData, pVertList, sizeof(ListVertex) * NumVerts );

  pVB->Unlock();

  pD3DDevice->SetStreamSource( 0, pVB, sizeof(ListVertex) );
  pD3DDevice->SetVertexShader( FVF_LISTVERTEX );
  r = pD3DDevice->DrawPrimitive( PrimitiveType, 0, NumPrimitives );

  //release stuff, delete memory, etc.
  pVB->Release();
  pD3DDevice->Release();
  delete[] pVertList;
  pVertList = NULL;
  bRenderBegun = FALSE;
}

First, we make a quick check to insure that d3dBegin() has been called. Second, we take the number of vertices and work out how many primitives we have to draw. Why Direct3D can't do this by itself, I don't know. We just use a simple switch statement and a formula depending on the type of primitive requested. We then create a vertex buffer for the vertices and copy the vertex list into the buffer using a simple Lock(), memcpy, Unlock() sequence. We render the vertex list by passing the necessary parameters to DrawPrimitive. Don't forget however, that we need to set the stream source and vertex shader. If nothing is showing up on the screen when you know something should, you probably forgot to set one of these. Lastly, we release the vertex buffer and device, and delete the memory used for the vertex list. Don't forget that since the render has now ended, we need to mark the bRenderBegun flag as FALSE. If you forget to set that flag, you could run into serious trouble.

Writing the Header File

Now that we've finished implementing our functions, we need to prototype them in a header file so that our programs can actually use them. In addition, we want to add that one #define for the list size. Here's the header in full:

#ifndef _D3DVERTS_H_
#define _D3DVERTS_H_

#include <d3d8.h>

//The start vertex list array size, and also how much to expand the array each time
//To change the value, simply define it before including the header
#ifndef MIN_VERTEX_LIST_SIZE
#define MIN_VERTEX_LIST_SIZE 2048
#endif

//Function prototypes for the vertex functions
void d3dBegin( D3DPRIMITIVETYPE Type, IDirect3DDevice8* pDevice );
void d3dVertex3f( float x, float y, float z );
void d3dNormal3f( float nx, float ny, float nz );
void d3dTexCoord2f( float tu, float tv );
void d3dColor4f( float a, float r, float g, float b );
void d3dEnd();

#endif

That's all there is to it. At this point, you can include the CPP and H file in your program and test them out. I've also written a sample program that might give you a feeling of deja vu as it puts your first triangle back up on screen ;). You can get it here.

Adding More Functions

It's a good bet that you'll want to add some more versions of these functions. It simply involves adding the new functions to the CPP and H files, and implementing them. With some of the functions you might need to change their arguments to the types that D3D wants. Just to show you a sample, here's the code I wrote for d3dColor3ub:

void d3dColor3ub( unsigned char r, unsigned char g, unsigned char b )
{
  CurDiffuse = D3DCOLOR_XRGB( r, g, b );
}

It simply takes 3 byte values and uses the convenient D3DCOLOR_XRGB macro to change the 3 bytes into a single DWORD. Here's some code for d3dVertex3fv, which is also quite simple:

void d3dVertex3fv( const float* pVector )
{
  d3dVertex3f( pVector[0], pVector[1], pVector[2] );
}

This function just dereferences the pointer and passes the values on to the normal d3dVertex3f() function.

Since the major framework is already written, most of the new functions will be a couple of lines at most. The sample program has many more new functions that I've written.

Performance

While this solution runs quite fast, it hasn't been optimized at all. Amongst other things, many of these functions could be inlined. A fast call directive might help under MSVC 6.0 as well. I've omitted these directives for brevity and to not distract from the topic at hand.

Conclusion

I hope that this article and source code helps you out. I've already found that these vertex commands are incredibly useful, and they've only been working for a day. If you happen to use this source code, a modified version of it, or just the idea, please add a reference to me. This could be just a note mentioning my name as the originator of the idea and/or source code. If you have any questions or comments, you can email me at imind3d@zgx.cjb.net.

This article is Copyright © 2003. Although you are free to save or print out this article for your own use, under no condition may you distribute it to others without written consent of the author.




Contents
  Page 1
  Page 2
  Page 3

  Source code
  Printable version
  Discuss this article