And COM is what?
COM stands for Component Object Model. COM is a method of creating very advanced
objects that, well, they act a lot like Legos actually.
Legos, as you
know, can be stuck together to create more advanced shapes.
No single Lego actually cares about any other Lego in the set. They are all
compatible with each other, and all you have to do is stick them together to get
them to work. If you want to change pieces, all you have to do is unplug one
piece and put another in its place.
And so it is with COM. COM objects are actually C++ classes or groups of classes
from which you can call functions and achieve certain aims. No class requires
another to operate, and they don't really need to work together to get things done
together, but you can plug them in or unplug them as you desire without changing
the rest of the program also.
For example, say you had a game distributed broadly and you want to upgrade it.
Well, instead of keeping track of and shipping a new copy to every single user who
ever bought your game, all you have to do is say "Upgrade! Right Here!".
They download
the updated COM object, and the new object plugs right in to your
program without further hassle. Nice, huh?
I won't get too detailed into COM, because it is far too complex for what we need.
It's job is to get all the complex stuff out of the way so that you have an easy
time. And if that's its job, what would be the purpose of learning all that
complex material?
So why COM? Well, DirectX is actually a series of COM objects, one of which
is Direct3D. Direct3D is a rather advanced class that contains everything
you need to run 2D and 3D graphics using software, hardware, or whateverware.
So don't be surprised when you see Direct3D functions being called like this:
d3d->CreateDevice()
d3d->Release()
We use the indirect member access operator here to access the functions CreateDevice()
and Release() from the Direct3D interface class. We'll get more into this
when we see how it is applied in practice. I'm going to try to avoid unneeded
theory from here on out.
For now let's go on to the next topic:
A graphics adapter contains in its memory a pointer to a buffer of pixels that contains
the image currently being displayed on the screen. When you need to render
something, such as a 3D model or texture, the graphics adapter updates this array
and sends the information to the monitor to display. The monitor then redraws
the screen from top to bottom, adding in the new part rendered.
However, there is a slight problem with this in that the monitor does not refresh
as fast as needed for real-time rendering. Most refresh rates range from 60
Hz (60 fps) to about 100 Hz. If another model were rendered to the graphics
adapter while the monitor was refreshing, the image displayed would be cut in two,
the top half containing the old image and the bottom half containing the new.
This effect is called tearing.
To avoid this, DirectX implements a feature called swapping.
Instead of rendering new images directly to the monitor, Direct3D draws your images
onto a secondary buffer of pixels, known as a back buffer. The front buffer
would be the buffer currently being displayed. You draw all your images onto
the back buffer, and when you are done, Direct3D will update the front buffer with
the contents of the back buffer, discarding the old image.
However, doing this can still cause tearing, because the image transfer can still
occur while the monitor is refreshing (the CPU is faster than the monitor).
In order to avoid this (and to make the whole thing go much faster), DirectX uses
a pointer for each buffer (both front and back) and simply switches their values.
The back buffer then becomes the front buffer (and vice versa), and no
tearing occurs.

Image 1.1 - Addresses Swap Instantly
Of course, we could make our game have better performance by adding additional back
buffers, like this.

