The Directdraw Code Bible, by Sastraxi

 

Table of Contents

            1 – Trials of a Programmer (2)

          2 – Introduction to DirectX (3)

            3 – Getting started in a Visual Basic Environment (3)

                        3.1 – Referencing DirectX (3)

                        3.2 – Retrieving a DirectX Object (3)

                        3.3 – Setting up the essentials (4)

                             3.31 – For Full-Screen Exclusive Mode (5)

                             3.32 – For Windowed Mode (7)

          4 – A Change of Style (9)

                   4.1 – Try Loops (9)

                   4.2 -

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Trials of a Programmer

 

Getting started with DirectX is now easy, because instead of using Patrice Scribe’s wonderful type libraries, Microsoft has actually given us access to the real thing – DirectX 7. This opens up a whole new window for Visual Basic programmers – and has allowed us to do things never before possible. I wandered onto the scene in 1999, when DirectX 7 first came out. (I think that’s when it came out, at least…) I was excited as ever, seeing that my hobby language had finally gained a valuable piece that had been missing ever since the debug draft of DirectX 1.

 

Then I tried to learn it… At that time there wasn’t much resource on how to learn DirectX for Visual Basic; bar my number one learning source at the time, the MSDN library at Microsoft. I translated some of the code myself, found out how it worked, etc. But I still wasn’t able to get past the BLTFAST function or anything like that – what exactly did Directdraw mean? It wasn’t until I learned about a web-site called DX7VB. It explained a lot and helped me, and I was able to get things working for once. But then something was wrong; I had tried doing the alpha-blend functions and it didn’t work. I tried on another computer, and it still didn’t work.

 

I was getting frustrated. I decided Directdraw programming wasn’t for me, and I stopped trying. It wasn’t until late 2000 that I decided to join the scene again. With the arrival of new community sites, such as places like the Visual Basic Forums and Lucky’s VB Gaming Community, I was able to get started again. I asked questions, and got answers. Everything has been going fine since – especially with the addition of an alpha-blending library by David Goodlad – and some especially nice tutorials from Jack Hoxley’s DirectX 4 VB.

 

I found out the reason that the alpha blending didn’t work was because it wasn’t supported by almost any of the graphics cards; you would have to do it by yourself, by directly modifying the source bits. This worked, but was pretty slow. (About 10-15 FPS on a full-screen basis, on a 320x240 screen) But the possibilities were now endless… Anything could be done (if it was small) by modifying the surface’s bits.

 

After finding that this was true of most of Directdraw’s BLTFX functions, some developers switched over to Direct3D, finding that it could do all of this in real-time, and fast too. Then DirectX8 came….

 

Total Chaos is the only way to describe the horror that proceeded. Microsoft had destroyed Directdraw in DirectX8. I was crushed, thinking that DirectX8 would bring more support and features for Directdraw, including alpha-blending. I stayed with DirectX7, and this is why I am writing this tutorial. This is not a cry for help; this is to start you on your journey…

 

Next will come a short history of DirectX, and then we’ll get on to some code.

 

Introduction to DirectX

 

Well, you’ve pretty much heard it, although I must admit, I did leave a few things out. DirectX was originally made in 1995 but I’m not too sure on that. With it came several primitive drawing calls and some sound functions. With the creation of DirectX2 came more 2D support, and some primitive 3D.

 

          DirectX pretty much just became more and more complicated and documented up to DirectX 7. I believe there is some legacy Directdraw 4 support, as there are numerous structures such as DirectDrawSurface4, etc.

 

          DirectX8 came, and destroyed Directdraw. However, DirectX Graphics is more easy and powerful than it’s ever been. (I’ve already created an Enumeration Class that works for it, that’s how easy it is!) Also, DirectMusic and DirectSound have merged into DirectX Audio. That’s how much DirectX has changed, so we’ll have to live with it for now.

 

Getting started in a Visual Basic Environment

 

You know, if you didn’t want to hear that stuff you could’ve just skipped down here, but you may be missing some information, if you have skipped I suggest you continue unless something confuses you… here’s my shot at a tutorial…

 

Referencing DirectX

 

            Open Visual Basic, and start a new project. Go to Project -> References, and find the DirectX7 for Visual Basic reference. Check the box and close the window. You’ve referenced DirectX and you’re ready to start some code. Double-click on the form to go to your code window now.

 

Retrieving a DirectX Object

 

            If you haven’t worked with DLLs or Class-Modules before, this part could be a pain. However, if you’re comfortable with this style of programming you’ll be right at home with DirectX. Go to the declaration section, and add an Option Explicit. This is very useful. If you add an Option Explicit, instead of misspelled variables equalling zero (which returns unexpected results) they will cause an error, and your program will terminate.

         

          Anyway, add this code:

 

