A 2D Guide To DirectX 8 Graphics - Using 2D graphics in a 3D Environment
by (13 June 2001)



Return to The Archives
Introduction


Back in the day when we worked with DirectDraw, it was easier to write to the screen because there was a drawing surface that could be accessed. This was more efficient because we could access the video memory directly and save time (just like in the days of MS-DOS graphics where you had to access the video memory directly.)  With the release of DirectX 8, Microsoft changed the API. DirectDraw and Direct3D has been combined into a new component called the DirectX Graphics component.  Yes the old DirectDraw interface is still there, but it is there for backward compatibility.

My first thought was how I could still draw lines and points on the screen without having to mess with any of the Direct3D 3D configurations (such as Z-Buffers, Lighting, and Back-Face Culling).  I began by setting up the Direct3D Interface like it showed in the SDK Help docs, and then read that the front-buffer (the display screen) cannot be accessed directly but only through back-buffers (well it can be accessed directly, except that option is for testing purposes only).  So the next idea was to lock the back buffer and draw to that.  Unfortunately, accessing the back buffer by locking it is a performance cut for some video cards.  The method that I ended up with is plotted out below. I described every step from setting up the interface to drawing and displaying it. If you are used to using DirectDraw and have never used Direct3D (like me), you should read this tutorial thoroughly because the DirectX API has changed quite a bit and it could be confusing.

This tutorial will demonstrate how to draw 500 lines all over the screen (it will be a full screen app) until the user presses Escape.  I chose lines instead of rectangles to show a general form of drawing in 2d (after reading you should understand how to draw points or rectangles).


Preparation


The first thing that you must do before you do any coding is make sure that you have setup the DirectX compiler directories and linker libraries correctly. (nothing will work until you have them setup right)

The header files that will be needed are:
windows.h, windowsx.h (general Win32 headers)
d3d8.h (general Direct3D header)
dxerr8.h (for debugging purposes).
WIN32_LEAN_AND_MEAN will throw out the rarely used Win32 stuff.

The last two constants will define the screen width and height that Direct3D will operate using.  You can change these values to whatever resolution you desire to work in, just be sure that you test to make sure the display adapter will support it.


#define WIN32_LEAN_AND_MEAN

#include <windows.h> #include <windowsx.h> #include <d3d8.h> #include <dxerr8.h>

const UINT SCREEN_WIDTH = 640; const UINT SCREEN_HEIGHT = 480;


After everything is perfect, the coding will commence...


A Short Lesson On Debugging In DX 8


One of the most important tools in the DirectX 8 SDK is the debug function, DXTrace().  This function can display a message box when one of your interface methods fail and you need information about why it failed. Optionally it can display a message in the debugger window if you are working in Full Screen and can't see the message box.  The prototype for DXTrace():


