Monday, February 4, 2008

MinGW + DirectX

I should first point out to beginner game developers that it is entirely possible to avoid using DirectX at all by using SDL (Simple DirectMedia Layer) for all your graphics, sound, input, timing, etc., and it integrates well with OpenGL for 3D graphics (it actually uses DirectX for 2D graphics, sounds, and so on) -- with the added benefit of being cross-platform. So unless you need or want to work with the DirectX API directly for some specific reason, this is a good solution that offers portability to a number of other platforms and a rabid following of both pro and amateur game developers. If you're just starting out and not sure, I would recommend looking into it before getting into DirectX. You can have a look at my MinGW + SDL page for MinGW/SDL-related setup tips.

Now, for those of you remaining, I assume you do know how to use MinGW, and I assume you are interested in getting it to compile DirectX apps. We'll need the DirectX headers and libraries for the version of DirectX we want to use. These are typically included in the DirectX SDK available from Microsoft. Whichever way you do this, you will at least need the header files, so get the SDK you want to use. The latest version can be found at www.microsoft.com/directx/. You can, at the time of this writing, still get the DirectX7 SDK from Microsoft. With DirectX8, Microsoft has combined DirectDraw into Direct3D. Some people wishing to use DirectDraw, or older interfaces may opt to use this download. (If you do want to use an older version, this version 7 SDK is a much leaner download and install.)

The Easy Way

The most straightforward way to go about building a DirectX app is simply to use Microsoft's DirectX SDK. You can link to Microsoft's .lib files to your application by specifying the .lib name explicitly to the compiler. Adding the headers location to the include path would also be a good idea. Example:

g++ main.cpp -o test.exe -I../dxsdk/include ../dxsdk/lib/dxguid.lib

Other Ways
  • There also exist MinGW-compatible libs (typically named lib*.a, where * is the name of the library). The advantage to using MinGW-compatible libraries using this naming convention is that you can put them into a location on your library search path (specified by the -L flag). The version of the DirectX static libs shipping with MinGW is probably out of date, but if they do support the functions you need then you can use those simply by adding, for example:

    -ldsound

    to your linker command.

    The implication of this library search naming convention gives us one more way to link to Microsoft's DirectX SDK libraries: simply change the names of the MS libraries to lib*.a (e.g. rename ddraw.lib to libddraw.a, and then don't forget to add the library search path with the -L option.)

  • You can also get a ready-to-go Cygwin/MinGW-compatible DirectX6 SDK from John Fortin's site (you only need the directx6_1-cyg.tar.gz file). This includes everything you need to build DirectX 6 (or earlier) apps with either Cygwin or MinGW, however keep in mind that this download is the version 6 API. If you want later versions, I suggest getting them from Peter Puck's excellent site which also contains more detail regarding DirectX and MinGW and import library issues. The reason I'm using the version 6 SDK is because I only need version 5 for what I'm working on. If there are features missing from these older versions that you require, you'll need to get a later version. Note that DirectX8 has removed the DirectDraw API; now all 2D and 3D graphics is accomplished via the Direct3D8 API. The last version of DirectX to support DirectDraw API was version 7. You can still use the latest headers to compile and build for older versions of DirectX, however the v7 & v8 headers were causing some annoying compiler warnings so I stuck with version 6. I get the feeling Microsoft doesn't like you using 3rd party compilers to build DirectX apps.

More Tips
  • When using an ad-hoc DirectX SDK, I create Microsoft-like directory for it called, eg. dx6sdk, and in that I create an include and lib directory. I leave this in a convenient spot so that I can link to the lib and include files from multiple projects.

  • Another trick I use in order to avoid a lot of hassle is to use Windows' LoadLibrary()/FreeLibrary() calls to load the DDRAW.DLL, DSOUND.DLL, etc. libraries explicitly. This minimizes your static link requirements down to just the dxguid library. (I was unsuccessful getting the #define INITGUID trick to work, which would eliminate the need to link to any DirectX static libraries. For some reason, the IID_IDirect3D2 symbol remained undefined. Perhaps there is another way around it, but as far as my experiments showed, you need to at least be able to link to libdxguid.a.)

Backwards Compatibility, Version Decisions

