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 wanted 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 COM object that has other COM objects inside it.
Ultimately, it contains everything
you need to run 2D and 3D graphics using software, hardware, or whateverware.
So because Direct3D is already stored in classes, don't be surprised when you see Direct3D functions being called like this:
device->CreateRenderTargetView()
device->Release()
We use the indirect member access operator to access the functions CreateRenderTargetView()
and Release() from a Direct3D 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.
Even though COM's job is to hide all the complexity, there are four things you need
to know about it.
1. A COM object is a class or set of classes controlled by an interface.
An interface is a set of functions that, well, controls a COM object. In the
example above, "device" is a COM object, and the functions control it.
2. Each type of COM object has a unique ID. For example, the Direct3D object
has its own ID, and the DirectSound object has its own ID. Sometimes you need
to use this ID in the code.
3. When done using a COM object, you must always call the function Release().
This will tell the object to free its memory and close its threads.
4. COM objects are easy to identify, because they typically start with an 'I', such
as 'ID3D10Device'.
Don't worry about the details of these four points. We'll see how they are
all applied in a moment.
For now let's go on to the next topic:
Direct3D is a very complicated API, with many COM objects and many other parts which
are never seen or directly interacted with. In order to keep from being lost
in all the code, it's important to understand a few basic components that exist
as part of Direct3D, and some that work with it from the outside.
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 image, 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 GPU 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.
The DirectX Graphics Infrastructure is a component that lies at the base
of all the most recent versions of Direct3D. Its job is to handle fundamental
tasks such as displaying images on the screen, managing the swap chain, and finding
out what resolutions the monitor and video card can handle.
DXGI is not actually a part of Direct3D. It underlies it and other graphics
components, and it acts as an interface between Direct3D and the hardware.

Image 1.3 - DirectX Graphics Infrastructure
There are ways to deal with DXGI directly, but we will not get into these.
All that's important is that you are aware this component exists, and that it is
not directly a part of Direct3D.
If there is one thing you will hear about a lot in Direct3D, it is the rendering
pipeline. This is because the rendering pipeline is where everything happens.
The rendering pipeline is the process which produces a rendered 3D image on the
screen. It works like an assembly-line, one step after another. It consists
of steps which are executed on the video card's GPU.

Image 1.4 - The Rendering Pipeline
The Input-Assembler Stage is the first step in the pipeline. It's responsibility
is to collect information from system memory and the CPU about the 3D models you
wish to render. It then compiles them and gets them ready for rendering.
The programmable shader stages are a series of steps that convert that information
into an image. They are run one model at a time, resulting in a separate
image for each model. A shader is a program that is executed on the
GPU. This means that all rendering occurs on the graphics hardware, rather
than the CPU. We'll cover these stages in detail in Lesson 5.
The Output-Merger Stage is the final step in the pipeline. It's job is to
combine the individual model images into one whole image, and to place that image
correctly on the back buffer, so it can appear on the screen.
Sometimes you don't want to render to the back buffer. Sometimes it is necessary
to render a 3D image, and draw that image on the surface of another object. If you've played the game Portal, you've seen an example of this.