HRESULT DXTrace( char *strFile,  DWORD dwLine,  HRESULT hr, TCHAR *strMsg,  BOOL bPopMsgBox);
 


  • 1. strFile - this is the name of the file the error happened in, use __FILE__for the name of the file.
  • 2. dwLine - the line number of the file where the error occurred, use __LINE__for the line in the file.
  • 3. hr - the HRESULT code that was returned from the interface method called.
  • 4. strMsg - this is a message that you would like to have displayed as an extra description of the error, you can use a simple sentence or just the name of the method that was called.
  • 5. bPopMsgBox - if you send in TRUE, you will have a message box pop up on the screen with the error info. If you use FALSE, the message will be shown in the debugger output window instead.
  • To demonstrate this code, I've used the CreateDevice() method from the IDirect3D8 Interface as a model.

    
    HRESULT hResult;
    hResult = pD3DObject->CreateDevice(...); 

    if (hResult != D3D_OK) // D3D_OK is the signal that everything went ok (so if it doesn't) { // This will display a error message box DXTrace(__FILE__,__LINE__, hResult, "Couldn't Create D3D8 Device", TRUE); return E_FAIL; }

    return S_OK; // Else there wasn't a problem


    Setting Up The Data Structures


    The first thing I'm going to create is a structure for each point.  Each point will need an x, y, and z coordinate, and a reciprocal-homogeneous w component. (The w component is used when dividing the x, y, and z coordinates to determine depth.  Stepping ahead we will assign it as 1 so the x and y components are uneffected.) Each component will be of the float data type, and the color will be unsigned long.  I'm going ahead and defining the lineList global variable.  Since we will be generating 500 lines there are going to be 1000 points. 

    The #define preprocessor following the structure sets up the flags Direct3D will need to process our custom vertex points.  D3DFVF_XYZRHW is the flag that describes it as being a transformed vertice (since it has the RHW) and the D3DFVF_DIFFUSE flag says that it uses a diffuse color component.

    
    struct Point {
        float x, y, z, rhw;
        DWORD color;
    } lineList[1000];

    #define POINT_FLAGS (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)


    I figured the best way to demonstrate the Direct3D interfaces would be to create a class, that way the tutorial is easier to follow.  So the DirectX Graphics class is going to be called DXGraphics. Here it is in all it's glory, i'll explain the implementation of it as we move along...

    
    // The class for the DX 8 Graphics
    class DXGraphics
    {
    public:
        // Sets all the Interfaces to NULL
        DXGraphics();
        // Releases the Interfaces
        ~DXGraphics();

    private: LPDIRECT3D8 D3DObject; // The Direct3d Object LPDIRECT3DDEVICE8 D3DDevice; // The Direct3d Device LPDIRECT3DVERTEXBUFFER8 D3DVertexBuffer; // The Vertex Buffer D3DDISPLAYMODE D3DDisplayMode; // The Display Mode HWND hMainWnd; // The Main Handle to the Window public: // Initializes the Graphics int createD3DObject(HWND window); // Enters into Full Screen mode int createD3DDevice(); // Creates the Vertex Buffer int createD3DVertexBuffer(); // Loads the Vertex Buffer with the point list and renders it int renderIt();

    // Creates the list of lines int generateLines(); // inserts 2 points into the point list (creating a line) void line(float x1, float y1, float x2, float y2, DWORD color, int &index); };


    Let's begin by talking about the private members of the DXGraphics class...


    The Little Demons Of Direct3D 8


    The first object that is necessary for using Direct3D is the Direct3D object (D3DObject).  This object is actually a pointer to the IDirect3D8 interface and holds the key to all the functionality of Direct3D. D3DDevice is the pointer to the IDirect3DDevice8 interface that performs all the rendering on the screen -- most of the time our code will be using methods from this interface. Finally, D3DVertexBuffer is a pointer to the IDirect3DVertexBuffer interface.  This interface manages the Vertex Buffer which holds the vertices of our lines. When the lines are ready to be displayed the Direct3D renderer will read the vertices from this buffer. The last two structures aren't as important right now, but they will be needed later.

    The first two member functions I'll talk about are the Constructor and Destructor.  In the Constructor, the three pointers need to be assigned to NULL.  That way we don't access a part of memory that isn't supposed to be touched accidently.

    
    DXGraphics::DXGraphics()
    {
        D3DObject = NULL;
        D3DDevice = NULL;
        D3DVertexBuffer = NULL;
    }
     


    In the Destructor we need to clean up the Direct3D pointers and release the memory, plus decrement the reference counter (the reference counter counts the number of things that are depending on the interface).  If it isn't released it will cause a memory leak. At the beginning you check to see if the pointer is pointing to NULL.  If it isn't NULL, it has been used and it needs to be released.  Starting with the latest pointer you go down the list until you finally get back to the D3D Object.

    
    DXGraphics::~DXGraphics()
    {
        if (D3DVertexBuffer != NULL) {
            D3DVertexBuffer->Release();
            D3DVertexBuffer = NULL;
        }
        if (D3DDevice != NULL) {
            D3DDevice->Release();
            D3DDevice = NULL;
        }
        if (D3DObject != NULL) {
            D3DObject->Release();
            D3DObject = NULL;
        }
    }
     


    The next section will talk about gaining access to Direct3D...


    Setting Up Direct3D's Main Object


    We want to start up Direct3D so we need to create the Direct3D object.  The function Direct3DCreate8() will load the interface and then send us a pointer so we can operate it.  Here's the prototype for it:

    
    IDirect3D8* Direct3DCreate8( UINT SDKVersion );
     


    It's really easy. For the SDKVersion, you must use the D3D_SDK_VERSION flag so that it will be built with the right header files (In case something changes in a header file this will signal that it needs to be recompiled.)  If something goes wrong the function will return NULL.  So here is how we'll use it...

    
    if (( D3DObject = Direct3DCreate8(D3D_SDK_VERSION) ) == NULL) { return E_FAIL; }
     


    Alright, now we have a pointer to the IDirect3D8 interface.  The next step is to get the Display Adapter settings from Windows. An IDirect3D8 interface method called GetAdapterDisplayMode() will perform this task. And here's the prototype:

    
    HRESULT GetAdapterDisplayMode( UINT Adapter,  D3DDISPLAYMODE* pMode );
     


    The first parameter asks which adapter to query because there might be multiple video cards.  The primary display adapter can be found by sending in the D3DADAPTER_DEFAULT flag. The last parameter will return the current display mode settings.  The D3DDISPLAYMODE structure is needed to get the results and that was already defined in the class definition.  So all together here's how DXGraphics::createD3DObject() is implemented:

    
    int DXGraphics::createD3DObject(HWND window)
    {
        HRESULT hResult;
        hMainWnd = window;

    if ((D3DObject = Direct3DCreate8(D3D_SDK_VERSION)) == NULL) { return E_FAIL; }

    hResult = D3DObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &D3DDisplayMode); if (hResult != D3D_OK) { DXTrace(__FILE__, __LINE__, hResult, "Can't Get Adapter Mode", TRUE); return E_FAIL; }

    return S_OK; }


    At the beginning of the function, the handle to the main window (where Direct3D will operate from) is passed to the member variable hMainWnd. This is important because hMainWnd will be used in the next function that will be discussed.

    Note: This will be the only time I will use the DXTrace() feature.  In the rest of the tutorial I will be using the FAILED() and SUCCEEDED() debug functions.


    Creating The Direct3D Device


    The Direct3D Device Interface is where all the power lies.  Once you call the function that creates the Device, it will set everything up that you asked for.  It will setup the Front Buffer ( the primary display screen ) and then the number of Back Buffers you specify.  You can change the resolution higher or change the bit depth from 16-bit to 32-bit ( just as long as the display adapter supports these modes ;) ). To specify these settings a structure named D3DPRESENT_PARAMETERS will need to be initialized and cleaned out ( using ZeroMemory() ).

    
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
     


    The presentation parameters that I will talk about are not the only ones available.  Look in the DirectX 8 SDK for the others and their descriptions. The parameters that are going to be used (from D3DPRESENT_PARAMETERS structure) are:

  • BOOL Windowed - this will be set to FALSE since the application will be in full screen mode.

  • HWND hDeviceWindow - this parameter requests the handle for the window that will be displaying the Direct3D stuff. The window's handle for our application was assigned to our own hMainWnd variable in the createD3DObject() function.

  • D3DSWAPEFFECT SwapEffect - the method that Direct3D will use to rotate the back buffer to the front buffer. There are three ways to do this, I've picked D3DSWAPEFFECT_FLIP.  If you feel like experimenting with the other two try it out and see which one is better.

  • UINT BackBufferCount - this is the number of back buffers that Direct3D will need to create.  I've chosen 2 back buffers to be added.

  • UINT BackBufferWidth, UINT BackBufferHeight - the height and width of the back buffers. Since we are working in fullscreen mode these must be set.  For the back buffers I've used the resolution that had been chosen earlier and assigned to the two constants SCREEN_WIDTH and SCREEN_HEIGHT.

  • D3DFORMAT BackBufferFormat - the pixel format of the back buffers.  Because we really can't determine what the pixel format of the default display adapter is on the user's computer is, unless we do some testing, you should use Format from the D3DDisplayMode structure. It will have the current pixel format from the user's computer.

  • UINT FullScreen_RefreshRateInHz, UINT FullScreen_PresentationInterval - the refresh rate of the screen's display and the time it takes to swap the back buffer with the front buffer.  For the RefreshRateInHz, the D3DPRESENT_RATE_DEFAULT flag was used for the default refresh rate of the system (you could use D3DPRESENT_RATE_UNLIMITED if you wanted to push the hardware to its limit ;) ). And for the PresentationInterval, i've used the D3DPRESENT_INTERVAL_ONEflag, which waits for the screen to retrace.
  • 
    d3dpp.Windowed = FALSE;
    d3dpp.hDeviceWindow = hMainWnd;
    d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP;
    d3dpp.BackBufferCount = 2;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;
    d3dpp.BackBufferFormat = D3DDisplayMode.Format;
    d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
    d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE;
     


    The next part is where DirectX 8 is now a lot easier to handle than it was the previous versions.  Instead of calling QueryInterface(), like in the past, all that needs to be done in this version is a call to D3DObject's interface method, CreateDevice().  There will be several parameters to this method.  Here's the prototype of IDirect3D8's CreateDevice():

    
    HRESULT CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow,
                          DWORD BehaviorFlags, D3DPRESENT_PARAMETERS* pPresentationParameters,
                          IDirect3DDevice8** ppReturnedDeviceInterface);
     


    Here's how it would be implemented in our code with the DXGraphics class:

    
    D3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hMainWnd, 
    			D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &D3DDevice);
     


    For the Adapter parameter we use the D3DADAPTER_DEFAULT flag again.  For the device type I chose the hardware rasterization flag D3DDEVTYPE_HAL ( the other flags available for this parameter are for software rasterization.)  The window handle we have saved in the DXGraphics class is used for the rendering window (hFocusWindow). Then we pass in our presentation parameters we defined and then when the device has been created successfully we recieve the pointer and assign it to D3DDevice.

    The last three things that should be done now that the device has been created is to turn off the 3D render settings like Lighting, the Z-Buffer, and Back-face culling.

    
    D3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE); // Disable Z-Buffer
    D3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // Disable Culling
    D3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); // Disable Lighting
     


    Finally, the complete createD3DDevice() function of our DXGraphics class:

    
    int DXGraphics::createD3DDevice()
    {
        // Create the Direct3D Device for rendering
        D3DPRESENT_PARAMETERS d3dpp;
        ZeroMemory(&d3dpp, sizeof(d3dpp));

    d3dpp.Windowed = FALSE; d3dpp.hDeviceWindow = hMainWnd; d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP; d3dpp.BackBufferCount = 2; d3dpp.BackBufferWidth = SCREEN_WIDTH; d3dpp.BackBufferHeight = SCREEN_HEIGHT; d3dpp.BackBufferFormat = D3DDisplayMode.Format; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE;

    // Create the D3D Device if (FAILED (D3DObject->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hMainWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &D3DDevice))) { return E_FAIL; }

    // Configure for 2d operations D3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE); D3DDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); D3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    return S_OK; }


    Bringing The Monster To Life


    All that's left to do now is explain how to render the graphics in Direct3D.  This section might become a little confusing so I'll explain it the best I can.  The first few functions that need to be defined are the line(), generateLines() and createD3DVertexBuffer() functions in our DXGraphics class.

    Back at the beginning when I defined the structure for Point, I went ahead and defined the lineList global variable. I defined it as an array because of the way the Direct3D's Vertex Buffer works. The generateLines() will store each endpoint in the lineList array. They will be stored in order so the first two items in the array create the first line and the next two create the second line and so forth. The Vertex Buffer will read the points from that lineList array and then render them onto the screen by the primitive list I tell it to use (which will be a line list.) So here are the generateLines() and line() functions:

    
    void DXGraphics::line(float x1, float y1, float x2, float y2, DWORD color, int &index)
    {
        // insert the line into the buffer
        // the z component will be the correct depth for us to see the line
        lineList[index].x = x1;
        lineList[index].y = y1;
        lineList[index].z = 0.5f;
        lineList[index].rhw = 1.0f;
        lineList[index].color = color;

    lineList[index+1].x = x2, lineList[index+1].y = y2; lineList[index+1].z = 0.5f; lineList[index+1].rhw = 1.0f; lineList[index+1].color = color;

    index += 2; }

    int DXGraphics::generateLines() { for (int index = 0; index < 1000;) { // D3DCOLOR_XRGB() will generate the correct DWORD value // of the color you specify (red, green, blue) each RGB value // can be from 0 to 256 DWORD color = D3DCOLOR_XRGB(rand()%256, rand()%256, rand()%256);

    // line() increments the index for us line( rand()%SCREEN_WIDTH, rand()%SCREEN_HEIGHT, rand()%SCREEN_WIDTH, rand()%SCREEN_HEIGHT, color, index); }

    return S_OK; }


    Now the Vertex buffer needs to be created.  The CreateVertexBuffer() method is part of the IDirect3DDevice8 Interface. It will need to know how big the buffer will be, how it will be used so it can determine the type of memory it needs, and what the flags are for the vertices.

    
    HRESULT CreateVertexBuffer( UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool,
                                IDirect3DVertexBuffer8** ppVertexBuffer);
     


    For Length, we need to get the exact amount in bytes for how big the buffer will be.  So you take the sizeof() one Point and times it by 1000. The Usage flags that are to be chosen will determine what kind of memory and what extra memory will be needed for this buffer.  I used the D3DUSAGE_DONOTCLIP, D3DUSAGE_DYNAMIC, and D3DUSAGE_WRITEONLY flags.  We won't need a clipper for the 2d line drawing (since the lines are limited to the size of the screen). Because we are generating 500 new lines over and over again it will be useful to have it stored in AGP memory, (or if the system doesn't have AGP memory Direct3D will figure out a good place to put it) and having it as a writeonly buffer lets Direct3D choose the best place for the memory. The vertex flags (FVF) we had already defined at the beginning so just pass in the POINT_FLAGS.

    The Pool parameter will decide where the buffer will be in the memory pool, just use D3DPOOL_DEFAULT so it is stored where it should be in video memory. Last we will get the pointer to our new Vertex Buffer and assign it to D3DVertexBuffer.

    
    int DXGraphics::createD3DVertexBuffer()
    {
        if (FAILED(D3DDevice->CreateVertexBuffer( 1000*sizeof(Point),
                   D3DUSAGE_DONOTCLIP | D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
                   POINT_FLAGS, D3DPOOL_DEFAULT, &D3DVertexBuffer)) )
        { return E_FAIL; }

    return S_OK; }


    Watching The Monster Dance: Rendering In 2D


    The final function that will be created is the renderIt() function of our DXGraphics class.  This will render the points on the screen and create a spectacular display of thousands of randomly colored lines - well it isn't that spectacular but what can I say?

    The first line of the renderIt() function initializes the pBuffer pointer.  Next the Vertex Buffer is locked. Why? When the buffer is locked you will be able to gain access to the certain spot in memory where we will place our list of vertices.  Here's the prototype for the method Lock() from ID3DVertexBuffer8's interface:

    
    HRESULT Lock( UINT OffsetToLock, UINT SizeToLock, BYTE** ppbData, DWORD Flags);
     


    The first parameter OffsetToLock is where we want the pointer to start at, use 0 to start from the beginning.  SizeToLock is how much memory we will be using, so we'll need the same memory size of our list of lines to be taken out. Next is the pointer to the spot in memory that will be returned, that's going to be the job for pBuffer. The flags that are going to be used are D3DLOCK_DISCARD and D3DLOCK_NOSYSLOCK.  The first flag will tell the Direct3D that when the buffer is done being rendered and a new list of lines has been generated, it should overwrite the old list that was in the buffer with the new list.  The second flag prevents the system-wide critical section from being locked, so we can still move the mouse cursor and reset the computer if need be.

    
    if (FAILED (D3DVertexBuffer->Lock(0, sizeof(lineList), (BYTE **)&pBuffer, 
                D3DLOCK_DISCARD | D3DLOCK_NOSYSLOCK) ) )
    { return E_FAIL; }
     


    After the buffer has been locked you fill it with the lineList array.  That is achieved with a simple memcpy().

    
    memcpy(pBuffer, lineList, sizeof(lineList));
     


    Then after the buffer is filled, unlock it.

    
    if ( FAILED ( D3DVertexBuffer->Unlock() ) ) { return E_FAIL; }
     


    Now the buffer is set to go.  First we call the IDirect3DDevice8's method BeginScene().  This has Direct3D confirm that all the internal settings and flags are ready.  If it is successful ( and only if it is successful ) we can start rendering the lines. Here's what happens inside the BeginScene() code block:

    
    if ( SUCCEEDED ( D3DDevice->BeginScene() ) )
    {
            D3DDevice->SetStreamSource(0, D3DVertexBuffer, sizeof(Point));
            D3DDevice->SetVertexShader( POINT_FLAGS );
            D3DDevice->DrawPrimitive( D3DPT_LINELIST, 0, 500 );

    if ( FAILED( D3DDevice->EndScene() ) ) { return E_FAIL; } D3DDevice->Present(NULL, NULL, NULL, NULL); } else { return E_FAIL; }


    First the stream source is set.  The stream source is where the Direct3D device will be able to find the Vertex Buffer.  We specify 0 for the first stream number and then send in the Vertex Buffer ( D3DVertexBuffer ). Last we define the stride, which is the size of the point structure. 

    Next we set the Vertex Shader. The Vertex Shader is the mechanism that reads and processes the points (for more info read the DirectX 8 SDK), all it needs is the definition flags ( POINT_FLAGS) of our custom vertex Point.  At last we draw the lines.  DrawPrimitive() asks what type of primitive it will be drawing, I've used the D3DPT_LINELIST flag for our lines.  It needs to start from the beginning so 0 is used and we will be rendering 500 lines.

    Finally end the scene and use Present() to flip the back buffer onto the front buffer.  The first two NULL's mean that the entire back buffer will be copied (not just a section) and it will be copied onto the entire front buffer.  The third NULL means that it will use the default window handle that we specified in the D3DPRESENT_PARAMETERS.  The last parameter hasn't been implemented yet so it is obviously NULL.  Here the complete renderIt() function:

    
    int DXGraphics::renderIt()
    {
        VOID *pBuffer = NULL;

    // Lock the Vertex Buffer to get the pointer if (FAILED (D3DVertexBuffer->Lock(0, sizeof(lineList), (BYTE **)&pBuffer, D3DLOCK_DISCARD | D3DLOCK_NOSYSLOCK) ) ) { return E_FAIL; }

    // fill the buffer with the vertices memcpy(pBuffer, lineList, sizeof(lineList));

    // Unlock the buffer if ( FAILED ( D3DVertexBuffer->Unlock() ) ) { return E_FAIL; } if ( SUCCEEDED ( D3DDevice->BeginScene() ) ) { D3DDevice->SetStreamSource(0, D3DVertexBuffer, sizeof(Point)); D3DDevice->SetVertexShader( POINT_FLAGS ); D3DDevice->DrawPrimitive( D3DPT_LINELIST, 0, 500 );

    if ( FAILED( D3DDevice->EndScene() ) ) { return E_FAIL; } D3DDevice->Present(NULL, NULL, NULL, NULL); } else { return E_FAIL; }

    return S_OK; }


    That's All For Now


    Included with this tutorial is the full source code (article_guidedx82d.cpp) that is compatible with Visual C++ 6.  I did not explain how the code would be worked into WinMain(), if you need to see how the class works in WinMain() download the source code ( I really don't feel like writing and explaining anymore ;) ).  I hope you have good luck with this code. And this code is by no means optimitized, so once you figure it out see where you can make it run faster. Thanks for reading!


    Article Series:
  • A 2D Guide To DirectX 8 Graphics - Using 2D graphics in a 3D Environment
  •  

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.