[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Linux Games Programming tut. Part 3



Hi everybody!
First of all, sorry for my non-participation these days to the list.
Sorry too for being so late with my tutorial. Finally here it is. As
usual, can you check it, the english, and make your suggestions before I
htmlized it? Thanks in advance, and sorry again. This won't happen
anymore :).

Alex.

Now we know how to load and display 8bpp sprites. The problem with 8bpp sprites is that you are limited by 256 colors and the sprite's data doesn't reflect directly the color that will be displayed - you have to check the palette. So before the keyboard manipulation, let's see how we can load and display 16bpp and 24bpp pictures.

1)Displaying 8bpp into 16 or 24bpp targets (GGI only).

As the 16bpp and 24bpp handling is a bit chaotic in svgalib (depending of your video chipset it can crash your system) we'll only make a GGI version.

Setting a 16bpp or 24bpp mode is quite simple: instead of:

ggiSetGraphMode(vis,x,y,vx,vy,GT_8BIT);

we'll use:

ggiSetGraphMode(vis,x,y,vx,vy,GT_16BIT);

or:

ggiSetGraphMode(vis,x,y,vx,vy,GT_24BIT);

and that's all!

Now, how are represented the colors in 16 and 24bpp modes? Contrary to 8bpp which is an indexed mode, what means it use a color palette, 16 and 24bpp are true color modes. The value stored in the sprite buffer is NOT a palette index to the color, but the color itself, i.e the red, green and blue components.
Here is how the colors are stored in those modes:
16bpp:

   byte 0               byte 1
X X X X X X X X     X X X X X X X X
\-------/ \-------------/ \-------/
   red         green         blue

The 5 first bits are used to store the red value, the 6 nexts the green, and the 5 lasts the blue. As it use 2 bytes, this mode allow you to display 65 536 different colors.

24bpp:
   byte 0               byte 1               byte 2
X X X X X X X X     X X X X X X X X     X X X X X X X X
\-------------/     \-------------/     \-------------/
     red                 green                blue

Each one of the 3 bytes are a component, which make working with this mode easier. With this mode you can display 1 6777 216 (ouch!) different colors.
But quality has a price: working with (and often displaying) a pixel in 24bpp is usually slower than in 16bpp: the hardware can make a 16bits memory access, but to store 24 bits you have to make one 16 bits and one 8 bits access, or three 8 bits accesses.

Working with differents graphical depths create a new problem:A 16x16 pixels sprite will need:
-256 (16x16x1) bytes of memory in 8bpp mode,
-512 (16x16x2) bytes of memory in 16bpp mode,
-768 (16x16x3) bytes of memory in 24bpp mode.

So we must be carefull of the graphical depth we are using when we do the sprites memory allocation. We'll use a bytes_per_pixel variable which will be defined at initialisation time and which value will be:
-1 in 8bpp mode,
-2 in 16bpp mode,
-3 in 24bpp mode.

And all our memory allocations for sprites will be:

sprite=(void*)malloc(lenght*height*bytes_per_pixel);

or

sprite=(void*)calloc(lenght*height,bytes_per_pixel);

Converting 8bpp sprites to true-color:
GGI offer us a set of functions which return the true-color value of a given index of the palette. They will allow us to display 8bpp images into true-color depths. In order to have a complete description, see the ggiMapcolor manpage ("man ggiMapColor"). To convert 8bpp to true color we'll use ggiMapColor:

ggi_pixel ggiMapColor(ggi_visual_t vis, ggi_color *col);

This function return a ggi_pixel (32 bits unsigned integer), which is the true color representation of the ggi_color we gave as argument. Remember the ggi_color structure:

typedef struct 
{ 
  uint16 r,g,b,a; 
}ggi_color;

As the result size depend of the graphical depth, this function must be called AFTER the visual depth is set. 

Mmmh looks like we are ready for making our PCX viewer work with each depth. The pcx loading functions don't change at all, only ggi stuff do. The source is commented for more convenience:

ggipcxview.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ggi/ggi.h>

