DirectX:DirectDraw:Tutorials:VB:DX7:Loading and Displaying Bitmaps

From GPWiki
Jump to: navigation, search

The first thing you need to do to create a program using DirectX is to include the DirectX type library in your project's references. Start a new project, and under the "Projects" menu select "References". When you're at the References dialog box, scroll down and check the box that says "DirectX 7 for Visual Basic Type Library". Now you will have access to DirectX functions, subroutines, types, and constants.

Now, in order to be able to load and display a bitmap we must first initialize the DirectX variables and screen modes.

Dim dx As New DirectX7 Dim dd As DirectDraw7

   Set dd = dx.DirectDrawCreate("")
   Call dd.SetCooperativeLevel(Me.hWnd, DDSCL_FULLSCREEN Or DDSCL_EXCLUSIVE Or DDSCL_ALLOWREBOOT)
   Call dd.SetDisplayMode(640, 480, 16, 0, DDSDM_DEFAULT)

The variable dx is an instance of the DirectX7 variable itself. From this object we invoke the DirectDrawCreate method and we pass it an empty string to indicate that we wish to use the active display driver. The value returned from this method is a DirectDraw7 object which we will store in the variable dd. Using the variable dd we can set the Cooperative Level we wish to use. The Cooperative Level will dictate how we access the screen and how other programs running at the same time will be allowed to access the screen as well. The first argument we pass is a handle to the window we wish to use as our main display window, and if you place this command within that form's code we can simply pass Me.hWnd to return the handle to the current window. The second argument we pass to the SetCooperativeLevel method is a flag indicating how the window should act. DDSCL_FULLSCREEN means that we want to use fullscreen mode (we could instead use DDSCL_NORMAL if we wish to create a windowed program). DDSCL_EXCLUSIVE means that we want to be the only one with access to the screen, and finally DDSCL_ALLOWREBOOT shows the we will allow soft reboots using the "CTRL-ALT-DEL" key combination.

Next we have to define the primary surface and attach a flippable back buffer to it.

Dim Primary As DirectDrawSurface7 'Primary surface Dim BackBuffer As DirectDrawSurface7 'Backbuffer surface Dim ddsdPrimary As DDSURFACEDESC2 'Primary surface description Dim caps As DDSCAPS2 'Capabilities description

   ddsdPrimary.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT
   ddsdPrimary.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX
   ddsdPrimary.lBackBufferCount = 1
   Set Primary = dd.CreateSurface(ddsdPrimary)
   caps.lCaps = DDSCAPS_BACKBUFFER
   Set BackBuffer = Primary.GetAttachedSurface(caps)

First the variables. Primary and BackBuffer are simply surfaces when we declare them. They'll become the the primary display surface and the back buffer later. ddsdPrimary is a DirectDraw surface description, with this we specify how we'd like the surface to behave. Finally, caps is a DirectDraw capabilities description used to attach the back buffer to the primary surface.

Now we have to set up the surface description of the primary surface, ddsdPrimary. The ddsdPrimary.lFlags setting tells the dd.CreateSurface method which of the ddsdPrimary values should be considered when creating the surface. Passing DDSD_CAPS causes the ddsdPrimary.ddsCaps.lCaps to be taken into account and DDSD_BACKBUFFERCOUNT causes the ddsdPrimary.lBackBufferCount to be taken into account. Next, we set ddsdPrimary.ddsCaps.lCaps to DDSCAPS_PRIMARYSURFACE to define it as our primary surface, DDSCAPS_FLIP indicates that this surface is part of a flipping chain, and DDSCAPS_COMPLEX allows other surfaces (ie. the Back Buffer) to be attached to the primary surface. Finally, ddsdPrimary.lBackBufferCount is set to 1 meaning that we will have only 1 back buffer attached to our primary surface.

Now that we have described how we'd like the primary surface to be created we can go ahead and create it. To do this we Set the Primary surface equal to the return value of dd.CreateSurface(ddsdPrimary). This method returns a surface based on the description that we pass it as an argument. Since we've passed the description ddsdPrimary we will get a surface just the way we described it above.

