Creating Your Device


For the rest of the development of this game, you should still be working with the project you started in the last chapter. One of the first things you want to do now is set up the project to actually work with the sample framework. In the Main method you created in the last chapter, add the code in Listing 4.1 immediately following the creation of the GameEngine class.

Listing 4.1. Hooking Events and Callbacks
 // Set the callback functions. These functions allow the sample framework // to notify the application about device changes, user input, and Windows // messages. The callbacks are optional so you need only set callbacks for // events you're interested in. However, if you don't handle the device // reset/lost callbacks, then the sample framework won't be able to reset // your device since the application must first release all device resources // before resetting. Likewise, if you don't handle the device created/destroyed // callbacks, then the sample framework won't be able to re-create your device // resources. sampleFramework.Disposing += new EventHandler(blockersEngine.OnDestroyDevice); sampleFramework.DeviceLost += new EventHandler(blockersEngine.OnLostDevice); sampleFramework.DeviceCreated +=  new DeviceEventHandler(blockersEngine.OnCreateDevice); sampleFramework.DeviceReset +=  new DeviceEventHandler(blockersEngine.OnResetDevice); sampleFramework.SetKeyboardCallback(new KeyboardCallback( blockersEngine.OnKeyEvent)); // Catch mouse move events sampleFramework.IsNotifiedOnMouseMove = true; sampleFramework.SetMouseCallback(new MouseCallback(blockersEngine.OnMouseEvent)); sampleFramework.SetCallbackInterface(blockersEngine); 

A lot of things are happening in this small section of code. A total of four events are being hooked to let you know when the rendering device has been created, lost, reset, and destroyed. You'll need to add the implementations for these handlers in a moment from Listing 4.2. After that, you'll notice that you're hooking two callbacks the sample framework has for user input, namely the keyboard and mouse (Listing 4.3). Finally, you call the SetCallbackInterface method passing in the game engine instance; however, you might notice that the instance doesn't implement the correct interface. You'll need to fix that as well.

Listing 4.2. Framework Event Handlers
 /// <summary> /// This event will be fired immediately after the Direct3D device has been /// created, which will happen during application initialization and /// windowed/full screen toggles. This is the best location to create /// Pool.Managed resources since these resources need to be reloaded whenever /// the device is destroyed. Resources created here should be released /// in the Disposing event. /// </summary> private void OnCreateDevice(object sender, DeviceEventArgs e) {     SurfaceDescription desc = e.BackBufferDescription; } /// <summary> /// This event will be fired immediately after the Direct3D device has been /// reset, which will happen after a lost device scenario. This is the best /// location to create Pool.Default resources since these resources need to /// be reloaded whenever the device is lost. Resources created here should /// be released in the OnLostDevice event. /// </summary> private void OnResetDevice(object sender, DeviceEventArgs e) {     SurfaceDescription desc = e.BackBufferDescription; } /// <summary> /// This event function will be called fired after the Direct3D device has /// entered a lost state and before Device.Reset() is called. Resources created /// in the OnResetDevice callback should be released here, which generally /// includes all Pool.Default resources. See the "Lost Devices" section of the /// documentation for information about lost devices. /// </summary> private void OnLostDevice(object sender, EventArgs e) { } /// <summary> /// This callback function will be called immediately after the Direct3D device /// has been destroyed, which generally happens as a result of application /// termination or windowed/full screen toggles. Resources created in the /// OnCreateDevice callback should be released here, which generally includes /// all Pool.Managed resources. /// </summary> private void OnDestroyDevice(object sender, EventArgs e) { } 

Listing 4.3. User Input Handlers
 /// <summary>Hook the mouse events</summary> private void OnMouseEvent(bool leftDown, bool rightDown, bool middleDown,     bool side1Down, bool side2Down, int wheel, int x, int y) { } /// <summary>Handle keyboard strokes</summary> private void OnKeyEvent(System.Windows.Forms.Keys key, bool keyDown,     bool altDown) { } 

Now, the SetCallbackInterface method you called earlier expects a variable of type IFrameworkCallback, and you passed in the game engine class, which does not implement this type. You can fix this easily by changing the game engine class declaration:

 public class GameEngine : IFrameworkCallback, IDeviceCreation 

Of course, now you need to add the implementation of the two methods this interface defines (Listing 4.4).

Listing 4.4. Framework Callback Interface
 /// <summary> /// This callback function will be called once at the beginning of every frame. /// This is the best location for your application to handle updates to the /// scene but is not intended to contain actual rendering calls, which should /// instead be placed in the OnFrameRender callback. /// </summary> public void OnFrameMove(Device device, double appTime, float elapsedTime) { } /// <summary> /// This callback function will be called at the end of every frame to perform /// all the rendering calls for the scene, and it will also be called if the /// window needs to be repainted. After this function has returned, the sample /// framework will call Device.Present to display the contents of the next /// buffer in the swap chain /// </summary> public void OnFrameRender(Device device, double appTime, float elapsedTime) { } 