Option Explicit

 

Dim DX As New DirectX7

Dim DD As DirectDraw7

 

Dim Primary As DirectDrawSurface7

Dim BackBuffer As DirectDrawSurface7

 

Dim dPrime As DDSURFACEDESC2

 

          You most likely are wondering what all this means. You may know most of it if you are a veteran in graphical programming, but if you aren’t I’ll give a short explanation. DX and DD are references to DirectX 7 and Directdraw themselves. Notice the use of the New keyword – that must be present.

         

          Moving on, Primary is basically the screen (or the object that you are drawing to in Windowed mode, such as a picturebox) and dPrime is it’s ‘descriptor’. dPrime stores all of the information that is needed to create Primary. Think of it as a human generator. dPrime has all of the statistics – hair colour, height, gender, weight, etc. Then you create a human from those statistics.

 

          The ‘BackBuffer’ is similar to Primary. You don’t directly draw things on the screen, that is a common error in graphics programming. If you did that, there would be flickering and tearing showing on the screen; not a good idea. Instead, you draw to the BackBuffer and the screen can instantly be replaced by it, even in sync with your computer’s monitor. This reduces any chance of visual flickering or tearing. You don’t need a descriptor for your backbuffer.

 

          Now we’ll move on to something more than declarations. Here is some code that does ‘stuff’:

 

Private Sub Form_Load()

     Set DD = DX.DirectDrawCreate(“”)

End Sub

 

That is basically the ‘master’ line; without doing that (or anything in this section, really) your program will not run. This creates a Directdraw object from the DirectX object. The variable in this method has been given empty quotes. This is the GUID that you want Directdraw to use, you usually won’t tamper with that.

 

Setting up the essentials

 

Now we’re at a crossroads; windowed mode and full-screen exclusive mode break apart from here. First we’ll cover Full-Screen exclusive mode, which is used mostly for fast-paced action games and GUI applications.

 

Windowed mode is more suited towards graphics applications (such as MS Paint and Adobe PhotoShop) and small, not really graphic-intensive games, such as Solitaire and Minesweeper. (I know for a fact that neither used Directdraw though)

 

 

 

 

 

For Full-Screen Exclusive Mode

 

Full-Screen Directdraw apps are much faster than windowed, natively use a backbuffer, and don’t require clippers. Unfortunately, full-screen apps are very hard to debug and usually there is little you can do to get out of your programs if there is an error.

 

To set up Full-Screen mode, change some settings on your form. Set the back colour to black (or white, it’s your pick) and the border-style to none. Also, set the form to be maximised, and the scale-mode to 3 – vbPixels. Also, we’re going to try to set a font up later on, set the font on the form to your liking. (Note: keep it small. Directdraw full-screen applications are usually 640x480 or 800x600, sometimes 1024x768. Size your font accordingly. I suggest 10-14 size.) It’s also useful to set KeyPreview to true if you aren’t using DirectInput for your controls.

 

Now go back into the code menu. Add this hunk of code to add functionality for a full-screen exclusive mode application:

 

Private Sub Form_Load()

     Set DD = DX.DirectDrawCreate(“”)

     Call DD.SetCooperativeLevel(Me.hWnd, DDSCL_FULLSCREEN Or DDSCL_ALLOWREBOOT Or DDSCL_EXCLUSIVE)

Call DD.SetDisplayMode(640, 480, 32, 0, DDSDM_DEFAULT)

     Me.Show

End Sub

 

     That’s a sizeable lot of code, compared to what I’ve given you so far. You’ve seen the first line before, I’ll skip it this time around. The next two lines are new, here’s what they mean.

         

          The first one explains why it’s called Full-Screen Exclusive Mode, you can see the two constants in the method call. This tells DirectX to give all the control of the screen to you, by default everything is drawn below it, showing only your application. This removes sight of anything but your form. The “allow reboot” constant says that you will allow the user to use Control+Alt+Delete to get out of a jam.

         

           This next line is self-explanatory, it sets the user’s display mode. You needn’t worry about the zero there, you’re interested in the first three lines. This particular line says to run the program in 640x480 mode, at a bit-depth of 32-bit colour. The constant (DDSDM_DEFAULT) tells Directdraw that you don’t want to play in “Standard VGA Mode”. This VGA mode is when the screen is 320x200 with 8-bit colour. You may ask why you don’t just put it 320, 200 and 8 in there, well it’s because this gives a phenomenal speed boost.

 

          The Me.Show is called after the calls, so the form has time to resize itself. After this, we need to set up the Primary and BackBuffer ‘surfaces’ as they are called in Directdraw. Most things are done with these surfaces, and information is mostly gathered from the descriptors. This next portion of code should be placed in the form’s load procedure, right after everything else.

 

