Sol's dirty DX7 tutorial

Initialization

There are three really awkward things in direct3d; first is initialization, second is blend modes and third is texture formats. When I first heard of d3dx ("d3d helper lib") I knew I just had to get hold of dx7. Of these three things d3dx helps in initialization and in texture formats. It does not help with blending modes, which I can partially understand (but all 3d accelerators should be able to do multiplicative, additive and average blending IMO - they may do so, but with different combinations of parameters.. I don't know if openGL or glide is better at this).

I'll start out with a rather bare-bones program here.. just the initialization and normal windows inits. (Feel free to read my win32 tutorial for more about the basic windows stuffs).

#define WIN32_LEAN_AND_MEAN		
#include <windows.h> // windows stuff
#include <stdio.h>   // standard IO
#include <stdlib.h>  // standard C lib
#include <ddraw.h>   // DirectDraw
#include <d3d.h>     // Direct3D
#include <d3dx.h>    // Direct3DX
/* 
 * Griddy0
 * d3dx7 single threaded app
 * sol/trauma 1999
 */

char progname[]=&quot;Griddy0 - Sol&quot;; // our program name
HWND mainhWnd;                   // our program main window
HINSTANCE mainhInst;             // and main instance
LPD3DXCONTEXT dxctx;             // Direct3DX context handle.
LPDIRECT3DDEVICE7 d3dd;          // Direct3d device
LPDIRECT3D7 d3d;                 // Direct3d itself

LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam) {
  if (uMsg==WM_DESTROY) { // kill command
    d3d->Release();       // release d3d
    d3dd->Release();      // release d3d device
    D3DXUninitialize();   // shut down d3dx
    exit(wParam);         // bail out
  }
  return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

void reindeer(void) 
{
  dxctx->Clear(D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER); // clear out

  d3dd->BeginScene(); // all rendering should happen between begin and endscene..

  // here be 3d rendering commands
  // nothing yet though

  d3dd->EndScene();
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow) 
{
  WNDCLASSEX winclass;
  HWND hWnd;
  MSG msg;

  int fs=0;
  if (MessageBox(NULL,&quot;Shall we do it in fullscreen mode?&quot;,
                 &quot;Silly question&quot;,MB_YESNO)==IDYES) fs=1;

  lpCmdLine=lpCmdLine;            // remove warning
  hPrevInstance=hPrevInstance;    // remove warning

  if (FAILED(D3DXInitialize())) return 0;

  mainhInst=hInstance;

  winclass.cbSize=sizeof(WNDCLASSEX);
  winclass.style=CS_DBLCLKS;
  winclass.lpfnWndProc=&WindowProc;
  winclass.cbClsExtra=0;
  winclass.cbWndExtra=0;
  winclass.hInstance=hInstance;
  winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
  winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
  winclass.hbrBackground=GetSysColorBrush(COLOR_APPWORKSPACE);
  winclass.lpszMenuName=NULL;
  winclass.lpszClassName=progname;
  winclass.hIconSm=NULL;

  if (!RegisterClassEx(&winclass))
    return 0;

  hWnd=CreateWindow(
    progname,
    progname,
    WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
    CW_USEDEFAULT,
    0,
    640,
    480,
    NULL,
    NULL,
    hInstance,
    NULL);

  mainhWnd=hWnd;

  if (FAILED(D3DXCreateContext(  
              D3DX_DEFAULT,  
              D3DX_CONTEXT_FULLSCREEN*fs,  //windowed = 0
              hWnd,
              D3DX_DEFAULT,  
              D3DX_DEFAULT,  
              &dxctx))) return 0;
  d3dd=dxctx->GetD3DDevice();
  d3d=dxctx->GetD3D();
  d3dd->SetRenderState(D3DRENDERSTATE_CULLMODE,D3DCULL_NONE ); // no cull
  d3dd->SetRenderState(D3DRENDERSTATE_DITHERENABLE,TRUE); // dither on
  d3dd->SetRenderState(D3DRENDERSTATE_ZENABLE,D3DZB_FALSE); // no zbuf

  d3dd->SetRenderState(D3DRENDERSTATE_CLIPPING,FALSE); // no clipping 
  d3dd->SetRenderState(D3DRENDERSTATE_LIGHTING,FALSE); // no lighting

  ShowWindow(hWnd,nCmdShow);

  int frame=0;
  int starttime;
  starttime=GetTickCount();;
  char str[200];
  while (1) {
    reindeer();
    frame++;
    int sec=GetTickCount()-starttime;
    if (sec>0) 
      sprintf(str,&quot;frame:%05d sec:%05d.%03d fps:%3.3f (alt-F4 quits)&quot;,
              frame,sec/1000,sec%1000,(frame*1000.0)/sec);
    dxctx->DrawDebugText((float)(2/640.0),(float)(2/480.0),0xffffff,str); 

    dxctx->UpdateFrame(0);

    if(PeekMessage(&msg,hWnd,0,0,PM_REMOVE)) {
      switch(msg.message) {
      case WM_QUIT:
      case WM_DESTROY:        // kill command
        d3d->Release();       // release d3d
        d3dd->Release();      // release d3d device
        D3DXUninitialize();   // shut down d3dx
        return (msg.wParam);
      default: 
        DefWindowProc(hWnd,msg.message,msg.wParam,msg.lParam);
      }          
    }  
  }    
}

The source should be enough to describe itself, but since I hate tutorials that just leave noncommented sources for people to read, let's go through the key bits..

dxctx->Clear(D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER);
d3dd->BeginScene();
d3dd->EndScene();

The rendering bit. If you don't use zbuffer, there's no point in clearing it.. and if you always draw the whole screen, there's no point in clearing the target.

All polygon blits must be between begin and endscene, and there should only be one pair of these per frame (for 'scene capture cards' which take your scene and generate bsp trees and stuff with it on the fly before rendering).

if (FAILED(D3DXCreateContext(  
            D3DX_DEFAULT,  
            D3DX_CONTEXT_FULLSCREEN*fs,
            hWnd,
            D3DX_DEFAULT,  
            D3DX_DEFAULT,  
            &dxctx))) return 0;

D3dx context creation. You must run D3DXInit before this or this will simply fail. This call actually does all initialization that is needed, including clippers for windowed mode and mode changes and lots and lots of other stuff you'll meet sooner or later. Let's hope it's later.

There's also CreateContextEx which gives you more control on what we'll create, but we'll just use CreateContext here. The SDK doc says:

HRESULT D3DXCreateContext(
  DWORD deviceIndex,
  DWORD flags,
  HWND hwnd,
  DWORD width,
  DWORD height,
  LPD3DXCONTEXT* ppCtx);

You can specify the device to be used with deviceIndex or set it to default (as we did) to let d3dx figure out the best device. Flags say whether to go full screen, or whether to render offscreen. Default means windowed mode. hwnd is our primary window. Width and height are what they are, and default values are derived from either resolution we're setting (default being 640x480) or the window's client area. The last parameter is pointer to our context handle, which we will use shortly.

d3dd=dxctx->GetD3DDevice();
  d3d=dxctx->GetD3D();
  d3dd->SetRenderState(D3DRENDERSTATE_CULLMODE,D3DCULL_NONE ); 
  d3dd->SetRenderState(D3DRENDERSTATE_DITHERENABLE,TRUE); 
  d3dd->SetRenderState(D3DRENDERSTATE_ZENABLE,D3DZB_FALSE); 

  d3dd->SetRenderState(D3DRENDERSTATE_CLIPPING,FALSE); 
  d3dd->SetRenderState(D3DRENDERSTATE_LIGHTING,FALSE);

First off we request handles to direct3ddevice and direct3d, and then set up some rendering options for our 3d device. We set culling off as we're presumably done it before getting to blitting and the hardware (or drivers) may waste time doing it again. We set dithering on (as default mode is 16bit), and set z-buffer use off (naturally you should set it on here if you wish to use it! (which is most likely)). Then just to make sure we set clipping and lighting off so the drivers don't mess things up. (The way we do things here clipping may work on some cards but may not in others, so let's play safe - d3d does its clipping before getting to screen space and since we're doing stuff directly to screen space it's tad bit late).

dxctx->DrawDebugText((float)(2/640.0),(float)(2/480.0),0xffffff,str); 

    dxctx->UpdateFrame(0);

These are two other things that make d3dx helpful; you can easily render debug texts (which for some perverse reason take offset coordinates as floats ranging from 0 to 1), and you can flip pages easily with one call, without needing to care whether you're full screen or not.

Now then, take the source and compile it. You'll probably get loads of unresolved externals. You have to link in with the code at least dxguid.lib (global unique identifiers), ddraw.lib (directdraw), d3dx.lib and d3dim.lib (d3d immediate mode). If you're compiling from command line add gdi32.lib and user32.lib as well. You should end up with an executable, when run, will show our status info (with frames per second counter).

Next we'll add some polys..