A technique for fast Win32 console drawing

G

Guest

Guest
Archived from groups: rec.games.roguelike.development (More info?)

This is a little technique I stumbled upon while designing a display
engine for the roguelike I am dabbling with. It provides a means to
apply a double-buffered text engine system to a win32 console
application using C++. This tutorial does not require knowledge of the
windows application programming interface (API) or graphics device
interface (GDI), but it is assumed that the reader is comfortable with
structures, unions and arrays/pointers.

All examples were run and compiled using Microsoft Visual C++ 6.0

What is a windows console?

A windows console is a high-level interface for character-mode
applications (in our case roguelikes) that provides a simple input and
output interface to applications not requiring the robust, and painful
to write, standard windows application that most applications use
(MSDN). In English, we use consoles instead of windows because consoles
are easier. Even a basic windows program that opens and closes requires
at least two pages of code. Consoles handle everything for us so that
we can worry about the program rather than the interface.

The problem for us is that, for roguelikes, the standard I/O interfaces
like cout/in or prinf/scanf don't give us two of our most important
requirements: the ability to set output coordinates and set colors.
Usually this means that we have to write our own, enhanced versions of
these.

Accessing the Win32 Console

Creating code to write to the console is actually fairly simple. The
following source code demonstrates this:

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

void main( void )
{
HANDLE hOutput = (HANDLE)GetStdHandle( STD_OUTPUT_HANDLE );

// Set the text output position to (5,10)
COORD sPos;
sPos.X = 5;
sPos.Y = 10;
SetConsoleCursorPosition( hOutput, sPos );

// Set the color to bright green
SetConsoleTextAttribute( hOutput,
FOREGROUND_INTENSITY | FOREGROUND_GREEN );

// Write the text
DWORD nWritten;
WriteConsole( hOutput, "This is a test", 14, &nWritten, NULL );

}

The most important line of code is the first statement within the main
function. This essentially gets Windows to tell us the handle (think of
it as a ID number) for our console. Then when we want to perform
operations on the console, we insert the handle into the function.

Why Are Console Mode Roguelikes Slow?

Quite simply, because Windows is slow. Windows is programmed to work
regardless of the hardware of the computer and any kind of high-level
abstraction like this will be slower. It's one of the fundamental
laws of programming. So what can be done?

Usually, when we write our roguelikes we end up drawing each character
of our game map individually. If we're using a screen with 80x25
characters, this requires 2000 calls to the WriteConsole(..) function,
not to mention SetConsoleTextAttribute(..). These function calls take
time and performing them 2000 times a turn only makes things worse.

Buffering the Console

Please bear in mind that this is just one way to do this. The basic
idea behind this concept is to create a buffer of the console in RAM
and operate on it before writing all of it to the screen. This means
that we only have to perform one Windows console function per turn.

Creating the buffer is simple. MSDN describes the data structure that
the console uses to store information on each character written to the
console. The structure is called CHAR_INFO and I've copied the
declaration below.

typedef struct _CHAR_INFO {
union { /* Unicode or ANSI character */
WCHAR UnicodeChar;
CHAR AsciiChar;
} Char;
WORD Attributes; // text and background colors
} CHAR_INFO, *PCHAR_INFO;

This structure is 32 bits (4 bytes) long and contains two properties:
Attributes, and Char.

Attributes describes the foreground and background colors for the
character. You can use the flags provided for you (like the source
example above) or you can provide a numerical value. The color codes
are the same as those defined back in the DOS era, but for those who
understand what I'm talking about, please note that anything above
128 (0x80 in hex) will not blink, but will make the background
brighter.

The Char property is a union that allows you to specify the character
in ASCII or UNICODE format. Take your pick. I live in North America -
ASCII works for me. Besides ASCII makes up the first 256 characters of
UNICODE anyway, so it really doesn't matter.

Back to creating the buffer, we create an array of this structure that
has the exact same number of characters as the console does. I prefer
to use a two-dimensional array - it's simpler to work with but
doing so will require some typecasting when you actually write the
buffer to the screen. Nothing to sweat about though. Also note that the
coordinates using this method will be reversed - instead of (x,y)
they'll be [y][x].

This is how to create the array:

CHAR_INFO buffer[SCREEN_HEIGHT][SCREEN_WIDTH];

And then you can write to the buffer all you like:

buffer[10][5].Char.AsciiChar = 'H';
buffer[10][5].Attributes = 0x0E; // This number is 14 in decimal, which
is yellow

And so on. Fill the buffer until you're ready to draw it to the
screen. To do that, you use the WriteConsoleOutput(..) function for the
console.

COORD dwBufferSize = { SCREEN_WIDTH,SCREEN_HEIGHT };
COORD dwBufferCoord = { 0, 0 };
SMALL_RECT rcRegion = { 0, 0, SCREEN_WIDTH-1, SCREEN_HEIGHT-1 };

WriteConsoleOutput( hOutput, (CHAR_INFO *)buffer, dwBufferSize,
dwBufferCoord, &rcRegion );

