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
73 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:

Contents
 Introduction
 Getting Started
 The Main Loop
 Drawing Something
 The Theory

 Drawing Something
 The Practical Part


 Printable version
 Discuss this article
 Get the source

The Series
 Part 1
 Part 2
 Part 3

Getting Started

DirectX Graphics all come under the name of Direct3D, which will be the term used from now on (it’s shorter), but the names are interchangeable. Direct3D when it gets hard gets very, very hard indeed; but luckily the basics are very simple, and a basic application can be set up in a 100 or so lines of code. So here goes:

First we need to attach DirectX 8 to our VB program - so it knows how to use it. Open up VB and create a new "Standard EXE" project. A single form should be added to the project view. Go to the Project menu, then click on references to display the library dialog. You should see a long list of objects and libraries in the middle of the window - all of them with a small checkbox to the left. Scroll down until you see an entry called "DirectX 8 for Visual Basic Type Library", select the check box and click "Ok".

We have now referenced our project to the DirectX 8 runtime library; that is all we need to do in order to use DirectX 8 features in Visual Basic. Bear in mind that the end user will have to have DirectX 8 installed on their computer for your application to even begin execution - if it’s not there your program will terminate as soon as it’s started. I have a template set up for this type of application, so it appears in my "New Project" dialog box - something you may wish to do.

A Simple Application

Now that we can use DirectX 8 we’re going to set up a very simple example - all it will do is create an instance of Direct3D and clear the screen, then terminate.

The first thing we need to do is put some variables into our (Declarations) section of the form:

Dim Dx As DirectX8                'The master Object, everything comes from here
Dim D3D As Direct3D8              'This controls all things 3D
Dim D3DDevice As Direct3DDevice8  'This actually represents the hardware doing the rendering
Dim bRunning As Boolean           'Controls whether the program is running or not...

The first 3 variables here (Dx, D3D, D3Ddevice) are all classes - we’ll need to initialize them and terminate them; the fourth variable, bRunning, is just a simple Yes/No flag that states if the application is running or not - more on that one later.

Now seems like a good time to explain what these different objects do. DirectX 8 has a hierarchy of objects and interfaces, each one with a parent, and in this case a "DirectX 8" object is as far back as they go. The "Direct3D8" object deals with creating devices and enumerating their capabilities. Finally, the "Direct3DDevice8" object represents your 3D card - you tell it to do things and (within reason) it’ll do it. We therefore create a "DirectX 8" object; this then helps us create a "Direct3D8" object, which in turn will setup a "Direct3DDevice8" object for us to use.

There are several other interfaces/objects that we can create, but right now we don’t really need to know much about them - you’ll see them as we go. It is very useful to have a copy of the DirectX 8 SDK help file when dealing with these objects. While VB’s Intellisense and object browser are very useful, the SDK help file explains and lists all the functions and features of each interface/object.

Now that we have the variables defined we can start to do something with them; for this we’re going to create a function called "Initialize()" which does exactly what it says it will - when it’s finished execution (and assuming no errors) we’ll be able to use all the objects and start making things appear on screen.

Public Function Initialize() As Boolean
On Error GoTo ErrHandler:

Initialize = True '//We succeeded
Exit Function

ErrHandler:
Debug.Print "Error Number Returned: " & Err.Number, Err.Description
Initialize = False
End Function

Above is the basic framework for the function - and you’ll be seeing that most of the functions are designed like this. Technically, Initialize() does not need to be a function as it doesn’t return any particular data. But I particularly like this layout because it allows me to design a good function that should never bring down the rest of the application - if it fails all it will do is return false to whoever called it. When calling this function we should use code like this:

If Not (Initialize() = True) Then GoTo Error_Handler:

Which will execute the initialization code. Then, if it succeeds, it will carry on as normal, but if it fails it will go to the "Error_Handler" for processing / correction. The above call is just a simplified (and easier to read) version of:

If Initialize() = False Then
  GoTo Error_Handler:
End If

Now that we’ve got the basic function structure laid out we’ll put something in it. The first thing we need to do is define two structures and initialize the Direct3D objects:

Dim DispMode As D3DDISPLAYMODE         '//Describes our Display Mode
Dim D3DWindow As D3DPRESENT_PARAMETERS '//Describes our Viewport

