text: /*         ______   ___    ___ 
   *        /\  _  \ /\_ \  /\_ \ 
   *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
   *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
   *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
   *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
   *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
   *                                           /\____/
   *                                           \_/__/
   *
   *      Graphics mode set and bitmap creation routines.
   *
   *      By Shawn Hargreaves.
   *
   *      See readme.txt for copyright information.
   */
  
  
  #include <string.h>
  
  #include "allegro.h"
  #include "allegro/internal/aintern.h"
  
  extern void blit_end(void);   /* for LOCK_FUNCTION; defined in blit.c */
  
  
  
  #define PREFIX_I                "al-gfx INFO: "
  #define PREFIX_W                "al-gfx WARNING: "
  #define PREFIX_E                "al-gfx ERROR: "
  
  
  
  static int gfx_virgin = TRUE;          /* is the graphics system active? */
  
  int _sub_bitmap_id_count = 1;          /* hash value for sub-bitmaps */
  
  int _gfx_mode_set_count = 0;           /* has the graphics mode changed? */
  
  int _screen_split_position = 0;        /* has the screen been split? */
  
  int _safe_gfx_mode_change = 0;         /* are we getting through GFX_SAFE? */
  
  RGB_MAP *rgb_map = NULL;               /* RGB -> palette entry conversion */
  
  COLOR_MAP *color_map = NULL;           /* translucency/lighting table */
  
  int _color_depth = 8;                  /* how many bits per pixel? */
  
  int _refresh_rate_request = 0;         /* requested refresh rate */
  static int current_refresh_rate = 0;   /* refresh rate set by the driver */
  
  int _wait_for_vsync = TRUE;            /* vsync when page-flipping? */
  
  int _color_conv = COLORCONV_TOTAL;     /* which formats to auto convert? */
  
  static int color_conv_set = FALSE;     /* has the user set conversion mode? */
  
  int _palette_color8[256];               /* palette -> pixel mapping */
  int _palette_color15[256];
  int _palette_color16[256];
  int _palette_color24[256];
  int _palette_color32[256];
  
  int *palette_color = _palette_color8; 
  
  BLENDER_FUNC _blender_func15 = NULL;   /* truecolor pixel blender routines */
  BLENDER_FUNC _blender_func16 = NULL;
  BLENDER_FUNC _blender_func24 = NULL;
  BLENDER_FUNC _blender_func32 = NULL;
  
  BLENDER_FUNC _blender_func15x = NULL;
  BLENDER_FUNC _blender_func16x = NULL;
  BLENDER_FUNC _blender_func24x = NULL;
  
  int _blender_col_15 = 0;               /* for truecolor lit sprites */
  int _blender_col_16 = 0;
  int _blender_col_24 = 0;
  int _blender_col_32 = 0;
  
  int _blender_alpha = 0;                /* for truecolor translucent drawing */
  
  int _rgb_r_shift_15 = DEFAULT_RGB_R_SHIFT_15;     /* truecolor pixel format */
  int _rgb_g_shift_15 = DEFAULT_RGB_G_SHIFT_15;
  int _rgb_b_shift_15 = DEFAULT_RGB_B_SHIFT_15;
  int _rgb_r_shift_16 = DEFAULT_RGB_R_SHIFT_16;
  int _rgb_g_shift_16 = DEFAULT_RGB_G_SHIFT_16;
  int _rgb_b_shift_16 = DEFAULT_RGB_B_SHIFT_16;
  int _rgb_r_shift_24 = DEFAULT_RGB_R_SHIFT_24;
  int _rgb_g_shift_24 = DEFAULT_RGB_G_SHIFT_24;
  int _rgb_b_shift_24 = DEFAULT_RGB_B_SHIFT_24;
  int _rgb_r_shift_32 = DEFAULT_RGB_R_SHIFT_32;
  int _rgb_g_shift_32 = DEFAULT_RGB_G_SHIFT_32;
  int _rgb_b_shift_32 = DEFAULT_RGB_B_SHIFT_32;
  int _rgb_a_shift_32 = DEFAULT_RGB_A_SHIFT_32;
  
  
  /* lookup table for scaling 5 bit colors up to 8 bits */
  int _rgb_scale_5[32] =
  {
     0,   8,   16,  24,  33,  41,  49,  57,
     66,  74,  82,  90,  99,  107, 115, 123,
     132, 140, 148, 156, 165, 173, 181, 189,
     198, 206, 214, 222, 231, 239, 247, 255
  };
  
  
  /* lookup table for scaling 6 bit colors up to 8 bits */
  int _rgb_scale_6[64] =
  {
     0,   4,   8,   12,  16,  20,  24,  28,
     32,  36,  40,  44,  48,  52,  56,  60,
     65,  69,  73,  77,  81,  85,  89,  93,
     97,  101, 105, 109, 113, 117, 121, 125,
     130, 134, 138, 142, 146, 150, 154, 158,
     162, 166, 170, 174, 178, 182, 186, 190,
     195, 199, 203, 207, 211, 215, 219, 223,
     227, 231, 235, 239, 243, 247, 251, 255
  };
  
  
  GFX_VTABLE _screen_vtable;             /* accelerated drivers change this */
  
  
  typedef struct VRAM_BITMAP             /* list of video memory bitmaps */
  {
     int x, y, w, h;
     BITMAP *bmp;
     struct VRAM_BITMAP *next_x, *next_y;
  } VRAM_BITMAP;
  
  
  static VRAM_BITMAP *vram_bitmap_list = NULL;
  
  
  #define BMP_MAX_SIZE  46340   /* sqrt(INT_MAX) */
  
  /* the product of these must fit in an int */
  static int failed_bitmap_w = BMP_MAX_SIZE;
  static int failed_bitmap_h = BMP_MAX_SIZE;
  
  
  
  static int _set_gfx_mode(int card, int w, int h, int v_w, int v_h, int allow_config);
  static int _set_gfx_mode_safe(int card, int w, int h, int v_w, int v_h);
  
  
  
  /* lock_bitmap:
   *  Locks all the memory used by a bitmap structure.
   */
  void lock_bitmap(BITMAP *bmp)
  {
     LOCK_DATA(bmp, sizeof(BITMAP) + sizeof(char *) * bmp->h);
  
     if (bmp->dat) {
        LOCK_DATA(bmp->dat, bmp->w * bmp->h * BYTES_PER_PIXEL(bitmap_color_depth(bmp)));
     }
  }
  
  
  
  /* request_refresh_rate:
   *  Requests that the next call to set_gfx_mode() use the specified refresh
   *  rate.
   */
  void request_refresh_rate(int rate)
  {
     _refresh_rate_request = rate;
  }
  
  
  
  /* get_refresh_rate:
   *  Returns the refresh rate set by the most recent call to set_gfx_mode().
   */
  int get_refresh_rate(void)
  {
     return current_refresh_rate;
  }
  
  
  
  /* _set_current_refresh_rate:
   *  Sets the current refresh rate.
   *  (This function must be called by the gfx drivers)
   */
  void _set_current_refresh_rate(int rate)
  {
     /* sanity check to discard bogus values */
     if ((rate<40) || (rate>200))
        rate = 0;
  
     current_refresh_rate = rate;
  
     /* adjust retrace speed */
     _vsync_speed = rate ? BPS_TO_TIMER(rate) : BPS_TO_TIMER(70);
  }
  
  
  
  /* sort_gfx_mode_list:
   *  Callback for quick-sorting a mode-list.
   */
  static int sort_gfx_mode_list(GFX_MODE *entry1, GFX_MODE *entry2)
  {
     if (entry1->width > entry2->width) {
        return +1;
     }
     else if (entry1->width < entry2->width) {
        return -1;
     }
     else {
        if (entry1->height > entry2->height) {
           return +1;
        }
        else if (entry1->height < entry2->height) {
           return -1;
        }
        else {
           if (entry1->bpp > entry2->bpp) {
              return +1;
           }
           else if (entry1->bpp < entry2->bpp) {
              return -1;
           }
           else {
              return 0;
           }
        }
     }
  }
  
  
  
  /* get_gfx_mode_list:
   *  Attempts to create a list of all the supported video modes for a certain
   *  GFX driver.
   */
  GFX_MODE_LIST *get_gfx_mode_list(int card)
  {
     _DRIVER_INFO *list_entry;
     GFX_DRIVER *drv = NULL;
     GFX_MODE_LIST *gfx_mode_list = NULL;
  
     ASSERT(system_driver);
  
     /* ask the system driver for a list of graphics hardware drivers */
     if (system_driver->gfx_drivers)
        list_entry = system_driver->gfx_drivers();
     else
        list_entry = _gfx_driver_list;
  
     /* find the graphics driver, and if it can fetch mode lists, do so */
     while (list_entry->driver) {
        if (list_entry->id == card) {
           drv = list_entry->driver;
           if (!drv->fetch_mode_list)
              return NULL;
  
           gfx_mode_list = drv->fetch_mode_list();
           if (!gfx_mode_list)
              return NULL;
  
           break;
        }
  
        list_entry++;
     }
  
     if (!drv)
        return NULL;
  
     /* sort the list and finish */
     qsort(gfx_mode_list->mode, gfx_mode_list->num_modes, sizeof(GFX_MODE),
           (int (*) (AL_CONST void *, AL_CONST void *))sort_gfx_mode_list);
  
     return gfx_mode_list;
  }
  
  
  
  /* destroy_gfx_mode_list:
   *  Removes the mode list created by get_gfx_mode_list() from memory.
   */
  void destroy_gfx_mode_list(GFX_MODE_LIST *gfx_mode_list)
  {
     if (gfx_mode_list) {
        if (gfx_mode_list->mode)
           _AL_FREE(gfx_mode_list->mode);
  
        _AL_FREE(gfx_mode_list);
     }
  }
  
  
  
  /* set_color_depth:
   *  Sets the pixel size (in bits) which will be used by subsequent calls to 
   *  set_gfx_mode() and create_bitmap(). Valid depths are 8, 15, 16, 24 and 32.
   */
  void set_color_depth(int depth)
  {
     _color_depth = depth;
  
     switch (depth) {
        case 8:  palette_color = _palette_color8;  break;
        case 15: palette_color = _palette_color15; break;
        case 16: palette_color = _palette_color16; break;
        case 24: palette_color = _palette_color24; break;
        case 32: palette_color = _palette_color32; break;
        default: ASSERT(FALSE);
     }
  }
  
  
  
  /* get_color_depth:
   *  Returns the current color depth.
   */
  int get_color_depth(void)
  {
     return _color_depth;
  }
  
  
  
  /* set_color_conversion:
   *  Sets a bit mask specifying which types of color format conversions are
   *  valid when loading data from disk.
   */
  void set_color_conversion(int mode)
  {
     _color_conv = mode;
  
     color_conv_set = TRUE;
  }
  
  
  
  /* get_color_conversion:
   *  Returns the bitmask specifying which types of color format
   *  conversion are valid when loading data from disk.
   */
  int get_color_conversion(void)
  {
     return _color_conv;
  }
  
  
  
  /* _color_load_depth:
   *  Works out which color depth an image should be loaded as, given the
   *  current conversion mode.
   */
  int _color_load_depth(int depth, int hasalpha)
  {
     typedef struct CONVERSION_FLAGS
     {
        int flag;
        int in_depth;
        int out_depth;
        int hasalpha;
     } CONVERSION_FLAGS;
  
     static CONVERSION_FLAGS conversion_flags[] =
     {
        { COLORCONV_8_TO_15,   8,  15, FALSE },
        { COLORCONV_8_TO_16,   8,  16, FALSE },
        { COLORCONV_8_TO_24,   8,  24, FALSE },
        { COLORCONV_8_TO_32,   8,  32, FALSE },
        { COLORCONV_15_TO_8,   15, 8,  FALSE },
        { COLORCONV_15_TO_16,  15, 16, FALSE },
        { COLORCONV_15_TO_24,  15, 24, FALSE },
        { COLORCONV_15_TO_32,  15, 32, FALSE },
        { COLORCONV_16_TO_8,   16, 8,  FALSE },
        { COLORCONV_16_TO_15,  16, 15, FALSE },
        { COLORCONV_16_TO_24,  16, 24, FALSE },
        { COLORCONV_16_TO_32,  16, 32, FALSE },
        { COLORCONV_24_TO_8,   24, 8,  FALSE },
        { COLORCONV_24_TO_15,  24, 15, FALSE },
        { COLORCONV_24_TO_16,  24, 16, FALSE },
        { COLORCONV_24_TO_32,  24, 32, FALSE },
        { COLORCONV_32_TO_8,   32, 8,  FALSE },
        { COLORCONV_32_TO_15,  32, 15, FALSE },
        { COLORCONV_32_TO_16,  32, 16, FALSE },
        { COLORCONV_32_TO_24,  32, 24, FALSE },
        { COLORCONV_32A_TO_8,  32, 8 , TRUE  },
        { COLORCONV_32A_TO_15, 32, 15, TRUE  },
        { COLORCONV_32A_TO_16, 32, 16, TRUE  },
        { COLORCONV_32A_TO_24, 32, 24, TRUE  }
     };
  
     int i;
  
     ASSERT((_gfx_mode_set_count > 0) || (color_conv_set));
  
     if (depth == _color_depth)
        return depth;
  
     for (i=0; i < (int)(sizeof(conversion_flags)/sizeof(CONVERSION_FLAGS)); i++) {
        if ((conversion_flags[i].in_depth == depth) &&
  	  (conversion_flags[i].out_depth == _color_depth) &&
  	  ((conversion_flags[i].hasalpha != 0) == (hasalpha != 0))) {
  	 if (_color_conv & conversion_flags[i].flag)
  	    return _color_depth;
  	 else
  	    return depth;
        }
     }
  
     ASSERT(FALSE);
     return 0;
  }
  
  
  
  /* _get_vtable:
   *  Returns a pointer to the linear vtable for the specified color depth.
   */
  GFX_VTABLE *_get_vtable(int color_depth)
  {
     GFX_VTABLE *vt;
     int i;
  
     ASSERT(system_driver);
  
     if (system_driver->get_vtable) {
        vt = system_driver->get_vtable(color_depth);
  
        if (vt) {
  	 LOCK_DATA(vt, sizeof(GFX_VTABLE));
  	 LOCK_CODE(vt->draw_sprite, (long)vt->draw_sprite_end - (long)vt->draw_sprite);
  	 LOCK_CODE(vt->blit_from_memory, (long)vt->blit_end - (long)vt->blit_from_memory);
  	 return vt;
        }
     }
  
     for (i=0; _vtable_list[i].vtable; i++) {
        if (_vtable_list[i].color_depth == color_depth) {
  	 LOCK_DATA(_vtable_list[i].vtable, sizeof(GFX_VTABLE));
  	 LOCK_CODE(_vtable_list[i].vtable->draw_sprite, (long)_vtable_list[i].vtable->draw_sprite_end - (long)_vtable_list[i].vtable->draw_sprite);
  	 LOCK_CODE(_vtable_list[i].vtable->blit_from_memory, (long)_vtable_list[i].vtable->blit_end - (long)_vtable_list[i].vtable->blit_from_memory);
  	 return _vtable_list[i].vtable;
        }
     }
  
     return NULL;
  }
  
  
  
  /* shutdown_gfx:
   *  Used by allegro_exit() to return the system to text mode.
   */
  static void shutdown_gfx(void)
  {
     if (gfx_driver)
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
  
     if (system_driver->restore_console_state)
        system_driver->restore_console_state();
  
     _remove_exit_func(shutdown_gfx);
  
     gfx_virgin = TRUE;
  }
  
  
  #define GFX_DRIVER_FULLSCREEN_FLAG    0x01
  #define GFX_DRIVER_WINDOWED_FLAG      0x02
  
  
  /* gfx_driver_is_valid:
   *  Checks that the graphics driver 'drv' fulfills the condition
   *  expressed by the bitmask 'flags'.
   */
  static int gfx_driver_is_valid(GFX_DRIVER *drv, int flags)
  {
     if ((flags & GFX_DRIVER_FULLSCREEN_FLAG) && drv->windowed)
        return FALSE;
  
     if ((flags & GFX_DRIVER_WINDOWED_FLAG) && !drv->windowed)
        return FALSE;
  
     return TRUE;
  }
  
  
  
  /* get_gfx_driver_from_id:
   *  Retrieves a pointer to the graphics driver identified by 'card' from
   *  the list 'driver_list' or NULL if it doesn't exist.
   */
  static GFX_DRIVER *get_gfx_driver_from_id(int card, _DRIVER_INFO *driver_list)
  {
     int c;
  
     for (c=0; driver_list[c].driver; c++) {
        if (driver_list[c].id == card)
  	 return driver_list[c].driver;
     }
  
     return NULL;
  }
  
  
  
  /* init_gfx_driver:
   *  Helper function for initializing a graphics driver.
   */
  static BITMAP *init_gfx_driver(GFX_DRIVER *drv, int w, int h, int v_w, int v_h)
  {
     drv->name = drv->desc = get_config_text(drv->ascii_name);
  
     /* set gfx_driver so that it is visible when initializing the driver */
     gfx_driver = drv;
  
     return drv->init(w, h, v_w, v_h, _color_depth);
  }
  
  
  
  /* get_config_gfx_driver:
   *  Helper function for set_gfx_mode: it reads the gfx_card* config variables and
   *  tries to set the graphics mode if a matching driver is found. Returns TRUE if
   *  at least one matching driver was found, FALSE otherwise.
   */
  static int get_config_gfx_driver(char *gfx_card, int w, int h, int v_w, int v_h, int flags, _DRIVER_INFO *driver_list)
  {
     char buf[512], tmp[64];
     GFX_DRIVER *drv;
     int found = FALSE;
     int card, n;
  
     /* try the drivers that are listed in the config file */
     for (n=-2; n<255; n++) {
        switch (n) {
  
  	 case -2:
  	    /* example: gfx_card_640x480x16 = */
  	    uszprintf(buf, sizeof(buf), uconvert_ascii("%s_%dx%dx%d", tmp), gfx_card, w, h, _color_depth);
  	    break;
  
  	 case -1:
  	    /* example: gfx_card_24bpp = */
  	    uszprintf(buf, sizeof(buf), uconvert_ascii("%s_%dbpp", tmp), gfx_card, _color_depth);
  	    break;
  
  	 case 0:
  	    /* example: gfx_card = */
  	    uszprintf(buf, sizeof(buf), uconvert_ascii("%s", tmp), gfx_card);
  	    break;
  
  	 default:
  	    /* example: gfx_card1 = */
  	    uszprintf(buf, sizeof(buf), uconvert_ascii("%s%d", tmp), gfx_card, n);
  	    break;
        }
  
        card = get_config_id(uconvert_ascii("graphics", tmp), buf, 0);
  
        if (card) {
  	 drv = get_gfx_driver_from_id(card, driver_list);
  
  	 if (drv && gfx_driver_is_valid(drv, flags)) {
  	    found = TRUE;
  	    screen = init_gfx_driver(drv, w, h, v_w, v_h);
  
  	    if (screen)
  	       break;
  	 }
        }
        else {
  	 /* Stop searching the gfx_card#n (n>0) family at the first missing member,
  	  * except gfx_card1 which could have been identified with gfx_card.
  	  */
  	 if (n > 1)
  	    break;
        }
     }
  
     return found;
  }
  
  
  
  /* set_gfx_mode:
   *  Sets the graphics mode. The card should be one of the GFX_* constants
   *  from allegro.h, or GFX_AUTODETECT to accept any graphics driver. Pass
   *  GFX_TEXT to return to text mode (although allegro_exit() will usually 
   *  do this for you). The w and h parameters specify the screen resolution 
   *  you want, and v_w and v_h specify the minumum virtual screen size. 
   *  The graphics drivers may actually create a much larger virtual screen, 
   *  so you should check the values of VIRTUAL_W and VIRTUAL_H after you
   *  set the mode. If unable to select an appropriate mode, this function 
   *  returns -1.
   */
  int set_gfx_mode(int card, int w, int h, int v_w, int v_h)
  {
     TRACE(PREFIX_I "Called set_gfx_mode(%d, %d, %d, %d, %d).\n",
  	 card, w, h, v_w, v_h);
           
     /* TODO: shouldn't this be incremented only IF successful? */
     _gfx_mode_set_count++;
  
     /* special bodge for the GFX_SAFE driver */
     if (card == GFX_SAFE)
        return _set_gfx_mode_safe(card, w, h, v_w, v_h);
     else
        return _set_gfx_mode(card, w, h, v_w, v_h, TRUE);
  }
  
  
  
  /* _set_gfx_mode:
   *  Called by set_gfx_mode(). Separated to make a clear difference between
   *  the virtual GFX_SAFE driver and the rest. The allow_config parameter,
   *  if true, allows the configuration to override the graphics card/driver
   *  when using GFX_AUTODETECT.
   */
  static int _set_gfx_mode(int card, int w, int h, int v_w, int v_h, int allow_config)
  {
     _DRIVER_INFO *driver_list;
     GFX_DRIVER *drv;
     char tmp1[64], tmp2[64];
     AL_CONST char *dv;
     int flags = 0;
     int c;
     ASSERT(system_driver);
     ASSERT(card != GFX_SAFE);
  
     /* remember the current console state */
     if (gfx_virgin) {
        TRACE(PREFIX_I "First call, remembering console state.\n");
        LOCK_FUNCTION(_stub_bank_switch);
        LOCK_FUNCTION(blit);
  
        if (system_driver->save_console_state)
  	 system_driver->save_console_state();
  
        _add_exit_func(shutdown_gfx, "shutdown_gfx");
  
        gfx_virgin = FALSE;
     }
  
     timer_simulate_retrace(FALSE);
     _screen_split_position = 0;
  
     /* close down any existing graphics driver */
     if (gfx_driver) {
        TRACE(PREFIX_I "Closing graphics driver (%p) ", gfx_driver);
        TRACE("%s.\n", gfx_driver->ascii_name);
        if (_al_linker_mouse)
           _al_linker_mouse->show_mouse(NULL);
  
        while (vram_bitmap_list)
  	 destroy_bitmap(vram_bitmap_list->bmp);
  
        bmp_read_line(screen, 0);
        bmp_write_line(screen, 0);
        bmp_unwrite_line(screen);
  
        if (gfx_driver->scroll)
  	 gfx_driver->scroll(0, 0);
  
        if (gfx_driver->exit)
  	 gfx_driver->exit(screen);
  
        destroy_bitmap(screen);
  
        gfx_driver = NULL;
        screen = NULL;
        gfx_capabilities = 0;
     }
  
     /* We probably don't want to do this because it makes
      * Allegro "forget" the color layout of previously set
      * graphics modes. But it should be retained if bitmaps
      * created in those modes are to be used in the new mode.
      */
  #if 0
     /* restore default truecolor pixel format */
     _rgb_r_shift_15 = 0; 
     _rgb_g_shift_15 = 5;
     _rgb_b_shift_15 = 10;
     _rgb_r_shift_16 = 0;
     _rgb_g_shift_16 = 5;
     _rgb_b_shift_16 = 11;
     _rgb_r_shift_24 = 0;
     _rgb_g_shift_24 = 8;
     _rgb_b_shift_24 = 16;
     _rgb_r_shift_32 = 0;
     _rgb_g_shift_32 = 8;
     _rgb_b_shift_32 = 16;
     _rgb_a_shift_32 = 24;
  #endif
  
     gfx_capabilities = 0;
  
     _set_current_refresh_rate(0);
  
     /* return to text mode? */
     if (card == GFX_TEXT) {
        TRACE(PREFIX_I "Closing, restoring original console state.\n");
        if (system_driver->restore_console_state)
  	 system_driver->restore_console_state();
  
        if (_gfx_bank) {
  	 _AL_FREE(_gfx_bank);
  	 _gfx_bank = NULL;
        }
  
        TRACE(PREFIX_I "Graphic mode closed.\n");
        return 0;
     }
  
     /* now to the interesting part: let's try to find a graphics driver */
     usetc(allegro_error, 0);
  
     /* ask the system driver for a list of graphics hardware drivers */
     if (system_driver->gfx_drivers)
        driver_list = system_driver->gfx_drivers();
     else
        driver_list = _gfx_driver_list;
  
     /* filter specific fullscreen/windowed driver requests */
     if (card == GFX_AUTODETECT_FULLSCREEN) {
        flags |= GFX_DRIVER_FULLSCREEN_FLAG;
        card = GFX_AUTODETECT;
     }
     else if (card == GFX_AUTODETECT_WINDOWED) {
        flags |= GFX_DRIVER_WINDOWED_FLAG;
        card = GFX_AUTODETECT;
     }
  
     if (card == GFX_AUTODETECT) {
        /* autodetect the driver */
        int found = FALSE;
  
        tmp1[0] = '\0';
  
        /* first try the config variables */
        if (allow_config) {
  	 /* try the gfx_card variable if GFX_AUTODETECT or GFX_AUTODETECT_FULLSCREEN was selected */
  	 if (!(flags & GFX_DRIVER_WINDOWED_FLAG))
  	    found = get_config_gfx_driver(uconvert_ascii("gfx_card", tmp1), w, h, v_w, v_h, flags, driver_list);
  
  	 /* try the gfx_cardw variable if GFX_AUTODETECT or GFX_AUTODETECT_WINDOWED was selected */
  	 if (!(flags & GFX_DRIVER_FULLSCREEN_FLAG) && !found)
  	    found = get_config_gfx_driver(uconvert_ascii("gfx_cardw", tmp1), w, h, v_w, v_h, flags, driver_list);
        }
  
        /* go through the list of autodetected drivers if none was previously found */
        if (!found) {
  	 TRACE(PREFIX_I "Autodetecting graphic driver.\n");
  	 for (c=0; driver_list[c].driver; c++) {
  	    if (driver_list[c].autodetect) {
  	       drv = driver_list[c].driver;
  
  	       if (gfx_driver_is_valid(drv, flags)) {
  		  screen = init_gfx_driver(drv, w, h, v_w, v_h);
  
  		  if (screen)
  		     break;
  	       }
  	    }
  	 }
        }
        else {
  	 TRACE(PREFIX_I "GFX_AUTODETECT overridden through configuration:"
  	       " %s.\n", tmp1);
        }
     }
     else {
        /* search the list for the requested driver */
        drv = get_gfx_driver_from_id(card, driver_list);
  
        if (drv)
  	 screen = init_gfx_driver(drv, w, h, v_w, v_h);
     }
  
     /* gracefully handle failure */
     if (!screen) {
        gfx_driver = NULL;  /* set by init_gfx_driver() */
  
        if (!ugetc(allegro_error))
  	 ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Unable to find a suitable graphics driver"));
  
        TRACE(PREFIX_E "Failed setting graphic driver %d.\n", card);
        return -1;
     }
  
     /* set the basic capabilities of the driver */
     if ((VIRTUAL_W > SCREEN_W) || (VIRTUAL_H > SCREEN_H)) {
        if (gfx_driver->scroll)
  	 gfx_capabilities |= GFX_CAN_SCROLL;
  
        if ((gfx_driver->request_scroll) || (gfx_driver->request_video_bitmap))
  	 gfx_capabilities |= GFX_CAN_TRIPLE_BUFFER;
     }
  
     /* check whether we are instructed to disable vsync */
     dv = get_config_string(uconvert_ascii("graphics", tmp1),
                            uconvert_ascii("disable_vsync", tmp2),
                            NULL);
  
     if ((dv) && ((c = ugetc(dv)) != 0) && ((c == 'y') || (c == 'Y') || (c == '1')))
        _wait_for_vsync = FALSE;
     else
        _wait_for_vsync = TRUE;
  
     TRACE(PREFIX_I "The driver %s wait for vsync.\n",
  	 (_wait_for_vsync) ? "will" : "won't");
  
     /* Give the gfx driver an opportunity to set the drawing mode */
     if ((gfx_driver->drawing_mode) && (!_dispsw_status))
        gfx_driver->drawing_mode();
  
     clear_bitmap(screen);
  
     /* set up the default colors */
     for (c=0; c<256; c++)
        _palette_color8[c] = c;
  
     set_palette(default_palette);
  
     if (_color_depth == 8) {
        gui_fg_color = 255;
        gui_mg_color = 8;
        gui_bg_color = 0;
     }
     else {
        gui_fg_color = makecol(0, 0, 0);
        gui_mg_color = makecol(128, 128, 128);
        gui_bg_color = makecol(255, 255, 255);
     }
  
     if (_al_linker_mouse)
        _al_linker_mouse->set_mouse_etc();
  
     LOCK_DATA(gfx_driver, sizeof(GFX_DRIVER));
  
     _register_switch_bitmap(screen, NULL);
  
     TRACE(PREFIX_I "set_gfx_card success for %dx%dx%d.\n",
  	 screen->w, screen->h, bitmap_color_depth(screen));
     return 0;
  } 
  
  
  
  /* _set_gfx_mode_safe:
   *  Special wrapper used when the card parameter of set_gfx_mode()
   *  is GFX_SAFE. In this case the function tries to query the
   *  system driver for a "safe" resolution+driver it knows it will
   *  work, and set it. If the system driver cannot get a "safe"
   *  resolution+driver, it will try the given parameters.
   */
  static int _set_gfx_mode_safe(int card, int w, int h, int v_w, int v_h)
  {
     char buf[ALLEGRO_ERROR_SIZE], tmp1[64];
     struct GFX_MODE mode;
     int ret, driver;
  
     ASSERT(card == GFX_SAFE);
     ASSERT(system_driver);
     TRACE(PREFIX_I "Trying to set a safe graphics mode.\n");
     
     if (system_driver->get_gfx_safe_mode) {
        ustrzcpy(buf, sizeof(buf), allegro_error);
  
        /* retrieve the safe graphics mode */
        system_driver->get_gfx_safe_mode(&driver, &mode);
        TRACE(PREFIX_I "The system driver suggests %dx%dx%d\n",
  	    mode.width, mode.height, mode.bpp);
  
        /* try using the specified resolution but current depth */
        if (_set_gfx_mode(driver, w, h, 0, 0, TRUE) == 0)
           return 0;
  
        ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, buf);
  
        /* finally use the safe settings */
        set_color_depth(mode.bpp);
        if (_set_gfx_mode(driver, mode.width, mode.height, 0, 0, TRUE) == 0)
           return 0;
  
        ASSERT(FALSE);  /* the safe graphics mode must always work */
     }
     else {
        TRACE(PREFIX_W "The system driver was unable to get a safe mode, "
  	    "I'll try with the specified parameters...\n");
        /* no safe graphics mode, try hard-coded autodetected modes with
         * custom settings */
        _safe_gfx_mode_change = 1;
  
        ret = _set_gfx_mode(GFX_AUTODETECT, w, h, 0, 0, TRUE);
  
        _safe_gfx_mode_change = 0;
  
        if (ret == 0)
           return 0;
     }
  
     /* failing to set GFX_SAFE is a fatal error */
     TRACE(PREFIX_E "Bad bad, not even GFX_SAFE works?\n");
     _set_gfx_mode(GFX_TEXT, 0, 0, 0, 0, TRUE);
     allegro_message(uconvert_ascii("%s\n", tmp1),
  		   get_config_text("Fatal error: unable to set GFX_SAFE"));
     return -1;
  }
  
  
  
  /* _sort_out_virtual_width:
   *  Decides how wide the virtual screen really needs to be. That is more 
   *  complicated than it sounds, because the Allegro graphics primitives 
   *  require that each scanline be contained within a single bank. That 
   *  causes problems on cards that don't have overlapping banks, unless the 
   *  bank size is a multiple of the virtual width. So we may need to adjust 
   *  the width just to keep things running smoothly...
   */
  void _sort_out_virtual_width(int *width, GFX_DRIVER *driver)
  {
     int w = *width;
  
     /* hah! I love VBE 2.0... */
     if (driver->linear)
        return;
  
     /* if banks can overlap, we are ok... */ 
     if (driver->bank_size > driver->bank_gran)
        return;
  
     /* damn, have to increase the virtual width */
     while (((driver->bank_size / w) * w) != driver->bank_size) {
        w++;
        if (w > driver->bank_size)
  	 break; /* oh shit */
     }
  
     *width = w;
  }
  
  
  
  /* _make_bitmap:
   *  Helper function for creating the screen bitmap. Sets up a bitmap 
   *  structure for addressing video memory at addr, and fills the bank 
   *  switching table using bank size/granularity information from the 
   *  specified graphics driver.
   */
  BITMAP *_make_bitmap(int w, int h, uintptr_t addr, GFX_DRIVER *driver, int color_depth, int bpl)
  {
     GFX_VTABLE *vtable = _get_vtable(color_depth);
     int i, bank, size;
     BITMAP *b;
  
     if (!vtable)
        return NULL;
  
     size = sizeof(BITMAP) + sizeof(char *) * h;
  
     b = (BITMAP *)_AL_MALLOC(size);
     if (!b)
        return NULL;
  
     _gfx_bank = _AL_REALLOC(_gfx_bank, h * sizeof(int));
     if (!_gfx_bank) {
        _AL_FREE(b);
        return NULL;
     }
  
     LOCK_DATA(b, size);
     LOCK_DATA(_gfx_bank, h * sizeof(int));
  
     b->w = b->cr = w;
     b->h = b->cb = h;
     b->clip = TRUE;
     b->cl = b->ct = 0;
     b->vtable = &_screen_vtable;
     b->write_bank = b->read_bank = _stub_bank_switch;
     b->dat = NULL;
     b->id = BMP_ID_VIDEO;
     b->extra = NULL;
     b->x_ofs = 0;
     b->y_ofs = 0;
     b->seg = _video_ds();
  
     memcpy(&_screen_vtable, vtable, sizeof(GFX_VTABLE));
     LOCK_DATA(&_screen_vtable, sizeof(GFX_VTABLE));
  
     _last_bank_1 = _last_bank_2 = -1;
  
     driver->vid_phys_base = addr;
  
     b->line[0] = (unsigned char *)addr;
     _gfx_bank[0] = 0;
  
     if (driver->linear) {
        for (i=1; i<h; i++) {
  	 b->line[i] = b->line[i-1] + bpl;
  	 _gfx_bank[i] = 0;
        }
     }
     else {
        bank = 0;
  
        for (i=1; i<h; i++) {
  	 b->line[i] = b->line[i-1] + bpl;
  	 if (b->line[i]+bpl-1 >= (unsigned char *)addr + driver->bank_size) {
  	    while (b->line[i] >= (unsigned char *)addr + driver->bank_gran) {
  	       b->line[i] -= driver->bank_gran;
  	       bank++;
  	    }
  	 }
  	 _gfx_bank[i] = bank;
        }
     }
  
     return b;
  }
  
  
  
  /* create_bitmap_ex
   *  Creates a new memory bitmap in the specified color_depth
   */
  BITMAP *create_bitmap_ex(int color_depth, int width, int height)
  {
     GFX_VTABLE *vtable;
     BITMAP *bitmap;
     int nr_pointers;
     int padding;
     int i;
  
     ASSERT(width >= 0);
     ASSERT(height > 0);
     ASSERT(system_driver);
  
     if (system_driver->create_bitmap)
        return system_driver->create_bitmap(color_depth, width, height);
  
     vtable = _get_vtable(color_depth);
     if (!vtable)
        return NULL;
  
     /* We need at least two pointers when drawing, otherwise we get crashes with
      * Electric Fence.  We think some of the assembly code assumes a second line
      * pointer is always available.
      */
     nr_pointers = MAX(2, height);
     bitmap = _AL_MALLOC(sizeof(BITMAP) + (sizeof(char *) * nr_pointers));
     if (!bitmap)
        return NULL;
  
     /* This avoids a crash for assembler code accessing the last pixel, as it
      * read 4 bytes instead of 3.
      */
     padding = (color_depth == 24) ? 1 : 0;
  
     bitmap->dat = _AL_MALLOC_ATOMIC(width * height * BYTES_PER_PIXEL(color_depth) + padding);
     if (!bitmap->dat) {
        _AL_FREE(bitmap);
        return NULL;
     }
  
     bitmap->w = bitmap->cr = width;
     bitmap->h = bitmap->cb = height;
     bitmap->clip = TRUE;
     bitmap->cl = bitmap->ct = 0;
     bitmap->vtable = vtable;
     bitmap->write_bank = bitmap->read_bank = _stub_bank_switch;
     bitmap->id = 0;
     bitmap->extra = NULL;
     bitmap->x_ofs = 0;
     bitmap->y_ofs = 0;
     bitmap->seg = _default_ds();
  
     if (height > 0) {
        bitmap->line[0] = bitmap->dat;
        for (i=1; i<height; i++)
           bitmap->line[i] = bitmap->line[i-1] + width * BYTES_PER_PIXEL(color_depth);
     }
  
     if (system_driver->created_bitmap)
        system_driver->created_bitmap(bitmap);
  
     return bitmap;
  }
  
  
  
  /* create_bitmap:
   *  Creates a new memory bitmap.
   */
  BITMAP *create_bitmap(int width, int height)
  {
     ASSERT(width >= 0);
     ASSERT(height > 0);
     return create_bitmap_ex(_color_depth, width, height);
  }
  
  
  
  /* create_sub_bitmap:
   *  Creates a sub bitmap, ie. a bitmap sharing drawing memory with a
   *  pre-existing bitmap, but possibly with different clipping settings.
   *  Usually will be smaller, and positioned at some arbitrary point.
   *
   *  Mark Wodrich is the owner of the brain responsible this hugely useful 
   *  and beautiful function.
   */
  BITMAP *create_sub_bitmap(BITMAP *parent, int x, int y, int width, int height)
  {
     BITMAP *bitmap;
     int nr_pointers;
     int i;
  
     ASSERT(parent);
     ASSERT((x >= 0) && (y >= 0) && (x < parent->w) && (y < parent->h));
     ASSERT((width > 0) && (height > 0));
     ASSERT(system_driver);
  
     if (x+width > parent->w) 
        width = parent->w-x;
  
     if (y+height > parent->h) 
        height = parent->h-y;
  
     if (parent->vtable->create_sub_bitmap)
        return parent->vtable->create_sub_bitmap(parent, x, y, width, height);
  
     if (system_driver->create_sub_bitmap)
        return system_driver->create_sub_bitmap(parent, x, y, width, height);
  
     /* get memory for structure and line pointers */
     /* (see create_bitmap for the reason we need at least two) */
     nr_pointers = MAX(2, height);
     bitmap = _AL_MALLOC(sizeof(BITMAP) + (sizeof(char *) * nr_pointers));
     if (!bitmap)
        return NULL;
  
     acquire_bitmap(parent);
  
     bitmap->w = bitmap->cr = width;
     bitmap->h = bitmap->cb = height;
     bitmap->clip = TRUE;
     bitmap->cl = bitmap->ct = 0;
     bitmap->vtable = parent->vtable;
     bitmap->write_bank = parent->write_bank;
     bitmap->read_bank = parent->read_bank;
     bitmap->dat = NULL;
     bitmap->extra = NULL;
     bitmap->x_ofs = x + parent->x_ofs;
     bitmap->y_ofs = y + parent->y_ofs;
     bitmap->seg = parent->seg;
  
     /* All bitmaps are created with zero ID's. When a sub-bitmap is created,
      * a unique ID is needed to identify the relationship when blitting from
      * one to the other. This is obtained from the global variable
      * _sub_bitmap_id_count, which provides a sequence of integers (yes I
      * know it will wrap eventually, but not for a long time :-) If the
      * parent already has an ID the sub-bitmap adopts it, otherwise a new
      * ID is given to both the parent and the child.
      */
     if (!(parent->id & BMP_ID_MASK)) {
        parent->id |= _sub_bitmap_id_count;
        _sub_bitmap_id_count = (_sub_bitmap_id_count+1) & BMP_ID_MASK;
     }
  
     bitmap->id = parent->id | BMP_ID_SUB;
     bitmap->id &= ~BMP_ID_LOCKED;
  
     if (is_planar_bitmap(bitmap))
        x /= 4;
  
     x *= BYTES_PER_PIXEL(bitmap_color_depth(bitmap));
  
     /* setup line pointers: each line points to a line in the parent bitmap */
     for (i=0; i<height; i++)
        bitmap->line[i] = parent->line[y+i] + x;
  
     if (bitmap->vtable->set_clip)
        bitmap->vtable->set_clip(bitmap);
  
     if (parent->vtable->created_sub_bitmap)
        parent->vtable->created_sub_bitmap(bitmap, parent);
  
     if (system_driver->created_sub_bitmap)
        system_driver->created_sub_bitmap(bitmap, parent);
  
     if (parent->id & BMP_ID_VIDEO)
        _register_switch_bitmap(bitmap, parent);
  
     release_bitmap(parent);
  
     return bitmap;
  }
  
  
  
  /* add_vram_block:
   *  Creates a video memory bitmap out of the specified region
   *  of the larger screen surface, returning a pointer to it.
   */
  static BITMAP *add_vram_block(int x, int y, int w, int h)
  {
     VRAM_BITMAP *b, *new_b;
     VRAM_BITMAP **last_p;
  
     new_b = _AL_MALLOC(sizeof(VRAM_BITMAP));
     if (!new_b)
        return NULL;
  
     new_b->x = x;
     new_b->y = y;
     new_b->w = w;
     new_b->h = h;
  
     new_b->bmp = create_sub_bitmap(screen, x, y, w, h);
     if (!new_b->bmp) {
        _AL_FREE(new_b);
        return NULL;
     }
  
     /* find sorted y-position */
     last_p = &vram_bitmap_list;
     for (b = vram_bitmap_list; b && (b->y < new_b->y); b = b->next_y)
        last_p = &b->next_y;
  
     /* insert */
     *last_p = new_b;
     new_b->next_y = b;
  
     return new_b->bmp;
  }
  
  
  
  /* create_video_bitmap:
   *  Attempts to make a bitmap object for accessing offscreen video memory.
   *
   *  The algorithm is similar to algorithms for drawing polygons. Think of
   *  a horizontal stripe whose height is equal to that of the new bitmap.
   *  It is initially aligned to the top of video memory and then it moves
   *  downwards, stopping each time its top coincides with the bottom of a
   *  video bitmap. For each stop, create a list of video bitmaps intersecting
   *  the stripe, sorted by the left coordinate, from left to right, in the
   *  next_x linked list. We look through this list and stop as soon as the
   *  gap between the rightmost right edge seen so far and the current left
   *  edge is big enough for the new video bitmap. In that case, we are done.
   *  If we don't find such a gap, we move the stripe further down.
   *
   *  To make it efficient to find new bitmaps intersecting the stripe, the
   *  list of all bitmaps is always sorted by top, from top to bottom, in
   *  the next_y linked list. The list of bitmaps intersecting the stripe is
   *  merely updated, not recalculated from scratch, when we move the stripe.
   *  So every bitmap gets bubbled to its correct sorted x-position at most
   *  once. Bubbling a bitmap takes at most as many steps as the number of
   *  video bitmaps. So the algorithm behaves at most quadratically with
   *  regard to the number of video bitmaps. I think that a linear behaviour
   *  is more typical in practical applications (this happens, for instance,
   *  if you allocate many bitmaps of the same height).
   *
   *  There's one more optimization: if a call fails, we cache the size of the
   *  requested bitmap, so that next time we can detect very quickly that this
   *  size is too large (this needs to be maintained when destroying bitmaps).
   */
  BITMAP *create_video_bitmap(int width, int height)
  {
     VRAM_BITMAP *active_list, *b, *vram_bitmap;
     VRAM_BITMAP **last_p;
     BITMAP *bmp;
     int x = 0, y = 0;
  
     ASSERT(width >= 0);
     ASSERT(height > 0);
     
     if (_dispsw_status)
        return NULL;
  
     /* let the driver handle the request if it can */
     if (gfx_driver->create_video_bitmap) {
        bmp = gfx_driver->create_video_bitmap(width, height);
        if (!bmp)
  	 return NULL;
  
        b = _AL_MALLOC(sizeof(VRAM_BITMAP));
        b->x = -1;
        b->y = -1;
        b->w = 0;
        b->h = 0;
        b->bmp = bmp;
        b->next_y = vram_bitmap_list;
        vram_bitmap_list = b;
  
        return bmp;
     }
  
     /* check bad args */
     if ((width > VIRTUAL_W) || (height > VIRTUAL_H) ||
         (width < 0) || (height < 0))
        return NULL;
  
     /* check cached bitmap size */
     if ((width >= failed_bitmap_w) && (height >= failed_bitmap_h))
        return NULL;
  
     vram_bitmap = vram_bitmap_list;
     active_list = NULL;
     y = 0;
  
     while (TRUE) {
        /* Move the blocks from the VRAM_BITMAP_LIST that intersect the
         * stripe to the ACTIVE_LIST: look through the VRAM_BITMAP_LIST
         * following the next_y link, grow the ACTIVE_LIST following
         * the next_x link and sorting by x-position.
         * (this loop runs in amortized quadratic time)
         */
        while (vram_bitmap && (vram_bitmap->y < y+height)) {
  	 /* find sorted x-position */
  	 last_p = &active_list;
  	 for (b = active_list; b && (vram_bitmap->x > b->x); b = b->next_x)
  	    last_p = &b->next_x;
  
  	 /* insert */
  	 *last_p = vram_bitmap;
  	 vram_bitmap->next_x = b;
  
  	 /* next video bitmap */
  	 vram_bitmap = vram_bitmap->next_y;
        }
  
        x = 0;
  
        /* Look for holes to put our bitmap in.
         * (this loop runs in quadratic time)
         */
        for (b = active_list; b; b = b->next_x) {
  	 if (x+width <= b->x)  /* hole is big enough? */
  	    return add_vram_block(x, y, width, height);
  
           /* Move search x-position to the right edge of the
            * skipped bitmap if we are not already past it.
            * And check there is enough room before continuing.
            */
  	 if (x < b->x + b->w) {
  	    x = (b->x + b->w + 15) & ~15;
  	    if (x+width > VIRTUAL_W)
  	       break;
  	 }
        }
  
        /* If the whole ACTIVE_LIST was scanned, there is a big
         * enough hole to the right of the rightmost bitmap.
         */
        if (b == NULL)
  	 return add_vram_block(x, y, width, height);
  
        /* Move search y-position to the topmost bottom edge
         * of the bitmaps intersecting the stripe.
         * (this loop runs in quadratic time)
         */
        y = active_list->y + active_list->h;
        for (b = active_list->next_x; b; b = b->next_x) {
  	 if (y > b->y + b->h)
  	    y = b->y + b->h;
        }
  
        if (y+height > VIRTUAL_H) {  /* too close to bottom? */
  	 /* Before failing, cache this bitmap size so that later calls can
  	  * learn from it. Use area as a measure to sort the bitmap sizes.
            */
  	 if (width * height < failed_bitmap_w * failed_bitmap_h) {
  	    failed_bitmap_w = width;
  	    failed_bitmap_h = height;
  	 }
  
  	 return NULL;
        }
  
        /* Remove the blocks that don't intersect the new stripe from ACTIVE_LIST.
         * (this loop runs in quadratic time)
         */
        last_p = &active_list;
        for (b = active_list; b; b = b->next_x) {
  	 if (y >= b->y + b->h)
  	    *last_p = b->next_x;
  	 else
  	    last_p = &b->next_x;
        }
     }
  }
  
  
  
  /* create_system_bitmap:
   *  Attempts to make a system-specific (eg. DirectX surface) bitmap object.
   */
  BITMAP *create_system_bitmap(int width, int height)
  {
     BITMAP *bmp;
  
     ASSERT(width >= 0);
     ASSERT(height > 0);
     ASSERT(gfx_driver != NULL);
  
     if (gfx_driver->create_system_bitmap)
        return gfx_driver->create_system_bitmap(width, height);
  
     bmp = create_bitmap(width, height);
  
     return bmp;
  }
  
  
  
  /* destroy_bitmap:
   *  Destroys a memory bitmap.
   */
  void destroy_bitmap(BITMAP *bitmap)
  {
     VRAM_BITMAP *prev, *pos;
  
     if (bitmap) {
        if (is_video_bitmap(bitmap)) {
  	 /* special case for getting rid of video memory bitmaps */
  	 ASSERT(!_dispsw_status);
  
  	 prev = NULL;
  	 pos = vram_bitmap_list;
  
  	 while (pos) {
  	    if (pos->bmp == bitmap) {
  	       if (prev)
  		  prev->next_y = pos->next_y;
  	       else
  		  vram_bitmap_list = pos->next_y;
  
  	       if (pos->x < 0) {
  		  /* the driver is in charge of this object */
  		  gfx_driver->destroy_video_bitmap(bitmap);
  		  _AL_FREE(pos);
  		  return;
  	       } 
  
  	       /* Update cached bitmap size using worst case scenario:
  		* the bitmap lies between two holes whose size is the cached
  		* size on each axis respectively.
  		*/
  	       failed_bitmap_w = failed_bitmap_w * 2 + ((bitmap->w + 15) & ~15);
  	       if (failed_bitmap_w > BMP_MAX_SIZE)
  		  failed_bitmap_w = BMP_MAX_SIZE;
  
  	       failed_bitmap_h = failed_bitmap_h * 2 + bitmap->h;
  	       if (failed_bitmap_h > BMP_MAX_SIZE)
  		  failed_bitmap_h = BMP_MAX_SIZE;
  
  	       _AL_FREE(pos);
  	       break;
  	    }
  
  	    prev = pos;
  	    pos = pos->next_y;
  	 }
  
  	 _unregister_switch_bitmap(bitmap);
        }
        else if (is_system_bitmap(bitmap)) {
  	 /* special case for getting rid of system memory bitmaps */
  	 ASSERT(gfx_driver != NULL);
  
  	 if (gfx_driver->destroy_system_bitmap) {
  	    gfx_driver->destroy_system_bitmap(bitmap);
  	    return;
  	 }
        }
  
        /* normal memory or sub-bitmap destruction */
        if (system_driver->destroy_bitmap) {
  	 if (system_driver->destroy_bitmap(bitmap))
  	    return;
        }
  
        if (bitmap->dat)
  	 _AL_FREE(bitmap->dat);
  
        _AL_FREE(bitmap);
     }
  }
  
  
  
  /* set_clip_rect:
   *  Sets the two opposite corners of the clipping rectangle to be used when
   *  drawing to the bitmap. Nothing will be drawn to positions outside of this 
   *  rectangle. When a new bitmap is created the clipping rectangle will be 
   *  set to the full area of the bitmap.
   */
  void set_clip_rect(BITMAP *bitmap, int x1, int y1, int x2, int y2)
  {
     ASSERT(bitmap);
  
     /* internal clipping is inclusive-exclusive */
     x2++;
     y2++;
  
     bitmap->cl = CLAMP(0, x1, bitmap->w-1);
     bitmap->ct = CLAMP(0, y1, bitmap->h-1);
     bitmap->cr = CLAMP(0, x2, bitmap->w);
     bitmap->cb = CLAMP(0, y2, bitmap->h);
  
     if (bitmap->vtable->set_clip)
        bitmap->vtable->set_clip(bitmap);
  }
  
  
  
  /* add_clip_rect:
   *  Makes the new clipping rectangle the intersection between the given
   *  rectangle and the current one.
   */
  void add_clip_rect(BITMAP *bitmap, int x1, int y1, int x2, int y2)
  {
     int cx1, cy1, cx2, cy2;
  
     ASSERT(bitmap);
  
     get_clip_rect(bitmap, &cx1, &cy1, &cx2, &cy2);
  
     x1 = MAX(x1, cx1);
     y1 = MAX(y1, cy1);
     x2 = MIN(x2, cx2);
     y2 = MIN(y2, cy2);
  
     set_clip_rect(bitmap, x1, y1, x2, y2);
  }
  
  
  
  /* set_clip:
   *  Sets the two opposite corners of the clipping rectangle to be used when
   *  drawing to the bitmap. Nothing will be drawn to positions outside of this 
   *  rectangle. When a new bitmap is created the clipping rectangle will be 
   *  set to the full area of the bitmap. If x1, y1, x2 and y2 are all zero 
   *  clipping will be turned off, which will slightly speed up drawing 
   *  operations but will allow memory to be corrupted if you attempt to draw 
   *  off the edge of the bitmap.
   */
  void set_clip(BITMAP *bitmap, int x1, int y1, int x2, int y2)
  {
     int t;
  
     ASSERT(bitmap);
  
     if ((!x1) && (!y1) && (!x2) && (!y2)) {
        set_clip_rect(bitmap, 0, 0, bitmap->w-1, bitmap->h-1);
        set_clip_state(bitmap, FALSE);
        return;
     }
  
     if (x2 < x1) {
        t = x1;
        x1 = x2;
        x2 = t;
     }
  
     if (y2 < y1) {
        t = y1;
        y1 = y2;
        y2 = t;
     }
  
     set_clip_rect(bitmap, x1, y1, x2, y2);
     set_clip_state(bitmap, TRUE);
  }
  
  
  
  /* scroll_screen:
   *  Attempts to scroll the hardware screen, returning 0 on success. 
   *  Check the VIRTUAL_W and VIRTUAL_H values to see how far the screen
   *  can be scrolled. Note that a lot of VESA drivers can only handle
   *  horizontal scrolling in four pixel increments.
   */
  int scroll_screen(int x, int y)
  {
     int ret = 0;
     int h;
  
     /* can driver handle hardware scrolling? */
     if ((!gfx_driver->scroll) || (_dispsw_status))
        return -1;
  
     /* clip x */
     if (x < 0) {
        x = 0;
        ret = -1;
     }
     else if (x > (VIRTUAL_W - SCREEN_W)) {
        x = VIRTUAL_W - SCREEN_W;
        ret = -1;
     }
  
     /* clip y */
     if (y < 0) {
        y = 0;
        ret = -1;
     }
     else {
        h = (_screen_split_position > 0) ? _screen_split_position : SCREEN_H;
        if (y > (VIRTUAL_H - h)) {
  	 y = VIRTUAL_H - h;
  	 ret = -1;
        }
     }
  
     /* scroll! */
     if (gfx_driver->scroll(x, y) != 0)
        ret = -1;
  
     return ret;
  }
  
  
  
  /* request_scroll:
   *  Attempts to initiate a triple buffered hardware scroll, which will
   *  take place during the next retrace. Returns 0 on success.
   */
  int request_scroll(int x, int y)
  {
     int ret = 0;
     int h;
  
     /* can driver handle triple buffering? */
     if ((!gfx_driver->request_scroll) || (_dispsw_status)) {
        scroll_screen(x, y);
        return -1;
     }
  
     /* clip x */
     if (x < 0) {
        x = 0;
        ret = -1;
     }
     else if (x > (VIRTUAL_W - SCREEN_W)) {
        x = VIRTUAL_W - SCREEN_W;
        ret = -1;
     }
  
     /* clip y */
     if (y < 0) {
        y = 0;
        ret = -1;
     }
     else {
        h = (_screen_split_position > 0) ? _screen_split_position : SCREEN_H;
        if (y > (VIRTUAL_H - h)) {
  	 y = VIRTUAL_H - h;
  	 ret = -1;
        }
     }
  
     /* scroll! */
     if (gfx_driver->request_scroll(x, y) != 0)
        ret = -1;
  
     return ret;
  }
  
  
  
  /* poll_scroll:
   *  Checks whether a requested triple buffer flip has actually taken place.
   */
  int poll_scroll(void)
  {
     if ((!gfx_driver->poll_scroll) || (_dispsw_status))
        return FALSE;
  
     return gfx_driver->poll_scroll();
  }
  
  
  
  /* show_video_bitmap:
   *  Page flipping function: swaps to display the specified video memory 
   *  bitmap object (this must be the same size as the physical screen).
   */
  int show_video_bitmap(BITMAP *bitmap)
  {
     if ((!is_video_bitmap(bitmap)) || 
         (bitmap->w != SCREEN_W) || (bitmap->h != SCREEN_H) ||
         (_dispsw_status))
        return -1;
  
     if (gfx_driver->show_video_bitmap)
        return gfx_driver->show_video_bitmap(bitmap);
  
     return scroll_screen(bitmap->x_ofs, bitmap->y_ofs);
  }
  
  
  
  /* request_video_bitmap:
   *  Triple buffering function: triggers a swap to display the specified 
   *  video memory bitmap object, which will take place on the next retrace.
   */
  int request_video_bitmap(BITMAP *bitmap)
  {
     if ((!is_video_bitmap(bitmap)) || 
         (bitmap->w != SCREEN_W) || (bitmap->h != SCREEN_H) ||
         (_dispsw_status))
        return -1;
  
     if (gfx_driver->request_video_bitmap)
        return gfx_driver->request_video_bitmap(bitmap);
  
     return request_scroll(bitmap->x_ofs, bitmap->y_ofs);
  }
  
  
  
  /* enable_triple_buffer:
   *  Asks a driver to turn on triple buffering mode, if it is capable
   *  of that.
   */
  int enable_triple_buffer(void)
  {
     if (gfx_capabilities & GFX_CAN_TRIPLE_BUFFER)
        return 0;
  
     if (_dispsw_status)
        return -1;
  
     if (gfx_driver->enable_triple_buffer) {
        gfx_driver->enable_triple_buffer();
  
        if ((gfx_driver->request_scroll) || (gfx_driver->request_video_bitmap)) {
  	 gfx_capabilities |= GFX_CAN_TRIPLE_BUFFER;
  	 return 0;
        }
     }
  
     return -1;
  }
  
  
  
  /* get_gfx_mode_type:
   *  Evaluates the type of the graphics driver card
   *  and tells you whether it is a windowed, fullscreen,
   *  definitely windowed or fullscreen, and/or a magic driver.
   *  See gfx.h for the GFX_TYPE_* defines.
   */
  int get_gfx_mode_type(int graphics_card)
  {
     int gfx_type = GFX_TYPE_UNKNOWN;
     _DRIVER_INFO *gfx_driver_info;
     GFX_DRIVER *gfx_driver_entry;
  
     ASSERT(system_driver);
  
     /* Ask the system driver for a list of graphics hardware drivers */
     if (system_driver->gfx_drivers) {
        gfx_driver_info = system_driver->gfx_drivers();
     }
     else {
        gfx_driver_info = _gfx_driver_list;
     }
  
     ASSERT(gfx_driver_info);
  
     while (gfx_driver_info->driver) {
        if (gfx_driver_info->id == graphics_card) {
           gfx_driver_entry = (GFX_DRIVER*)gfx_driver_info->driver;
           if (gfx_driver_entry->windowed) {
              gfx_type |= (GFX_TYPE_WINDOWED   | GFX_TYPE_DEFINITE);
           }
           else {
              gfx_type |= (GFX_TYPE_FULLSCREEN | GFX_TYPE_DEFINITE);
           }
           break;
        }
        gfx_driver_info++;
     }
  
     switch (graphics_card) {
        case GFX_AUTODETECT:
           gfx_type |= GFX_TYPE_MAGIC;
           break;
        case GFX_AUTODETECT_FULLSCREEN:
           gfx_type |= (GFX_TYPE_MAGIC | GFX_TYPE_FULLSCREEN | GFX_TYPE_DEFINITE);
           break;
        case GFX_AUTODETECT_WINDOWED:
           gfx_type |= (GFX_TYPE_MAGIC | GFX_TYPE_WINDOWED   | GFX_TYPE_DEFINITE);
           break;
        case GFX_SAFE:
           gfx_type |= GFX_TYPE_MAGIC;
           break;
        case GFX_TEXT:
           gfx_type |= GFX_TYPE_MAGIC;
           break;
     }
     return gfx_type;
  }
  
  
  
  /* get_gfx_mode:
   *  Tells you which graphics mode is currently set.
   *  Useful for determining the actual driver that
   *  GFX_AUTODETECT* or GFX_SAFE set successfully.
   */
  int get_gfx_mode(void)
  {
     if (!gfx_driver)
        return GFX_NONE;
  
     return gfx_driver->id;
  }
  
  
  
  