Creating a Full Screen DirectDraw Device

Before you can create your DirectDraw device, a new project will need to be created. Since this will still be an application where items will need to be rendered, windows forms should still be used. Use these steps to get started:

  1. Create a new Windows Forms project named whatever you like.

  2. Add a reference to Microsoft.DirectX.dll and Microsoft.DirectDraw.dll.

  3. Include a new using clause for Microsoft.DirectX.DirectDraw into your main code file.

  4. Set the window style to Opaque and AllPaintingInWmPaint, just like you did for 3D applications.

  5. Add a private variable for your DirectDraw device.

You'll notice that most of these steps are similar to what was done for the 3D applications that were built in earlier chapters, with the major difference being the references that were added.

In DirectDraw you don't have textures that are used to represent your 2D images; instead you have "surfaces." A surface is essentially a data store for a rectangular image. When rendering a single sprite with a full-screen device, you will need a total of three surfaces. Add these to your application now:

 private Surface primary = null; private Surface backBuffer = null; private Surface sprite = null; 

Obviously the sprite surface will be where the actual image being rendered is stored. The first two parameters are something new, though. Since the screen itself is essentially a rectangular surface that you will be rendering to, you will use a surface to access this as well. Rendering directly to the video screen is never a good idea, because then you can have problems with what is called "tearing," which is when part of two (or more) different rendered scenes end up on the screen at the same time. To ensure this behavior doesn't happen, all of the rendering should happen in a stored back buffer, and then be copied to the primary buffer in one chunk. This is handled for you automatically in Direct3D (see Chapter 1, "Introducing Direct3D"), but you must handle it yourself in DirectDraw.

