The Direct3D Device

The root of all drawing in Direct3D is the device class. You can think of this class as analogous to the actual graphics device in your computer. A device is the parent of all other graphical objects in your scene. Your computer may have zero or many devices, and with Managed Direct3D, you can control as many of them as you need.

There are three constructors that can be used to create a device. For now, we're only going to use one of them; however we will get to the others in later chapters. The one we're concerned about takes the following arguments:

 public Device ( System.Int32 adapter , Microsoft.DirectX.Direct3D.DeviceType deviceType ,   System.Windows.Forms.Control renderWindow , Microsoft.DirectX.Direct3D.CreateFlags   behaviorFlags, Microsoft.DirectX.Direct3D.PresentParameters presentationParameters ) 

Now, what do all of these arguments actually mean, and how do we use them? Well, the first argument "adapter" refers to which physical device we want this class to represent. Each device in your computer has a unique adapter identifier (normally 0 through one less than the number of devices you have). Adapter 0 is always the default device.

UNDERSTANDING THE DEVICE CONSTRUCTOR OVERLOADS

The second overload for the device is identical to the first, with the exception of the renderWindow parameter, which takes an IntPtr for an unmanaged (or non-windows form) window handle. The final overload takes a single IntPtr that is the unmanaged COM pointer to the IDirect3DDevice9 interface. This can be used if you have a need to work with an unmanaged application from your managed code.

The next argument, DeviceType, tells Direct3D what type of device you want to create. The most common value you will use here is DeviceType.Hardware, which means you want to use a hardware device. Another option is DeviceType.Reference, which will allow you to use the reference rasterizer, which implements all features of the Direct3D runtime, and runs extremely slowly. You would use this option mainly for debugging purposes, or to test features of your application that your card doesn't support.

The "renderWindow" argument binds a window to this device. Since the windows forms control class contains a window handle, it makes it easy to use a derived class as our rendering window. You can use a form, panel, or any other control-derived class for this parameter. For now, we'll use forms.

USING A SOFTWARE DEVICE

You should note that the reference rasterizer is only distributed with the DirectX SDK, so someone with only the DirectX runtime will not have this feature. The last value is DeviceType.Software, which will allow you to use a custom software rasterizer. Unless you know you have a custom software rasterizer, ignore this option.

The next parameter is used to control aspects of the device's behavior after creation. Most of the members of the CreateFlags enumeration can be combined to allow multiple behaviors to be set at once. Some of these flags are mutually exclusive, though, and I will get into those later. We will only use the SoftwareVertexProcessing flag for now. This flag specifies that we want all vertex processing to happen on the CPU. While this is naturally slower than having the vertex processing happen on the GPU, we don't know for sure whether or not your graphics card supports this feature. It's safe to assume your CPU can do the processing for now.

The last parameter of this constructor controls how the device presents its data to the screen. Every aspect of the device's presentation parameters can be controlled via this class. We will go into further details on this structure later, but right now, the only members we will care about are the "Windowed" member, and the SwapEffect member.

The windowed member is a Boolean value used to determine whether the device is in full screen mode (false), or windowed mode (true).

The SwapEffect member is used to control implied semantics for buffer swap behavior. If you choose SwapEffect.Flip, the runtime will create an extra back buffer, and copy whichever becomes the front buffer at presentation time. The SwapEffect.Copy is similar to Flip, but requires you to set the number of back buffers to 1. The option we'll select for now is SwapEffect.Discard, which simply discards the contents of the buffer if it isn't ready to be presented.

Now that we have this information, we can create a device. Let's go back to our code, and do this now. First we will need to have a device object that we can use for our application. We can add a new private member device variable. Include the following line in your class definition:

 private Device device = null; 

We will add a new function to our class called "InitializeGraphics" that will be where we can actually use the constructor we've discussed. Add the following code to your class:

 /// <summary> /// We will initialize our graphics device here /// </summary> public void InitializeGraphics() {     // Set our presentation parameters     PresentParameters presentParams = new PresentParameters();     presentParams.Windowed = true;     presentParams.SwapEffect = SwapEffect.Discard;     // Create our device     device = new Device(0, DeviceType.Hardware, this,        CreateFlags.SoftwareVertexProcessing, presentParams); } 