Set Dx = New DirectX8         '//Create our Master Object
Set D3D = Dx.Direct3DCreate() '//Let our Master Object create the Direct3D Interface

The two structures are used in a minute to help create the final Direct3DDevice8 object, but right now all we do is define them. Next we create the DirectX 8 object - you may well know that it would have been perfectly legal to have defined the object like "Dim Dx as New DirectX 8", but this is bad for what we want to do. The method just mentioned is known as Early Binding, the method we’re using is called Late Binding; the difference being that if you late bind it Visual Basic will not check to see if it is created when you try to use it (but will return errors). If you early bind it VB will compile the code with a statement around EVERY call to the object along the lines of "If the object is nothing, create it". While that may well only be true the first time around, it’s still something extra for the computer to think about, and being a game we want all the speed we can get, and these objects will be used 1000’s of times a second - so you can imagine the sort of speed we’ll be wasting. Secondly, we make our master interface create the generic Direct3D interface. You will always be able to create a Direct3D interface - no matter what the hardware installed can do. Now we need to fill out the two structures we just defined:

D3D.GetAdapterDisplayMode D3DADAPTER_DEFAULT, DispMode '//Retrieve the current display Mode

D3DWindow.Windowed = 1                          '//Tell it we're using Windowed Mode
D3DWindow.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC '//We'll refresh when the monitor does
D3DWindow.BackBufferFormat = DispMode.Format    '//We'll use the format we just retrieved...

Not too complicated really - but it gets more complicated if we want to use fullscreen rendering (covered later on). While it really wont matter for this sample, if you are using windowed mode it’s a good idea to keep your window fairly small - 400x300 in pixels is a good size for most resolutions. The next part actually involves creating an instance of a Direct3D device. This part can be slightly dangerous. If you send parameters that the end-user's computer can't handle, then it’ll fail and cause an error. This mostly tends to happen when you’re using fullscreen modes and you need to choose a resolution/colour depth that suits their hardware/monitor.

Set D3DDevice = D3D.CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _
                                 frmMain.hWnd, _
                                 D3DCREATE_SOFTWARE_VERTEXPROCESSING, _
                                 D3DWindow)

The actual code isn’t too complicated, but it’s what goes in the parameters that is. The parameters are as follows:

Adapter As Long : While they don’t seem to be making 3D cards with primary/secondary devices this is where you can change it. Almost all graphics cards will be on the default adapter (D3DADAPTER_DEFAULT). Set it to 0 or 1 otherwise

DeviceType As Const_D3DDEVTYPE : What type of device we want to use; there are 2 main types of device and an optional 3rd:

  • D3DDEVTYPE_HAL - Hardware acceleration, where the actual 3D card does the rendering
  • D3DDEVTYPE_REF - A reference device, purely for developers - you’ll be lucky if you get more than 0.25 frames a second out of this. On the other hand you can do absolutely anything with it - full feature support.
  • D3DDEVTYPE_SW - This can't be used unless you register a software renderer (a plugin for DirectX), but there aren’t any bundled with Direct3D, so you’ll have to make your own (very hard) or get a 3rd party one.

hFocusWindow As Long : This lets Direct3D know which window it needs to render to, mostly for windowed mode, so it can check if it’s gone behind other windows or it’s been closed and so on… always pass <FormName>.hWnd here, but make sure that the window is visible first.

BehaviourFlags As Long : How this device will behave, and what does what (processor and/or 3D card). This should be D3DCREATE_SOFTWARE_VERTEXPROCESSING on most computers - or computers where there is no hardware transform and lighting or better (almost every card except the GeForce cards); in which case you can put in D3DCREATE_HARDWARE_VERTEXPROCESSING, which will force the 3D card to do transform and lighting operations; alternatively you can use the D3DCREATE_PUREDEVICE option - which is new to Direct3D8, and is only available on the £300+ GeForce2 Ultra chipsets (at time of writing anyway).

PresentationParameters As D3DPRESENT_PARAMETERS : Just place the structure that we filled earlier in here…

