Visual Basic Explorer
Visual Basic Explorer
 Navigation
 Home


 Coding
 Source Code

 FAQ Center

 VB Tips

 Downloads

 ToolBox

 Tutorials

 VB Games

 VB News

 VB Award

 VB Forums



 Affiliates
 Planet Source Code

 Rent a Coder

 DirectX4VB


 Misc
 Search

 Feedback

 Advertise

 About


Need to hire
a VB coder?

Please support our sponsor:

 Home 
 Site Map 
 Forums 
 News 
 Feedback 

DirectDraw for Visual Basic®

 

Sub Case 2: Getting some stuff on the screen

Purpose

The purpose of this sub case is to get some graphics onto the screen. We will delve into the art of loading simple bitmap data files (ordinary bitmap format) and get these into the video buffers. With these loaded and ready to go we will create the primary surface and show the graphics on the screen. Along the way we will also create some simple reusable types to assist us in our efforts.

Notice

Pay special attention to the details surrounding the loading of the graphics and also the section on reloading them.

Getting into it

The project presented is the DXFile1_2 sample project.

This simple project will simply show a single image on the primary screen. It sounds simple and easy – and it is. But be warned! In a real world scenario this section of any game or multimedia is critical – meaning that if it does not succeed your application can not run. So pay close attention as we intend to show you how to do this in a reliable and easily reusable way.

Surfaces

A major part of any DirectDraw program involves the creation and usage of surfaces. Surfaces are the memory buffers where all your beautiful graphics are placed, manipulated and blitted to and from. Basically a surface is just a chunk of linear memory residing either in the video memory chips on a video card, or in the ordinary system memory. The actual layout and format of the memory can vary depending on the color format specified when a surface is created. There is a lot to be said about surfaces, but in order to avoid confusing you, we will only address some of the most basic issues involving surfaces in this article.

Conceptually you can view a surface as a canvas for your graphics. Unlike a painter who usually works on one canvas at a time, we will frequently use multiple surfaces -often with different purposes and properties- in the creation of our multimedia applications.  In this DirectX-file™ we will discuss two kinds of surfaces – the Primary surface and a simple Off-Screen surface.

The primary surface is the surface that is shown, or rather drawn, onto the monitor. In a game this would be represented by the visible screen or play area. The off-screen surface is the simplest of the surfaces and basically provides a method for storing graphics or other application data which you might want to move to the primary surface during the course of your game.

The various surface properties and descriptors of a DDraw surface are contained in a special type called DDSURFACEDESC2 described in Listing 4:

Type DDSURFACEDESC2
     ddckCKDestBlt As DDCOLORKEY
     ddckCKDestOverlay As DDCOLORKEY
     ddckCKSrcBlt As DDCOLORKEY
     ddckCKSrcOverlay As DDCOLORKEY
     ddpfPixelFormat As DDPIXELFORMAT
     ddsCaps As DDSCAPS2
     lAlphaBitDepth As Long
     lBackBufferCount As Long
     lFlags As CONST_DDSURFACEDESCFLAGS
     lHeight As Long
     lLinearSize As Long
     lMipMapCount As Long
     lPitch As Long
     lRefreshRate As Long
     lTextureStage As Long
     lWidth As Long
     lZBufferBitDepth As Long
End Type

Listing 4 shows the DDSURFACEDESC structure

For a complete description on the various members of this type you can refer to the DirectDraw documentation in the DirectX SDK. The members used in this and later DirectX-files will be explained as they are used.

To fill the above listed type with valid data for a surface we will use the GetSurfaceDesc function that is a method in the DirectDrawSurface7 class. The GetSurfaceDesc is defined as shown in Listing 5:

GetSurfaceDesc(surface As DDSURFACEDESC2)

Listing 5 shows the GetSurfaceDesc function

This function is a very important function that we will use often to get certain properties for use in our programs. Take some time to familiarize yourself with it now and take a look at the documentation in the DirectX SDK.

So now we know, albeit on a very basic level, what a surface is (we will get into more details in later files) and we also know how to get information about any given surface. Let's examine how we actually create and use these surfaces.

A surface is created using the CreateSurface method on the DirectDraw7 object. The method is declared as shown in Listing 6. The one parameter the method takes is the surface description type – DDSURFACEDESC2. In this type you specify the kind of surface you want to create (a primary surface, an off-screen plain surface etc…) and also the various properties that a surface should have (number of colors, width, height etc…).

CreateSurface( dd As DDSURFACEDESC2) As DirectDrawSurface7

Listing 6 shows the CreateSurface function