As you can see, this creates our presentation parameters argument, sets the members we care about (Windowed and SwapEffect), and then creates the device. We used 0 as the adapter identifier, since that is the default adapter. We created an actual hardware device, as opposed to the reference rasterizer or a software rasterizer. You'll notice we used the "this" keyword as our rendering window. Since our application, and in particular, this class, is a windows form, we simply use that. We also let the CPU handle the vertex processing as mentioned previously.

All of this is great, but currently, the code is never called, so let's change the main function of our class to actually call this method. Let's change the static main method that is created by default to the following:

 static void Main() {     using (Form1 frm = new Form1())     {         // Show our form and initialize our graphics engine         frm.Show();         frm.InitializeGraphics();         Application.Run(frm);     } } 

We made a few changes to this function. First, we added the using statement around the creation of our form. This ensures that our form is disposed when the application leaves the scope of this block. Next, we added the Show command to the form. We do this to ensure that the window is actually loaded and displayed (and thus the window handle is created) before we try to create our device. We then call our function to handle our device creation, and use the standard run method for our application. You can compile this application now and run it. Congratulations, you've just successfully created your first Direct3D application!

Okay, so admittedly the application is pretty boring. While it does create a device, it doesn't actually do anything with it, so just by looking at the running application, you can't tell that it's any different from the "empty" C# project we originally created. Let's change that now, and actually render something on our screen.

The windows forms classes have a built-in way to determine when it is time to redraw themselves: using the OnPaint override (you can also hook the Paint event). Each time the window needs to be redrawn, this event will be fired. This seems like the perfect place to put our rendering code. We don't want to do anything amazingly fancy right now, so let's just clear the window to a solid color. Add the following code to your class definition:

 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) {     device.Clear(ClearFlags.Target, System.Drawing.Color.CornflowerBlue, 1.0f, 0);     device.Present(); } 

We use the Clear method on the device to fill our window with a solid color. In this case, we use one of the predefined colors, namely CornflowerBlue. The first parameter of the clear method is what we want to actually clear; in this example, we are clearing the target window. We will get to other members of the ClearFlags enumeration at a later time. The second parameter is the color we are using to clear our target, and for now the last few parameters aren't that important. After the device has been cleared, we want to actually update the physical display. The present method will do this presentation. There are a few different overloads of this method; the one shown here presents the entire area of the device. We will discuss the other overloads later.

If you run the application now, you will notice that while running, the background color of the window is cornflower blue. You can resize the window or maximize it, and each time you do the entire window surface is filled with the cornflower blue color. While we can now see that something is going on with our device, it's still pretty boring. You could accomplish the same thing by setting the background color of the form in the designer. We need to draw something else to be impressive.

The basic object drawn in three dimensional graphics is the triangle. With enough triangles, you can represent anything, even smooth curved surfaces. So our first drawing will naturally need to be a single triangle. To simplify the act of drawing this triangle, we won't worry about dealing with things such as "world space" or "transforms" (which are topics we'll delve into shortly), but instead will draw a triangle using screen coordinates. So, in order to draw our amazing triangle, we will need two things. First, we will need some data construct that will hold the information about our triangle. Second, we will tell the device to actually draw it.

Luckily for us, the Managed DirectX runtime already has a construct to hold our triangle's data. There is a CustomVertex class in the Direct3D namespace that houses many of the common "vertex format" constructs used in Direct3D. A vertex format structure holds data in a format that Direct3D understands and can use. We will discuss more of these structures soon, but for now, we will use the TransformedColored structure for our triangle. This structure tells the Direct3D runtime that our triangle doesn't need to be transformed (that is, rotated or moved) since we will be specifying the coordinates in screen coordinates. It also includes a color component for each point (vertex) in the triangle. Go back to our OnPaint override, and add the following code after the clear call:

 CustomVertex.TransformedColored[] verts = new CustomVertex.TransformedColored[3]; verts[0].SetPosition(new Vector4(this.Width / 2.0f, 50.0f, 0.5f, 1.0f)); verts[0].Color = System.Drawing.Color.Aqua.ToArgb(); verts[1].SetPosition(new Vector4(this.Width - (this.Width / 5.0f), this.Height      (this.Height / 5.0f), 0.5f, 1.0f)); verts[1].Color = System.Drawing.Color.Black.ToArgb(); verts[2].SetPosition(new Vector4(this.Width / 5.0f, this.Height - (this.Height / 5.0f)      , 0.5f, 1.0f)); verts[2].Color = System.Drawing.Color.Purple.ToArgb(); 