dPrime.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT

dPrime.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX

    dPrime.lBackBufferCount = 1

Set Primary = DD.CreateSurface(dPrime)

 


          Okay, this is pretty long, too. So here we go… First we set the Screen’s descriptor’s flags. DDSD_CAPS means to pay attention to the ddsCaps.lCaps property. DDSD_BACKBUFFERCOUNT means that we should also prepare the surface for a backbuffer.

 

Sidebar – You may wonder why we’re using OR instead of AND. It would make sense to use AND because it means this as well as that, right? Wrong. If you know Boolean Logic, you’ll know what I mean, but if not, here’s a quick explanation. We don’t use AND because it compares what bits are both on, and if they are both on it adds them to the end result. If they are both of or one is on, it is set to 0 in the end result. OR checks if one is on, and if one of them is set to on, or 1, then the output is one. If we had 2 and 16, and we used AND, we would get 0. This is because no two bits are the same here: 01000 AND 00001 = 00000. Using OR, we would get 18. This is because 01000 OR 00001 = 01001, or 18.

 

     Then we set the ddsCaps.lCaps property. We set it to be the Primary Surface (or the Screen), that it’s a complex surface, and that it can be flipped. Flipping is the process of switching the pointers (or memory addresses) of the backbuffer and primary surface. This lets you display your backbuffer instantly. After this, the primary surface actually becomes the backbuffer, and vice versa, so you don’t need to change which surface you BLIT to each time.

 

          The lBackBufferCount property is set to 1, meaning that there will be 1 backbuffer. In special cases you may need more than one backbuffer, maybe even as many as 3 backbuffers. This is known as triple or quadruple buffering, while having one backbuffer is known as double buffering.

 

          After this, we tell Directdraw to create a surface (Primary) from the descriptor, dPrime. This is a very important function, as we will use it to create many a blank surface.

 

Now, let’s set up the backbuffer. For this, we’ll use a bit of a different approach, but the style will remain the same. Let’s get to it.

 

Dim Caps As DDSCAPS2

Caps.lCaps = DDSCAPS_BACKBUFFER

Set BackBuffer = Primary.GetAttachedSurface(Caps)

 


You can see that we didn’t need a backbuffer, instead we just used a DDSCAPS2 structure to do the work for us. In a surface descriptor, the ddsCaps property is a DDSCAPS2 structure. This means that we could’ve easily just passed that value from a DDSURFACEDESC2 structure to the GetAttachedSurface call.

 

But what does that call mean, exactly? It’s a method that creates a surface from any surfaces ‘attached’ to the surface that the call originated from (in this case, Primary). The surface that was attached was our backbuffer, and we get that from it, because we ask for it (see the DDSCAPS_BACKBUFFER in the Caps we sent to the method?). The Backbuffer is now stored in the surface.

 

Now, we’re done setting up all of the surfaces. That concludes this part of the book. In the next part, we’ll look at setting up a windowed-mode application in Directdraw.

 

For Windowed Mode

 

            Windowed mode is used most for things such as graphics applications, windowed-modes of games, and low-action games. Also, windowed mode can be used for fast pixel manipulation.

 

          You don’t natively use a backbuffer for windowed mode, but you can. Backbuffers are most important for any type of game. Since paint programs simply modify what’s already on the screen, you don’t have to use a backbuffer.

 

           But there is a catch, there are times when you will need a backbuffer for these graphics editing tools. This is for vector graphics and object manipulation. Paint wouldn’t use a backbuffer, something like Adobe PhotoShop would.

 

          Let’s set up the form now. You can have your Directdraw window anywhere there is an hDC – you may want to get the desktop’s hDC and try to use that, see where you get =). For our purposes let’s set up the form’s scale-mode to pixels, set a font that we would like to have, and resize the form to your liking.

 

          Moving on, lets add these lines to the declarations:

Option Explicit

 

Dim DX As New DirectX7

Dim DD As DirectDraw7

 

Dim Primary As DirectDrawSurface7

 

Dim Caps As DDSCAPS2

Dim ddC As DirectDrawClipper

 

Dim dPrime As DDSURFACEDESC2

          A ‘clipper’ is used to make sure Directdraw doesn’t escape its boundaries. It clips what Directdraw puts on the screen to where the hDC lies, making sure that it doesn’t overwrite any other windows and such. You can also use clippers in full-screen mode, but I’ll have more on that later in the book.

 

          Okay, to add Windowed-Mode functionality to your application, add these lines of code, which will be explained in a minute:

 

