DirectX 首次出现在 1995 年,当时称为“GameSDK”。在其原始形式中,针对的目标是使用 C 和 C++ 的开发人员。只有在 2002 年 12 月该 API 的第一个托管版本 (9.0) 发布以来,才可以使用 C# 或 VB.NET 开发 DirectX(实际上,如果您希望,那么可以使用任何符合 CLR 的语言)。
虽然与非托管版本相比,关于托管 DirectX 的性能争论很多,但商业游戏已使用托管 DirectX 进行创建的事实应该能够从根本上平息这样的争论。虽然某些需要极高性能的游戏可能需要使用非托管代码,但大多数游戏可以使用托管代码或者结合使用托管和非托管代码进行创建。编写托管代码使开发人员的效率更高,从而编写出更多的代码,生成更安全的代码。
安装 DirectX SDK 之后,在 C:\WINDOWS\Microsoft.NET\Managed DirectX 应该有一个目录,在机器上安装的每个版本的 SDK 都有一个子目录。我机器上使用的已经是第四版了,因此我有四个子目录。在每个子目录中应该有九个 DLL 和九个 XML 文件。由于托管环境中的 .NET 允许同一台机器上的同一个 DLL 文件有多个版本,而不会引起以前被称为 DLL Hell 的问题,所以我们可以使用多版本的托管 DirectX 库。这就允许您在安装新版本之后轻松地回滚到以前的版本。
如果您以前有在 Windows 下处理 DLL 文件的经验,您可能会担心在同一台计算机上安装同一个文件的多个版本会产生问题。自从 .NET 引入并行版本控制,这些版本控制问题就不复存在了。这意味着当新版本的 SDK 发布时,您可以使用多个版本来检查兼容性问题,而不必强迫自己进行升级。
九个 DLL 文件大致对应于 DirectX 中的十个命名空间。在创建游戏时,我们使用其中的大量命名空间来提供对输入设备、声音、网络播放(当然还有 3D 图形)的支持。
Microsoft.DirectX
|
公共类和数学结构
|
Microsoft.DirectX.Direct3D
|
3D 图形和助手库
|
Microsoft.DirectX.DirectDraw
|
Direct Draw 图形 API。这是旧式命名空间,您不需要使用它。
|
Microsoft.DirectX.DirectPlay
|
用于多玩家游戏的网络 API
|
Microsoft.DirectX.DirectSound
|
声音支持
|
Microsoft.DirectX.DirectInput
|
输入设备支持(例如,鼠标和游戏杆)
|
Microsoft.DirectX.AudioVideoPlayback
|
播放视频和音频(例如,在 PC 上播放 DVD)
|
Microsoft.DirectX.Diagnostics
|
疑难解答
|
Microsoft.DirectX.Security
|
访问安全性
|
Microsoft.DirectX.Security.Permissions
|
访问安全权限
|
HEL和HAL
在图5.2中,可以看到在DirectX下有两个层面,分别是HEL(硬件模拟层)和HAL(硬件抽象层)。这是一个约定:DirectX是一种非常有远见的设计思路,它假定可以通过硬件来实现高级性能。但是,如果硬件并不支持某些性能,那怎么办呢?这就是HAL和HEL双重模式设计思路的基础。
HAL(硬件抽象层)是接近硬件的底层。它直接和硬件交流。该层通常有设备生产商提供的设备驱动程序。可以通过常规DirectX调用直接和硬件进行联系。使用HAL要求硬件能够直接支持所要求的性能,这时使用HAL,就能够提高性能。例如,当绘制一个位图时,使用硬件要比软件更胜任该项工作。
当硬件不支持所要求的性能时,使用HEL(硬件仿真层)。我们以使用视频卡旋转一个位图为例来说明。如果硬件不支持旋转动作,则使用HEL,软件算法取代硬件。很明显这样做速度要慢一些,但关键是这样做至少不会中断应用程序,应用程序仍然在运行,仅仅是速度慢一些而已。另外,HAL和HEL之间的切转是透明的。如果要求DirectX做某工作,而HAL直接处理该工作,也就是硬件直接处理该项工作。否则,HEL将调用软件仿真来处理该工作。
Section 1 – Introduction
Section 1.1 – What is DirectX?
DirectX is the name given to a series of API for running and displaying multimedia rich applications. Using it, you can have graphics, video, 3D animation, surround sound and so on. The different API’s available in DirectX are Direct Graphics, Direct Input, Direct Sound, Direct Play and Direct Show.
Direct Graphics deals with everything related to graphics. With it you can load 3D meshes, render textures, animate 3D models, light your scene, add particle effects and much more. It is concentrated more towards 3D making it ideal for rendering 3D graphics, although it will also work just as well doing 2D graphics too.
Direct Input is the API that allows you to use many different kinds of input devices such as keyboards, mice, joysticks, joy pads and so on. It gives you much more control than the Win32 API does, and is ideal for games.
Direct Sound does everything you could ever want to do with audio. You can play midi files and wav files and other types of music. You can add special effects such as an echo or flanging. You can also add 3D surround sound effects by telling Direct Sound to apply special 3D algorithms and so on. Again, this is ideal for games.
Direct Play is the networking API used mainly in games. If you wish to make a game run over a network then you can use DirectPlay?. There isn’t much more to say about that really.
Direct Show can be used for all kinds of multimedia tasks such as video playback. It is ideal for audio or video recording, editing and manipulation.
Section 1.2 - Assumptions
Readers should have a basic understanding of the C# language and should have at least enough experience using it to understand the examples. The code examples are fairly simple and don’t use anything overly complicated, but you should still have a firm grasp of the language nevertheless. If you have any experience with using previous versions of DirectX in other languages then this would certainly help, but isn’t a requirement. The tutorial assumes you’ve never used DirectX before.
I will be using the .Net framework version 1.1. Readers using the new .Net 2.0 Framework that comes with Microsoft Visual C# 2005 Express Edition should be able to convert the code without too much trouble. Most of it should be compatibl. I will be using the Microsoft Visual Studio .Net 2003 IDE so any screenshots or descriptions will explain how to perform the task in that IDE. If you are using a different IDE such as Sharp Develop, Borland C# Builder or Microsoft Visual C# 2005 Express Edition, then I assume that you’re competent enough in it’s use that you can understand how to do the tasks in your own IDE. You should at least know how to create a new project, add references to your project, add files and code to your project, save it, and build it. Obviously knowing how to do other important things such as use the debugger would be a strong advantage.
I will use the DirectX Summer Update 2004 SDK. That is: version 1.0.1901.0. There may be some slight incompatibilities with previous versions, but they shouldn’t be too hard to fix if you’re using a previous version. I suggest you get the update though if you don’t already have it.
Note that I won’t be using the DirectX wizards or any template files in my project. I’ll be showing you how to do everything yourself so that we can gain a deeper understanding of what’s actually going on and what we’re actually writing.
Section 2 – Creating a New DirectX Project
Section 2.1 – Creating a New Project
Start up your IDE. As I mentioned above, I use Microsoft Visual Studio .Net 2003. Start a new project, and save it. Choose either “Windows Application” or “Console Application” depending on which type of project you wish to make. If you installed DirectX9? properly then you should be able to create a new DirectX project, which has a wizard to guide you through creating a simple application. As I mentioned, I won’t be using this. I prefer to write the code myself to gain a deeper understanding of what is actually going on in my project.
Section 2.2 – Adding the DirectX References
In order to use DirectX in your applications, you must add a reference to the assemblies. To do this in Microsoft Visual Studio .Net 2003 you can click the project menu and click “Add Reference”, or right click on the “References” section in the project explorer, and click “Add Reference”.
Now you should be presented with the “Add Reference” dialog. Make sure the “.Net” tab is selected, and scroll down to “Microsoft.DirectX”. You will see that there are several entries. At the very least, add “Microsoft.DirectX”. The other assembly names reflect quite clearly what they contain. For example “Microsoft.DirectX.Direct3D” is the 3D graphics API, “Microsoft.DirectX.DirectSound” is the Direct Sound API and so on. Go ahead and add whichever ones you require.
Some of you may notice that you have 2 versions of each assembly with different versions. You will have this if you installed the Summer 2004 Update. I have one set using version 1.0.1901.0 and another set using version 1.0.900.0. I will be adding the assemblies with the newer version: 1.0.1901.0.
Adding the DirectX references in Microsoft Visual Studio .Net 2003.
Section 2.3 – The “using” Directives
You may wish to add some “using” directives for the namespaces in DirectX. You should know what the “using” directive does. Here are some simple ones for using Direct3D:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
Section 3 – Direct Graphics Enumeration
Section 3.1 – Enumerating Adapters and Display Modes
An adapter is basically a graphics card. Most systems will have just 1 adapter. Systems with multiple monitors may have more than 1 adapter. If you wish to support systems with multiple adapters then you will have to enumerate them, otherwise you can just use the default one. The code to enumerate and choose an adapter is incredibly simple though, so it’s worth the little bit of extra effort.
An adapter is described by an AdapterInformation? class located inside the Microsoft.DirectX.Direct3D namespace. This class has some useful properties such as the CurrentDisplayMode? property, which funnily enough, returns the current display mode; the AvailableDisplayModes? property that returns a list of supported display modes, which can be enumerated using a foreach loop; and an Information property, which returns an AdapterDetails? class. This class contains several useful properties which you can use to identify individual cards and so on. One of the most useful properties of this class is the Description property. This gives you a human readable description of the graphics card such as “NVIDIA GeForce2? MX/MX 400” (My old crappy card). There are many other properties and methods inside the AdapterInformation? class too. Explore the properties and methods using the tooltips and the intellisense (Intellisense is the name given to the lists that pop up in the code editor containing all the accessible members of an object for those of you who don’t know what that is).
To actually enumerate an adapter, we use the Manager class located inside the Microsoft.DirectX.Direct3D namespace. You don’t create a new instance of this class as all of it’s methods are static. The Manager class contains a static property called “Adapters” which returns a list of AdapterInformation? classes described above, that you can enumerate using a foreach loop. It really is that simple. If you wish to use the default adapter, or just want to know what it is at least, then you can get that simply by using Manager.Adapters.Default.
Here’s an example program showing the main things we’ve learned so far:
using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
class AdapterEnumeration
{
static void Main(string[] args)
{
foreach(AdapterInformation adapter in Manager.Adapters)
{
// Check whether or not this is the default adapter
if(adapter.Equals(Manager.Adapters.Default))
{
Console.WriteLine("This is the default adapter\n");
}
// Write out some information about the adapter
Console.WriteLine("" + adapter.Information.ToString() +
"\n\nDisplay Modes:\n");
// Write out each display mode
foreach(DisplayMode display in adapter.SupportedDisplayModes)
{
Console.WriteLine("{0}x{1} : {2} @ {3} Hz",
display.Width,
display.Height,
display.Format,
display.RefreshRate);
}
// Write out the current display mode
Console.WriteLine ("\nCurrent Display Mode: {0}x{1} : {2} @ {3} Hz",
adapter.CurrentDisplayMode.Width,
adapter.CurrentDisplayMode.Height,
adapter.CurrentDisplayMode.Format,
adapter.CurrentDisplayMode.RefreshRate);
Console.WriteLine("\n\n");
}
Console.ReadLine();
}
}
That’s extremely simple, I think you’ll agree. The output isn’t the nicest looking output in the world, but it shows you how to do it. You can make it look pretty quite easily. In a windows application you can add each adapter description to a combo box or some other suitable control. When an adapter in the combo box is selected, you could add information about that adapter to a listview, and add it’s available display modes to another combo box, selecting the current display mode by default. That’s how I usually do it, and that’s an example of the kind of things you can do with the very simple code above.
As a by the way, you can get a handle to the monitor associated with each adapter using the following code:
IntPtr MonitorHandle = Manager.GetAdapterMonitor(adapter.Adapter);
I’m not quite sure what you could do with that handle, but I’m sure it’s useful somewhere, such as with some Win32 API functions that return information about the monitor or something like that. Note that the “Adapter” property of the AdapterInformation? class is the “adapter ordinal” which is just a fancy name for the adapter number. The first one has ordinal 0, the second ordinal 1 and so on. Usually there will be just one default adapter with an adapter ordinal of 0.
Section 3.2 – Device Capabilities (Caps)
Device Caps is one of those terms you will hear a lot with DirectX programming. Caps is simply short for Capabilities. The Device Caps are therefore just what the device is and isn’t capable of doing. There are literally hundreds of things you can check. Retrieving device capabilities is very simple. Again, you use the Manager class to do this. Here is a short snippet of code showing you how to do it:
AdapterInformation ai = Manager.Adapters.Default;
Caps caps = Manager.GetDeviceCaps(ai.Adapter, DeviceType.Hardware);
There’s nothing hard there, is there? The only thing that might confuse you is the DeviceType?. The 2 main types are “DeviceType.Hardware” and “DeviceType.Reference”. There is also another device type called a Software Device, but this is very rarely used. I think it’s something to do with plugging in your own software renderer or something. Using the hardware device type will query the capabilities of the graphics adapter. Using the reference device type will query the capabilities of software emulation, basically. A lot of drivers will be able to make up for lack of hardware features by providing software emulation. A common difference for example, is that the hardware will only usually be able to do a few active lights such as 8 or 16 whereas the reference device may be able to do an unlimited amount. The software device will be able to do pretty much everything, but it’s much slower than the hardware.
Anyway, back to the point. You can probably figure out how to use the Caps class by looking at the intellisense again. This is a very powerful way to learn about classes. Most of it should be fairly self explanatory. You can test for all kinds of things. The tooltips will explain what all of the properties mean. For example, if you want to see whether or not textures must be square, you can test that using the following code:
if(caps.TextureCaps.SupportsSquareOnly)
{
Console.WriteLine("Square textures only are supported");
}
As I mentioned, there are literally hundreds of things you can check for. They are all organised into sensible properties. For example, all texture capabilities are stored inside the TextureCaps? property, vertex processing capabilities are inside the VertexProcessingCaps? property and so on. You can explore it using the object browser as well as the intellisense. (Press F2 in Visual Studio .Net 2003 to open the object browser):
Exploring the Caps class inside the object browser.
Section 3.3 – Adapter Formats and BackBuffer? Formats
There are 3 main questions I will answer in this section: What is a format? What is a backbuffer (format), and what is an adapter format?
In answer to the first question, the format is just the way that pixel data is encoded. For example, you may have 5 bits to store the amount of red, 6 bits to store the amount of green and another 5 bits to store the amount of blue. This adds up to 16 bits, or 2 bytes per pixel. As a by the way, we give green more bits for 2 reasons. Firstly, to make the pixel data up to 2 bytes without wasting a pixel, and secondly, we chose the green component because our eyes are more sensitive to green and can therefore distinguish between more shades of green than they can red or blue, so it makes sense to give the green component the extra bit. The 2 most common formats are 16 bit and 32 bit. There is a 24 bit format and there are 8 bit formats, but these are rarely used. Some formats have some bits set aside for an “alpha” component. This is to do with something called alphablending, which is a type of transparency. You don’t need to know what that is exactly right now, but just bear in mind that some formats have an extra alpha component. Yet other formats have unused bits, denoted by “X”. For example, there is a 32 bit mode called “X8R8G8B8” which means that the red, green and blue components are all 8 bits each, and there is an unused 8 bits to round the format up to 4 bytes per pixel. An example of a format with an alpha component is one called “A8R8G8B8” which has the same as the other format, but instead of having an unused 8 bits, it uses the extra 8 bits for the alpha component.
Now in order to explain what the backbuffer format is, I suppose I better mention briefly what a backbuffer is. When you draw things on the screen, they aren’t all drawn at the same time obviously. So you may draw a tree, then draw a bush, then draw another tree, then draw a character, then draw a house for example. They are all drawn one after the other. There is a phenomena called “artifacts” where the screen encounters a vertical resync (a refresh) while you’re in the middle of drawing your scene on to the screen. This results in flickering graphics where you can briefly see things being drawn on the screen. The way this is fixed is we draw everything offscreen to an area of memory, and only once the scene is finished, we draw this entire area of memory onto the screen at once. This way you don’t see each individual part being drawn. This area of memory where you do your drawing is called a “backbuffer” and the process of drawing the backbuffer onto the screen is called “flipping” or “presenting”. Hopefully the term “backbuffer format” should be quite clear now that I’ve explained what a backbuffer is, and what a format is.
Now for the adapter format, this is basically just your display mode. The format of the pixels on the main screen. One question may arise from this – why aren’t the backbuffer format and the adapter format the exact same? Well the answer to that is in most cases they are. Although there is one difference. In a backbuffer you can have an alpha component, but you can’t have an alpha component in the adapter format. This is why they may differ.
Section 3.4 – Device Types
This is quite a short section. I’ve already mentioned device types before, but here it is again. There are 3 device types: Hardware, Software and Reference. Hardware means use the graphics card where possible, reference means use software emulation. And the software device is rarely used. It’s something to do with writing your own software renderer I think. Now you may be wondering why you would ever want to use software instead of hardware. Well there is actually a good reason. Because the reference device supports absolutely everything. You would need to fork out a huge amount of money to get a graphics card that can come anywhere near close to being able to do everything. I’m talking about a top of the range card – the best you can get. The problem with software is that it’s painfully slow compared to using hardware. So if you just want to test out some features in your game that your graphics card doesn’t support, you can switch to software emulated mode and check that it works. Or if you just really want to use some feature not supported by the hardware so much that you’re willing to slow down everything to a crawl to get it to work then you can use software emulation for that. You probably wouldn’t want to do that in a game unless you want a frame rate of about 0.5 frames per second. That would be for an application such as a 3D rendering application, where you really want to have some special effect in your scene, and don’t care if it takes an extra hour to render (These things take hours anyway).
Section 3.5 – Using Manager.CheckDeviceType
At this point I’ve explained the following things to you: how to enumerate adapters, how to enumerate display modes, device types and backbuffer and adapter formats. Now you need to begin choosing them. So choose an adapter, a device type, an adapter format (from your list of display modes), a backbuffer format, and a device type. Now you need to choose one more thing: whether or not you want to run this application in a window, or in full screen mode. You will need all of these to create a device (which I’ll explain later. Lets just say for now that it’s incredibly important). So now you’ve chosen all of these things. Unless you know what you’re doing or you got pretty lucky, they’re probably all incompatible with each other and wouldn’t work. Ideally you want to present a list of adapters to the user. Once they choose an adapter, you would then want to present a list of device types compatible with that adapter to them, and have them choose one of those. Once they’ve chosen an adapter and a device type, you would like them to choose a display mode (the important part being the format, for the adapter format). Once they’ve chosen that, you would then ideally like to present them with a list of backbuffer formats which are compatible with the adapter format to choose from. And finally, you want to present them with the option of fullscreen or windowed mode, if that’s still there.
The best way to do this is to check every possibly combination of the following: adapters, device types, available adapter formats, backbuffer formats, whether or not to use windowed or full screen mode. By checking all the combinations of the above, you’ll get a list of all the compatible combinations which you can present to the user whatever way you see fit. The method used to check these is Manager.CheckDeviceType. The following code snippet shows you how you can check all possible combinations. When you get a valid combination, you can add that to your combo boxes or list boxes or your arrays or whatever method of storing the combinations you choose.
DeviceType[] deviceTypes = new DeviceType[]
{DeviceType.Hardware, DeviceType.Software, DeviceType.Reference};
Format[] backBufferFormats = new Format[]
{Format.A8R8G8B8, Format.X8R8G8B8, Format.A2R10G10B10,
Format.R5G6B5, Format.A1R5G5B5, Format.X1R5G5B5};
bool[] windowModes = new bool[] {true, false};
// For each adapter
foreach (AdapterInformation adapter in Manager.Adapters)
{
ArrayList adapterFormats = new ArrayList();
// Build the list of adapter formats
foreach (DisplayMode dispMode in adapter.SupportedDisplayModes) {
if (!adapterFormats.Contains (dispMode.Format))
adapterFormats.Add (dispMode.Format);
}
foreach (DeviceType deviceType in deviceTypes) {
foreach (Format adapterFormat in adapterFormats) {
foreach (Format backBufferFormat in backBufferFormats) {
foreach (bool windowMode in windowModes) {
if (Manager.CheckDeviceType (
adapter.Adapter,
deviceType,
adapterFormat,
backBufferFormat,
windowMode)) {
// *** This combination is valid!
}
} // windowMode
} // backBufferFormat
} // adapterFormat
} // deviceType
} // adapter
Section 3.6 – Vertex Processing
The last thing we need before we can create a device (As I said earlier, this will be explained. Just know that it’s important. This is the main object we use for all our rendering) is a vertex processing type. I assume you’ve used something similar to the above code to enumerate all the possible combinations of adapters, device types, adapter/backbuffer formats and windowed or not and chosen one of each. Now before we can build a device, we need this vertex processing method. Basically the vertex processing method is just telling Direct3D where to do all of the vertex processing. How much should the hardware be involved, and how much should the software be involved, basically. Most cards these days should support hardware vertex processing. Lower end ones will support mixed vertex processing, where the processing is done by both hardware and software. Software only vertex processing will always work, but is slower. We can go through some methods to determine which type of processing is available like this:
Caps caps = Manager.GetDeviceCaps(Manager.Adapters.Default.Adapter,
DeviceType.Hardware);
if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
{
if (caps.DeviceCaps.SupportsPureDevice)
{
Console.WriteLine ("Pure Device");
}
Console.WriteLine ("Hardware Vertex Processing");
Console.WriteLine ("Mixed Vertex Processing");
}
Console.WriteLine ("Software Vertex Processing");
You can see that first we get the device capabilities, as we use them to check the vertex processing available. Then we check if the device supports hardware processing. If it does, we know that at least it supports hardware and mixed processing. If it supports hardware processing, then we check if it supports the “pure device” which means it also supports resterization and shading as well as just lighting and transformation. We know that software vertex processing will always be available so that’s a given.
Now you have everything you need to create a device. You can enumerate and choose an adapter, and a display mode, and a backbuffer format and a device type and finally you can choose whether or not to use windowed mode or full screen mode if it’s compatible with everything else you’ve chosen.
Section 3.7 – Enumerating Depth Stencil Formats
There is one other main thing you will need to be able to enumerate – depth stencil formats. Don’t worry about what these actually are, you will learn later. I’m just putting this in the enumeration section because that’s where it belongs – it is afterall enumeration. Just know how to do it. You already know what a format is. This is done quite easily. We first just check the format is valid for the device, then check if it’s compatible with the backbuffer format. If so, then we can use it (You’ll see what it’s for later).
DepthFormat[] depthStencilFormats = new DepthFormat[]
{
DepthFormat.D16,
DepthFormat.D15S1,
DepthFormat.D24X8,
DepthFormat.D24S8,
DepthFormat.D24X4S4,
DepthFormat.D32,
};
foreach (DepthFormat depthStencilFormat in depthStencilFormats)
{
if(Manager.CheckDeviceFormat(adapter, deviceType, adapterFormat,
Usage.DepthStencil, ResourceType.Surface, depthStencilFormat))
{
if (Manager.CheckDepthStencilMatch(adapter, deviceType,
adapterFormat, backbufferFormat, depthStencilFormat))
{
// This depth stencil format is compatible
}
}
}
Section 3.8 – Final Word on Enumeration
Note that I’ve covered more than enough on enumeration for you to get started. There is plenty more that you can figure out on your own though. Also remember all the stuff in the device capabilities (device caps) classes that can be checked. You check that stuff as you need it though. Unfortunately, enumeration is quite boring. I should have probably told you that at the start. We’ll start doing some more interesting stuff in the next section.
Section 4 – Rendering Something
Section 4.1 – Creating the Device
Finally we get on to this device I’ve mentioned so much in the enumeration section. What is a device? The device is the object we use in almost everything. It’s a representation of our actual adapter (well, in reality it’s driver). You will use the device to do all of the rendering (drawing, if I haven’t mentioned that yet) and lighting and so on. You can’t do very much without a device. So I think I’ve stressed just how important it is. Now to actually create a device is a little bit trickier than anything we’ve seen yet, but it’s still fairly trivial. Especially with all our enumeration done above.
// Assumes you have:
//
// AdapterInformation adapter;
// DeviceType deviceType;
// CreateFlags createFlags;
// DisplayMode displayMode;
// Format backbufferFormat;
// bool windowed;
PresentParameters presentation = new PresentParameters();
presentation.BackBufferFormat = backbufferFormat;
presentation.BackBufferWidth = displayMode.Width;
presentation.BackBufferHeight = displayMode.Height;
presentation.SwapEffect = SwapEffect.Flip;
presentation.Windowed = windowed;
Device device = new Device(
adapter.Adapter,
deviceType,
this,
createFlags,
presentation);
So first you need to choose an adapter, a device type, a vertex processing type (this is the createFlags variable) a display mode, a backbuffer format, and whether or not to use windowed mode. All of these are stored in the variables mentioned above. One you’ve chosen these, you then create a new PresentParameters? object. This stores information about the backbuffer, and the display mode, and other important presentation information. You can change things like the number of back buffers to use and so on. We just change the backbuffer format, width and height as appropriate. Then we change the swap effect. The swap effect is just how the backbuffer is presented to the screen. We just tell it to flip the backbuffer and the screen around so that the backbuffer now becomes drawn on the screen, and what was previously on the screen now becomes the backbuffer. Lastly, we set whether or not we want to use windowed mode or not. Now to finally create the device, you pass first the adapter ordinal, then the device type. The next parameter, “this”, must be a windows forms control! So this won’t work in console mode obviously. You must write the above code in a windows forms application. Using “this” will just draw onto the form. You could also draw onto a picturebox or something else if you pleased. The next parameter is the create flags, or the vertex processing type that I talked about a lot above in the enumeration section. And lastly, you pass the presentation parameters. This should hopefully give us a working device.
Section 4.2 – Rendering Something
Now we get to the fun part – actually rendering! Don’t get too excited yet though, we’re only going to clear the window with a color. First we need to know where to actually render though. You could override the OnPaint? event and render everything in there. For a game or something, you will need a loop though. This is usually called “the game loop”. This game loop usually goes after all the initialization code. Just remember to show your form before entering the loop. So now we know roughly where to put our rendering code, how do we actually write it? Well, we’ll learn 2 new methods of the Device object: Clear and Present. The “Clear” method simply clears the entire drawing area with your chosen color. The “Present” method performs the “flip”, drawing the backbuffer to the screen. That’s all we’ll use right now. So here’s the code for a typical rendering loop:
while (Running)
{
device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);
device.Present();
Application.DoEvents();
}
Notice that we have a variable, Running. This would be a boolean that you would set to true if your initialization was successful. Then when you want to end the loop, for example when the escape key is pressed, or the form is closing, then you set the Running variable to false. Notice that we also use Application.DoEvents to continue processing messages.
Section 4.3 – A Complete Sample Application
using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
public class MainForm : Form
{
Device device = null;
/// <summary>
/// When the form is clicked, we close it and stop
/// the application
/// </summary>
protected override void OnClick(EventArgs e)
{
this.Close();
base.OnClick (e);
}
/// <summary>
/// In the constructor we initialize the device using
/// the default adapter and the current display mode
/// </summary>
public MainForm()
{
AdapterInformation adapter = Manager.Adapters.Default;
PresentParameters presentation = new PresentParameters ();
presentation.BackBufferFormat = adapter.CurrentDisplayMode.Format;
presentation.BackBufferWidth = adapter.CurrentDisplayMode.Width;
presentation.BackBufferHeight = adapter.CurrentDisplayMode.Height;
presentation.SwapEffect = SwapEffect.Flip;
presentation.Windowed = false;
// Place it in a try catch block just
//in case it goes wrong for
// some reason.
try
{
// To make things simple, we'll just
//use the reference device, and
// software vertex processing.
//Although this will be extrelemly slow
// it doesn't matter in this case.
// It will definetly work on
// all machines, so it saves us doing
//enumeration for now.
device = new Device (adapter.Adapter,
DeviceType.Reference,
this,
CreateFlags.SoftwareVertexProcessing,
presentation);
}
catch (DirectXException e)
{
MessageBox.Show (e.Message);
return;
}
}
/// <summary>
/// Clear the screen with a blue color
/// </summary>
public void Render()
{
device.Clear(ClearFlags.Target, Color.Blue, 1.0f, 0);
device.Present();
}
/// <summary>
/// The entry point where the main render loop goes
/// </summary>
static void Main()
{
using (MainForm form = new MainForm())
{
form.Show();
while (form.Created)
{
form.Render();
Application.DoEvents();
}
}
}
}
As a simple exercise you could try changing the application so that it exits when the escape key is pressed instead of clicked. Also, try changing the color to red. And if you like you can try making the program run in windowed mode by commenting all the backbuffer parameters in the presentation parameters and just change the Windowed property to true.