typedef struct
{
  char signature;
  char version;
  char encoding;
  char bytes_per_pixel;
  unsigned short int xmin;
  unsigned short int ymin;
  unsigned short int xmax;
  unsigned short int ymax;
  unsigned short int vres;
  unsigned short int hres;
  char palette[48];
  char reserved;
  char color_layers;
  unsigned short int bytes_per_line;
  unsigned short int palette_type;
  char unused[58];
}PCX_Header;

unsigned char bytes_per_pixel;

void readpcximage(FILE * file,void * target,int size)
{
  unsigned char buf;
  unsigned int counter;
  int i=0;
  while(i<=size)  /* Image not entirely read? */
    {
      /* Get one byte */
      fread(&buf,1,1,file);
      /* Check the 2 most significant bits */
      if ((buf&192)==192)
	{
	  /* We have 11xxxxxx */
	  counter=(buf&63);         /* Number of times to repeat next byte */
	  fread(&buf,1,1,file);     /* Get next byte */
	  for(;counter>0;counter--) /* and copy it counter times */
	    {
	      ((char*)target)[i]=buf;
	      i++;   /* increase the number of bytes written */
	    }
	}
      else
	{
	  /* Just copy the byte */
	  ((char*)target)[i]=buf;
	  i++;   /* Increase the number of bytes written */
	}
    }
}

void *readpcx(FILE *file, char *palette,unsigned short int *length, unsigned short int *height)
     /* Returns NULL if failed, otherwise a pointer to the loaded image */
{
  PCX_Header header;
  void *target;
  fseek(file,0,SEEK_SET);
  fread(&header,sizeof(PCX_Header),1,file);   /* read the header */
  /* Check if this file is in pcx format */
  if((header.signature!=0x0a)||(header.version!=5))
    return(NULL);
  else
    {/* it is! */
      /* Return height and length */
      *length=header.xmax+1-header.xmin;
      *height=header.ymax+1-header.ymin;
      /* Allocate the sprite buffer */
      target=(void *)malloc((*length)*(*height));
      /* Read the image */
      readpcximage(file,target,(*length)*(*height));
      fseek(file,-768,SEEK_END);
      /* Get the palette */
      fread(palette,1,768,file);
      /* PCX succesfully read! */
      return(target);
    }
}

void init_ggi(ggi_visual_t * vis)
{
  ggi_mode sgmode;
  if (ggiInit()!=0) 
    {
      printf("Error initialising GGI!\n");
      exit(1);
    }
  if ((*vis=ggiOpen(NULL))==NULL) 
    {
      printf("Error opening visual!\n"); 
      ggiExit(); 
      exit(1);
    }
  /* Trying 8 bpp mode */
  if (!ggiCheckGraphMode(*vis,320,200,320,200,GT_8BIT,&sgmode))
    {
      if (ggiSetGraphMode(*vis,320,200,320,200,GT_8BIT))
	{
	  printf("Error initialising 8 bpp mode!\n"); 
	  exit(1);
	}
      /* Sucess */
      printf("Using 8bpp mode\n");
      bytes_per_pixel=1;
    }
  /* Can't use 8 bpp, trying 16... */
  else if (!ggiCheckGraphMode(*vis,320,200,320,200,GT_16BIT,&sgmode))
    {
      if (ggiSetGraphMode(*vis,320,200,320,200,GT_16BIT)) 
	{
	  printf("Error initialising 16 bpp mode!\n"); 
	  exit(1);
	}
      printf("Using 16bpp mode\n");
      bytes_per_pixel=2;
    }
  /* Neither 8 nor 16 work... Perhaps 24? */
  else if (!ggiCheckGraphMode(*vis,320,200,320,200,GT_24BIT,&sgmode))
    {
      if (ggiSetGraphMode(*vis,320,200,320,200,GT_24BIT)) 
	{
	  printf("Error initialising 24 bpp mode!!\n"); 
	  exit(1);
	}
      printf("Using 24bpp mode\n");
      bytes_per_pixel=3;
    }
  /* Here the user may have a 32bpp depth... But we don't handle this */
  else 
    {
      printf("No valid mode found!\n");
      exit(1);
    }
}

void exit_ggi(ggi_visual_t * vis)
{
  ggiClose(*vis);
  ggiExit();
}

