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 -
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.
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.
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
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.