Using DirectDraw with Delphi

This shows how to start using the DirectX library in Delphi programs and then demonstrates a simple application using the freeware library DelphiX.

If you have ever tried doing fast screen output under Windows, you'll know it's no easy task. With all the tricks possible it still falls lamentably far short of commercial game speeds. But I'll show you how to use DirectX in your apps to do smooth fast output that can be timed in microseconds not minutes!



A few years ago, Microsoft's developers realised that Windows was not a games platform and they vowed to change this. In 1995 the short lived WinG appeared. This allowed fast output in 256 color modes on Win 3.1 and Win 95. It was fast, but limited and soon replaced by DirectX. This is a family of APIs for fast output and input. Your apps can use video cards, sound cards and even force-feedback joysticks through DirectX. I'll look at DirectDraw the visual API and come back to the others in future. Other DirectX APIs include DirectSound for soundcards, DirectInput for joysticks etc, Direct3d for 3d graphics and DirectPlay for multi-player. Although DirectX is now at version 6.1, DirectDraw has been virtually unchanged since version 3. Version 3 BTW is the one built into Win NT 4.0.



Windows's strength is that it works with many different monitors, video cards and in multiple resolutions and colour depths. This requires a lot of code between your App and the video card hardware and this 'Thick Api' is why Windows output routines are slow. This was also true with Dos when it first appeared, and games developers went straight to the metal instead of using the BIOS or DOS API routines. Until two years ago, most games were still written for Dos, even those running under Windows 95. Now, probably 99% use DirectX and soon it seems Windows CE micros will be driving commercial arcade machines using DirectX.



DirectX/DirectDraw supports a thin API approach with few layers between the hardware and your App. DirectX is built on COM, the Component Object Model so that new versions can be released without breaking your existing code. If your App works with DirectX version 5, it is guaranteed to work with DirectX 6, 7 ...308!



DirectDraw provides information to your App about what hardware features the video or sound card has, and what is emulated in software. This DirectX approach guarantees that your software will run on cards not yet designed and will be able to use their advanced features. On the downside it means that you have to write two or three different ways of doing things and choose the best (= fastest) method at runtime.



DirectX and Delphi

Although C and C++ are the main languages ifor DirectX, Delphi is very well suited to ir. Blake Stone did the first DirectX header translation from C to Delphi and Borland's Charlie Calvert has brushed on the subject in his Delphi 2/4 'Unleashed' books. Mr. Hiroyuki Hori in Japan developed DelphiX- an excellent set of free Delphi components for DirectX. These are available from many sites on the Net or his home page at http://www.ingjapan.ne.jp/hori/. You will also need to install DirectX version 5 or higher. You've probably got it on your system already if you've bought a game in the last year. Windows 98 and the future NT5 come with DirectX built in. NT 4, Service Pack 3 also supports DirectX 3.0.



I'll now show you the essentials of DirectDraw programming and how to get the best out of the DelphiX components with an example App. If you use DelphiX, you'll find that Mr. Hori's help files are perhaps not crystal clear but you get complete source code and a number of good sample programs to overcome this. DelphiX is a superb piece of software and you could write near arcade quality games in Delphi using it.







Even if you are not into games programming, the extra speed that DirectDraw offers lets you create applications with flicker free high speed output like radar displays, oscilloscopes, scrolling maps etc. I've used it for a scrolling map and it draws a screen full of 256 colour hexagons at 1024 x 768 resolution (each hex fits in a rectangle 32 pixels wide by 34 deep) in 26 ms on a P2 300 equipped with an AGP Matrox Millenium card. That's almost 40 frames per second, not at all bad for Delphi..



In DirectDraw, Microsoft haven't chucked out the GDI either. One of the big pains of Dos game development was outputting text, particularly having to create your own fonts or message bitmaps. Thankfully, the GDI works with DirectDraw so that Truetype fonts can be used. In fact most of the GDI works, so long as a bit of care is taken. Of course GDI output is not very fast, so you should really only use it for creating text, menus etc.



