[color="#4B0082"] Be sure to read the license agreement before using the code.[/color]
[spoiler][font="Courier New"]"Code On The Cob"
Source Code License Agreement
Copyright (c) 1998 Chris Hargrove
The licenser, Chris Hargrove, is herein referred to as the "licenser".
This license agreement, the source code provided, the executable(s)
compilable thereof, and all other contents of the distributable archive
including the archive itself are herein referred to as "covered" by this
Reading, compiling, running, or otherwise using any item covered by this
license constitutes automatic acceptance of the rules described in this
license, as precedes and follows in the remainder of this file.
All items covered by this license agreement are the sole property of the
licenser, and are licensed as-is to the public for educational use only.
No items covered by this license may be distributed in any archive other
than that which is originally provided, and said archive may only be
distributed via mediums that require no cost for retrieval of said archive
other than those required by the medium itself and its direct providers
thereof. None of the items covered may be modified or duplicated in any
form, with the sole exception of modifications and duplications made by
licensees for educational use only. Said modifications and duplications
may only be made to source code covered by this license, and all said
modifications and duplications become the immediate and sole property of the
licenser. The licenser is not responsible for any damages, direct or
indirect, that may result from usage, proper or improper, of items covered
under this license.
Any violation of the above rules must be authorized via express written
consent of the licenser, or it may be punishable by civil legal action.
THE LICENSE PARAPHRASED:
I'm writing this code so you can read it and learn from it. If you
understand it and find some bits and pieces useful as-is, feel free to make
use of them. But don't go taking credit for that which is not yours, and
don't go trying to make a buck off of my effort directly without my
permission. I'm doing this for your personal growth, not for your wallet
or your ego. In general, don't screw me and I won't screw you. The spirit
of sharing information is why I volunteer my already limited time to write
this series. That same spirit is behind a lot of what I and many others have
learned, and what we're all still learning to this day. This license is
basically my way of saying that I don't want some random guy out there to
ruin it for myself and everyone else. Be ethical.
"What the public wants is the image of passion, not passion itself" - Roland Barthes
Hello again! Well, after all this time, we're finally ready to start diving into some graphics code. That's right, it's time for code with results you can see! I know some of you all may think I took way too long to get to this topic, but if you look at the overall theme of this whole column, hopefully you can understand why. Foundation and good architecture always come first.
We'll obviously be working with graphics in one form or another throughout the remainder of the project, but the pure graphics subsystem (or "video subsystem" as I often call it for 2D graphics) is actually not that large at all. I'll still be breaking it up over two articles though, since I'll be using DirectDraw code (which tends to read pretty thick... we're moving away from the "one article one topic" paradigm here). This first article will be focused purely on getting devices and display modes with some initial frame buffer access, and the second will wrap things up with some better surface management and integration with the resource manager for bitmap loading etc. If this last sentence here just went over your head, don't worry, I'll do my best to explain things somewhat
[size="5"]The Graphics Rundown
If you're a game programmer or would-be game programmer reading this series, you've probably already mucked with graphics code at least a little bit. Whether it be good old DOS VGA programming or actually working with DirectDraw, graphics seems to be one of the first targets of any game programming enthusiast. It's the route to making stuff that "looks cool", which is a lot of its charm. So if you've already done any graphics code (even in DOS), you're probably very familiar with what a frame buffer is, what a palette entry is, etc.
If you haven't, well, you should know by now that I can't teach everything in this column; there just isn't room for it. But here's the short-short-short overview of what you should know before reading the rest of this article: A "frame buffer" is a buffer of memory that represents your screen's drawing area, which you write to in order to generate the image you want. Frame buffers are generally organized linearly, visually starting at the top left of the screen and reading like a book (left to right, top to bottom). Each pixel may be 8 bits, 16 bits, 24 bits, 32 bits or whatever, depending on the color depth of the video mode you're using. Often you'll actually have two or more frame buffers being used, one called the "primary" or "front" buffer, which is currently being displayed, and the other(s) called "back" buffer(s), which are actually written to (either directly or copied from off screen). Swapping the front buffer's contents with a back buffer is called "page flipping", which is often done in graphics code to reduce screen flicker and cover up the fact that direct writes to the "primary" buffer (whose memory is generally onboard your video card) are slow. When you take an off screen buffer and actually copy its contents into a display buffer, it's called "blitting", and this term is often used even when display memory is not involved but a similar result is required (copying one rectangle's contents to another, etc).
There, that's the quick overview. Not much information, but hopefully it'll be enough to get you through all this stuff. Again, I expect that most programming-types who are reading this are probably already familiar with the terminology and the basics of how 2D graphics operations work, but I don't want to assume too much so, well, there you go.
So, ready to take the plunge?
[size="5"]You Just Can't Get To HEL Fast Enough
I'm warning you now, DirectDraw code can be rather nasty to read and even nastier to write. The initialization is a large process, and you have to contend with several enumeration functions, device choices between things like a HAL (hardware abstraction layer), HEL (hardware emulation layer), a boatload of code to deal with DirectDraw "surfaces", and on and on. So don't expect a walk in the park, without getting mugged at least. The code for today's article may be only 500 lines or so, but it's not a pretty 500 lines.
But, gotta bite the bullet sometime, right? Okay, I've thrown on two more files today, vid_win.cpp and vid_win.h. We'll have a couple vid_main files added on next time as well, but those will be saved until the DirectDraw side is mostly out of the way (and we'll need to do some more surface management code next time before that's the case). So keep in mind that all sample code you see in sys_main which uses vid_win is purely temporary; vid_win's interface will disappear from public eyes once the next article rolls around.
I'll go over the code layout here, but for DirectDraw function call specifics you'll want to have your DirectX SDK documentation handy (it's Microsoft's job to document their interfaces, not mine
[Look at vid_win.h]
Our interface to DirectDraw is pretty small, and although it won't be completed until the next article, most of it's already here. Aside from the "usual three" (init, shutdown, and frame) functions, all we need for the moment are a function to set a video mode, functions to get some current information like width, height, pitch, and frame buffer pointer, and finally some palette functions. For the moment I'm using palettes and assuming a 256 color (8-bit) video mode, since that's what this game will likely end up using.
For those of you who aren't familiar with screen "pitch", it's the logical width between scanlines, which is often the same as the actual screen width but sometimes more. When dealing with DirectDraw, you need to use pitch instead of width when calculating drawing offsets (look at the sample code in sys_main for an example).
Anyway, so it's not a very big interface. But as usual, there's more to it on the inside...
[Look at vid_main.cpp]
One of the first things you'll notice are two structures for "devices" and "modes". DirectDraw allows you to hook your video output into one of possibly many video devices, not just your primary display (you may have an auxiliary video card, or some headset thingy, or whatever), so we'll use a pool of these device structures to hold whatever devices we find in the system. The same goes for modes; each device may support a large number of video modes (screen resolutions). Both the device and mode lists will be filled in by two DirectDraw "enumeration" functions that we deal with in VIDW_Init. Once we have these filled in, we won't have to make DirectDraw fail on a call just to find out a video mode isn't there. Later on we'll also use the device and mode lists to let users choose their screen output device and resolution within the game.
Down in the implementation data section, we have the first of our DirectDraw internal stuff, followed by those device and mode pools I just mentioned, and then some current status variables for width, height, etc (accessible from the outside via interface calls). There's also the 768 bytes (256*3) set aside for our internal copy of the palette, so when using 8-bit output we can change video modes without losing color information.
Now down a bit lower in the implementation function area is where much of the work is being done. There are functions here which initialize and shut down a device, and which initialize and shut down a mode. If you haven't mucked with DirectDraw much or at all and want to see an example step-by-step process for getting a video mode up and running, walk through the device and mode initialization functions, because that's where it is. I'd suggest having your SDK documentation handy when walking through those functions.
Following those four functions are the two enumeration "callback" functions used by DirectDraw to enumerate devices and modes. Down in the interface section, VIDW_Init uses these to build up our device and mode lists as mentioned before (actually, that's all VIDW_Init does right now).
Everything else should be moderately straightforward, assuming that graphics code is not completely new to you and you aren't afraid of DirectDraw. If either of those two aren't the case, you might want to check out some other books or documentation online (including the DirectX SDK) before trying to make sense of this code. I've tried to keep the code clean and relatively commented, but I can't teach you everything (like I said, I'm not writing a book here). If it takes you a bit of time to work through this code, don't sweat it... after all it's two weeks until the next article, right?
We'll finish up the DirectDraw layer and its higher-level "vid_main" companion next time around, where we add in some more comprehensive surface support to deal with onboard sprites, etc.
Until next time,
- Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever.
Code on the Cob is (C) 1998 Chris Hargrove.
Reprinted with permission.