So now that we know in theory how to create a surface, we might as well explore what we are actually doing with the surfaces and how we are going to use them.

We mentioned earlier that a surface, with the exception of the primary surface, can be used as a storage place for graphical or other related visual data. This data can be manipulated and then blitted onto the primary surface for display on the screen. If you have previously programmed using the Win32 GDI system you might see some similarities between a surface and a bitmapped memory DC (device context). In this assumption you are not all that wrong although there are some very distinct differences. First and foremost DirectX is a 'lower' layer than GDI, meaning that you have some specific hardware options in your toolbox when you want to do serious pixel manipulation. One of these options is directly accessing the memory that a surface has acquired – whether this memory is normal system memory or is video memory on the video card. This is not possible with the normal GDI system. Although DirectX might already sound as a paradise for you, be warned that having these capabilities comes at a cost – namely the cost of lost convenience that GDI provided. By directly manipulating the memory of a surface you lose all the automatic color conversion functionality that takes place transparently when using the Win32 GDI system.

What has all this got to do with the simple use of surfaces that we intend to explore in this DirectX-file? Well, before we can actually begin to use the surfaces we create we need to fill them up with our intended graphical data, and this is where the above mentioned issues become relevant. In the sample project we will create a full-screen application with 16-bit colors. This means that the surface will have 16 bit data for each of the pixels in the surface. This will give you a total of 65.536 possible color values for each pixel. So what do we do with our cool 32 bit graphics that we spent hours creating with various image programs and optimized palette handlers? We just can't display the graphics as is – since each pixel takes up 32 bits and we only have 16 bits per pixel on the surface. So we now have two choices:

1) Make up a color converting algorithm that will translate the 32-bit graphic into something usable by the 16-bit surface or 2) Go back to the good old GDI system and let it handle the color conversion.

Huh? So are you saying the GDI system is available to us if we are using DirectX? The short answer is yes. The longer response is that just because we can use it does not mean that we should use it. The problem with the GDI is that it still does a lot of stuff by using the most common denominator technique – i.e. the GDI does what it 'thinks' is the best, and not necessarily what is best for your application. Using the GDI also  means a loss in performance since it is not optimized and cannot use the specific hardware options that you can use via DirectX. The conclusion is that if the GDI does what you need at an acceptable level and if performance is not an issue then by all means use it. 

 

The issue of hiding complexity at the expense of functionality is one that Visual Basic programmers should be familiar with. Like VB the GDI performs certain tasks for you and this results in more 'general' code being run than if you created the code in something like assembly or C. There is always some tradeoff between ease of use and power. You'll need to carefully analyze your projects and become familiar with all your options to find the right balance. 

One of the areas where the GDI is useful is in populating your surfaces with data. This is usually done during startup or in between levels, where extreme speed is not essential, but a secure and stable result is. The GDI system is somewhat limited since is can only work with bitmaps but if that is what you are using then it can be used effectively here. 

In sample (DXFile1_2) for this sub-case we will use the GDI system to populate our off-screen surface and return to DirectDraw for faster blitting to the primary surface.

Enough talk some code please...

This sample will display an image using the DirectX blit function. For this we need two surfaces – a primary surface and an off-screen surface for holding our graphics.

The primary surface will be 640 pixels wide, 480 pixel high and 16 bit deep (16-bit color). The off-screen surface will have the dimensions of the bitmap we want to display.

All the surfaces are created and filled with data in the Load event of our form after we have initialized the DirectX and DirectDraw systems.

The code is displayed in Listing 7:

Private Sub Form_Load()

Dim ddsd2 As DDSURFACEDESC2

AppPath = App.Path
If Right$(AppPath, 1) <> "\" Then
AppPath = AppPath & "\"
End If

''Get reference to new directx object
Set objDx = New DirectX7

''Create directdraw object
Set objDraw7 = objDx.DirectDrawCreate("")

''Set initial cooperative level
objDraw7.SetCooperativeLevel Me.hWnd, DDSCL_FULLSCREEN Or DDSCL_EXCLUSIVE

''Set initial display mode
objDraw7.SetDisplayMode 640, 480, 16, 0, DDSDM_DEFAULT

''Set primary surface
ddsdPrim.lFlags = DDSD_CAPS
ddsdPrim.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE
Set objPrimary = objDraw7.CreateSurface(ddsdPrim)

''Store the primary display properties
objPrimary.GetSurfaceDesc ddsdPrim

rPrimary.Right = ddsdPrim.lWidth
rPrimary.Bottom = ddsdPrim.lHeight