Getting Started

You will need the DirectX header files- there is no better place than the DelphiX library. The supplied Directx.pas is a whopping 300Kb in size. It includes all the DirectSound etc APIs records, GUIDs etc. If you have been wondering in the Gobi for the last five years, you may be unfamiliar with GUIDs, but they're simply a collection of numbers like this ['{5C690320-0BD2-11D3-89DF-00409545E8E7}'] that uniquely identify objects and classes in Windows. Microsoft thoughtfully put GUIDS containing your network cards' Mac Address into all Word and Excel files created on your PC. If you do Ctrl-Shift G in the Delphi Ide, the last 12 digits of the GUID generated are your network cards Mac Address (if you have a network card).



DirectDraw is not that tricky to work with, just a little fiddly in places. Here is my five point beginners guide to get you started. First create a DirectDraw object. This represents one video card/monitor set-up, and under Windows 98 will support multiple devices. This is the definition of DirectDrawCreate.



function DirectDrawCreate

                   (lpGUID: PGUID;

out lplpDD: IDirectDraw;

     pUnkOuter: IUnknown): HRESULT;



Anywhere there is a pUnkOuter parameter, just pass nil. This call should return a DD_OK value (=0). Anything else is an error and should be handled by your code..



Step two is to specify the co-operative level. Does it want the whole screen exclusively or to share it? Most games run in full screen mode so your app can choose the screen mode and resolution to use. If you use it in a window, your app should not change mode.



The dwFlags parameter lets you specify whether you use full screen or not, or whether your app controls the screen so no other app can change it and even if the user can Ctrl-Alt-Del to reboot.



function SetCooperativeLevel(hWnd: HWND; dwFlags: DWORD): HRESULT;



Its possible to ask the DirectDraw object for all supported video modes. Most video cards support lots of modes, but the higher frequency/colour modes may need higher bandwidth than your monitor can cope with. Switching to these modes will not work and could leave the monitor in an unsynched state, so be sure to include a disclaimer when you distribute your apps! Better still, get the user to choose the mode from a list. Quake and many games use this approach.



Step 3 is set the screen mode. This only applies if you want full screen access.



function SetDisplayMode(dwWidth, dwHeight, dwBpp: DWORD): HRESULT;



dwBpp is Bits per pixel, i.e. 8 for 256 colour. 640x 480 by 8 is probably a safe bet on any PC!



Step 4 is where surfaces are created. All images are held in surfaces. These are just blocks of ram with an optional palette and your app can create surfaces in the main PC's system ram, or in the video card ram. No prizes for guessing that images in video ram images are fastest to copy to video ram, although AGP cards can move data from your PC ram very quickly.



CreateSurface creates a surface according to the surface description parameter.



function CreateSurface(const lpDDSurfaceDesc: DDSURFACEDESC;

        out lplpDDSurface: IDirectDrawSurface; pUnkOuter: IUnknown): HRESULT;



The main surface represents the screen and is known as the Primary Surface. Updating this directly can lead to the display raster catching images as they are drawn. This gives a shearing or tearing effect and you can avoid it by using another block of video ram as a Back Buffer. The Primary Surface is always displayed, but your app always writes to the back buffer and then calls the flip method. This causes the Primary Surface and Back Buffer contents to swap positions and your monitor now shows the new image That is the secret of smooth computer animation and can happen up to 70 times per second.



A Primary Surface of 1024 x 768 in 256 colours plus a back buffer takes 1.5Mb of video ram. So a video card with 4Mb-video ram will have just 2.5Mb left for storing your images. You might prefer to allocate a large block of ram in main PC memory, do all manipulations in that and then copy this to the video card surface to be displayed. There are many techniques for optimising update speed and part of the fun is trying them out. Keeping track of which bit of screen has changed and only copying that bit is one way to minimise traffic. Not everyone has AGP so you should test your app on as many systems as possible.



