Topic : Directx Programming Intro
Author : Mark Klarer
Page : 1 Next >>
Go to page :


Directx Programming Tutorial: Intro


Microsoft has done a lot of work for game programmers. It is really worth a look. DirectX is Microsoft's answer for programmers who demanded a high performance API that works in windows, that is useful over a broad selection of hardware (including 3D cards!, yippee!). Yep, you heard right, DirectX is meant to work in Windows. You will write a windows program for DirectX, no more cheesy console apps. The API does have a pretty steep learning curve in the very beginning, but i think is still the best API for programming Windows games and other select applications.

The API has gone through some major changes since the beginning. I am currently using DirectX 6. If you find other sites using anything below DirectX 5, it is too dated. The information is somewhat useful, but not very.

DirectX includes Direct Draw, Direct 3D, Direct Play, Direct Input, Direct Sound, and Direct Setup, hence the name DirectX. All of the components offer great help for games programmers.

Microsoft uses, as always, backwards and jumbled-up nomenclature for the API. Oh well, I am used to it by now. It makes it that much harder to learn DirectX. Hopefully I can clear up some of the confusion. Another problem with DirectX is the evolution of the API. The API, while backwards compatible, contains pieces that are not compatible when mismatching versions. For example, I was thrown off very early when I tried to use viewports from one version with surfaces from another. It did not work! (illegal operations). The biggest hurdle of all in DirectX is understanding all of the setup and initialization code you need to write to get something to work. The amount of code really is ridiculous. Another problem you are going to encounter is the sheer number of ways you can access video hardware in Direct3D. It is nice however to have this capability, since it virtually gaurantees your app will run on just about any computer, perhaps a bit slower, but it will run. Oh well, I will stop crying and start off with a nice "little" example.

This example will use full screen, Direct3D, in "immediate" mode. Immediate mode is the highest performance 3D that DirectX offers. You can cheap out and use the "retained" mode - which has lots of high level functions built in, but you lose speed. I will avoid retained mode, since I want to make a high performance application.

Copy the text below into a new win32 project. You will have to include the ddraw.lib library file under PROJECT-SETTINGS-LINK. <<< WARNING >>> MS Visual C++ already comes with DirectX, and may try to include an older version of ddraw.lib. Go to TOOL-OPTIONS-DIRECTORIES and make sure your DirectX development library directories for include and library files are both listed BEFORE the Visual C library and include files. Or else your program will not compile/link/run. You will get tons of "unresolved external" and compiler errors! That really threw me off for a bit until I figured that one out. Thanks for the warning Microsoft. By changing the order, you tell the compiler to start looking in a different spot for the ddraw.lib file and other .h and .lib files for DirectX.

Ok enough of that, on for some code! Running the program should display a white triangle on a black background. You may need to change the values for "DevSel" and "DisSel" based on your machine. They are small integers. Valid values for DevSel are most likey 0-1, but may be higher. Valid values for DisSel is probably 0-20. DevSel is the DirectX enabled video card in your computer, DisSel is the display mode for that card. The code is about as short and simple as I can make it. It really needs some more lines of code to be more stable, but I decided to make an example as simple as possible so you can have a base to start your own projects. I am declaring the following code "public domain". No one may own a copyright for the following code. It is mine, you may use it as an example for tutorial purposes, but you should rewrite it if you decide to start selling and copyrighting.




//simple directx program
//public domain code, written by Mark Klarer

#define INITGUID
//#define D3D_OVERLOADS //might need to "unremark" this line, depending on if you use the overloads
#include <windows.h>
#include <stdio.h>
#include <ddraw.h>
#include <d3d.h>

const MAX_DEVICES = 3; //maximum number of hardware devices in the pc this program will deal with
const MAX_DISPLAYS = 40; //maximum number of displays for 1 device that program handles
const STRINGSIZE1 = 100; //a size for strings

const DWORD TOTAL_VERTS=6;

LPDIRECTDRAW4 m_pDDraw4; //need to declare here to make it global

int DevSel; //device selection (video board)
int DisSel; //display mode selection

// the windows program callback function declaration
LRESULT CALLBACK WndProcCallBack(HWND hWnd, UINT nMsg, WPARAM wParam,
LPARAM lParam);
//used to store away device-driver information
struct D3DDriverInfo{
GUID FAR *lpGUID; //pointer to the GUID of the driver
char lpDriverDescription[100]; //string to store the driver description
char lpDriverName[100]; //string to store the driver name
LPVOID lpContext; //what the hell is this???
HMONITOR hm; //again wth ???
LPDIRECTDRAW m_pDDraw; //directdraw interface
int error; //error code if any
};

//used to store away display descriptions
struct D3DDisplayInfo{
int width;
int height;
int BitPerPixel;
int RefreshRate;
char Description[STRINGSIZE1];
};

D3DDriverInfo devices[MAX_DEVICES];
D3DDisplayInfo Displays[MAX_DISPLAYS];
int device = 0; //increments while enumerating direct draw drivers
int error=0; //throw errors into here
int display = 0; //increments while enumerating display mode descriptions