Image 1.5 - Rendering to a Portal, and Rendering to the Back Buffer
Direct3D contains the concept of a render target. This is an address
that tells Direct3D where to draw the models. In Portal, there are
actually two rendering cycles. The first renders a full 3D image and draws
it on the surface of the wall. The second then renders the actual scene, in
which you can see the first image.
For Direct3D to work, it needs to know the render target. For us, this will
be the back buffer. We'll talk about how to tell Direct3D this in a moment.
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. Before we do this, we must initialize
Direct3D.
However, before we initialize Direct3D in the first place, we need to add a few
things to the top of our program. Let's take a look at these and see what they
are.
// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d10.h>
#include <d3dx10.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")
// global declarations
ID3D10Device* device; // the pointer to our Direct3D device interface
ID3D10RenderTargetView* rtv; // the pointer to the render target view
IDXGISwapChain* swapchain; // the
pointer to the swap chain 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 10 header files. These files consist of various declarations
to the actual methods contained in the Direct3D library.
This includes the Direct3D 10 library files. 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, "d3d10.lib" and "d3dx10.lib".
This variable is a pointer to what could be considered the core of Direct3D.
It is known as a device. What this line means is that we will create a
class called an ID3D10Device using COM. 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.
A device is a class that holds all the information pertaining to the graphics
drivers, the video card, and everything else having to do with the hardware side
of graphics.
It's responsible for letting your program work with the video card, allocating memory
for graphics data, and more.
In Direct3D, a view is an object which indicates what part of an image
the graphics hardware is able to work with. This variable is a pointer to
an interface that shows where to render to. We will be rendering to the window
we created earlier. We'll cover how this is done in a moment.
Just like with the device, we will create a pointer to the interface now,
and create the interface itself later.
As we covered before, the swap chain is the series of buffers which take turns being
rendered on. This variable is a pointer to such a chain.
Notice that this object does not belong to Direct3D, but is actually part of the
DXGI, underlying Direct3D.
The initD3D() Function
The first step to actually coding Direct3D is to create the interfaces and initialize
them. This is done using a set of functions and structs.
Let's take a look at this function
here, which we've called initD3D(), 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)
{
DXGI_SWAP_CHAIN_DESC scd; // create a struct to hold
various swap chain information
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC)); //
clear out the struct for use
scd.BufferCount = 1; // create two buffers, one for the
front, one for the back
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //
use 32-bit color
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //
tell how the chain is to be used
scd.OutputWindow = hWnd; // set the window to be used
by Direct3D
scd.SampleDesc.Count = 1; // set the level of multi-sampling
scd.SampleDesc.Quality = 0; // set the quality of multi-sampling
scd.Windowed = TRUE; // set to windowed or full-screen
mode
// create a device class and swap chain class using the information
in the scd struct
D3D10CreateDeviceAndSwapChain(NULL,
D3D10_DRIVER_TYPE_HARDWARE,
NULL,
0,
D3D10_SDK_VERSION,
&scd,
&swapchain,
&device);
// get the address of the back buffer and use it to create the render
target
ID3D10Texture2D* pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
device->CreateRenderTargetView(pBackBuffer, NULL, &rtv);
pBackBuffer->Release();
// set the render target as the back buffer
device->OMSetRenderTargets(1, &rtv, NULL);
D3D10_VIEWPORT viewport; // create a struct to hold the
viewport data
ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT)); //
clear out the struct for use
viewport.TopLeftX = 0; // set the left to 0
viewport.TopLeftY = 0; // set the top to 0
viewport.Width = 800; // set the width to the window's
width
viewport.Height = 600; // set the height to the window's
height
device->RSSetViewports(1, &viewport); // set the
viewport
}
This function is best described if broken down into three main parts:
1. First, we create a COM object representing Direct3D.
2. Second, we set Direct3D's render target as the back buffer.
3.
Third, we tell Direct3D where on the back buffer it should draw.
If the comments in the code are good enough for you, excellent. Otherwise, I've described
each of these commands below.
This is where the fun begins. Here is the first line of DirectX code you will
run. It is a struct that contains information about how Direct3D will operate.
More specifically, it contains all the properties of the swap chain. We won't
cover all the values of the struct, but we'll cover all the mandatory ones and cover new members
as they come up throughout the tutorial.
We're going to abbreviate swap chain description and call the struct 'scd'.
We use ZeroMemory() to quickly initialize the entire scd struct to NULL.
That way we don't have to go through every member of the struct and initialize them individually.
This member contains the number of back buffers to use. We'll only be using
one back buffer and one front buffer. Therefore, we'll set this value to 1.
We could use more, but 1 will probably be enough for anything we'll do.
This next member we will use to set the format of the colors. On the front
and back buffers, each pixel is stored by color. This value determines what
format that data is stored in. For our example, we've used DXGI_FORMAT_R8G8B8A8_UNORM.
The R8G8B8A8 indicates that there is 1 byte (8 bits) for each color channel: red,
green, blue and alpha. Naturally, red, green and blue are the primary colors
of light. Alpha is an additional value which can be used to set the transparency
of the pixel, so it shows a little of what is behind it. We'll get into this
more later.
The UNORM part at the end means that when we refer to colors, we want to refer to
them as a value between 0.0 and 1.0, where 0.0 is no color and 1.0 is full color.
We want to do this because this is the way the video card stores color data, so
it's the most efficient.
We'll get more into colors and how they are stored in Lesson 3.
These things just keep getting longer and longer! This member tells
how we intend to use our swap chain. Render target ouput means that
this swap chain is intended to be used as the render target.
This value sets the handle to the window Direct3D should use. We'll just use
the same hWnd we've always been using.
This member is advanced and a bit beyond this lesson, but I'll describe what it does so you're
not in mystery. It is used to store the number of multisamples used
by Direct3D.
Multisampling is where Direct3D takes pixel and looks at the other pixels around
it. It then uses averages to determine what the final color of the pixel should
be.
Image 1.6 - Multisampling
The effect is shown in the image above. On the left, you can see the line
has created a stair-like effect. On the right, the image is a little bit smoothed because
Direct3D looked around each pixel and blended them together.
This value sets the number of times Direct3D will do this.
We will put 1 in here, which tells Direct3D to only look at the pixel itself. This will look
ugly for now, but we'll fix it a bit later when we've covered more
of Direct3D.
When we run Direct3D in a window like we are now, this value is set to TRUE.
Otherwise, it is set to FALSE.
Note: There are other changes to make when an application goes full-screen.
Changing this one value will not make your application properly full-screen unfortunately.
Patience until the next lesson.
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.
This function does two things. First, it creates a graphics device interface.
What this means is a new class will be created from which we will handle all the
graphics we need. Second, it creates a swap chain using the properties given
in the scd struct.
Here's the function prototype:
HRESULT D3D10CreateDeviceAndSwapChain(
IDXGIAdapter* pAdapter,
D3D10_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
UINT SDKVersion,
DXGI_SWAP_CHAIN_DESC* pSwapChainDesc,
IDXGISwapChain** ppSwapChain,
ID3D10Device** ppDevice);
Now let's go into the parameters of this function. They are all simple, so
this will go fast.
This is a value that indicates what graphics adapter, or video card, Direct3D should
use. We could get detailed here, and try to find the best graphics card available,
but we'll let DXGI take care of that for us (because in most cases there's only
one). To tell DXGI that it needs to decide, we put into this parameter
the value NULL, indicating the default graphics card.
This parameter is used to determine whether Direct3D should use hardware or software
for rendering. Naturally, we would prefer hardware. To indicate this,
we use the value D3D10_DRIVER_TYPE_HARDWARE.
At this point, not everyone has a DirectX 10 video card. If you are not currently
enjoying one, you will have to use D3D10_DRIVER_TYPE_REFERENCE. All the DirectX 10 features will work, though it is quite slow.
We won't get into this parameter. It is used in cases where you wish to bypass
the hardware and have everything rendered with your own software. As this is typically
slow, it's definitely not what we want. We'll just set this value to NULL.
In this parameter, we have a number of flag-values which we can use to alter the
behavior of Direct3D. These can be logically ORed together, used by themselves,
or you can just put 0 here to use none. The table below shows a few of the
possible values.
[Table 1.1 - Device Creation
Flags]
|
Flag |
Description |
|
D3D10_CREATE_DEVICE_SINGLETHREADED |
Use this when your application only uses Direct3D from one thread. Ours
does, but we'll leave it out for simplicity. Go ahead and add this if you
like. |
|
D3D10_CREATE_DEVICE_DEBUG |
Direct3D will support the debug layer. |
|
D3D10_CREATE_DEVICE_SWITCH_TO_REF |
This allows your program to switch from hardware to reference mode. |
|
[Close Table] |
This parameter is always the same: D3D10_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 changes that have occurred
since then. Kinda useful, eh?
In different
versions of the SDK, this value returns different numbers. Note that you should not change this value, as it will cause confusion
and is not really needed anyway. Just use D3D10_SDK_VERSION, and everything
will work out all right.
This is a pointer to the scd struct that we filled out earlier. We just put
'&scd'.
This is a pointer to a pointer to the swap chain interface (if you can understand
that). Earlier in the program we defined this pointer as 'swapchain', so we
will put '&swapchain' in this parameter.
The function will then create the swap chain interface behind the scenes, and fill
this pointer with its address. Easy! It does all the work for us!
This is a pointer to a pointer to the graphics device interface. We defined
this pointer as 'device', so we will put '&device' in this parameter.
Like the swap chain, this function will create the device, and store the address
in our pointer, 'device'.
And that's all there is to the D3D10CreateDeviceAndSwapChain() function! There
are few functions in DirectX that are quite that complex, so there's no need to
panic.
Setting the Render Target
The next step to initializing Direct3D is to set the render target. Remember,
this is going to be the back buffer.
Here's the code in the function that does this:
// ...
// get the address of the back buffer and use it to create the
render target
ID3D10Texture2D* pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
device->CreateRenderTargetView(pBackBuffer, NULL, &rtv);
pBackBuffer->Release();
// set the render target as the back buffer
device->OMSetRenderTargets(1, &rtv, NULL);
// ...
Let's take up each line and see how it all works.
In 3D rendering, a texture is another name for an image. An ID3D10Texture2D
is a COM object which stores a flat texture. On this line we've created
a pointer to a texture object. In this pointer we're going to store the
address of the back buffer.
You may not believe me, but this command is actually simple. What this GetBuffer()
function does
is get the address of the back buffer and store it in pBackBuffer.
The first parameter is the number of the back buffer to get. We only have
one back buffer, and it is back buffer #0. Therefore, the first parameter
will be 0.
The second parameter is a number identifying the ID3D10Texture2D COM object. Each type
of COM object has it's own unique ID that is used to get information about it.
To get this ID, we can use the __uuidof operator. The exact details of how this
works are unimportant, but the reason we do it is so that the GetBuffer()
function
knows what format to provide the data in.
Unless you are at least intermediate with C++, the third parameter will probably
make no sense to you. Basically a void* is a pointer that points to no particular
type of variable. For example, a double* points to a double, whereas an int*
points to an int. Each type of pointer is handled differently, and so it can't
convert to another type without causing confusion. A void* points to any of
these, and can be converted without trouble.
In the third parameter, we have a pointer to a void*. This pointer gets filled
with the location of the buffer. The GetBuffer() function takes care of all
the details for you. All you need to know is that pBackBuffer will contain
the location of the back buffer.
In this command we will create the render target. A render target is actually
a separate COM object, and to make it requires its own separate function.
The first parameter here is the pointer to the target itself. In our case,
this is the back buffer. Therefore, we'll put the address of the back buffer
in here: &pBackBuffer.
The second parameter is advanced and we won't get into it. We'll just leave
it NULL.
The last parameter is a pointer to the render target object. In the beginning
of the program we created one and named it 'rtv'. Therefore, this parameter
will be '&rtv'. 'rtv' will then contain the address of the render target
COM object.
Here, we call the Release() function for the texture object we created, pBackBuffer.
Release is a simple function that most COM objects have. No parameters, nothing
spectacular. It just cleans everything up, deleting any memory the object
may have allocated.
Why? Well, let's say it would be a bad thing to do otherwise. Basically,
if you create a COM object, 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 COM objects let's everything off the hook and allows Windows to take back
it's memory.
We are releasing pBackBuffer now because we don't need it anymore. All we
needed it for was to create the render target object, and we've done that.
After creating the render target object, we must set it. OMSetRenderTargets does
just that. OM stands for output-merger, the last
stage of rendering.
The first parameter is the number of render target objects we wish to set.
Putting more than one would just be complicated, and we only have one anyway.
So this value will be 1.
The second parameter is the pointer to the render target object. We'll put
'&rtv' here.
The third parameter is advanced and we'll cover it a little later in the tutorial.
We can just put NULL for now.
That section was a bit more complex than the first. This third and last section will
be a piece of cake:
// ...
D3D10_VIEWPORT viewport; // create a struct to hold the
viewport data
ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT)); //
clear out the struct for use
viewport.TopLeftX = 0; // set the left to 0
viewport.TopLeftY = 0; // set the top to 0
viewport.Width = 800; // set the width to the window's
width
viewport.Height = 600; // set the height to the window's
height
device->RSSetViewports(1, &viewport); // set the
viewport
}
This section sets up a viewport. This tells Direct3D where in the
window it is allowed to draw.
This is a struct that contains the parameters of the viewport. We would just
use a RECT, but there is actually more information in this struct, which we will
cover in a later lesson.
Just like the last struct we dealt with, we will zero it out before using it.
These two values in the struct set the top-left corner of where we can render.
These will be zeroes, representing the top-left corner of the window.
These two values represent the width and height of where we can render. We'll
set these to be the same as the size of the window.
Now that we've filled out the viewport struct, we set it using the RSSetViewports()
function.
The first parameter tells how many viewports to set. We could make an array
of viewport structs and set more, but we only have one, so we'll put 1 in this parameter.
The second parameter is the address of the viewport struct.
At this point, Direct3D is up and running. However, if you run this program
(and you shouldn't just yet), 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!
Wow! That was quite a start, but things will get easier from here on.
Note: Remember, if you don't have your DirectX 10 card yet, you'll have to
use reference mode, or the program won't run.
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 files
#include <windows.h>
#include <windowsx.h>
#include <d3d10.h>
#include <d3dx10.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")
// global declarations
ID3D10Device* device; // the pointer to our Direct3D device interface
ID3D10RenderTargetView* rtv; // the pointer to the render target view
IDXGISwapChain* swapchain; // the
pointer to the swap chain 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)
{
if(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)
{
DXGI_SWAP_CHAIN_DESC scd; // create a struct to hold
various swap chain information
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC)); //
clear out the struct for use
scd.BufferCount = 1; // create two buffers, one for the
front, one for the back
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //
use 32-bit color
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //
tell how the chain is to be used
scd.OutputWindow = hWnd; // set the window to be used
by Direct3D
scd.SampleDesc.Count = 1; // set the level of multi-sampling
scd.SampleDesc.Quality = 0; // set the quality of multi-sampling
scd.Windowed = TRUE; // set to windowed or full-screen
mode
// create a device class and swap chain class using the information
in the scd struct
D3D10CreateDeviceAndSwapChain(NULL,
D3D10_DRIVER_TYPE_HARDWARE,
NULL,
0,
D3D10_SDK_VERSION,
&scd,
&swapchain,
&device);
// get the address of the back buffer and use it to create the render
target
ID3D10Texture2D* pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
device->CreateRenderTargetView(pBackBuffer, NULL, &rtv);
pBackBuffer->Release();
// set the back buffer as the render target
device->OMSetRenderTargets(1, &rtv, NULL);
D3D10_VIEWPORT viewport; // create a struct to hold the viewport data
ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT)); // clear out the struct for use
viewport.TopLeftX = 0; // set the left to 0
viewport.TopLeftY = 0; // set the top to 0
viewport.Width = 800; // set the width to the window's width
viewport.Height = 600; // set the height to the window's height
device->RSSetViewports(1, &viewport); // set the viewport
}
// this is the function used to render a single frame
void render_frame(void)
{
// clear the window to a deep blue
device->ClearRenderTargetView(rtv, D3DXCOLOR(0.0f, 0.2f,
0.4f, 1.0f));
// do 3D rendering on the back buffer here
// display the rendered frame
swapchain->Present(0, 0);
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
swapchain->Release(); // close and release the swap chain
rtv->Release(); // close and release the render target
view
device->Release(); // close and release the 3D device
}
And that's it! If you run this program you should get something like this:
Image 1.7 - Our First Direct3D Program