That’s our initialization code complete - assuming that code runs through successfully then we’ll have a fully initialized device attached to our form ready to play with. One word about DirectX errors - they always have the description "Automation Error" and a number in the negative 2 millions (-2001230 for example). Should you want to know what that means in English you’ll need to check the Err.Number against a set of constants that the DirectX 8 library provides us with. If you have the SDK you can check what error numbers each function might return and only check those, otherwise you’ll need to check them all - and there are a lot of them! Look for them in the object browser, they all tend to begin with "D3DERR_" or "E_"…

One final thing that I want to cover before we move on further is the topic of enumeration; you may not have heard of this before - but it’s something you’ll become familiar with if you spend any length of time programming in DirectX. Enumeration is the process of analysing the hardware to see what it’s capable of (or not capable of). You’ll meet most of it as we go along - but there are a couple that are relevant to device creation that I need to cover here.

In the above initialization code we specified D3DDEVTYPE_HAL, which may or may not be available on the host computer, and if we want to jump to fullscreen mode we’ll need to know what resolutions and colour depths the hardware supports as well. While software vertex processing works on all computers it would be nice to take advantage of any additional hardware features available. To do this we use the following code:

Dim DevCaps As D3DCAPS8
On Local Error Resume Next
  D3D.GetDeviceCaps D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, DevCaps
If Err.Number = D3DERR_INVALIDDEVICE Then
  'We couldn't get data from the hardware device - probably doesn't exist...
  D3D.GetDeviceCaps D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, DevCaps
  Err.Clear '//Remove the error value..
End If

'//For Hardware vertex processing:
If (DevCaps.DevCaps And D3DDEVCAPS_HWTRANSFORMANDLIGHT) Then
  Debug.Print "Hardware Transform and lighting supported"
Else
  Debug.Print "Hardware Transform and lighting is not supported"
End If

'//For Pure Device processing:
If (DevCaps.DevCaps And D3DDEVCAPS_PUREDEVICE) Then
  Debug.Print "Pure Device is supported."
Else
  Debug.Print "Pure device is not supported"
End If

'//To check the rest we use:
If D3D.CheckDeviceType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
                       DispMode.Format, DispMode.Format, 1) = D3D_OK Then
  Debug.Print "The selected device format is acceptable"
Else
  Debug.Print "The selected device format is not acceptable"
End If

At the moment the above code does nothing more than tell you, the developer, what the hardware can do - or can’t do. The first two lines retrieve all the enumeration data for the device we’ve specified; there’s a simple error handler in here that should stop us crashing if there is no hardware device present - if this is the case then the program gets data from the reference device (which will support almost everything). The D3DCAPS8 structure now holds all the information that we need to evaluate the device and it’s capabilities. If you know that your application requires other features you can enumerate them now and find out if there’s any point in using this device.

So we have our structure filled with data, all we need to do now is extract the information we want. At this point it’s a good idea to use the SDK help file - there are literally thousands of different flags and features we can check for, and the help file lists every one of them with a small description (which isn’t always that helpful). If you are just going to be reading tutorials off the net, or not poking around much yourself then it’s not too important - most tutorials will explain what enumerations you’ll need to perform.

In the above example we first check for hardware transform and lighting capabilities. The transform part is to do with vertex manipulation, and if it’s done in hardware then it would imply that we can set up a device that uses the D3DCREATE_HARDWARE_VERTEXPROCESSING flag. We then check for the presence of the pure device option, which is the next step up from hardware transform and lighting (therefore use this if it’s present). If this returns true then we can specify D3DCREATE_PUREDEVICE when creating the device. On the other hand if both of these return false we’ll just have to use D3DCREATE_SOFTWARE_VERTEXPROCESSING. Lastly we check if the device type can be created - we specify the type of device, the format and if it’s in windowed mode or not; if this call evaluates to D3D_OK then we’ll be able to create a device with the same parameters, if it doesn’t then we need to find some other parameters - this will usually just mean changing to the Reference device - as the call we made earlier to get the current display mode will of told us the correct format, and if we’re using fullscreen modes then we’ll have enumerated the possibilities (more on that later) - which only leaves the possibility that there’s no hardware device present. We could avoid this completely and just remember what happened when we got the enumeration data - and which device that came from.



Next : The Main Loop