That is really all there is. Step 5 is sitting back and thinking- wow- I can now use DirectDraw. There are a few minor complexities like getting your images from bitmap files or resources into a DirectDraw surface. The bitmap has to be loaded into memory and then copied into the back buffer.. Windows bitmaps contain a palette and are usually stored upside down. You can have fun coding this yourself or use the DelphiX library's components for bitmap and palette management.



Direct Access to Video Ram

If your App has to directly alter pixels in video memory then you must specify a rectangular area, call lock to gain exclusive access to the ram, alter or read the pixels and finally call unlock to release it. The code the Lock and Unlock should be kept short as Windows is stopped.



Watch the Pitch!

No its not about football. Every surface has a pitch. Its the adjusted width in bytes. If you manipulate images at the byte level, you must be aware of this. The pitch of a surface is often different to the width. Each pixel line starts 'pitch' bytes from the previous. This can happen because the width is an odd number of bytes and the pitch rounds up to the nearest double word or its just the way the video card does it. The width might be 105 but the pitch could be 108. The first line is 0 bytes from the start, the next 108, then 216 and so on.

GDI

Using the GDI is not a major problem. The GDI is a little stuck in its ways and always uses the same block of video ram. This means that a system message (like an unhandled exception) may not appear if you are using page flipping and displaying the other block of ram. However, you can call fliptoGDI to make the primary surface be the GDI or GetGDISurface to get a pointer to the surface object.











Losing Your Data

Losing images in video ram is a minor problem if the user does an Alt-Tab. Everything is lost. However DirectX keeps track of the 'lost' status of surfaces and will give a 'Surface Lost' error if you try to use it. Your app should have a handler to recreate the surfaces and reload images back in.



The drawbacks....

The main one is that debugging full screen DirectX Apps is tricky. Accessing video memory is done by temporarily stopping Windows. Unless you are comfortable working at the very lowest level; this can be a walk into SoftIce territory, there is no way on earth you can step through your code between a Lock..unlock pair. One way round this is to use a dump procedure to append strings parameter to a log file, or a debug window like in Gexperts.



If you know C or C++ of course Microsoft provide a free DirectDraw SDK with many examples and probably the most informative help file I have ever seen. I recommend the 'Inside DirectX' book from Microsoft press for all of the background information about DirectX. Microsoft Press; ISBN: 1572316969. An alternative book is Windows Game Programming for Dummies by Andre Lamothe publisher IDG Books Worldwide Inc; ISBN: 0764503375, but again this is C/C++ based.



Delphix

Having learnt how to do simple DirectDraw stuff in Delphi you now have a choice- develop your own components or use those defined for you- like DelphiX. I have no connection with the author except admiration for an excellent job and his generosity in giving it away. If you are new to DelphiX, it can be a bit overwhelming and I suggest you print out the samples and demos and study them in detail. Here is my overview on using them.



A TdxForm is a modified form that must be used as the ancestor form, not TForm- it does some message handling, including handling exceptions and switching primary surface to GDI so you see messages. A DxDraw component is dropped on it and the properties set for resolution and colour depth. This has event handlers to deal with initialising and terminating the object. These handlers are where your app must do its loading of images and preparing for the game loop.



The main game loop is actually a timer driven event handler. The timer keeps track of any lag so you can see if your code is taking too long to run.



There are image and imagelist components which let you set up bitmap pictures at design-time and you can specify in which type of ram (PC system or Video) the images are placed.



 A RestoreSurface handler is provided so your app can reload images after the user Alt-Tabs and loses the video ram contents.



The TDirectDrawSurface class implements just about all of the DirectX surface functionality. Surfaces can be created anywhere, any size and images blitted quickly with optional scaling and rotation between surfaces. Most video cards have a hardware blitter and DirectDraw will use this. Images are rarely rectangular but are always defined in a rectangular block. A map drawing routine draws 150 hexagons in under 4 ms i.e. each takes around 26 microseconds.



