|
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:
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:
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…).
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.
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:
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:
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:
Listing 9a shows the VBDXLoadOffScreenSurfaceFromType function
Listing 9b shows the declaration of the VBDXLoadOffScreenSurfaceFromType function The actual calling of this function is shown in Listing 10:
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:
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:
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:
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] DirectX-file™ : Case I: DirectDraw
|
Quick searches: Site Search | Advanced Site Search |
|
By using this site you agree to its terms and conditions VB Explorer and VBExplorer.com are trademarks of Exhedra Solutions, Inc. |