LPDDSURFACEDESC2 lpDDSurfaceDesc;

//directdraw device driver callback
BOOL WINAPI lpD3DCB(GUID *pGUID, LPSTR pDescription, LPSTR pName, LPVOID pContext, HMONITOR hm)
{
char string1[100]="";
HRESULT hRet;
devices[device].error=0; //start off optimistically
devices[device].lpGUID=pGUID;
strcpy(devices[device].lpDriverDescription,pDescription);
strcpy(devices[device].lpDriverName,pName);
devices[device].lpContext=pContext;
devices[device].hm=hm;

//****** looks like for some odd reason, you have to init (directdrawcreate) in the callback function!!! wont work outside it!!!! whoa research this one
// Create the main DirectDraw object
hRet = DirectDrawCreate(devices[device].lpGUID, &devices[device].m_pDDraw, NULL); //get the interface
if (hRet != DD_OK)
{
devices[device].error=error|1;
device++;
return DDENUMRET_CANCEL;
}
device++;
return D3DENUMRET_OK;
}

//direct draw enum display modes callback
long WINAPI lpDisplayEnumCallB( LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lpContext)
{
Displays[display].width = lpDDSurfaceDesc->dwWidth;
Displays[display].height = lpDDSurfaceDesc->dwHeight;
Displays[display].BitPerPixel = lpDDSurfaceDesc->ddpfPixelFormat.dwRGBBitCount;
Displays[display].RefreshRate = lpDDSurfaceDesc->dwRefreshRate;
display++;
return DDENUMRET_OK;
}



// start point for program
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd; // main window handle
MSG message; // message structure
WNDCLASSEX WC; // window class structure

// general attributes for the window
WC.cbSize = sizeof(WC);
WC.style = CS_HREDRAW | CS_VREDRAW;
WC.lpfnWndProc = WndProcCallBack;
WC.cbClsExtra = 0;
WC.cbWndExtra = 0;
WC.hInstance = hInstance;
WC.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WC.hCursor = LoadCursor(NULL, IDC_ARROW);
WC.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WC.lpszMenuName = NULL;
WC.lpszClassName = "simple window";
WC.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

// register window
RegisterClassEx(&WC);

// create window
hWnd = CreateWindowEx(WS_EX_WINDOWEDGE,
"simple window",
"simple window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, //use default x placement
CW_USEDEFAULT, //use default y placement
256, //set the window x size
128, //set the window y size
NULL,
NULL,
hInstance,
NULL);

// show window
ShowWindow(hWnd, nCmdShow);

//******************************
//Mark's directx init stuff (after main window made)
LPDIRECT3D pD3D;
HRESULT ddrval;
LPDIRECTDRAWSURFACE4 FrontBuffer4=NULL;
LPDIRECTDRAWSURFACE4 BackBuffer4=NULL;
LPDIRECT3DDEVICE3 PD3DDevice3 = NULL;
LPDIRECT3D3 pD3D3 = NULL;
LPDIRECT3DVIEWPORT3 PViewPort = NULL;
D3DVIEWPORT2 D3DView;
LPDIRECT3DMATERIAL3 D3DMat3 = NULL;
D3DMATERIAL D3DMat;
D3DMATERIALHANDLE HD3DMat;

//make some vertices for our triangle
D3DTLVERTEX verts[] = {
{ 150, 150, 0.5, 1, RGBA_MAKE(255, 255, 255, 127),RGBA_MAKE(0,0,0,0), 0, 0 },
{ 200, 200, 0.5, 1, RGBA_MAKE(255, 255, 255, 127),RGBA_MAKE(0,0,0,0), 0, 1 },
{ 150, 200, 0.5, 1, RGBA_MAKE(255, 255, 255, 127),RGBA_MAKE(0,0,0,0), 1, 1 },
};

int DisW=Displays[DisSel].width;
int DisH=Displays[DisSel].height;

DDSURFACEDESC2 DirectDrawSurDesc2;
DDSCAPS DirectDrawSCaps;

//next couple of lines set up a surface description for making front and back buffers
memset(&DirectDrawSurDesc2,0,sizeof(DirectDrawSurDesc2));
DirectDrawSurDesc2.dwSize=sizeof(DirectDrawSurDesc2);
DirectDrawSurDesc2.dwFlags= DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
DirectDrawSurDesc2.ddsCaps.dwCaps= DDSCAPS_PRIMARYSURFACE |
DDSCAPS_FLIP |
DDSCAPS_3DDEVICE |
DDSCAPS_COMPLEX;
DirectDrawSurDesc2.dwBackBufferCount=1;

DirectDrawEnumerateEx(lpD3DCB, NULL, DDENUM_NONDISPLAYDEVICES ); //enumerate all the directx devices

DevSel=device-1; //************** might need to change this value (on my pc device-1 = 1 which is voodoo card, 0 is 2d card)

//get a directdraw4 interface from the directdraw interface
devices[DevSel].m_pDDraw->QueryInterface(IID_IDirectDraw4, (void **)&m_pDDraw4);