Once we have the primary surface set up, we have to attach a back buffer to it. caps.lCaps describes the type of capabilities we would like to get from the primary surface. We pass caps to the Primary.GetAttachedSurface method and as output we receive a surface that will be able to be used as the backbuffer.

So, now we have our primary and back buffer surfaces set up. If we want to display a bitmap using these surfaces we will have to load the bitmap into ANOTHER surface and then copy (Blit) it to the back buffer. Here's how we do that:

Dim Sprite as DirectDrawSurface7 Dim ddsdNewSprite As DDSURFACEDESC2

   ddsdNewSprite.lFlags = DDSD_CAPS Or DDSD_WIDTH Or DDSD_HEIGHT
   ddsdNewSprite.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
   ddsdNewSprite.lWidth = 60
   ddsdNewSprite.lHeight = 60
   Set Sprite = dd.CreateSurfaceFromFile(App.Path & "\Ships\Vegan00.BMP", ddsdNewSprite)

Sprite will be the surface onto which we will load our bitmap. Once again we declare a DirectDraw surface description, this time we call it ddsdNewSprite. We have to fill out the flags again, and this time we use a few different options. DDSD_CAPS is used again in addition to DDSD_WIDTH and DDSD_HEIGHT. The last two indicate that the width and height variables within the description are to be considered when creating the surface. Now, ddsdNewSprite.ddsCaps.lCaps will be set to DDSCAPS_OFFSCREENPLAIN so that this surface will be stored as an off-screen (non-displayed) surface. ddsdNewSprite.lWidth and .lHeight will be set to the number of pixels of width and height our bitmap has.

Having set up the description object we can now create the surface. If the bitmap we want to use is a file we use the dd.CreateSurfaceFromFile method. If the bitmap is in a resource file we could alternatively use dd.CreateSurfaceFromResource. In this case we'll simply use a file, contained in the Ships directory, and called Vegan00.BMP. This Bitmap is 60x60 pixels (as indicated in the surface description object). To the dd.CreateSurfaceFromFile method we pass a string containing the path to the file and the description of the surface. We then Set the value returned to the Sprite surface. We now have our bitmap stored in the surface called Sprite.

Dim SrcRect As RECT Dim DestRect As RECT

   With SrcRect
       .Bottom = 60
       .Left = 0
       .Right = 60
       .Top = 0
   End With
   With DestRect
       .Bottom = 60
       .Left = 0
       .Right = 60
       .Top = 0
   End With
   BackBuffer.Blt DestRect, Sprite, SrcRect, DDBLT_WAIT

Next, we have to take this Sprite surface we've created and load it (Blt it) onto the back buffer. In order to do that we have to first define the size of the rectangles we want to blt from and blt to. The SrcRect will be equal in size to the source bitmap since we want to display the whole bitmap, so we describe it as 60x60. The DestRect also should be the same size as the source bitmap since in this case we want to display it at 1:1 (changing the destination rectangle to something other than this would result in a ZOOM factor, which I will describe in another tutorial). The BackBuffer.Blt method can now be invoked to place the contents of the Sprite surface onto the back buffer. We also pass the DestRect and SrcRect rectangles in order to tell the function where we want them placed. Additionally, we pass a flag, in this case DDBLT_WAIT, to ensure that the Blt waits until the back buffer is free before attempting to blit, thus avoiding possible errors.

Ok, we're almost there! Stick with it!

   Primary.Flip Nothing, DDFLIP_WAIT

This is all we have left to do. Primary.Flip causes the next surface in the flipping chain (in our case this is the BackBuffer surface) to become the primary surface, and the primary surface to become the backbuffer. We pass Nothing as the first argument since we want the flipping chain to go in order, we could alternatively indicate which surface we want to flip to (ie. By passing BackBuffer) if we have more than one possibility. DDFLIP_WAIT is a flag indicating that we want to ensure that any previous flips are complete before attempting another, thus avoiding possible errors.

Done! The bitmap should now be displayed in the top left corner of the screen! Click here to download sample source code demonstrating these principles.