Now that you've studied the concepts on which Direct3D operates, let's start diving into the practical end of things by building a simple Direct3D program. In this program we'll just initialize Direct3D. It isn't much, it's not even "Hello World", but it's a good start.
Before we get to the actual Direct3D code, let's talk about code organization. Let's make a file for all our DirectX code, and a separate file for all the window code we've written so far.
I'll make it quick. We'll add two files: Game.cpp and Game.h.
Game.h should have:
using namespace Microsoft::WRL;
using namespace Windows::UI::Core;
using namespace DirectX;
Inside our header we have a few namespaces we'll need access to, and a simple class called CGame to organize our game code.
In Initialize() we'll put our startup code. We'll be dealing with this function in this lesson.
Update() is where we'll put any code that manipulates your game, such as timing and input.
Render() is where we'll draw graphics.
Of course, you can add anything else you want, and you will as we go on.
Game.cpp should have:
// this function initializes and prepares Direct3D for use
// this function performs updates to the state of the game
// this function renders a single frame of 3D graphics
Nothing exciting here.
In our main source file (which I've been calling App.cpp) we should add some things. First of all, game.h:
Next, add a Game class to our App class:
ref class App sealed : public IFrameworkView
And also update the Run() function:
virtual void Run()
CoreWindow^ Window = CoreWindow::GetForCurrentThread();
What we are doing here is calling Initialize() when we start our program, then calling Update() and Render() repeatedly, one after the other, until we quit.
Okay, back to the lesson now.
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.
I won't get too detailed into COM, because it is far too complex for what we need. Its 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:
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 a few things you should know about it.
1. A COM object is a class or set of classes controlled by an interface. When we create an instance of a COM object, we don't create it directly, we create an interface, and access the object through that.
2. Interfaces are easy to identify, because they typically start with an 'I', such as 'IMyCOMObject'.
3. Under the hood, COM can get quite complex and be a real pain in the ass. Fortunately COM makes use of something called a smart pointer, which is really just a special class that understands COM and takes care of the whole mess for us.
Creating and using a COM object is simple. Creating one looks like this:
// Create a pointer to the COM interface
// Create the COM object
Here, we are creating an IMyCOMObject and naming it MyObject. We then create the object using the CreateObject() function.
In the first line, we create a pointer to the COM interface. ComPtr is a smart pointer for COM objects. We'll use this a lot in DirectX.
Inside the template parameters, we put what kind of COM interface we are creating. In this case, it's IMyCOMObject.
In the second line, we create the COM object and store the interface address in MyObject. To clarify, CreateObject() is a made-up function. Each COM object type has its own way of being created, and we'll learn what these are as we move forward.
This section barely scratches the surface of COM. It really is a full subject of its own and deserves intesive study by any serious programmer.
However, we'll stop here and get on with the lesson.
At the very core of Direct3D are two COM objects. These are called the device and the device context.
The device object is a virtual representation of your video adapter. Through it we access the video memory and create other Direct3D COM objects, such as graphics and special effects.
The device context object is a sort of "control panel" for the GPU. Through it we control the rendering sequence and the process that translates 3D models into a final, 2D image on the screen.
The interfaces for these objects are called ID3D11Device1 and ID3D11DeviceContext1. The '1' at the end of each indicates that the interface is used with DirectX 11.1 apps. DirectX 11 apps use ID3D11Device and ID3D11DeviceContext.
Let's start by defining these two interfaces inside our Game class. It will look like this:
ComPtr<ID3D11Device1> dev; // the device interface
ComPtr<ID3D11DeviceContext1> devcon; // the device context interface
In case you missed it, a ComPtr is a special class that wraps up a lot of the complexities created by COM. It acts like a pointer, but does some other neat tricks too.
The first step to actually coding Direct3D is to create the above two COM objects and initialize them. This is done by using a single function. 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
// Define temporary pointers to a device and a device context
// Create the device and device context objects
// Convert the pointers from the DirectX 11 versions to the DirectX 11.1 versions
The comments are rather vague, so I've described each part of this code below.
There is no function that directly creates ID3D11Device1 and ID3D11DeviceContext1 interfaces. Instead the function creates their Direct3D 11 equivalents: ID3D11Device and ID3D11DeviceContext, without the 1 at the ends.
We'll create the objects using the Direct3D 11 interfaces, then convert them to the 11.1 versions later.
This is a long function. Its job is to initialize Direct3D and to create the device and device context objects. As simple as its job is, it has many parameters, but it's not hard to use.
Here is the function prototype:
Like I said, a lot of parameters. Now let's go into them and find out what they do.
This is a pointer to an interface that describes the 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 anyway). To tell Direct3D that it needs to decide for us, we put into this parameter the value nullptr, indicating that we are not providing a graphics adapter. If there is more than one, Direct3D will figure it out for us.
While there are six 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 D3D_DRIVER_TYPE_HARDWARE.
D3D_DRIVER_TYPE_HARDWARE tells Direct3D to use the hardware accelerated graphics chip to process graphics (and we would use...what else?)
One of the options in the driver type parameter allows us to build our own software engine. This parameter is where we include that software engine. However, we want hardware and so we'll just insert another nullptr here.
Flags are typically predefined integers which can be combined using a logical OR (like, FLAG1 | FLAG2 | FLAG3). This makes them handy for setting advanced customization in a function call.
None of the flags that this function accepts are useful to us right now, and so we'll set this parameter to a simple '0'.
If you're interested, the flags can be found here.
Each major version of Direct3D has a series of video card features that are required. If you know what version your hardware meets the requirements of, you can more easily understand the capabilities of the hardware (given that your customers will have different video cards).
This parameter allows you to create a list of feature levels. This list tells Direct3D what features you are expecting your program to work with.
For this tutorial you will not need any advanced hardware, and so we will not get into this parameter. We can set it to nullptr, and we won't have to worry about it.
This parameter indicates how many feature levels you had in your list. We'll just put 0.
This parameter is always the same: D3D11_SDK_VERSION. Why is this? Well, it really only matters for compatibility on other machines. Each machine will usually have varying minor versions of DirectX. 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.
In different versions of the SDK, this value returns different numbers. Note that you should not change this value, as it causes confusion and isn't really needed anyway. Just use D3D11_SDK_VERSION, and everything will work out all right.
This is a pointer to a pointer to the device interface. This function will create the object for us, and the address of the interface will be stored in our smart pointer. All the work is done for us!
All we do here is put the location of the smart pointer: '&dev11'
Remember, we are using our temporary pointer, not the actual pointer in our class definition.
More about feature levels. This is a pointer to a feature level variable. When the function is completed, the variable will be filled with the flag of the highest feature level that was found. This lets the programmer know what hardware is available for him to use. We'll just set this to nullptr, because we don't care at this point.
This is a pointer to a pointer to the device context object. We defined this pointer as 'devcon11', so we'll put '&devcon11' in this parameter. It will then be filled with the address of the device context interface.
Well! That was quite a function! But it wasn't too hard. Most of the parameters were left blank.
The As() function is a member of the ComPtr class. Its job is to store the interface it represents into another ComPtr.
The ID3D11CreateDevice() function created an ID3D11Device and an ID3D11DeviceContext, but we need ID3D11Device1 and ID3D11DeviceContext1. We use the As() function in dev11 and devcon11, use '&dev' and '&devcon' as parameters, and presto! Our original ComPtrs point to our Direct3D interfaces!
Well, that was quite a start, but it will go downhill 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.
And that's it! If you run this program you should get...a simple blank screen, just like before. Only this time, Direct3D is running in the background!
Well, are you ready to go on?
It isn't much yet, but you have begun the journey into the near-infinite depths of 3D game programming. You've created a window and gotten DirectX start up.
At the end of each lesson, I'll ask a few questions and give a few exercises. If you can do these, you'll be ready for the lessons to come.
1. What is COM? How do you create new instances of COM objects?
2. What is the difference between the device and the device context?
1. Instead of creating an ID3D11Device1 and an ID3D11DeviceContext1, create their Direct3D 11 equivalents.
Next Lesson: Preparing the Swap Chain
GO! GO! GO!
© 2006-2013 DirectXTutorial.com. All Rights Reserved. Expand