''Set the graphics for the surface
OffSurface.strFileName = AppPath & "directxfiles.bmp"
VBDXLoadOffScreenSurfaceFromType OffSurface, -1, objDraw7


''Draw it...
DrawIt

End Sub

Listing 7 shows the load event of DirectX-file sample DXFile1_2

The first thing we do is initialize the DirectX system and then set up the DirectDraw system, using code similar to that in Sample 1. We create a full-screen application with exclusive access to the primary display.

After this we initialize and create the primary surface. The primary surface is declared as a global entity for the project in the objPrimary variable. As you can see we just declare it as a standard DirectDrawSurface7 class. The description type for the primary surface is likewise declared as a global variable – ddsdPrim. We will use this description type both in the makings of our surface, but also to contain state for the surface (width and height).

The first step in creating the surface is to set the properties for the surface in the ddsdPrim type. First we set the ddsdPrim.lFlags member to the value of DDSD_CAPS. This will tell the DirectX system that we provide some information in the ddsdPrim.ddsCaps member type that should be evaluated. Failing to set this variable will give unpredictable results. The next thing we do is set the ddsdPrim.ddsCaps.lCaps member to the value of DDSCAPS_PRIMARYSURFACE. This marks the surface as a primary surface. That is actually all you need to do in order to create a primary surface. The next step is to call the CreateSurface function on the main DirectDraw object and passing the ddsdPrim type as a parameter. Remember to use the Set operator as we are creating an object.  If the CreateSurface function fails you will get a run-time error. We have purposely not included an error handler in order to keep the code simple, but you would need to do so in a real-world application.

The next thing we do on the primary surface is to store the various properties using the GetSurfaceDesc function. We do this now so we don't have to do it later when we might need the processor cycles for something else. Finally we copy the dimensions of the primary surface into a RECT structure, which we will use in the Blt function described below.

Now that we have dealt with the primary surface we need to setup the off-screen surface that will contain our graphics. As you can see from Listing 7 we do this in a special way. First and foremost our off-screen surface is declared as a VBDXSurface type. This type is found in the VBDXHelper module. It is shown in Listing 8:

Public Type VBDXSurface
          dds As DirectDrawSurface7
          strFileName As String ''File name of the bitmap source for surface
          ddsd2 As DDSURFACEDESC2
          rectDDS As RECT
End Type


Member description:
  dds: The actual DirectDrawSurface7 class
  strFileName: The file name to a bitmap that the surface will load. 
  ddsd2: The surface description type that will hold the information on the dds surface.
  rectDDS: The RECT structure that will contain the dimensions of the dds surface. 

Listing 8 shows the VBDXSurface type

We created this structure in order to simplify the handling of surfaces, at the same time keeping the functions pretty basic. With the VBDXSurface structure comes a few helper functions, also declared in the VBDXHelper module. The one we will use here is the VBDXLoadOffScreenSurfaceFromType function, which basically initializes and creates an off-screen plain surface. The function is described in Listing 9:

Public Function VBDXLoadOffScreenSurfaceFromType(ByRef SurfaceType As VBDXSurface, lgSourceColorKey As Long, ByRef objDraw As DirectDraw7)
On Error GoTo ErrHandler

  If lgSourceColorKey <> -1 Then
  SurfaceType.ddsd2.lFlags = DDSD_CAPS Or DDSD_CKSRCBLT

  ''Set source color key
    SurfaceType.ddsd2.ddckCKSrcBlt.high = lgSourceColorKey
    SurfaceType.ddsd2.ddckCKSrcBlt.low = lgSourceColorKey
  Else

    SurfaceType.ddsd2.lFlags = DDSD_CAPS
  End If

  SurfaceType.ddsd2.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN

  Set SurfaceType.dds = objDraw.CreateSurfaceFromFile(SurfaceType.strFileName,              SurfaceType.ddsd2)

  ''Get the size of the surface and load the rect structure
  SurfaceType.dds.GetSurfaceDesc SurfaceType.ddsd2

  SurfaceType.rectDDS.Bottom = SurfaceType.ddsd2.lHeight
  SurfaceType.rectDDS.Right = SurfaceType.ddsd2.lWidth

  ErrHandler:

  Select Case Err.Number

    Case 0 ''No errors - just clean up

    Case Else ''Clean up and throw (raise) err

    Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpContext, Err.HelpContext

  End Select
End Function

Listing 9a shows the VBDXLoadOffScreenSurfaceFromType function

 

Public Function VBDXLoadOffScreenSurfaceFromType(ByRef SurfaceType As VBDXSurface, 
lgSourceColorKey As Long, ByRef objDraw As DirectDraw7)