Each member of the array we have created will represent one point in our triangle, thus the need to create three members. We then call the SetPosition method on each member of our array using a newly created Vector4 structure. The position for a transformed vertex includes the x and y positions in screen coordinates (relative to the windows 0, 0 origin), as well as the z position and rhw member (reciprocal of homogenous w). Each of these two members are ignored for our current sample. The Vector4 structure is a convenient way of holding this information. We then set each of the vertices' colors. Notice here that we need to call the ToArgb method of the standard colors we are using. Direct3D expects colors to be a 32-bit integer, and this method will convert the stock color into this format.

You'll notice that we use the current window's width and height to determine our triangle's coordinates as well. We do this just so the triangle resizes with the window.

Now that we have the data, we need to tell Direct3D that we want to draw our triangle, and how to draw it. We can do that by adding the following code below our clear call in the OnPaint override:

 device.BeginScene(); device.VertexFormat = CustomVertex.TransformedColored.Format; device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, verts); device.EndScene(); 

What the heck does that mean? Well, it's relatively simple. The BeginScene method lets Direct3D know that we are about to draw something and to be ready for it. Now that we've told Direct3D that we're going to draw something, we need to tell it what we're going to draw. This is what the VertexFormat property is for. This tells the Direct3D runtime which fixed function pipeline format we will be using. In our case, we are using the transformed and colored vertices pipeline. Don't worry if you don't necessarily understand what a "fixed function pipeline" is; we'll get to that soon.

The DrawUserPrimitives function is where the actual drawing takes place. So what exactly do the parameters mean? The first parameter is the type of primitive we plan on drawing. There are numerous different types available to us, but right now, we just want to draw a list of triangles, so we choose the TriangleList primitive type. The second parameter is the number of triangles we plan on drawing; for a triangle list this should always be the number of vertices in your data divided by three. Since we are only drawing one triangle, naturally, we use 1 here. The last parameter for this function is the data Direct3D will use to draw the triangle. Since we've already filled up our data, we're all set now. The last method, EndScene, just informs the Direct3D runtime that we are no longer drawing. You must call EndScene after every time you've called BeginScene.

Now compile and run our new application. You'll notice that our colored background now has a colored triangle as well. One important thing to note is that the colors at the points of the triangle are the colors we specified in our code, but on the inside of the triangle, the color fades from one color to another. Direct3D automatically interpolates the colors between the triangles for you. Feel free to modify the stock colors I've chosen to see this effect in action.

There are some things you may have noticed if you were playing around with the application. For example, if you resize the window smaller, it doesn't update its contents, but instead simply does nothing. The reason for this is that Windows does not consider shrinking a window a case where you need to repaint the entire window. After all, you've simply removed some data that was displayed; you didn't erase any data that was already there. Fortunately, there is a simple way around this. We can just tell Windows that we always need the window to be repainted. This can be accomplished easily by invalidating the window at the end of our OnPaint override.

 this.Invalidate(); 

Uh oh, it appears we've broken our application! Running it now shows mainly a blank screen, and sometimes our triangle "flickers" onscreen. This effect is much more pronounced as you resize your window. This is no good; why did this happen? It turns out Windows is trying to be smart and redraws our current window form (the blank one) after invalidating our window. There is painting going on outside of our OnPaint override. This is easily fixed by changing the "style" of window we create. In the constructor for your form, replace the "TODO" section with the following line:

 this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); 

Now when you run the application, everything works as expected. All we did was tell windows that we want all of the painting to happen inside our OnPaint override (WmPaint comes from the classic Win32 message), and that our window will not be transparent. This ensures that no extra painting from windows will occur, and everything will go through us. You may also notice that if you resize the window to where there is no visible client area, the application will throw an exception. You can change the minimum size property of the form if this really bothers you.



Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net