Striving For Graphics API Independence II
by Erik "Wazoo" Yuzwa


ADVERTISEMENT

Get the source code for this article here.

Introduction

For those of you just joining the discussion, in my previous article I began a conversation about creating a small engine design which would allow our scene to be rendered in both Direct3D and OpenGL using an abstract DLL system. We had a good start, and now that we've had some time to digest the information, we should proceed with the next level.

Oh Primitive Where Art Thou?

We left our previous discussion with the obvious cliffhanger, "Great. I can create a renderer DLL system. Now how do I render my primitives?". That's what we'll discuss right now. Just to remind the reader, again this is just one way of solving the cross-graphics API problem.

There are a plethora of primitive types that both OpenGL (1.1) and Direct3D support. Triangles, triangle strips, line lists, etc. I think for ease of use (and to keep this article less than 20 pages in length), we'll focus on just using the Triangle primitive type. Our goal is to create a system where we can just keep lobbing triangles at our engine and then let the engine take care of the rendering.

Within OpenGL, this is a pretty much easy and straight forward thing to do. We simply just keep adding GL_TRIANGLE data, and let the OpenGL pipeline take care of the rest.

With Direct3D8, however, we aren't QUITE as lucky (or maybe we are, depends on how you look at it). The Direct3DDevice8 interface doesn't just allow us to lob data at it, no matter how primitive it is. We first have to pass our triangle data into what's known as a "Vertex Buffer", which is then passed over to our Direct3D pipeline.

The SDK documentation explains the concept of a Vertex Buffer in great detail well enough, so we won't bother going over ground that's been repeated already. Suffice it to say, it's what you would expect it to be with a name like Vertex Buffer.

To proceed from where we are now, to our goal of rendering triangles to the screen, we need to form an action plan that we can follow for our rendering DLL system. Remember, "If you fail to plan, you plan to fail.":

  1. Create a VertexBuffer interface that we can instantiate with our DLL system
  2. Implement the OpenGL code for said interface
  3. Implement Direct3D8 code for said interface
  4. Test by sending triangles to the interface
  5. Get happy!

Step 1: A VertexBuffer interface

Similar to our Renderer interface for our DLL system, we need a common interface that we can use to abstract our VertexBuffer object. The way I say it, we only need a few methods for this interface:

  • Create the object
  • Lock down a section of video hardware memory
  • Add a primitive to the buffer
  • Unlock the section of video hardware memory
  • Flush the buffer (ie. send the buffer over to our graphics pipeline)
//vbInterface.h
//This class is to provide us with an interface object to our vertex buffer object,
//to free us from having specific graphic API calls inside our static library.
class vbInterface
{
  protected:
    //some base level objects can go here

  public:
    vbInterface(){};
    virtual ~vbInterface(){};

    //method to create and initialize our object
    virtual HRESULT createVB(int, VOID* = NULL) = 0;

    //method to destroy it
    virtual void destroyVB() = 0;

    //method to lock down video hardware memory
    virtual HRESULT lockVB() = 0;

    //method to unlock video hardware memory
    virtual void unlockVB() = 0;

    //method to add triangle to the buffer
    virtual void addTriToVB(D3DXVECTOR3 pos, FLOAT r, FLOAT g, FLOAT b, FLOAT a, D3DXVECTOR2 tex) = 0;

    //method to flush the buffer
    virtual void flushBuffer() = 0;

};

//Note that for now we're going to create this object
//within our existing rendererInterface object, so we'll add it to that now..

class rendererInterface
{
   protected:
      //keep the existing data members the same

   public:
      vbInterface* m_pVB;//declare a base pointer to our object
   
   //... the rest of the object is the same

};

This interface definition is straight forward enough I think. We pretty much explained everything before coding this interface.

Because we're stuffing our vbInterface object inside the rendererInterface, we won't be able to instantiate our rendererInterface object until we properly define derivations of the vbInterface object.

Step 2: Our OpenGL implementation

Because we're covering this information as more of a "proof of concept" rather than a full-blown implementation, we're only going to stick to the 1.1 implementation of OpenGL. Therefore, we don't have to concern ourselves with the extension mechanism of the user's hardware. An exercise for the reader might be to implement a vertex array mechanism (if the hardware supports it), within the implementation of our OpenGL vbInterface.

Step 3: Our Direct3D implementation

Again, there's nothing too advanced for our Direct3D implementation of our vbInterface. The SDK samples as well as the material contained within the MSDN has provided us with a way to create a way to encapsulate the LPDIRECT3DVERTEXBUFFER8 interface. Rather than simply wasting our graphics pipeline by sending one or two primitives at a time, we'll create a way to batch our primitives together (as is the preferred method plainly discussed in SDK documentation as well as vendor developer sites such as nvidia.com). Again, this isn't too hard a task. We'll set a boundary variable, and keep adding primitives until we hit the boundary. Then we send that batch off to the graphics pipeline, while setting up the object to receive new triangle data.

Feel free to browse through the code to get an idea of where we're going.

(Note: The only thing to note of importance here is that for our Direct3D implementation I decided to use the D3DXMatrixRH* methods for calculating the resulting projection matrix. Remember that by default Direct3D is Left-Handed, while OpenGL is Right-Handed. To keep the z-plane information the same, I just switched the Direct3D calculations to use the Right-Handed functions).

Step 4: Test out the interface!

Now that we've finished an OpenGL and Direct3D implementation, it's time to put this thing into gear! We don't really need to do much to get it working within our framework. For the moment, we're just gonna render a simple square to the display.

//winmain.cpp
//we just need to modify the renderProgram method to include
//the newly created vertex buffer interface code


//first lock down an area in video memory
if(SUCCEEDED(pInterface->m_pVB->lockVB())){

	pInterface->m_pVB->addTriToVB(D3DXVECTOR3(-1.0f, -1.0f, -10.0f),
				1.0f, 1.0f, 1.0f, 1.0f);//bottom-left vertex
	pInterface->m_pVB->addTriToVB(D3DXVECTOR3(-1.0f, 1.0f, -10.0f),
				1.0f, 1.0f, 1.0f, 1.0f);//upper-left vertex
	pInterface->m_pVB->addTriToVB(D3DXVECTOR3(1.0f, -1.0f, -10.0f),
				1.0f, 1.0f, 1.0f, 1.0f);//lower-right vertex


	pInterface->m_pVB->addTriToVB(D3DXVECTOR3(1.0f, -1.0f, -10.0f),
				1.0f, 1.0f, 1.0f, 1.0f);//lower-right vertex
	pInterface->m_pVB->addTriToVB(D3DXVECTOR3(-1.0f, 1.0f, -10.0f),
				1.0f, 1.0f, 1.0f, 1.0f);//upper-left vertex
	pInterface->m_pVB->addTriToVB(D3DXVECTOR3(1.0f, 1.0f, -10.0f),
				1.0f, 1.0f, 1.0f, 1.0f);//upper-right vertex

	//we're done with the video memory, so unlock it
	pInterface->m_pVB->unlockVB();
}

Once we run the code (with either Graphics DLL specified in the .ini file), we'll see our main window with a white square centered on a blue background!

Conclusion

As you can see, this isn't a bad way to go for our cross-graphics API rendering DLL system. We've made an attempt at providing a solution to render triangle primitives in both OpenGL and Direct3D.

Discuss this article in the forums


Date this article was posted to GameDev.net: 6/25/2003
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Featured Articles
General

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!