Member description:
  SurfaceType: The VBDXSurface that should be initialized and created.
  lgSourceColorKey: The source color key for the surface
  objDraw: The DirectDraw object that the surface should be created from. 

Listing 9b shows the declaration of the VBDXLoadOffScreenSurfaceFromType function

The actual calling of this function is shown in Listing 10:

''Set the graphics for the surface
  OffSurface.strFileName = AppPath & "directxfiles.bmp"
  VBDXLoadOffScreenSurfaceFromType OffSurface, -1, objDraw7

Listing 10 shows the implementation of the VBDXLoadOffScreenSurfaceFromType function

The first section of the function code deals with color keying. This subject is not relevant for us at the moment (we will cover this in the next DirectX-file). Basically we check whether the lgSourceColorKey parameter value is  –1. If it is it we will set the values for the color key. If it is not, as is the case with our sample, it just sets the ddsd.lFlags member of the DDSDSURFACEDESC2 structure in the VBDXSurface type. As with the creation of the primary surface this indicates that we want the DirectDraw system to check out the ddsdCaps type member for additional information when the surface is created. Next we set the ddsdCaps.lCaps member to the value of the constant DDSCAPS_OFFSCREENPLAIN, which basically indicates that we just want a plain vanilla off-screen surface. To create the actual surface we use the CreateSurfaceFromFile member method of the DirectDraw7 object. This function will create the surface, load the graphics and fill the surface with the loaded graphics. Very convenient. The function is shown in Listing 11:

DirectDraw7.CreateSurfaceFromFile( _ 
file As String, _ 
dd As DDSURFACEDESC2) As DirectDrawSurface7

Listing 11 show the declaration of the CreateSurfaceFromFile function

Finally, we use the function to get the dimensions of the surface, using the GetSurfaceDesc function and store these values in the rectDDS member of the VBDXSurface type. The reasoning behind this will be revealed shortly.

Now we have created a primary surface, an off-screen plain surface and gotten the loading of the graphics squared away. We are now ready to do some blitting -drum roll please!

The last code block in the Form load event is a call to the drawing function, which will transport the pixel data from the off-screen plain surface onto the primary display surface. The function is shown in Listing 12:

Private Sub DrawIt()
Dim rt As Long

    rt = objPrimary.Blt(rPrimary, OffSurface.dds, OffSurface.rectDDS, DDBLT_WAIT)

    If rt = DDERR_SURFACELOST Then
      ''The surface is lost we need to reload the graphics
      objDraw7.RestoreAllSurfaces
      VBDXLoadSurface OffSurface.strFileName, OffSurface.dds

       rt = objPrimary.Blt(rPrimary, OffSurface.dds, OffSurface.rectDDS, DDBLT_WAIT)
    End If

End Sub

Listing 12 shows the DrawIt function

The code block that does the actual pixel transfer (or Blt) is the objPrimary.Blt method. The Blt method is described in Listing 13:

DirectDrawSurface7.Blt( _ 
  destRect As RECT, _ 
  ddS As DirectDrawSurface7, _
  srcRect As RECT, _ 
  flags As CONST_DDBLTFLAGS) As Long


Parameters:
  Object: A valid DirectDrawSurface7 object. 
  destRect: A RECT type specifying the destination area on which to blit
  srcRect: A RECT type specifying the source rect. 
  Flags: Flags for various blitting functionalities.

Listing 13 shows the declaration of the Blt function

Two things are worth pointing out here. The two RECT parameters, each specifying a rectangle on the source surface and the destination surface, can be specified as 'Empty' meaning that no members of the type have been set. The flags parameter is used to specify certain specialized options for the Blt. These options specify how the Blt is to be performed and can be used to create some special effects.

In the current sample we call the method on the primary surface, meaning that this surface is the destination surface. We specify the size of the destination surface in the RECT type that we initially filled with the dimensions of the primary surface. The source surface is specified as the second parameter. The third parameter is the source RECT, which we specify as the RECT that our VBDXSurface type contains. 

You'll remember that earlier we used the GetSurfaceDesc function and stored the surface dimension values in the rectDDS member of the VBDXSurface type. If we hadn't done it this way we would have to get the dimensions now and this could be a problem in a real-world application. The problem is the consumption of processor cycles for something that is basically unnecessary at this point of execution. By pre-filling the type we use a bit more memory (nothing to worry about), but we save the time it takes to build the members of the type. 