void adapttodepth(ggi_visual_t vis, void * target, void * source, int lenght, char * palette, ggi_color * ggipalette)
/* Return in target a ready-to-display image from the 8bit source. 
   The corresponding palette must be set and target must be allocated */
{
  int i;
  switch(bytes_per_pixel)
    {
    case 1:
      /* 256 color mode, no adaptation needed */
      memcpy(target,source,lenght);
      break;
    case 2:
      /* 16bpp mode, we can use ggiMapColor */
      for(i=0;i<lenght;i++)
	((unsigned short int*)target)[i]=ggiMapColor(vis,&(ggipalette[((unsigned char*)source)[i]]));
      break;
    case 3:
      /* 24bpp mode, read the values directly from the palette */
      for(i=0;i<lenght;i++)
	{
	  ((unsigned char*)target)[(i*3)]=palette[(((unsigned char*)source)[i]*3)+2];
	  ((unsigned char*)target)[(i*3)+2]=palette[(((unsigned char*)source)[i]*3)];
	  ((unsigned char*)target)[(i*3)+1]=palette[(((unsigned char*)source)[i]*3)+1];
	}
      break;
    }
}

int main(int argc, char ** argv)
{
  ggi_visual_t vis;
  FILE * file;
  void * image;  /* 8 bit image as read from the pcx */
  void * trueimage;  /* Adapted to depth image */
  int i;
  unsigned short int lenght, height;
  unsigned char palette[768];
  ggi_color ggi_palette[768];
  if (argc!=2) 
    {
      printf("Usage: svgadisplaypcx filename.pcx.\n"); 
      return(1);
    }
  if ((file=fopen(argv[1],"r"))==NULL) 
    {
      printf("Can't open file!\n");
      return(1);
    }
  if ((image=readpcx(file,palette,&lenght,&height))==NULL)
    {
      printf("Error loading file!\n"); 
      return(1);
    }
  fclose(file);
  printf("Image is %dx%d sized.\n",lenght,height);
  if((lenght>320)||(height>200)) 
    {
      printf("Image is too big!\n");
      return(1);
    }
  for (i=0;i<256;i++)
    {
      ggi_palette[i].r=palette[i*3]<<8;
      ggi_palette[i].g=palette[(i*3)+1]<<8;
      ggi_palette[i].b=palette[(i*3)+2]<<8;
    }
  init_ggi(&vis);
  ggiSetPalette(vis,0,256,ggi_palette);
  trueimage=(void*)calloc(lenght*height,bytes_per_pixel);
  adapttodepth(vis, trueimage, image, lenght*height,palette,ggi_palette);
  ggiSetGCForeground(vis,0);
  ggiFillscreen(vis);
  ggiPutBox(vis,0,0,lenght,height,trueimage);
  ggiFlush(vis);
  ggiGetc(vis);
  /* Don't forget to free the allocated images! */
  free(trueimage);
  free(image);
  exit_ggi(&vis);
  return(0);
}

Compilation:

$gcc ggipcxview.c -o ggipcxview -lggi

You've certainly noticed that A LOT of the code is for error handling. If the graphic mode set fails, if the file can't be read, etc... We can discuss a lot about the necessity of writing so many code just to check if everything is okay. But from the user side, it just look more professional to see "Error! Can't open file!" than "Segmentation fault". Even if the result is the same and come from a user's error :).

Okay, now let's quit graphics programming for the moment. They give headhaches. Keyboard handling is easier and will allow you to interact with your programs.

Keyboard Input with Svgalib.
You have to include one more file to use the keyboard with Svgalib:

#include <vgakeyboard.h>

First of all, the keyboard has to be initialized. This is done by:

keyboard_init();

It will initialize the keyboard in raw mode. Svgalib will be directly informed of the status of each key. You have to update it with

keyboard_update();

NEVER FORGET IT! For example if you wait for a keypress to break a loop and don't call keyboard_update(); in that loop, you won't be able to quit the program (even by ctrl-c) and you will have to hardware reset your machine!

To have access to the status, query Svgalib with

keyboard_keypressed(int scancode);