Before you go on, you should once again ensure that you handle the Escape key to quit. Since the application will be running in full screen mode, you want an easy way to quit. Add the following code to ensure your application exits on escape:

 protected override void OnKeyUp(KeyEventArgs e) {     if (e.KeyCode == Keys.Escape) // Quit         this.Close();     base.OnKeyUp (e); } 

Now that an easy method to quit has been established, you can create your initialization method. Add the method found in Listing 17.1 to your application.

Listing 17.1 Initialize a DirectDraw Device
 public void InitializeGraphics() {     SurfaceDescription description = new SurfaceDescription();     device = new Device();     // Set the cooperative level.     device.SetCooperativeLevel(this, CooperativeLevelFlags.FullscreenExclusive);     // Set the display mode width and height, and 16 bit color depth.     device.SetDisplayMode(ScreenWidth, ScreenHeight, 16, 0, false);     // Make this a complex flippable primary surface with one backbuffer     description.SurfaceCaps.PrimarySurface =         description.SurfaceCaps.Flip =         description.SurfaceCaps.Complex = true;     description.BackBufferCount = 1;     // Create the primary surface     primary = new Surface(description, device);     SurfaceCaps caps = new SurfaceCaps();     caps.BackBuffer = true;     // Get the backbuffer from the primary surface     backBuffer = primary.GetAttachedSurface(caps);     // Create the sprite bitmap surface.     sprite = new Surface(@"..\..\logo.bmp",         new SurfaceDescription(), device);     // Set the colorkey to the bitmap surface.     // which is what the ColorKey struct is initialized to.     ColorKey ck = new ColorKey();     sprite.SetColorKey(ColorKeyFlags.SourceDraw, ck); } 

As you can see, this method is slightly more complicated than the Direct3D version. After your DirectDraw device has been created, you will need to call the SetCooperativeLevel method, which notifies DirectDraw how you plan on cooperating with other applications wishing to share the graphics card resources. Since this application will be full screen, there's no need to share the resources. Using the option FullScreenExclusive tells DirectDraw this.

After the device has been created and the cooperative level has been set, you can now change the display mode. The first two parameters are the width and height of the updated display mode, respectively. The third parameter is the color depth you want the display mode to be in. 16-bit color is the most commonly used format. 32-bit color gives you a larger array of colors to use, but uses double the amount of resources. The fourth parameter is the refresh rate the monitor should use; for most applications simply using zero for the monitor default should be sufficient. The last parameter indicates whether this mode is a standard VGA mode. It's a rare case when this would be used.

With the device in a good state now, it's time to create our surfaces. First, the primary surface should be created. Each surface that is created must be created with a surface description describing that surface. In this case, the primary surface description will include the options of PrimarySurface (naturally), Flip, and Complex. These options inform DirectDraw that not only will this be the primary surface, it will be flippable and complex. Complex surfaces have other surfaces attached, and in this case, the attached surface will be the back buffer surface. This is the reason for the back buffer count of one for this description.

With this surface description, you can now create the primary surface. From that surface, the back buffer surface can be retrieved by calling the GetAttachedSurface method on the primary surface. With the first two surfaces created, the last item that needs to be created is the actual sprite surface. The code included on the CD uses the dx5_logo.bmp that ships as part of the DirectX SDK as the sprite to render. Since the surface will be created from the file, no extra information is needed for creating this surface, thus the "empty" surface description.

SHOP TALK: TRANSPARENCY IN DIRECTDRAW

DirectDraw does not support the alpha blending features that exist in Direct3D. In the Direct3D version of this application, we used simple alpha blending techniques to ensure that the "background" of our sprite was rendered transparent. DirectDraw has no support for doing alpha blending, so transparency is rendered by using a "color key." This color key essentially turns one particular color in a sprite transparent. While this is useful and works quite well for 2D applications, it has its drawbacks, the most notable of which is that you cannot use the color specified as the color key anywhere in your image.

In this application, the image being used has a black background, so black will be used as the color key. Since the color key structure initializes to zero, which happens to be the color key for black, no further work needs to be done other than to set the color key on the sprite.

The initialization method uses a few constants that haven't been declared yet. Go ahead and add those now:

 public const int ScreenWidth = 800; public const int ScreenHeight = 600; private static readonly Rectangle SpriteSize =     new Rectangle(0,0,256,256); 

The screen width and height constants have been used before. Since the image file that will be used as the sprite is of known size, storing this size is easier than determining the size later. If you did want to calculate the size, you could simply look at the Width and Height members of the surface description structure after the surface has been created.

You will need to call the initialize method at some point, and the main method is the ideal place. Update this method much like the Direct3D applications were used:

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

Now, once again you will need a wrapper class that will be used to maintain your sprites' information. Add the following class to your application:

 public class GraphicsSprite {     // static data for our sprites     private static readonly Random rnd = new Random();     // Instance data for our sprites     private int xPosition = 0;     private int yPosition = 0;     private float xUpdate = 1.4f;     private float yUpdate = 1.4f;     /// <summary>     /// Constructor for our sprite     /// </summary>     /// <param name="posx">Initial x position</param>     /// <param name="posy">Initial y position</param>     public GraphicsSprite(int posx, int posy)     {         xPosition = posx;         yPosition = posy;         xUpdate += (float)rnd.NextDouble();         yUpdate += (float)rnd.NextDouble();     } } 

This class will store the current position of the sprite's upper-left corner. You will notice that in this case, two separate integer values are stored, rather than the Vector3 that was used in the Direct3D example. Since DirectDraw only deals with screen coordinates, the position will be integer values rather than the floats used in Direct3D. Since there is no depth in a DirectDraw application, the third member isn't needed at all.

The class will still store the velocity for each direction separately, in order to have the sprites bounce around the screen. The constructor for the class will take the initial position of the sprite, as well as randomly incrementing the velocity in each direction, so all sprites don't follow the same pattern.

Now, you should update your sprite class to handle the movement and drawing. Add the two methods found in Listing 17.2 to your sprite class.

Listing 17.2 Updating and Drawing Your Sprites
 public void Draw(Surface backBuffer, Surface spriteSurface,     Rectangle spriteSize) {     backBuffer.DrawFast(xPosition, yPosition, spriteSurface, spriteSize,         DrawFastFlags.DoNotWait | DrawFastFlags.SourceColorKey); } public void Update(Rectangle spriteSize) {     // Update the current position     xPosition += (int)xUpdate;     yPosition += (int)yUpdate;     // See if we've gone beyond the screen     if (xPosition > (Form1.ScreenWidth - spriteSize.Width))     {         xUpdate *= -1;     }     if (yPosition > (Form1.ScreenHeight - spriteSize.Height))     {         yUpdate *= -1;     }     // See if we're too high or too the left     if (xPosition < 0)     {         xUpdate *= -1;     }     if (yPosition < 0)     {         yUpdate *= -1;     } } 

The drawing call takes in the back buffer surface that you will render to (remember, you don't want to render directly to the screen), as well as the sprite's surface and its size. The DrawFast call from the back buffer simply draws the sprite at the location specified, using the full sprite as the source material (the size). The flags tell DirectDraw that it shouldn't wait if the draw cannot be completed immediately, and that the source color key should be used for transparency (which was set up when the sprite was created).

UNDERSTANDING THE DIFFERENCES BETWEEN DRAW AND DRAWFAST

When the calls to Draw and DrawFast are hardware accelerated (which is most likely the case in any modern graphics card), there is no difference between these two calls. However, if there is a case where the methods are called using software, the DrawFast method will be approximately 10% faster than the Draw method.

This speed increase does not come without a cost, though. The DrawFast method is much less robust. The Draw method allows the sprite being rendered to be stretched or shrunk. It allows many different drawing operations as well. For simple drawing operations like this one, you will always want to use DrawFast.

The update method is virtually identical to the Direct3D version. The position of the sprite is updated by the velocity, and the direction is reversed if the sprite has come to a border. With the sprite class now complete, you will need to maintain a list of sprites for your application. The same method that was used for the Direct3D example will be used here, so add the following variable to your main class:

 System.Collections.ArrayList ar = new System.Collections.ArrayList(); 

You'll still need to add some sprites to your collection now, and you can use the same code that was used before. Add the following to the end of your initialization method:

 // Add a few sprites ar.Add(new GraphicsSprite(0,0)); ar.Add(new GraphicsSprite(64,128)); ar.Add(new GraphicsSprite(128,64)); ar.Add(new GraphicsSprite(128,128)); ar.Add(new GraphicsSprite(192,128)); ar.Add(new GraphicsSprite(128,192)); ar.Add(new GraphicsSprite(256,256)); 

The only thing left is to add the rendering to our main class. This application will use the same mechanism for the rendering, so add the following override into your windows form class:

 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) {     Microsoft.DirectX.DirectXException.IgnoreExceptions();     foreach(GraphicsSprite gs in ar)         gs.Update(SpriteSize);     backBuffer.ColorFill(0);     foreach(GraphicsSprite gs in ar)         gs.Draw(backBuffer, sprite, SpriteSize);     primary.Flip(backBuffer, FlipFlags.DoNotWait);     this.Invalidate(); } 

The very first thing this method does is to turn off all Managed DirectX exception throwing. This will be explained briefly, but you should use this method with extreme caution, as it will disable all exception throwing from Managed DirectX.

Now each sprite is updated based on the stored size, much like the 3D version of this application. Next, in place of the Clear method the Direct3D application used, the back buffer will simply be filled with a solid color. In this case, black will be used.

Next, each sprite in your collection will be rendered to the back buffer. With everything now rendered into the back buffer, you are ready to update the screen. Simply call the Flip method on the primary buffer, passing in the back buffer where everything has been rendered. This will update the screen with the contents of the back buffer. Finally, invalidate the window's contents, so that the OnPaint override will be called again immediately, and there is a constant state of rendering going on.

DISABLING EXCEPTION HANDLING

As written, this application can be expected to throw two separate exceptions. The first is the WasStillDrawingException, which will happen if you attempt to flip the primary surface or draw to the back buffer while the system is busy completing a previous draw operation. In Direct3D, this scenario is simply ignored if you are using the SwapEffect.Discard flag for your device. Ignoring this exception simply emulates that behavior.

The second exception that can be expected is the InvalidRectangleException. In Direct3D, if your sprites happen to be slightly offscreen, it doesn't really matter since the device deals in world space, which is essentially infinite. In DirectDraw, this isn't the case, and trying to render a sprite when the rectangle will go out of the bounds of the screen is not allowed. You could alter the logic of the application to ensure that this scenario never happens, but for our simple example, ignoring this case is desirable.

Running the application now should show similar results to the Direct3D version of this application.



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