Image 1.2 - Multiple Back Buffers Gets Better Performance
How can adding additional back buffers get better performance? Well, let's
say that every once in a while you finish rendering the back buffer and are ready
to swap, but the screen hasn't yet finished drawing the contents of the front buffer.
Swapping now would cause more tearing. So what happens instead is your program
stops and waits for the screen to finish. You could, of course, be spending
this valuable time preparing the next image, and having multiple back buffers
allows your program to do just that.
This setup called a swap chain, as it is a chain of buffers, swapping positions each
time a new frame is rendered.
We won't be making a "Hello World!" application for Direct3D. We will assume
that Direct3D is not a language on its own (which it isn't). Instead, we will
start by filling our window with blue. For this we have four steps:
1. Create global variables and function prototypes
2. Create a function to initialize Direct3D and create the Direct3D Device
3. Create a function to render a frame
4. Create a function to close Direct3D
Let's go over each function and its parts, then look at the whole picture and how
to plug it in to your windows program.
We need to add a few things to the top of our program before we start working with
Direct3D in the first place. Let's take a look at these and see what they
are.
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
This includes the Direct3D 9 header file. This consists of various declarations
to the actual methods contained in the Direct3D library.
This includes the Direct3D 9 library file. The #pragma comment directive places
a certain piece of information in your project's object file. With our first
paramter, lib, we indicate that we want to add a library file to the project.
We then specify which file, "d3d9.lib".
This variable is a long pointer to Direct3D. What this means is that we will
create a class called iDirect3D9. When COM makes this class, we will ignore
it, and access it only indirectly using this pointer. We will cover how the
class is created in a moment.
The Direct3D Device interface holds all the information pertaining to the graphics
drivers, the video card, and everything else having to do with the hardware side
of graphics. This is a pointer to the class that stores all this information.
2. Create a function to initialize Direct3D and create the Direct3D Device
The first step to actually coding Direct3D is to create the interface and initialize
the graphics device. This is done using two functions and a struct containing
graphics device information. Let's take a look at this function here, then
go over its parts. I didn't bother to bold the new parts, because the entire
thing is new.
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the
Direct3D interface
D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold
various device information
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the
struct for use
d3dpp.Windowed = TRUE; // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard
old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used
by Direct3D
// create a device class using this information and information from
the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
}
If the comments here are good enough for you, excellent. Otherwise, I've described
each of these commands below.
This is where the fun begins. This is the first Direct3D function you will
run. It's primary purpose is to create the Direct3D interface. The return
value is the address of the interface created, and so we will store this address
in the pointer we created earlier, d3d.
Then there is the parameter. This parameter is always the same: D3D_SDK_VERSION.
Why is this? Well, it really only matters for compatibility on other machines.
When a later release of DirectX is published, the users will usually have varying
levels of DirectX upgrades. This tells the user's DirectX which version you
developed your game for. The user's DirectX can then look back and properly
execute your program without implementing the upgrades that have occurred
since then. Kinda useful, eh?
In the version of Direct3D 9c, this value returns 32. Previous
versions would return different values, causing DirectX to function in different
ways. Note that you should not change this value, as it will cause confusion
and is not really needed anyway. Just use D3D_SDK_VERSION, and everything
will work out all right.
There are certain factors involved in both beginning and advanced game programming which require certain information to be fed into the graphics device from the start.
There are plenty of these, but we will only go into three of them here. For
now, D3DPRESENT_PARAMETERS is a struct whose members will contain information about
the graphics device. We will go over the ones we use here and cover new members
as they come up throughout the tutorial.
We use ZeroMemory() to quickly initialize the entire d3dpp struct to NULL.
That way we don't have to go through every member of the struct and set them individually.
When we run Direct3D in a window like we are now, this value is set to TRUE.
Otherwise, it is set to false. Later, when we make our window full-screen,
we will change this value to FALSE.
Note: There are other changes to make when an application goes full-screen.
Changing this one value will not make your application full-screen, unfortunately.
Patience until the next lesson.
Before, we talked about swap chains as the method Direct3D uses to change images
on the screen. However, we get to pick which type of swap chain is
used. There are three different kinds.
[Table 1.1 - d3dpp.SwapEffect Flags]
|
Value |
Description |
|
D3DSWAPEFFECT_DISCARD |
This type of swap chain is used
to get the best speed possible. However, if you later want to look at the previous
back buffer (which can be useful for various effects), you cannot guarantee the
image will still be intact. |
|
D3DSWAPEFFECT_FLIP |
This type is similar to discarding, but is
reasonably slower, because it has to take the time to ensure your previous back
buffer(s) are protected and unchanged. |
|
D3DSWAPEFFECT_COPY |
This last type is
the one I least recommend. Instead of switching pointers like the other two,
this method copies the image, pixel by pixel, from the back buffer to the front
buffer. This is not prefered, although required for some advanced techniques. |
|
[Close Table] |
So which one to use? Throughout this tutorial I will be using D3DSWAPEFFECT_DISCARD.
The only exception to this will be at some later date when I add additional tutorials
on various effects that use the other two methods.
This value sets the handle to the window Direct3D should use. We'll just use
the same hWnd we've always been using.
This is a big function, but in actual fact, it is quite simple. Most of the
parameters will probably stay the same in every game you write.
What this function does is create a graphics device interface. What this means
is a new class will be created from which you will handle all the graphics you need.
Notice that this function is part of the d3d class. Most of the functions
you use in Direct3D will come from here, so we give the pointer a rather simple
name, d3ddev, as seen before.
Here is this function's prototype:
HRESULT CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 **ppReturnedDeviceInterface);
Now let's go into the parameters of this function. They are all simple, so
this will go fast.
This is an unsigned integer that stores a value indicating what graphics adapter,
or video card, Direct3D should use. We could get detailed here, and try to
find the better graphics card, but we'll let Direct3D take care of that for us (because
in most cases there's only one). To tell Direct3D that it needs to decide,
we put into this parameter the value D3DADAPTER_DEFAULT, indicating the default
graphics card.
While there are four possible values for this parameter, we are only going to be
concerned with one of them, and stay away from the others until later. The
one we will use is D3DDEVTYPE_HAL.
D3DDEVTYPE_HAL tells Direct3D to use what is called the Hardware Abstraction Layer.
The Hardware Abstraction Layer, or HAL, is used to indicate that Direct3D should
be using hardware to process graphics (and we would use...what else?). If
for some reason our graphics device cannot use hardware to handle something, that
something will be rendered by software instead. This is done automatically,
but probably won't be done, considering the capabilities of modern cards.
This is the handle to our window. We can just put 'hWnd' in here as we passed
the value from WinMain().
While there are plenty of values we can store in this parameter, there are only
three we will cover just now. They are D3DCREATE_SOFTWARE_VERTEXPROCESSING,
D3DCREATE_MIXED_VERTEXPROCESSING and D3DCREATE_HARDWARE_VERTEXPROCESSING.
These three values are fairly self-explanatory. The software one indicates
that all 3D calculations should be done with software, the hardware one with hardware,
and the mixed one using a combination of both, depending on which Direct3D sees
fit. We will use the software value for compatibility, but you can experiment
with these as you desire. At first you will notice no difference, but as we
move into advanced topics, you may start to see some differences appear.
This is a pointer to the d3dpp struct that we filled out earlier. We just
fill this with '&d3dpp'.
This is a pointer to a pointer to the graphics device interface (if you can understand
that). Earlier in the program we defined this pointer as d3ddev, so we will
put '&d3ddev' in this parameter.
And that's it! You have an entire function using Direct3D. However,
if you run this program (and you should not), you will find that absolutely nothing
different occurs. All we have done at this point is prepare for action.
Now let's have some action!
In this function we will render a single frame. The frame will be rather simple,
and will consist of a blue background. Of course, you can change the color
if you want. Here is the code for this function.
// this is the function used to render a single frame
void render_frame(void)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100),
1.0f, 0);
d3ddev->BeginScene(); // begins the 3D scene
// do 3D rendering on the back buffer here
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL); // displays
the created frame
}
There are four functions to rendering a frame, two of them big (but simple) and
two of them small. We'll go over them here.
This clears a buffer to a specific color. In this case, we are going to clear
our back buffer. It's pretty simple.
Because most of the parameters are irrelevant at this point, we will leave most
of them alone. The first two have to do with clearing a specific area.
You will rarely need this, but will just leave them at 0 and NULL, indicating the
entire back buffer is to be cleared. The third parameter, set to D3DCLEAR_TARGET,
indicates that we should clear the back buffer. There are other types of buffers,
but we want this type. Next we have color. The parameter is the struct
D3DCOLOR, and we will use the function D3DCOLOR_XRGB() to build the color.
The next two parameters we will get into in later lessons. For now, just set
them to '1.0f' and 0.
Next we call the function BeginScene() which tells Direct3D you are ready to start
rendering. This function needs to be called for two reasons. First,
you need to tell Direct3D that you are in control of the memory. Second, this
function does something called locking, where the buffer in the video RAM is 'locked',
granting you exclusive access to this memory. There is no guarantee that your
memory will stay put and not be moved around while you are trying to work on it.
Having your memory suddenly appear somewhere else can be quite a hassle for sure.
Next we call the function EndScene(). While BeginScene() locked the video
memory we wanted, EndScene() unlocks it, making it available by other processes
that need access to it.
Note: Locking video RAM is slow, but required. You should call BeginScene()
and EndScene() only once per frame to save your valuable CPU ticks.
And finally we call the Present() function. The four parameters, all set to
NULL, are not going to be used in this tutorial. Three of them have to do
with the slowest method of handling the swap chain (which we don't use) and the
other one has to do with presenting to a different window (which we won't need and
won't go into).
This is the last (and easiest) step to creating a Direct3D program. We just
have two steps here:
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
}
Here, we call the Release() function from each of the two interfaces we created,
d3ddev and d3d. No parameters, nothing spectacular. Just cleans everything
up.
Why? Well, let's say it would be a bad thing to do otherwise. Basically,
if you create Direct3D, but never close it, it will just keep on
running in the
background of the computer until your next reboot, even after the program itself
closes. Bad. Especially bad if you have a lot of resources in your game.
Releasing these two interfaces let's everything off the hook and allows Windows
to take back it's memory.
Wow! That was quite a start, but it will go down from here.
Let's take a look at what we just did. Following is the code we added
to our program. The new parts are now in bold.
[
Show Code]
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL,
L"WindowClass",
L"Our First Direct3D Program",
WS_OVERLAPPEDWINDOW,
300, 300,
800, 600,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
initD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(msg.message == WM_QUIT)
break;
render_frame();
}
// clean up DirectX and COM
cleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface
D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold various device
information
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for
use
d3dpp.Windowed = TRUE; // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D
// create a device class using this information and the info from
the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
}
// this is the function used to render a single frame
void render_frame(void)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100),
1.0f, 0);
d3ddev->BeginScene(); // begins the 3D scene
// do 3D rendering on the back buffer here
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL); // displays the created
frame on the screen
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
}
And that's it! If you run this program you should get something like this:
Image 1.3 - Our First Direct3D Program