Scancode is an integer. As you certainly don't know the scancode of each key, a large number of constants have been defined. You can check them in the keyboard_keypressed manpage or directly in /usr/(local/)include/vgakeyboard.h. If the scancode key is pressed this function will return 1, else it will return 0.

If there is a init, there is a close. Don't forget to close the keyboard when you don't need it anymore with:

keyboard_close();

And that's all! Quite easy, isn't it? Now a little example for fun:

vgakeytest.c:
#include <vga.h>
#include <vgagl.h>
#include <vgakeyboard.h>

int main()
{
  vga_init();
  keyboard_init();
  while(!keyboard_keypressed(ESCAPE))
    {
      keyboard_update();
      if(keyboard_keypressed(LEFTCONTROL)) printf("LEFTCONTROL ");
      if(keyboard_keypressed(LEFTCONTROL)) printf("RIGHCONTROL ");
      printf("\n");
    }
  keyboard_close();
  return(0);
}

The compilation line is

$gcc vgakeytest.c -o vgakeytest -lvgagl -lvga

Keyboard Input with GGI, an introduction to GGI events.
With GGI things are a bit different. Svgalib has a set of keyboard-only functions. GGI directly handle all the inputs. When you press a key, move your mouse, click,... an event is queued, ready to be read. Concretely when with svgalib you just have to check if the key is pressed, with GGI you'll have to update yourself your keypress variables depending of the events you've read. First let's examine the data structure:

typedef union ggi_event 
{
  uint8           size;           /* size of this event   */
  ggi_any_event   any;            /* access COMMON_DATA   */
  ggi_cmd_event   cmd;            /* command/broadcast    */
  ggi_raw_event   raw;            /* raw data event       */
  ggi_val_event   val;            /* valuator change      */
  ggi_key_event   key;            /* key press/release    */
  ggi_pmove_event pmove;          /* pointer move         */
  ggi_pbutton_event pbutton;      /* pointer buttons      */
} ggi_event;

In this structure two elements are interesting for us:

typedef struct
{
  COMMON_DATA;
  uint32	modifiers;	/* current modifiers in effect */
  uint32	sym;		/* meaning of key	*/
  uint32	label;		/* label on key		*/
  uint32	button;		/* button number	*/
} gii_key_event;

typedef gii_key_event		ggi_key_event;

typedef struct 
{
  COMMON_DATA;
} gii_any_event;

typedef gii_any_event		ggi_any_event;

Where COMMON_DATA is:

#define	COMMON_DATA  \
  uint8	size;		/* size of event in bytes	*/\
  uint8	type;		/* type of this event		*/\
  sint16 error;		/* error (for replies)		*/\
  uint32 origin;	/* origin device (etc)		*/\
  uint32 target;	/* target device (etc)		*/\
  struct timeval time	/* timestamp			*/

To read a key we'll need only two elements:
ggi_event.any.type, to check if the event is a keypress or a keyrelease,
ggi_event.key.sym, to know which key is concerned.

The type of the event can be one of these:
typedef enum ggi_event_type 
{
  evNothing = 0,  /* event is not valid. (must be zero)   */
  evCommand,      /* report command/do action             */
  evBroadcast,    /* notification of general interest     */
  evDeviceInfo,   /* report input device information      */
  evRawData,      /* raw data received from device        */
  evKeyPress,     /* key has been pressed                 */
  evKeyRelease,   /* key has been released                */
  evKeyRepeat,    /* automatically repeated keypress      */
  evKeyState,     /* resynchronize keys state             */

  evPtrRelative,  /* pointer movements reported relative  */
  evPtrAbsolute,  /* pointer movements reported absolute  */
  evPtrButtonPress,       /* pointer button pressed       */
  evPtrButtonRelease,     /* pointer button released      */
  evPtrState,     /* resynchronize pointer state          */

  evValRelative,  /* valuator change (reported relative)  */
  evValAbsolute,  /* valuator change (reported absolute)  */
  evValState,     /* resynchronize valuator state         */

  evLast          /* must be less than 33                 */
}  ggi_event_type;

In our case, only evKeyPress and evKeyRelease are interesting.

Now, let's program!