//set the cooperative level for the application (window mode)
m_pDDraw4->SetCooperativeLevel(hWnd,DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

//enumerate the valid display modes for the device
m_pDDraw4->EnumDisplayModes(DDEDM_REFRESHRATES,NULL,lpDDSurfaceDesc,lpDisplayEnumCallB);

DisSel=display-6; //************* choose a display mode that you want, on my machine display-1 = 18

//set the display mode
m_pDDraw4->SetDisplayMode(Displays[DisSel].width,Displays[DisSel].height
,Displays[DisSel].BitPerPixel,Displays[DisSel].RefreshRate,0) );

//create the front buffer surface
m_pDDraw4->CreateSurface(&DirectDrawSurDesc2,&FrontBuffer4,NULL);

//create the back buffer surface
DirectDrawSurDesc2.ddsCaps.dwCaps=DDSCAPS_BACKBUFFER;
FrontBuffer4->GetAttachedSurface(&DirectDrawSurDesc2.ddsCaps,&BackBuffer4);

//get a D3D interface from the Direct Draw interface
m_pDDraw4->QueryInterface( IID_IDirect3D3, (VOID**)&pD3D3 );

//create the IDirect3DDevice
pD3D3->CreateDevice( IID_IDirect3DHALDevice, FrontBuffer4, &PD3DDevice3, NULL);

//create the viewport
pD3D3->CreateViewport(&PViewPort,NULL);

//add the viewport
PD3DDevice3->AddViewport(PViewPort);

// set viewport data
D3DView.dwSize = sizeof(D3DView);
D3DView.dwX = 0;
D3DView.dwY = 0;
D3DView.dwWidth = Displays[DisSel].width;
D3DView.dwHeight = Displays[DisSel].height;
D3DView.dvClipX = -1;
D3DView.dvClipY = 1;
D3DView.dvClipWidth = 2;
D3DView.dvClipHeight = 2;
D3DView.dvMinZ = 0;
D3DView.dvMaxZ = 1;

PViewPort->SetViewport2(&D3DView);

PD3DDevice3->SetCurrentViewport(PViewPort);

// Create material for the objects.
pD3D3->CreateMaterial( &D3DMat3, NULL );

//Set ambient light for the object. Might need to specify diffuse and specular
//light, polygon vertice normals also
ZeroMemory( &D3DMat, sizeof(D3DMat) );
D3DMat.dwSize = sizeof(D3DMat);
D3DMat.dcvAmbient.r = 1.0f;
D3DMat.dcvAmbient.g = 1.0f;
D3DMat.dcvAmbient.b = 1.0f;
D3DMat3->SetMaterial( &D3DMat );

//start using the object material for the next renderings
D3DMat3->GetHandle( PD3DDevice3, &HD3DMat );
PD3DDevice3->SetLightState( D3DLIGHTSTATE_MATERIAL, HD3DMat );

//set the light state for rendering
PD3DDevice3->SetLightState( D3DLIGHTSTATE_AMBIENT, 0xffffffff );

//make the identity matrix
D3DMATRIX M;
M._11 = M._22 = M._33 = M._44 = 1.0f;
M._12 = M._13 = M._14 = M._41 = 0.0f;
M._21 = M._23 = M._24 = M._42 = 0.0f;
M._31 = M._32 = M._34 = M._43 = 0.0f;

// make the world matrix (defines position and orientation for poloygons in the world
D3DMATRIX MWorld = M;
PD3DDevice3->SetTransform( D3DTRANSFORMSTATE_WORLD, &MWorld );

//make the view matrix for the camera, zoom out 20 units
D3DMATRIX MView = M;
MView._43 = 200;
PD3DDevice3->SetTransform( D3DTRANSFORMSTATE_VIEW, &MView );

//make a projection matrix for mapping the 3d world onto a 2d screen
D3DMATRIX MProj = M;
MProj._11 = 2.0;
MProj._22 = 2.0;
MProj._34 = 2; // 1/D
MProj._43 = -.5; // -D
MProj._44 = 0.0;
PD3DDevice3->SetTransform( D3DTRANSFORMSTATE_PROJECTION, &MProj );



// Begin the scene
PD3DDevice3->BeginScene();

PD3DDevice3->DrawPrimitive(D3DPT_TRIANGLELIST,D3DFVF_TLVERTEX, &verts[ 0], 3, D3DDP_DONOTLIGHT | D3DDP_WAIT);

// End the scene.
PD3DDevice3->EndScene();



// the programs message loop
while(GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}

// exit the application with the error code
return message.wParam;
}

LRESULT CALLBACK WndProcCallBack(HWND hWnd, UINT nMsg, WPARAM wParam,
LPARAM lParam)
{

HDC hDC;

switch(nMsg)
{
case WM_CREATE:
SetTimer(hWnd, 1, 50, NULL);
break;
case WM_LBUTTONDOWN:
break;
case WM_DESTROY:
KillTimer(hWnd, 1);
PostQuitMessage(5);
break;
case WM_KEYDOWN:
if(wParam==69)exit(0); //hit the 'e' key and exit the application
if(wParam==78) //hit the 'n' key and get


Page : 1 Next >>