The last parameter, the flags parameter, is set to the constant value of DDBLT_WAIT. This causes the function to delay its return until it has completed execution – i.e. until the blitter has finished its job. If we did not specify this flag the function would have returned immediately. This is a very powerful feature, since it basically means that we could continue with our work while the blitter does its job. This is possible because the blitter itself is a processor located on the video card. In effect we achieve the effect of multi-tasking. A properly designed DirectDraw application can gain many CPU cycles by employing a scheme that uses this 'multi-tasking' environment. In later DirectX-files we will look more into this feature. The reason for specifying the DDBLT_WAIT flag in this application is that we have nothing to gain from letting the graphics processor run off on its own. In future DirectX-file™ we will examine other possible values that let us do some cool things.

As you can see from the code listing (NR 12) we catch and examine the return value. There is a very good reason for doing this, namely the fact that we can not be sure that the Blt is able to perform its stuff as we intend. One of the most common failures that the blitter encounters is the DDERR_SURFACELOST error. This error basically tells us that something has happened to our surfaces, something so terrible that the blitter has given up. What has actually happened is that our surfaces have lost their allocated memory and thus are just empty classes with no real purpose anymore. This usually happens when our application loses focus and some other program takes over the video memory. Remember that Windows is still a multi-processor environment where other programs can and should be able to do stuff even though we had -or thought we had- full control of the screen. The remedy for this error is quite simple – we call the RestoreAllSurfaces method on the main DirectDraw object and in a flash we have acquired the surface memory again. 

The story does not end here. Even though the memory is recaptured we don't have any old data left on the surfaces – meaning that all the graphics we have carefully loaded are lost. So we have to load them all again. No problem you might say, we can simply use the CreateSurfaceFromFile function that we initially used when we loaded and created the surface. Unfortunately we can't do that. We still have an empty surface sitting in memory and we have a fully functional surface that is also using some amount of memory. If we were to create a new surface using the above mentioned function we would actually get a completely new surface and not just a 'refill' of the old one. How the DirectDraw function handles our surface internally is unknown to us and thus we will minimize the possibility of creating problems for our application by making our own function for refilling a surface. This is what the VBDXLoadSurface function does. It is declared in the VBDXHelper module. We won't go deeper into this function here, since it is mostly GDI based, but we will return to it in a later DirectX-file™ (You may have noticed we have a few things we will follow up on in later files. This was done to try to help you focus on the fundamentals of what is admittedly a tough subject. Following every topic into extreme detail would probably lose many of those reading this).

After reloading the surface we simply Blt it to the primary surface one more time. This time we are so certain of ourselves that no error checking is done. Of course you should not do the same in your applications (the reason that we are ignoring error handling as much as we do, is simply because implementing a proper error handling routine would take up half the code in these samples, and move the focus away from what we are trying to demonstrate).

To see this reloading of the surface in action you could try and run the sample program and then switch it out (by pressing ALT-TAB). Then you should be returned to the normal Windows Desktop with your standard resolution. By setting focus on the DirectX sample again you will see a change in the resolution (provided that you don't run your windows in the same resolution as we use in the sample) and after some nanoseconds the graphics will be blitted onto the surface again – a reload of the surfaces has occurred.

Afterthoughts

In this DirectX-file we have taken the first look at surfaces and how we can use them to store and display graphics. We have covered the basics of the surface class and the very basics on how we can use them. In later DirectX-file™ we will dive deep into the DirectDraw7 surface class and examine the functionality it provides for us.

Conclusion of DirectX-file 1

In this file we have taken our first step into the world of DirectDraw and examined how to set up a DirectDraw program and the very basics of showing graphics. You are encouraged to experiment with the sample and make changes.

In the next DirectX-file (number 2) we will examine basic animation, transparent blitting and something called the clipper (watch your hands :-). We will also be looking at color keys and how we can use them for some special effects. 

See the file Reference for the DirectX-files  or the DirectX SDK for a description of this and other functions used. 

[DirectDraw 1-1] [DirectDraw 1-2]


Download Sample 1-2

VBDXHelper Module


DirectX-file™ : Case I: DirectDraw





Home | About | What's New | Source Code | FAQ | Tips & Tricks | Downloads | ToolBox | Tutorials | Game Programming | VB Award | Search | VB Forums | Feedback | VBNews | Copyright & Disclaimer | Advertise | Privacy Policy |

Quick searches: Site Search | Advanced Site Search 

Copyright 2002 by Exhedra Solutions, Inc.
By using this site you agree to its terms and conditions
VB Explorer and VBExplorer.com are trademarks of Exhedra Solutions, Inc.