This website is obsolete. Please follow this link or use a search engine to get at the new document root.
This short introdution was initially an answer to a question that was appearing rather frenquently on the Gnome mailing-list (gnome-list@gnome.org).
A lot of people that do not have a strong programming experience with the X-Window system often forget that it is a network-transparent solution. It is thus impossible to do direct pixel manipulation of the image that appears on the screen, and therefore to obtain a pointer to that data, since it isn't in the same process (it is often not even on the same machine).
The following was a pure ASCII e-mail, it has been reformatted in HTML (I have essentially added paragraph markup) to integrate nicely with any web browser. The "[clip]" lines mention that some of the original content of the question was suppressed.
To: gnome-list@gnome.org
Subject: Re: Fast Image Manipulation?
Date: Wed, 18 Nov 1998 14:48:12 +0100
[clip]
> Sorry, I realize I can't play with the actual display bits.
[clip]
Hello everybody,
As this is a subject that pops up quite often, I think I'll devote some time to explain the whole thing (or at least what I'm understanding of it) ...
As Gtk / Gnome use gdk, which is basically a thin layer over X
calls, the reasoning in this document also applies to raw X
programming. I will list the X equivalents to Gdk calls between
brackets, e.g. gdk_draw_line()
[XDrawLine()]. The name of the function
calls/structures may also be wrong, since I'm not on my Linux
machine to write this...
So, we will consider three kinds of objects : the
GdkImage [XImage], the
GdkPixmap [XPixmap] and another
player, the GdkImlibImage [ImlibImage].
A GdkImage is an image (as you should have guessed)
that resides in the adress space of your application. You can
therefore manipulate it yourself. It has the same byte/bit
organization that the video memory of the graphic mode your X
server is currently using. That means you must
have your own manipulation routines to be able to draw in it,
for any mode the server may support (even the
very exotic ones), or your app is basically
broken.
Some people (read : game coders) therefore require that you put your server in a particular mode (e.g. 8bpp) before running their apps because they were unable to code the other modes (for speed or laziness reasons). But keep in mind this is not possible for everyone, since a given mode is not even guaranteed to exist on a particular platform.
Another problem with the GdkImage model is that at
any time you want to update the screen, you
must transfer the (whole ?) image in the
server's adress space. When using X trough a network, or in the
case of some old/broken servers, this has to be done using
TCP/IP (uck !). However most of the new X servers
support another way to transfer the image : it's the famous
MITSHM (M.I.T. shared memory) extension to the X
server. Note that even in this case your image has to be copied
two times : first to the memory the two processes share, and
then in the video memory.
Games like XQuake or XDoom do use XImages to do their work (however restricting you to a 8bpp server only - at least last time I checked), because their major task is the rendering of the image. When the calculated image is ready, they just have to flip it on the screen, and they're done.
A GtkPixmap is a server side image. You
can't manipulate its byte/bits directly since
it is not in the adress space of your program (you
cannot get a pointer to its data). These
pixmaps are the main reason an X server can grow fairly large
memory-wise : it contains every pixmap of
every X app in its own memory. But don't be
afraid, this is just displacing memory, since you no longer need
it in your app, so there's no waste.
The advantage is that since the X server owns the pixmaps, you
can use any gdk_draw_*() [XDraw*()]
call on them. The server may even cache some pixmaps in video
memory, and use the graphic board's accelerator to draw
line/boxes on them
(1).
When the times comes to update the screen, the server only has
to do one memory copy from the pixmap to the screen memory. If
the pixmaps were already in video memory, the server could even
use the board's accelerator
(1).
Platform games (or any game that contains sprites) should use
the pixmap approach : When starting, they transfer the sprites
as pixmaps on the server, and then they only have to use
gdk_draw_pixmap() [XDrawPixmap()]
calls to put them on the screen. Also note that a pixmap using
game should also perform very well over a network (or a
broken/old X server that doesn't support SHM), since the only
wire traffic consists in the X calls.
1) This is of course X server implementation dependant !
Well, this one has nothing to do with the
previous ones. It doesn't even even belong to gdk or X, but
rather comes from the gdk_imlib
[Imlib] library. A GdkImlibImage
basically stores an array of RGB triples describing an image
(and also some private things). You can load various image
formats into them using gdk_imlib, and you can
render them to
[GtkPixmapsXPixmaps] using gdk_imlib too (the
latter takes care of the pixmap format, as well as palette and
dithering issues).
Please don't even try to implement a game using
GdkImlibImages ! While Raster's (Imlib's author)
scaling/rendering code is Great Stuff[TM], the
GdkImlibImages were not created for that
purpose. Only imagine the CPU overhead of converting your RGB
data to a screen image (including two memory copies in the
shared memory's case - which is the better), using arbitrarily
complex conversion/dithering bit manipulations (you don't know
on what server you're running), the whole thing at 30+ fps!
Gdk version 1.1.2 (?) and higher also contain
gdk_rgb*() calls to draw a rgb image on the
screen. But it has to be used only when other
methods are not possible (e.g. if the data comes from a RGB
video source
(1),
you need to convert it anyway, so chances are
gdk_rgb functions are already optimized for that).
1) Does such a thing exist ? Perhaps
we'll need gdk_yuv*() calls ;).
[ Update : The aforementioned "very own case" was the creation of a small realtime strategy game like the well known StarCraft, Command & Conquer, etc. ]
In the case of a StarCr*ft-like game, I would suggest the following approach :
Say we are creating a new game, called Star Conquer. The two opposite camps are be the humans and the fromens (I put a lot of imagination into that one ;). The whole set of background tiles is stored in a file (tiles.png). All the sprites are stored in humans.png and fromens.png. Of course whe have a table describing at which coordinates we can find any frame in any file.
The game startup looks like :
gdk_imlib.
gdk_imlib.
GtkPixmap of the size of your game
area. It will be your virtual screen buffer.
Ok, now what we need is the main game loop. It should look like the following function :
/* NOTE: this is pseudo-but-looks-like-real-code,
so cut'n'paste won't work :) */
void game_loop_do_one_iteration(void)
{
/* tile the background */
for(first_tile; last_tile; tile++) {
tid = get_tile_from_map_at_coordinates(x, y);
get_tile_coordinates_from_table(tid, tile_table,
&tile_x, &tile_y);
gdk_draw_pixmap(double_buffer, scr_x, scr_y, tile_pixmap,
tile_x, tile_y, TILE_WIDTH, TILE_HEIGHT);
}
/* draw the buddies */
for(first_buddy; last_buddy; buddy++) {
get_buddy_frame_from_table(buddy->type, buddy->frame++,
buddy->table, &buddy_x, &buddy_y,
&buddy_width, &buddy_height);
/* of course, since buddies are not rectangular, there should
be a bit of masking, etc., but ... */
gdk_draw_pixmap(double_buffer, buddy->pos_x, buddy->pos_y,
buddy->pixmap, buddy_x, buddy_y,
buddy_width, buddy_height);
}
/* update the screen */
gdk_draw_pixmap(drawing_area->window, 0, 0, double_buffer, 0, 0,
game_area_width, game_area_height);
/* this will be the trickier part */
move_buddies_with_at_least_a_bit_of_AI();
}
Then you connect that function to a timeout, and you're done. The AI part is left as an exercise to the reader :). There are some facts you should examine :
1) Since not hardcoding them would save me a lot of work when converting your game into a Kdemen vs Gnomes wargame - hmm ... pleasing scenario, I would be ROTFL :)
Well, I hope all this will be of use for somebody. Since these are questions that pop up very often, I hope this document will enlighten some X / Gdk 'newbies' (1) :).
Of course the function/structure names in this document may be wrong, since I don't do much X programming for now and I'm not in front of my Linux (2). box. But hey man, man is your man.
Feel free to correct this and distribute at will - even commercially and without releasing the source code (3) if you wish ... I'm also sorry for my bad english, but I would be glad to hear of corrections - that's the way I learn !
Cu,
Damien.
1) Note that I'm one of them, I have just collected pieces of information around.
2) Especially for RMS : read GNU/Linux :)
3) Oh my, I'm tired of these licence flamewars. [ Update : This document should only published unmodified (except with the author's permission) and accompanied by the contact information available. Any modification must be returned to the author. ]
Please follow this link for information on how to contact me.