First we have to check if there is an interesting event for us.

ggi_event_mask ggiEventPoll(ggi_visual_t vis, ggi_event_mask*mask, struct timeval t);

This function will return a mask of events which are available from the mask parameter for the visual vis. With t you can control the timeout. Easier to understand with an example:

ggi_event_mask mask;
ggi_visual_t vis;
struct timeval t={0,0};

mask=ggiEventPoll(vis,emKey,t);

Will return EmKey if a keyboard event is available immediatly in vis.

struct timeval t={1,0}

mask=ggiEventPoll(vis,EmKey | emPointer,t);

This one will wait for 1 second for a keyboard or pointer event. If one of them (or the two) happen before the timeout it is returned. Else the function will return 0.

But what can be ggi_event_mask? See below:

typedef enum 
{
  EVMASK(Nothing),
  EVMASK(Command),
  EVMASK(Information),
  EVMASK(Expose),

  EVMASK(KeyPress),
  EVMASK(KeyRelease),
  EVMASK(KeyRepeat),
  emKey		= emKeyPress | emKeyRelease | emKeyRepeat,
  emKeyboard	= emKey,

  EVMASK(PtrRelative),
  EVMASK(PtrAbsolute),
  EVMASK(PtrButtonPress),
  EVMASK(PtrButtonRelease),
  emPtrMove	= emPtrRelative | emPtrAbsolute,
  emPtrButton	= emPtrButtonPress | emPtrButtonRelease,
  emPointer	= emPtrMove | emPtrButton,

  EVMASK(ValRelative),
  EVMASK(ValAbsolute),
  emValuator	= emValRelative | emValAbsolute,

  emZero  = 0,
  emAll	= ((1 << evLast) - 1) & ~emNothing
} gii_event_mask;

typedef gii_event_mask		ggi_event_mask;

If ggiEventPoll returns positive, we can get our event with

int ggiEventRead(ggi_visual_t vis, ggi_event *ev, ggi_event_mask mask);

It will place in ev the event of type mask, provided it is available. After that you can check the event.

Now our traditionnal example program (need a 16bpp depth to run, but you can easily change that):

ggikeytest.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <ggi/ggi.h>

int main()
{
  ggi_visual_t vis;
  int exit=0;
  ggi_event event;
  ggi_event_mask mask;
  struct timeval t={0,0};
  char ctrlpressed=0,spacepressed=0;

  ggiInit();
  vis=ggiOpen(NULL);
  ggiAddFlags(vis,GGIFLAG_ASYNC);
  ggiSetGraphMode(vis,320,200,320,200,GT_16BIT);
  ggiSetGCBackground(vis,0);
  while(!exit)
    {
      while((mask=ggiEventPoll(vis,emKey,&t)))
	{
	  ggiEventRead(vis,&event,emKey);
	  if(event.any.type==evKeyPress)
	    {
	      switch (event.key.sym)
		{
		case GIIUC_Space: spacepressed=1;
		  break;
		case GIIK_Ctrl: ctrlpressed=1;
		  break;
		}
	    }
	  if(event.any.type==evKeyRelease)
	    {
	      switch (event.key.sym)
		{
		case GIIUC_Space: spacepressed=0;
		  break;
		case GIIK_Ctrl: ctrlpressed=0;
		  break;
		}
	    }
	}
      ggiSetGCForeground(vis,0);
      ggiFillscreen(vis);
      ggiSetGCForeground(vis,0xFFFFFF);
      if(ctrlpressed)  ggiPuts(vis,0,0,"Ctrl");
      if(spacepressed) ggiPuts(vis,50,0,"Space");
      ggiFlush(vis);
    } 
  ggiClose(vis);
  ggiExit();
}

$gcc ggikeytest.c -o ggikeytest -lggi

to compile.

The sym of each key can be found in the /usr/(local/)include/ggi/keyboard.h file.

Synthesis
This one is a do-it-yourself part :). Use the display and keyboard functions we've seen to make the display dependant of the keyboard. For example, try to make a moving sprite... I'll write a concrete example later, but for now try to do it yourself! If you manage, that mean you are capable of writing a small pong or tron like game.