Creation of a back buffer is automatically done if the Dxdraw.Doflip option is true. Note that output goes to the back buffer in that case and you won't see anything until dxdraw.flip is called. If the doflip option is false, as in running in a window, output goes direct and you don't need to call dxdraw.flip (though it does no harm).

The Code of Radar.Exe.

In 370 lines of code, I have written a small simulation of aircraft flying in random directions- this is very barebones functionality, so don't expect collision detection or explosions!. The keys are ESC to exit, 'A' to add an airplane and Alt-1, 2 or 3 to change to higher screen resolutions of 640 by 480, 800 x 600 and 1024 x 768.



The aircraft in three different colours fly randomly across the screen at slightly different speeds and bearings and some will flash. There are 25 to start with but hold down the A key and the numbers will increase real quick. The aircraft are shown as rectangles surrounding the screen co-ordinates and this text is output by the Gdi TextOut routine; this slows things down somewhat. To see the effect of this, try it with 100 aircraft then comment out these lines below in the method tPlane.MovenDraw and run again. All that will be displayed is the enclosing frame. This is drawn by some assembly code; I was just showing off!



if fflashcount >=0 then

    with fdxdraw.Surface.Canvas do

      begin

        brush.color := fcolor;

        str := inttostr(fx)+inttostr(fy);

        textout(fx,fy,str);

        release;

      end;



Note, the use of release in this bit of code. This is a method of TDirectDrawSurface and it frees up the handle of the underlying Display Context. Any time you use a Gdi call you should call release.



You can now have a lot more aircraft (well rectangular frames) smoothly whizzing around the screen. Its worth trying it out with flipping enabled and disabled to see what difference if any that makes.



How It works.

Much of the donkey work is done by setting properties in the DXDraw component.- eg Screen resolution, full screen or windowed, doFlip etc etc.



I use a tstringlist to hold every aircraft object. These are created with random co-ordinates and movement directions. In the timer handler, all planes are moved each frame by the randomly specified x and y offsets, bouncing off screen edges. The screen is cleared and all aircraft redrawn each time.



The text at the top left shows how many aircraft are on screen and the lag. A normal lag value is 1. Everything is output in one frame. If this goes to 2 or more (lean on the A key), the aircraft movement becomes jerky as it is taking longer than one frame period to draw. Try to avoid this!



The unused DrawFrame method draws the frame of rectangles in white and is very fast. Both this and the unused Drawrect method are good examples of directly accessing video ram via Lock..Unlock and both routines use the pitch member of the Surface Descriptor (dds) to move down from one pixel line to the next.



Notes for Delphi 4.0 and NT 4.0 Users

There are two versions of DelphiX available and both will work with Delphi 3.0, the earlier one has a smaller Zip file (606Kb). The second one (841Kb) has some Delphi 4.0 specific code (overloading and default parameters) and supports DirectX 6 but programs built with it, will NOT run under Win NT 4.0- instead you will get the error "Interface not supported". Use the earlier version in that case.



---------------------

The zip file included has source files, but not the DirectX code. This can be downloaded from http://www.yks.ne.jp/~hori/DelphiX-e.html or various other places.



The file for this article may be downloaded here - ddraw.ZIP

 

Share this article!

Follow us!

Find more helpful articles:

Comments

Feb
5

DirectDraw and other DirectX 7 component is now obsolete. DirectDraw is no longer being developed. New application that want to use new DirectX technology to display 2D animation must migrate into 3D world. From DirectX 8, DirectDraw and old Direct3D is integrated into one new component called DirectX Graphics (but some people still calls it Direct3D).

On October 2008, Microsoft released new technology for 2D graphics called Direct2D which built upon Direct3D 10.1. This new technology will replace Windows GDI/GDI+.

By Zamrony P Juhara