This is messy, but what this does is write the entire buffer to the
entire viewable screen. Notice the typecasting? That's because the
function wants a one dimensional array, but one of the fun things about
C/C++ is that two dimensional arrays and one dimensional arrays are
pretty much formatted the same way in memory. This allows us to change
the type without screwing up anything.

The dwBufferSize structure defines the size of the buffer that we're
writing, and dwBufferCoord defines where to start copying the buffer on
the screen. The rcRegion structure is kinda interesting: it's used to
define the region to draw into and when the function is done, it
describes what actually got written. Point is, that you can test for
errors if you want to.

Important Note

Be sure to initialize the buffer!!! If you don't then anything that
you don't write over will be displayed. This is usually garbage left
over in RAM from other programs. The best way to initialize the buffer
is actually use the ReadConsoleOutput(..) function, which copies the
console's buffer to your own buffer. The function call looks pretty
much the same:

COORD dwBufferSize = { SCREEN_WIDTH, SCREEN_HEIGHT };
COORD dwBufferCoord = { 0, 0 };
SMALL_RECT rcRegion = { 0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1 };

ReadConsoleOutput( hOutput, (CHAR_INFO *)buffer, dwBufferSize,
dwBufferCoord, &rcRegion );

Now you have an image of the screen in your buffer. Modify to your
heart's content.

When to Use Double Buffering:

This is all nice and dandy, but now that you know how to do this the
next question is when. In my roguelike, I only use this when drawing
large amounts of text on the screen - such as when I'm drawing the
game world to the screen. My game loop looks something like this:

1. Perform logic on game world
2. Get buffer image
3. Draw player's view of game world to buffer
4. Write buffer to screen
5. Wait for player's input
6. Go to step 1

If I have to write anything on the screen that isn't part of the game
world, I would rather use the normal, slower console functions.
They're easier to work with when drawing strings and the like
onscreen.

Conclusion/Puttin' It All Together

Here is the code that demonstrates this entire process. It draws a
multicolored "Hi!" string at coordinates (5,10).

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25

void main( void )
{
HANDLE hOutput = (HANDLE)GetStdHandle( STD_OUTPUT_HANDLE );

COORD dwBufferSize = { SCREEN_WIDTH,SCREEN_HEIGHT };
COORD dwBufferCoord = { 0, 0 };
SMALL_RECT rcRegion = { 0, 0, SCREEN_WIDTH-1, SCREEN_HEIGHT-1 };

CHAR_INFO buffer[SCREEN_HEIGHT][SCREEN_WIDTH];

ReadConsoleOutput( hOutput, (CHAR_INFO *)buffer, dwBufferSize,
dwBufferCoord, &rcRegion );

buffer[5][10].Char.AsciiChar = 'H';
buffer[5][10].Attributes = 0x0E;
buffer[5][11].Char.AsciiChar = 'i';
buffer[5][11].Attributes = 0x0B;
buffer[5][12].Char.AsciiChar = '!';
buffer[5][12].Attributes = 0x0A;

WriteConsoleOutput( hOutput, (CHAR_INFO *)buffer, dwBufferSize,
dwBufferCoord, &rcRegion );
}

This should be enough to give you something to start with. Please point
out anything that I missed/screwed up on. If you've got any
questions, feel free to email me at:

cmartens 'at' gmail 'd0t' com.

Cheers,

- Chris Martens
 
G

Guest

Guest
Archived from groups: rec.games.roguelike.development (More info?)

In article <1127322144.806848.14540@g43g2000cwa.googlegroups.com>, "Chris Martens" <cmartens@gmail.com> wrote:
>
>Here is the code that demonstrates this entire process. It draws a
>multicolored "Hi!" string at coordinates (5,10).

Neat, and it compiles & runs in dev-cpp too.

Alan
 
G

Guest

Guest
Archived from groups: rec.games.roguelike.development (More info?)

Hello, Chris!
You wrote on 21 Sep 2005 10:02:24 -0700:

CM> All examples were run and compiled using Microsoft Visual C++ 6.0

Also every one, can look this sources.
http://www.avanor.com/download.php?a=console.zip
they works both linux and windows.

With best regards,
Vadim Gaidukevich
 
G

Guest

Guest
Archived from groups: rec.games.roguelike.development (More info?)

On 21 Sep 2005 10:02:24 -0700, "Chris Martens" <cmartens@gmail.com>
wrote:

>
>This is a little technique I stumbled upon while designing a display
>engine for the roguelike I am dabbling with. It provides a means to
>apply a double-buffered text engine system to a win32 console
>application using C++. This tutorial does not require knowledge of the
>windows application programming interface (API) or graphics device
>interface (GDI), but it is assumed that the reader is comfortable with
>structures, unions and arrays/pointers.

nice. but I'll stick to curses on win32. its fast and portable.



- Stu : Email via http://public.xdi.org/=stu
 

TRENDING THREADS