With that boilerplate code out of the way, you're ready to start doing something interesting now. First, you should tell the sample framework that you're ready to render your application and to start the game engine. Go back to the Main method, and add the code in Listing 4.5 immediately after your SetCallbackInterface call.

Listing 4.5. Starting the Game
 try { #if (!DEBUG)     // In retail mode, force the app to fullscreen mode     sampleFramework.IsOverridingFullScreen = true; #endif     // Show the cursor and clip it when in full screen     sampleFramework.SetCursorSettings(true, true);     // Initialize the sample framework and create the desired window and     // Direct3D device for the application. Calling each of these functions     // is optional, but they allow you to set several options that control     // the behavior of the sampleFramework.     sampleFramework.Initialize( false, false, true );     sampleFramework.CreateWindow("Blockers - The Game");     sampleFramework.CreateDevice( 0, true, Framework.DefaultSizeWidth,         Framework.DefaultSizeHeight, blockersEngine);     // Pass control to the sample framework for handling the message pump and     // dispatching render calls. The sample framework will call your FrameMove     // and FrameRender callback when there is idle time between handling     // window messages.     sampleFramework.MainLoop(); } #if(DEBUG) catch (Exception e) {     // In debug mode show this error (maybe - depending on settings)     sampleFramework.DisplayErrorMessage(e); #else catch { // In release mode fail silently #endif     // Ignore any exceptions here, they would have been handled by other areas     return (sampleFramework.ExitCode == 0) ? 1 : sampleFramework.ExitCode;     // Return an error code here } 

What is going on here? The first thing to notice is that the entire code section is wrapped in a TRy/catch block, and the catch block varies depending on whether you're compiling in debug or release mode. In debug mode, any errors are displayed, and then the application exits. In release mode, all errors are ignored, and the application exits. The first thing that happens in the block is that the sample framework is told to render in full-screen mode if you are not in debug mode. This step ensures that while you're debugging, the game runs in a window, allowing easy debugging, but when it's complete, it runs in full-screen mode, as most games do.

The next call might seem a little strange, but the basic goal of the call is to determine the behavior of the cursor while in full-screen mode. The first parameter determines whether the cursor is displayed in full-screen mode, and the second determines whether the cursor should be clipped. Clipping the cursor simply ensures that the cursor cannot leave the area of the game being rendered. In a single monitor scenario, it isn't a big deal either way, but in a multimon scenario, you wouldn't want the user to move the cursor to the other monitor where you weren't rendering.

The Initialize call sets up some internal variables for the sample framework. The three parameters to the call determine whether the command line should be parsed (no), whether the default hotkeys should be handled (no again), and whether message boxes should be shown (yes). You don't want the game to parse the command line or handle the default hotkeys because they are normally reserved for samples that ship with the DirectX SDK. The CreateWindow call is relatively self-explanatory; it creates the window where the rendering occurs with the title listed as the parameter.

Finally, the CreateDevice call is created. Notice that this is where you pass in the instance of the game engine class for the IDeviceCreation interface. Before the device is created, every combination is enumerated on your system, and the IsDeviceAcceptable method that you wrote in the last chapter is called to determine whether the device is acceptable to you. After the list is created, the ModifyDevice method is called to allow you to modify any settings right before the device is created. The constructor that you use for the device creation looks like this:

 public Device ( System.Int32 adapter ,    Microsoft.DirectX.Direct3D.DeviceType deviceType ,     System.IntPtr renderWindowHandle ,     Microsoft.DirectX.Direct3D.CreateFlags behaviorFlags ,     params Microsoft.DirectX.Direct3D.PresentParameters[]     presentationParameters ) 

The adapter parameter is the ordinal of the adapter you enumerated earlier. In the majority of cases, it is the default parameter, which is 0. The deviceType parameter can be one of the following values:

  • DeviceType.Hardware A hardware device. This is by far the most common device type created. The hardware device will most likely not support every feature of the Direct3D runtime. You need to check the capabilities of the device for specific features. This option is naturally the fastest option.

  • DeviceType.Reference A device using the reference rasterizer. The reference rasterizer performs all calculations for rendering in software mode. Although this device type supports every feature of Direct3D, it does so very, very slowly. You should also note that the reference rasterizer only ships with the DirectX SDK. End users will most likely not have this rasterizer installed.

  • DeviceType.Software A software rendering device. It is extremely uncommon to use this option. Unless you have written a software rendering library, and you have it pluggable with Direct3D, you will not be using this device type.

Obviously, because you are dealing with graphics, you need someplace to actually show the rendered image. In this overload of the device constructor, the sample framework uses the window it has created, but you could pass in any control from the System.Windows.Forms library that comes with the .NET framework. Although it won't be used in this game, you could use any valid control, such as a picture box.

The next parameter is the behavior of the device. You might remember from the last chapter when you determined the best set of flags, including whether the device supported transforming and lighting in hardware and whether the device can be pure. You find the possible value of this parameter by combining the values in Table 4.1.

Table 4.1. Possible Behavior Flags

CreateFlags.AdapterGroupDevice

Used for multimon-capable adapters. Specifies that a single device will control each of the adapters on the system.

CreateFlags.DisableDriverManagement

Tells Direct3D to handle the resource management rather than the driver. In most cases, you will not want to specify this flag.

CreateFlags.MixedVertexProcessing

Tells Direct3D that a combination of hardware and software vertex processing will be used. This flag cannot be combined with either the software or hardware vertex processing flags.

CreateFlags.HardwareVertexProcessing

Tells Direct3D that all vertex processing will occur in hardware. This flag cannot be combined with the software or mixed vertex processing flags.

CreateFlags.SoftwareVertexProcessing

Tells Direct3D that all vertex processing will occur in software. This flag cannot be combined with the hardware or mixed vertex processing flags.

CreateFlags.PureDevice

Specifies that this device will be a pure device.

CreateFlags.Multithreaded

Specifies that this device may be accessed for more than one thread simultaneously. Because the garbage collector runs on a separate thread, this option is turned on by default in Managed DirectX. Note that there is a slight performance penalty for using this flag.

CreateFlags.FpuPreserve

Tells Direct3D to preserve the current floating-point unit precision.


For this game, you should stick with either software or hardware vertex processing only, which is what the enumeration code picked during the last chapter. The final parameter of the device constructor is a parameter array of the PresentParameters class. You only need more than one of these objects if you are using the CreateFlags.AdapterGroupDevice flag mentioned earlier, and then you need one for each adapter in the group.

Construction Cue

Before the device is actually created by the framework, you might notice that it turns off the event-handling mechanism for the Managed Direct3D libraries.


Before you go on, it's important to understand why turning off the event-handling model is a good idea. The default implementation of the Managed DirectX classes hooks certain events on the Direct3D device for every resource that is created. At a minimum, each resource (such as textures or a vertex buffer) hooks the Disposing event and more likely also hooks other events, such as the DeviceLost and DeviceReset events. This step happens for maintaining object lifetimes. Why wouldn't you want this great benefit in your application?

The main reason is that this benefit comes at a cost, and that cost could potentially be quite large. To understand this point, you must first have a sense of what is going on behind the scenes. You can look at this simple case, written here in pseudo-code:

 SomeResource res = new SomeResource(device); device.Render(res); 

As you can see, this code looks harmless enough. You simply create a resource and render it. The object is obviously never used again, so the garbage collector should be smart enough to clean up the object. This thought is common, but this thought is incorrect. When the new resource is created, it hooks a minimum of one event on the device to allow it to clean up correctly. This hooking of the event is a double-edged sword.

One, there is an allocation of the EventHandler class when doing the actual hook. Granted, the allocation is small, but as you will see in a moment, even small allocations can add up quickly. Second, after the event is hooked, the resource has a hard link to the device. In the eyes of the garbage collector, this object is still in use and will remain in use for the lifetime of the device or until the events are unhooked. In the pseudo-code earlier, imagine if this code were run once every frame to render something. Imagine that your game was pushing around a thousand frames per second, and imagine it was running for two minutes. You've just created 120,000 objects that will not be collected while the device is around, plus another 120,000 event handlers. All these created objects can cause memory consumption to rise quickly, as well as extra garbage collections to be performed, which can hurt performance. If your resources are in video memory, you can be assured you will run out quickly.

This scenario doesn't even consider what happens when the device is finally disposed. In the preceding example, when the device is disposed, it fires the Disposing event, which has been hooked by 120,000 listeners. You can imagine that this cascading list of event handlers which must be called will take some time, and you'd be correct. It could take literally minutes and cause people to think the application has locked up.

You only want to use the event handling that is built in to Managed Direct3D in the simplest of cases. At any point where you care about memory consumption or performance (for example, in games), you want to avoid this process, as you've done in this example (or at the very least ensure that you are disposing of objects properly). The sample framework gives you the opportunity to do so.

You'll notice that last method you called in the Main method is the MainLoop method from the sample framework. This point is where you are telling the sample framework that you're ready to run your application now. This method will run and process Windows messages as well as call your rendering methods constantly until the application exits. This method will not return until it happens. From now on, all interaction with the sample framework comes from the events it fires and the callbacks it calls into.

A few times throughout the course of the code, you might need to know the name of the game. You can simply add this constant to your game engine class:

 public const string GameName = "Blockers"; 



Beginning 3D Game Programming
Beginning 3D Game Programming
ISBN: 0672326612
EAN: 2147483647
Year: 2003
Pages: 191
Authors: Tom Miller

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