Private Sub Form_Load()

     Set DD = DX.DirectDrawCreate(“”)

     If DX.SystemBpp < 24 Then

        Set DD = Nothing

        End

     End If

     Call DD.SetCooperativeLevel(Me.hWnd, DDSCL_NORMAL)

     Me.Show

End Sub

 


          We’ve added something new here – a check of DX.SystemBpp. This is something that checks the bit depth of the computer, so you can cease execution whilst it is not your desired bit-depth. For example, if you wanted 24 or 32 bit-colour, you would code the procedure as above. If you only wanted 8-bit, and nothing else, you would check if it wasn’t 8, etc.

 

          We used the DDSCL_NORMAL operator in the Co-operative Level declaration… Why you might ask? Because this sets who gets to control the display. If it was exclusive and fullscreen like before, no other applications would be able to draw. Instead, we simply set it so that other apps can draw as well. NORMAL means that it is at the same co-operative level as everything else.

 

Add this code, right after Me.Show in the above Form_Load module:

 

    dPrime.lFlags = DDSD_CAPS

    dPrime.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE

    Set Primary = DD.CreateSurface(dPrime)

       

    Set ddC = DD.CreateClipper(0)

    ddC.SetHWnd Me.hDC 'alternatively you can select a picbox's DC

    Primary.SetClipper ddC

    Caps.lCaps = DDSCAPS_BACKBUFFER

 

 

Now, I’ll give you a quick review. We’ve seen the Primary surface code before, and it’s nothing really new. The only thing different is that we’ve changed the dPrime.ddsCaps.lCaps line. We only have one value here – DDSCAPS_PRIMARYSURFACE. We’ve left out _FLIP and _COMPLEX because these are if we have a backbuffer – traditionally windowed games do not. If you’re adventurous you can try to add this functionality yourself – it involves an off-screen plain! We’ve also taken out the BackBufferCount line – for obvious reasons as well.

 

A Change of Style

 

          We’ve so far covered the basics, which isn’t very exciting. Programming anything takes a lot of practice, trial-and-error, determination, and patience. Patience is the key here, and it hasn’t changed much with DirectX programming.

 

          Yet, this API does require a bit of a change if you’re used to using PaintPicture, BitBlt, or any other “primitive” graphics method. So, here’s a bit of theory and code on what you can change to better your chances of succeeding in this new API…

 

1 – Try Loops

 

          Normal Visual Basic code is Event-Driven. Say goodbye to that method of programming for DirectDraw. You must make the events, and find a way to query them to see if they are flagged. It’s programming, and you guessed it; You’ll be using some Boolean values. Another way to do this is with querying functions and gathering the data at the same time.

 

          Let’s explore this at the moment. Look at this code:

 

Option Explicit


Dim bMoved As Boolean

Dim Damage As Long

Dim X As Long

Dim Y As Long

 

 

 

 

bMoved = GetMoved()

Damage = GetDamage()

GetPos X, Y


If bMoved Then

     If Damage < 0 Then

           ShowTEXT “Game Over”, 1, 1

     End If

End If

 

This shows how we poll at the beginning and then check when we need to. In this particular example, we’re looking at if the player has moved since the last loop interval, and we also look at its damage and position (X, Y).

 

Some other things to be careful for in loops are SPEED and CONTROL. What I mean by control is if you have an Exclusive Mode application, you’ll have control of the whole computer. Windows will not be able to do anything, this includes graphical capabilities… It will only be able to go through your code! To give Windows time to “do its dirty work”, add a “DoEvents” command inside the loop, preferably at the end. This will let it process and display the results of your code.

 

The speed is a bit different. All computers are different speeds, so your program must accommodate for it. If the loops goes as fast as it can, that’s all good, but if you have a great fast computer, and you check & update everything as fast as that, the game will be too fast! The point being that on each different computer, the game will be faster or slower – Not framerate wise, but speed-wise. To accommodate for this, we’ll check the time, by adding the GetTickCount API in.

 

Private Declare Function GetTickCount Lib "kernel32" Alias "GetTickCount" () As Long

 

That’s it right there. It’s in the PRIVATE declaration, so change it depending on what suits your application. Now back to the theory… You’ll have to decide how fast you want things to update. Say, you’ll be checking the controls 50 times every second, which gives 20ms for the computer to respond. That’s good – an ideal rate. Anyways, to make sure this happens in code, do this:

 

Dim LastTime As Long

 

LastTime = GetTickCount()

Do

     If GetTickCount >= LastTime + 20 Then

           … updating code …

     End If

     … displaying and rendering code …

     DoEvents

Loop

 

We render every loop, simply because a larger framerate means the game will look better in general. Note that all of this does not particularly suit to games – many applications that employ graphics can take advantage of this.