If you are looking to support relic Windows PCs that have never been patched since, like, 1996, you can just go with the version one interfaces of DirectDraw and DirectSound, and use the Win32 calls GetJoyPosEx()/GetKeyState() for input and Windows sockets for networking. In fact, there really is no reason not to use the old interfaces if you're not using Direct3D or the higher-level media APIs -- with a few caveats:
  • DirectSound version 1 has a bug that the streaming sound buffer read/write position to be off by 50ms or somesuch. If you are not doing any software sound filtering or realtime computation relying on accurate feedback from the API (i.e., if you just want to play/loop/stop/volume/pan stuff), DSound1 handles the basics just fine, otherwise you should look at using a later versions in which this bug was fixed.

  • DirectDraw version 1 does pretty much all you need a blitter/flipper to do, unless you're doing some strange video things. Most of the extra features later interfaces of DirectDraw provided were not implemented on all (or even hardly any) hardware, so unless you've got a specialized application/hardware, consumer hardware support for things like hardware alpha-blitting with DirectDraw never came to be. (Most features were implemented for Direct3D instead.)

  • Direct3D version 1 (DirectX3) -- this API was pretty much unusable until Direct3D2 (DirectX5) which added DrawPrimitive (OpenGL-style) calls. DirectX5 by the way was also on the Win95OSR2 release, and by that time people had really started to buy Windows machines, so I believe DX5 is quite widespread as well as being the first release to really start working like it was supposed to (this is all very scientific, you know). With the newer releases, now up to DirectX8, the interface has become much more flexible and has stripped alot of the butt-ugly setup code overhead that used to be necessary and you've got all the latest vertex shader & multi-texture features at your disposal -- at the expense of a smaller pre-installed user-base. Also keep in mind that the later the more recent the feature is, the more likely it is to be emulated in software on older hardware. Just be sure not to cut out the low-end, especially if you're not working on a AAA retail game with a multi-million dollar budget :) In any case, if you must use DirectX8 and all the latest flashy gadgets, you can always provide the DX setup utility with your game, or tell the user where to get it. DirectX 6 or DirectX 7 may be a good compromise for you as well.

    One final comment I might make about using Direct3D: this is probably the most compelling reason to use DirectX at all instead of using pure SDL/OpenGL. Direct3D (at least in my experience) tends to behave itself better on some cards (provided your app handles that cards capabilities properly) than OpenGL. In addition, a few things like swapping between fullscreen and the desktop, and synchronized page flipping are just better-handled with DirectDraw/Direct3D than OpenGL (which is partly due to the way Microsoft handles OpenGL, and partly due to some of the limits of a more hardware-abstracted 3D API). DirectDraw also provides access to the frame buffer, which is not accessible via SDL while OpenGL is running, should that be an issue. D3D also allows your app to load textures in their native format, theoretically allowing faster mixed-mode rendering. Of course, depending on your app/hardware, you may find OpenGL preferrable (it's certainly less work to port!). Many Windows games these days provide both OpenGL and Direct3D support.

  • Old Documentation can be hard to find. I have some on my resources page, however Microsoft seems to do a pretty good job of obfuscating it on their website. To constrain your usage of an interface to a certain version, use:
    #define DIRECTDRAW_VERSION 0x0500
    #define DIRECT3D_VERSION 0x0500
    ... for each DirectX component you're using, before including any DirectX headers. Note that the number refers to the version of DirectX for that API rather than the numbered version of the API. (Microsoft now synchronizes all version numbering for all DirectX interfaces.)

Roughing it

When you use static-link libraries for DirectX, they do not contain the actual DirectX functions; they only allow the linker to resolve dependencies at compile time, and Windows then searches for and loads the DLL is loaded at runtime. Keep in mind that any of the static functions that you link to, like DirectDrawCreate() need to be resolved at runtime when the dll is loaded. If the dll is missing, or is missing the function you called, your app will bail before it can start.

If, for whatever reason, you want to control this behaviour from your app, I would instead recommend using the Win32 functions LoadLibrary() and GetProcAddr() on the DLLs directly. This reduces your link requirements down to only dxguid.lib (or libdxguid.a), which does not require run-time binding. This allows you to test, for example, the presence of Direct3D2, and if it fails, resort to, say, OpenGL, or starts the DirectX installer, etc. This is a fair amount of work however. Most people can simply warn that the user "must install the latest DirectX runtimes, yadda yadda".


Final Notes

You will quickly discover that if you're attempting to use anything COM (like DirectX), you will need to add the -fvtable-thunks option to your compile commands. Using the MinIDE, you can set this from the Options->Target Switches dialog box. On the General tab, add -fvtable-thunks to the 'Other GCC options' text field. Important Gotcha: if you use the -fvtable-thunks flag to compile a static library, be sure to use that same compile option for anything you want to link to it! Otherwise the virtual function tables will get messed up, the linker won't warn you of this and all of your virtual function calls will fail badly.

originally posted on spacejack.org: 2001-06-06
edited: 2001-06-10
edited: 2001-07-30
edited: 2001-09-05
edited: 2001-09-18

1 comment:

Anonymous said...

Hi. Your first link "MinGW + SDL" is broken. I figured out the correct link, but you might want to fix it.