/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

// draw.c -- this is the only file outside the refresh that touches the
// vid buffer

#include "quakedef.h"

#define GL_COLOR_INDEX8_EXT     0x80E5


cvar_t		gl_nobind = CVAR2("gl_nobind", "0");
cvar_t		gl_max_size = CVAR2("gl_max_size", "1024");
cvar_t		gl_picmip = CVAR2("gl_picmip", "0");

byte		*draw_chars;				// 8*8 graphic characters
qpic_t		*draw_disc;
qpic_t		*draw_backtile;

int			translate_texture;
int			char_texture;

typedef struct
{
  int		texnum;
  float	sl, tl, sh, th;
} glpic_t;

typedef union
{
    qpic_t qpic;
    struct {
        // First part is from qpic
        int width;
        int height;

        glpic_t glpic;
    } g;
} packedGlpic_t;

typedef union
{
    byte buffer[sizeof(qpic_t) + sizeof(glpic_t)];
    packedGlpic_t pics;
} conback_t;

conback_t conbackUnion;

#define		conback_buffer (conbackUnion.buffer)
packedGlpic_t *conback = &conbackUnion.pics;

int		gl_lightmap_format = 4;
int		gl_solid_format = 3;
int		gl_alpha_format = 4;

#if 1 // Standard defaults
int		gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
int		gl_filter_max = GL_LINEAR;
#else
int		gl_filter_min = GL_NEAREST_MIPMAP_NEAREST;
int		gl_filter_max = GL_NEAREST;
#endif

int		texels;

typedef struct
{
  int		texnum;
  char	identifier[64];
  int		width, height;
  qboolean	mipmap;
} gltexture_t;

#define	MAX_GLTEXTURES	1024
gltexture_t	gltextures[MAX_GLTEXTURES];
int			numgltextures;

// GlQuake creates textures, but never deletes them. This approach works fine on
// computers with lots of RAM and/or swap, but not so well on our swapless
// RAM-constrained system.
//
// We work around this problem by adding a level of indirection. We
// hook GL_LoadTexture to store enough information to recreate a texture.
// Then we hook GL_BindTexture to consult a table to see whether a texture
// is currently in memory or not. If it isn't, we throw out some other
// texture and bring the required texture back into memory. In this way
// we can limit the working set of textures.
//
// The texture data is stored in a memory-mapped file that is backed by
// a file on the sd card. It is recreated each time the game is run. We
// don't bother deleting it.

#define USE_TEXTURE_STORE

#ifdef USE_TEXTURE_STORE

#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>

// Allow named textures to be evicted from memory.

#define TEXTURE_STORE_NAME "glquake/texture.store"

class textureStore {

private:
    static const GLuint UNUSED = (GLuint) -2;
    static const GLuint PAGED_OUT = (GLuint) -1;

    struct entry
    {
        entry* next;
        entry* prev;
        GLuint real_texnum;    // UNUSED, PAGED_OUT
        byte* pData; // 0 ==> not created by us.
        size_t size;
        qboolean alpha;
        int width;
        int height;
        qboolean mipmap;

        entry() {
            next = 0;
            prev = 0;
            real_texnum = UNUSED;
            pData = 0;
        }


        void unlink() {
            if (next) {
                next->prev = prev;
            }
            if (prev) {
                prev->next = next;
            }
            next = 0;
            prev = 0;
        }

        void insertBefore(entry* e){
            if (e) {
                prev = e->prev;
                if ( prev ) {
                    prev->next = this;
                }
                next = e;
                e->prev = this;
            }
            else {
                prev = 0;
                next = 0;
            }
        }
    };

public:

    static textureStore* get() {
        if (g_pTextureCache == 0) {
            g_pTextureCache = new textureStore();
        }
        return g_pTextureCache;
    }

    // Equivalent of glBindTexture, but uses the virtual texture table

    void bind(int virtTexNum) {
        if ( (unsigned int) virtTexNum >= TEXTURE_STORE_NUM_TEXTURES) {
            Sys_Error("not in the range we're managing");
        }
        mBoundTextureID = virtTexNum;
        entry* e = &mTextures[virtTexNum];

        if ( e->real_texnum == UNUSED) {
            glGenTextures( 1, &e->real_texnum);
        }

        if ( e->pData == 0) {
            glBindTexture(GL_TEXTURE_2D, e->real_texnum);
            return;
        }

        update(e);
    }

    void update(entry* e)
    {
        // Update the "LRU" part of the cache
        unlink(e);
        e->insertBefore(mFirst);
        mFirst = e;
        if (! mLast) {
            mLast = e;
        }

        if (e->real_texnum == PAGED_OUT ) {
            // Create a real texture
            // Make sure there is enough room for this texture
            ensure(e->size);

            glGenTextures( 1, &e->real_texnum);

            glBindTexture(GL_TEXTURE_2D, e->real_texnum);
            GL_Upload8 (e->pData, e->width, e->height, e->mipmap,
                    e->alpha);
        }
        else {
            glBindTexture(GL_TEXTURE_2D, e->real_texnum);
        }
    }

    // Create a texture, and remember the data so we can create
    // it again later.

    void create(int width, int height, byte* data, qboolean mipmap,
            qboolean alpha) {
        int size = width * height;
        if (size + mLength > mCapacity) {
            Sys_Error("Ran out of virtual texture space. %d", size);
        };
        entry* e = &mTextures[mBoundTextureID];

        // Call evict in case the currently bound texture id is already
        // in use. (Shouldn't happen in Quake.)
        // To Do: reclaim the old texture memory from the virtual memory.

        evict(e);

        e->alpha = alpha;
        e->pData = mBase + mLength;
        memcpy(e->pData, data, size);
        e->size = size;
        e->width = width;
        e->height = height;
        e->mipmap = mipmap;
        e->real_texnum = PAGED_OUT;
        mLength += size;

        update(e);
    }

    // Re-upload the current textures because we've been reset.
    void rebindAll() {
        grabMagicTextureIds();
        for (entry* e = mFirst; e; e = e->next ) {
            if (! (e->real_texnum == UNUSED || e->real_texnum == PAGED_OUT)) {
                glBindTexture(GL_TEXTURE_2D, e->real_texnum);
                if (e->pData) {
                    GL_Upload8 (e->pData, e->width, e->height, e->mipmap,
                        e->alpha);
                }
            }
        }
    }

private:

    textureStore() {
        grabMagicTextureIds();
        mFirst = 0;
        mLast = 0;
        mTextureCount = 0;

        char fullpath[MAX_OSPATH];
        sprintf(fullpath, "%s/%s", com_gamedir, TEXTURE_STORE_NAME);

        mFileId = open(fullpath, O_RDWR | O_CREAT, 0666);
        if ( mFileId == -1 ) {
            Sys_Error("Could not open texture store file %s: %d", fullpath,
                    errno);
        }

        if (-1 == lseek(mFileId, TEXTURE_STORE_SIZE-1, SEEK_SET)) {
            Sys_Error("Could not extend the texture store file size. %d",
                    errno);
        }
        char end;
        end = 0;
        if (-1 == write(mFileId, &end, 1)) {
            Sys_Error("Could not write last byte of the texture store file. %d",
                    errno);
        }

        mBase = (byte*) mmap((caddr_t)0, TEXTURE_STORE_SIZE,
                PROT_READ | PROT_WRITE, MAP_PRIVATE, mFileId, 0);

        if (mBase == (byte*) -1) {
            Sys_Error("Could not mmap file %s: %d", fullpath, errno);
        }
        mLength = 0;
        mCapacity = TEXTURE_STORE_SIZE;
        mRamUsed = 0;
        mRamSize = LIVE_TEXTURE_LIMIT;
    }

    ~textureStore() {
        munmap(mBase, mCapacity);
        COM_CloseFile(mFileId);
    }

    void grabMagicTextureIds() {
        // reserve these two texture ids.
        glBindTexture(GL_TEXTURE_2D, UNUSED);
        glBindTexture(GL_TEXTURE_2D, PAGED_OUT);
    }

    void unlink(entry* e) {
        if (e == mFirst) {
            mFirst = e->next;
        }
        if (e == mLast) {
            mLast = e->prev;
        }
        e->unlink();
    }

    void ensure(int size) {
        while ( mRamSize - mRamUsed < (unsigned int) size) {
            entry* e = mLast;
            if(! e) {
                Sys_Error("Ran out of entries");
                return;
            }
            evict(e);
        }
        mRamUsed += size;
    }

    void evict(entry* e) {
        unlink(e);
        if ( e->pData ) {
            glDeleteTextures(1, &e->real_texnum);
            e->real_texnum = PAGED_OUT;
            mRamUsed -= e->size;
        }
    }

    static const size_t TEXTURE_STORE_SIZE = 16 * 1024 * 1024;
    static const size_t LIVE_TEXTURE_LIMIT = 1 * 1024 * 1024;
    static const size_t TEXTURE_STORE_NUM_TEXTURES = 512;

    int mFileId;
    byte* mBase;    // Base address of the memory mapped file
    size_t mLength; // How much of the mm file we are currently using
    size_t mCapacity; // Total size of the memory mapped file

    // Keep track of texture RAM.
    size_t mRamUsed;
    size_t mRamSize;

    // The virtual textures


    entry mTextures[MAX_GLTEXTURES];
    entry* mFirst; // LRU queue
    entry* mLast;
    size_t mTextureCount; // How many virtual textures have been allocated

    static textureStore* g_pTextureCache;

    int mBoundTextureID;
};

textureStore* textureStore::g_pTextureCache;

#endif


void GL_Bind (int texnum)
{
  if (gl_nobind.value)
    texnum = char_texture;
  if (currenttexture == texnum)
    return;
  currenttexture = texnum;
#ifdef _WIN32
  bindTexFunc (GL_TEXTURE_2D, texnum);
#else

#ifdef USE_TEXTURE_STORE
     textureStore::get()->bind(texnum);
#else
  glBindTexture(GL_TEXTURE_2D, texnum);
#endif

#endif
}


/*
=============================================================================

  scrap allocation

  Allocate all the little status bar obejcts into a single texture
  to crutch up stupid hardware / drivers

=============================================================================
*/

#define	MAX_SCRAPS		2
#define	BLOCK_WIDTH		256
#define	BLOCK_HEIGHT	256

int			scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH];
byte		scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT*4];
qboolean	scrap_dirty;
int			scrap_texnum;

// returns a texture number and the position inside it
int Scrap_AllocBlock (int w, int h, int *x, int *y)
{
  int		i, j;
  int		best, best2;
  int		bestx;
  int		texnum;

  for (texnum=0 ; texnum<MAX_SCRAPS ; texnum++)
  {
    best = BLOCK_HEIGHT;

    for (i=0 ; i<BLOCK_WIDTH-w ; i++)
    {
      best2 = 0;

      for (j=0 ; j<w ; j++)
      {
        if (scrap_allocated[texnum][i+j] >= best)
          break;
        if (scrap_allocated[texnum][i+j] > best2)
          best2 = scrap_allocated[texnum][i+j];
      }
      if (j == w)
      {	// this is a valid spot
        *x = i;
        *y = best = best2;
      }
    }

    if (best + h > BLOCK_HEIGHT)
      continue;

    for (i=0 ; i<w ; i++)
      scrap_allocated[texnum][*x + i] = best + h;

    return texnum;
  }

  Sys_Error ("Scrap_AllocBlock: full");
  return 0;
}

int	scrap_uploads;

void Scrap_Upload (void)
{
  int		texnum;

  scrap_uploads++;

  for (texnum=0 ; texnum<MAX_SCRAPS ; texnum++) {
    GL_Bind(scrap_texnum + texnum);
    GL_Upload8 (scrap_texels[texnum], BLOCK_WIDTH, BLOCK_HEIGHT, false, true);
  }
  scrap_dirty = false;
}

//=============================================================================
/* Support Routines */

typedef struct cachepic_s
{
  char		name[MAX_QPATH];
  qpic_t		pic;
  byte		padding[32];	// for appended glpic
} cachepic_t;

#define	MAX_CACHED_PICS		128
cachepic_t	menu_cachepics[MAX_CACHED_PICS];
int			menu_numcachepics;

byte		menuplyr_pixels[4096];

int		pic_texels;
int		pic_count;


/*
================
GL_LoadPicTexture
================
load a lump file and use the date crate a texture, return texture number
*/
int GL_LoadPicTexture (qpic_t *pic)
{
  return GL_LoadTexture ("", pic->width, pic->height, pic->data, false, true);
}


qpic_t *Draw_PicFromWad (const char *name)
{
    packedGlpic_t	*pp;

  pp = (packedGlpic_t*) W_GetLumpName (name);

  qpic_t* p = & pp->qpic;
  glpic_t* gl = & pp->g.glpic;

  // load little ones into the scrap
  if (p->width < 64 && p->height < 64)
  {
    int		x, y;
    int		i, j, k;
    int		texnum;

    texnum = Scrap_AllocBlock (p->width, p->height, &x, &y);
    scrap_dirty = true;
    k = 0;
    for (i=0 ; i<p->height ; i++)
      for (j=0 ; j<p->width ; j++, k++)
        scrap_texels[texnum][(y+i)*BLOCK_WIDTH + x + j] = p->data[k];
    texnum += scrap_texnum;
    gl->texnum = texnum;
    gl->sl = (x+0.01)/(float)BLOCK_WIDTH;
    gl->sh = (x+p->width-0.01)/(float)BLOCK_WIDTH;
    gl->tl = (y+0.01)/(float)BLOCK_WIDTH;
    gl->th = (y+p->height-0.01)/(float)BLOCK_WIDTH;

    pic_count++;
    pic_texels += p->width*p->height;
  }
  else
  {
    gl->texnum = GL_LoadPicTexture (p);
    gl->sl = 0;
    gl->sh = 1;
    gl->tl = 0;
    gl->th = 1;
  }
  return p;
}


/*
================
Draw_CachePic
================
*/
qpic_t	*Draw_CachePic (const char *path)
{
  cachepic_t	*pic;
  int			i;
  qpic_t		*dat;
  glpic_t		*gl;

  for (pic=menu_cachepics, i=0 ; i<menu_numcachepics ; pic++, i++)
    if (!strcmp (path, pic->name))
      return &pic->pic;

  if (menu_numcachepics == MAX_CACHED_PICS)
    Sys_Error ("menu_numcachepics == MAX_CACHED_PICS");
  menu_numcachepics++;
  strcpy (pic->name, path);

//
// load the pic from disk
//
  dat = (qpic_t *)COM_LoadTempFile (path);
  if (!dat)
    Sys_Error ("Draw_CachePic: failed to load %s", path);
  SwapPic (dat);

  // HACK HACK HACK --- we need to keep the bytes for
  // the translatable player picture just for the menu
  // configuration dialog
  if (!strcmp (path, "gfx/menuplyr.lmp"))
    memcpy (menuplyr_pixels, dat->data, dat->width*dat->height);

  pic->pic.width = dat->width;
  pic->pic.height = dat->height;

  glpic_t temp;
  gl = &temp;
  gl->texnum = GL_LoadPicTexture (dat);
  gl->sl = 0;
  gl->sh = 1;
  gl->tl = 0;
  gl->th = 1;

  memcpy(pic->pic.data, &temp, sizeof(temp));

  return &pic->pic;
}


void Draw_CharToConback (int num, byte *dest)
{
  int		row, col;
  byte	*source;
  int		drawline;
  int		x;

  row = num>>4;
  col = num&15;
  source = draw_chars + (row<<10) + (col<<3);

  drawline = 8;

  while (drawline--)
  {
    for (x=0 ; x<8 ; x++)
      if (source[x] != 255)
        dest[x] = 0x60 + source[x];
    source += 128;
    dest += 320;
  }

}

typedef struct
{
  const char *name;
  int	minimize, maximize;
} glmode_t;

glmode_t modes[] = {
  {"GL_NEAREST", GL_NEAREST, GL_NEAREST},
  {"GL_LINEAR", GL_LINEAR, GL_LINEAR},
  {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
  {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
  {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
  {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
};

/*
===============
Draw_TextureMode_f
===============
*/
void Draw_TextureMode_f (void)
{
  int		i;
  gltexture_t	*glt;

  if (Cmd_Argc() == 1)
  {
    for (i=0 ; i< 6 ; i++)
      if (gl_filter_min == modes[i].minimize)
      {
        Con_Printf ("%s\n", modes[i].name);
        return;
      }
    Con_Printf ("current filter is unknown???\n");
    return;
  }

  for (i=0 ; i< 6 ; i++)
  {
    if (!Q_strcasecmp (modes[i].name, Cmd_Argv(1) ) )
      break;
  }
  if (i == 6)
  {
    Con_Printf ("bad filter name\n");
    return;
  }

  gl_filter_min = modes[i].minimize;
  gl_filter_max = modes[i].maximize;

  // change all the existing mipmap texture objects
  for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
  {
    if (glt->mipmap)
    {
      GL_Bind (glt->texnum);
      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
    }
  }
}

/*
===============
Draw_Init
===============
*/
void Draw_Init (void)
{
  int		i;
  qpic_t	*cb;
  byte	*dest, *src;
  int		x, y;
  char	ver[40];
  glpic_t	*gl;
  int		start;
  byte	*ncdata;
  int		f, fstep;


  Cvar_RegisterVariable (&gl_nobind);
  Cvar_RegisterVariable (&gl_max_size);
  Cvar_RegisterVariable (&gl_picmip);

  // 3dfx can only handle 256 wide textures
  if (!Q_strncasecmp ((char *)gl_renderer, "3dfx",4) ||
    strstr((char *)gl_renderer, "Glide"))
    Cvar_Set ("gl_max_size", "256");

  Cmd_AddCommand ("gl_texturemode", &Draw_TextureMode_f);

  // load the console background and the charset
  // by hand, because we need to write the version
  // string into the background before turning
  // it into a texture
  draw_chars = (byte*) W_GetLumpName ("conchars");
  for (i=0 ; i<256*64 ; i++)
    if (draw_chars[i] == 0)
      draw_chars[i] = 255;	// proper transparent color

  // now turn them into textures
  char_texture = GL_LoadTexture ("charset", 128, 128, draw_chars, false, true);

  start = Hunk_LowMark();

  cb = (qpic_t *)COM_LoadTempFile ("gfx/conback.lmp");
  if (!cb)
    Sys_Error ("Couldn't load gfx/conback.lmp");
  SwapPic (cb);

  // hack the version number directly into the pic
#if defined(__linux__)
  sprintf (ver, "(Linux %2.2f, gl %4.2f) %4.2f", (float)LINUX_VERSION, (float)GLQUAKE_VERSION, (float)VERSION);
#else
  sprintf (ver, "(gl %4.2f) %4.2f", (float)GLQUAKE_VERSION, (float)VERSION);
#endif
  dest = cb->data + 320*186 + 320 - 11 - 8*strlen(ver);
  y = strlen(ver);
  for (x=0 ; x<y ; x++)
    Draw_CharToConback (ver[x], dest+(x<<3));

#if 0
  conback->width = vid.conwidth;
  conback->height = vid.conheight;

   // scale console to vid size
   dest = ncdata = Hunk_AllocName(vid.conwidth * vid.conheight, "conback");

   for (y=0 ; y<vid.conheight ; y++, dest += vid.conwidth)
   {
     src = cb->data + cb->width * (y*cb->height/vid.conheight);
     if (vid.conwidth == cb->width)
       memcpy (dest, src, vid.conwidth);
     else
     {
       f = 0;
       fstep = cb->width*0x10000/vid.conwidth;
       for (x=0 ; x<vid.conwidth ; x+=4)
       {
         dest[x] = src[f>>16];
         f += fstep;
         dest[x+1] = src[f>>16];
         f += fstep;
         dest[x+2] = src[f>>16];
         f += fstep;
         dest[x+3] = src[f>>16];
         f += fstep;
       }
     }
   }
#else
  conback->g.width = cb->width;
  conback->g.height = cb->height;
  ncdata = cb->data;
#endif

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  gl = &conback->g.glpic;
  gl->texnum = GL_LoadTexture ("conback", conback->g.width, conback->g.height, ncdata, false, false);
  gl->sl = 0;
  gl->sh = 1;
  gl->tl = 0;
  gl->th = 1;
  conback->g.width = vid.width;
  conback->g.height = vid.height;

  // free loaded console
  Hunk_FreeToLowMark(start);

  // save a texture slot for translated picture
  translate_texture = texture_extension_number++;

  // save slots for scraps
  scrap_texnum = texture_extension_number;
  texture_extension_number += MAX_SCRAPS;

  //
  // get the other pics we need
  //
  draw_disc = Draw_PicFromWad ("disc");
  draw_backtile = Draw_PicFromWad ("backtile");
}



/*
================
Draw_Character

Draws one 8*8 graphics character with 0 being transparent.
It can be clipped to the top of the screen to allow the console to be
smoothly scrolled off.
================
*/
void Draw_Character (int x, int y, int num)
{
  byte			*dest;
  byte			*source;
  unsigned short	*pusdest;
  int				drawline;
  int				row, col;
  float			frow, fcol, size;

  if (num == 32)
    return;		// space

  num &= 255;

  if (y <= -8)
    return;			// totally off screen

  row = num>>4;
  col = num&15;

  frow = row*0.0625;
  fcol = col*0.0625;
  size = 0.0625;

  GL_Bind (char_texture);

#ifdef USE_OPENGLES
  DrawQuad(x, y, 8, 8, fcol, frow, size, size);
#else
  glBegin (GL_QUADS);
  glTexCoord2f (fcol, frow);
  glVertex2f (x, y);
  glTexCoord2f (fcol + size, frow);
  glVertex2f (x+8, y);
  glTexCoord2f (fcol + size, frow + size);
  glVertex2f (x+8, y+8);
  glTexCoord2f (fcol, frow + size);
  glVertex2f (x, y+8);
  glEnd ();
#endif
}

/*
================
Draw_String
================
*/
void Draw_String (int x, int y, const char *str)
{
  while (*str)
  {
    Draw_Character (x, y, *str);
    str++;
    x += 8;
  }
}

/*
================
Draw_DebugChar

Draws a single character directly to the upper right corner of the screen.
This is for debugging lockups by drawing different chars in different parts
of the code.
================
*/
void Draw_DebugChar (char num)
{
}

/*
=============
Draw_AlphaPic
=============
*/
void Draw_AlphaPic (int x, int y, packedGlpic_t *ppic, float alpha)
{
  byte			*dest, *source;
  unsigned short	*pusdest;
  int				v, u;
  glpic_t			*gl;

  if (scrap_dirty)
    Scrap_Upload ();
  gl = & ppic->g.glpic;
  glDisable(GL_ALPHA_TEST);
  glEnable (GL_BLEND);
//	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//	glCullFace(GL_FRONT);
  glColor4f (1,1,1,alpha);
  GL_Bind (gl->texnum);
#ifdef USE_OPENGLES
    DrawQuad(x, y, ppic->g.width, ppic->g.height, gl->sl, gl->tl, gl->sh - gl->sl, gl->th - gl->tl);
#else
  glBegin (GL_QUADS);
  glTexCoord2f (gl->sl, gl->tl);
  glVertex2f (x, y);
  glTexCoord2f (gl->sh, gl->tl);
  glVertex2f (x+pic->width, y);
  glTexCoord2f (gl->sh, gl->th);
  glVertex2f (x+pic->width, y+pic->height);
  glTexCoord2f (gl->sl, gl->th);
  glVertex2f (x, y+pic->height);
  glEnd ();
#endif
  glColor4f (1,1,1,1);
  glEnable(GL_ALPHA_TEST);
  glDisable (GL_BLEND);
}


/*
=============
Draw_Pic
=============
*/
void Draw_Pic (int x, int y, qpic_t *pic)
{
  byte			*dest, *source;
  unsigned short	*pusdest;
  int				v, u;
  glpic_t			*gl;

  if (scrap_dirty)
    Scrap_Upload ();
  glpic_t temp;
  memcpy(&temp, pic->data, sizeof(temp));
  gl = & temp;
  glColor4f (1,1,1,1);
  GL_Bind (gl->texnum);
#ifdef USE_OPENGLES
    DrawQuad(x, y, pic->width, pic->height, gl->sl, gl->tl, gl->sh - gl->sl, gl->th - gl->tl);
#else
  glBegin (GL_QUADS);
  glTexCoord2f (gl->sl, gl->tl);
  glVertex2f (x, y);
  glTexCoord2f (gl->sh, gl->tl);
  glVertex2f (x+pic->width, y);
  glTexCoord2f (gl->sh, gl->th);
  glVertex2f (x+pic->width, y+pic->height);
  glTexCoord2f (gl->sl, gl->th);
  glVertex2f (x, y+pic->height);
  glEnd ();
#endif
}


/*
=============
Draw_TransPic
=============
*/
void Draw_TransPic (int x, int y, qpic_t *pic)
{
  byte	*dest, *source, tbyte;
  unsigned short	*pusdest;
  int				v, u;

  if (x < 0 || (unsigned)(x + pic->width) > vid.width || y < 0 ||
     (unsigned)(y + pic->height) > vid.height)
  {
    Sys_Error ("Draw_TransPic: bad coordinates");
  }

  Draw_Pic (x, y, pic);
}


/*
=============
Draw_TransPicTranslate

Only used for the player color selection menu
=============
*/
void Draw_TransPicTranslate (int x, int y, qpic_t *pic, byte *translation)
{
  int				v, u, c;
  unsigned		trans[64*64], *dest;
  byte			*src;
  int				p;

  GL_Bind (translate_texture);

  c = pic->width * pic->height;

  dest = trans;
  for (v=0 ; v<64 ; v++, dest += 64)
  {
    src = &menuplyr_pixels[ ((v*pic->height)>>6) *pic->width];
    for (u=0 ; u<64 ; u++)
    {
      p = src[(u*pic->width)>>6];
      if (p == 255)
        dest[u] = p;
      else
        dest[u] =  d_8to24table[translation[p]];
    }
  }

  glTexImage2DHelper (GL_TEXTURE_2D, 0, gl_alpha_format, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans);

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glColor3f (1,1,1);
#ifdef USE_OPENGLES
    DrawQuad(x, y, pic->width, pic->height, 0, 0, 1, 1);
#else
  glBegin (GL_QUADS);
  glTexCoord2f (0, 0);
  glVertex2f (x, y);
  glTexCoord2f (1, 0);
  glVertex2f (x+pic->width, y);
  glTexCoord2f (1, 1);
  glVertex2f (x+pic->width, y+pic->height);
  glTexCoord2f (0, 1);
  glVertex2f (x, y+pic->height);
  glEnd ();
#endif
}


/*
================
Draw_ConsoleBackground

================
*/
void Draw_ConsoleBackground (int lines)
{
  int y = (vid.height * 3) >> 2;

  if (lines > y)
    Draw_Pic(0, lines - vid.height, &conback->qpic);
  else
    Draw_AlphaPic (0, lines - vid.height, conback, (float)(1.2 * lines)/y);
}


/*
=============
Draw_TileClear

This repeats a 64*64 tile graphic to fill the screen around a sized down
refresh window.
=============
*/

typedef union ByteToInt_t {
    byte b[4];
    int i;
} ByteToInt;

void Draw_TileClear (int x, int y, int w, int h)
{
  glColor3f (1,1,1);
  ByteToInt b;
  memcpy(b.b, draw_backtile->data, sizeof(b.b));
  GL_Bind (b.i);
#ifdef USE_OPENGLES
  DrawQuad(x, y, w, h, x/64.0, y/64.0, w/64.0, h/64.0);
#else
  glBegin (GL_QUADS);
  glTexCoord2f (x/64.0, y/64.0);
  glVertex2f (x, y);
  glTexCoord2f ( (x+w)/64.0, y/64.0);
  glVertex2f (x+w, y);
  glTexCoord2f ( (x+w)/64.0, (y+h)/64.0);
  glVertex2f (x+w, y+h);
  glTexCoord2f ( x/64.0, (y+h)/64.0 );
  glVertex2f (x, y+h);
  glEnd ();
#endif
}


/*
=============
Draw_Fill

Fills a box of pixels with a single color
=============
*/
void Draw_Fill (int x, int y, int w, int h, int c)
{
  glDisable (GL_TEXTURE_2D);
  glColor3f (host_basepal[c*3]/255.0,
    host_basepal[c*3+1]/255.0,
    host_basepal[c*3+2]/255.0);

#ifdef USE_OPENGLES
  DrawQuad_NoTex(x, y, w, h);
#else
  glBegin (GL_QUADS);

  glVertex2f (x,y);
  glVertex2f (x+w, y);
  glVertex2f (x+w, y+h);
  glVertex2f (x, y+h);

  glEnd ();
#endif
  glColor3f (1,1,1);
  glEnable (GL_TEXTURE_2D);
}
//=============================================================================

/*
================
Draw_FadeScreen

================
*/
void Draw_FadeScreen (void)
{
  glEnable (GL_BLEND);
  glDisable (GL_TEXTURE_2D);
  glColor4f (0, 0, 0, 0.8);
#ifdef USE_OPENGLES
  DrawQuad_NoTex(0, 0, vid.width, vid.height);
#else
  glBegin (GL_QUADS);

  glVertex2f (0,0);
  glVertex2f (vid.width, 0);
  glVertex2f (vid.width, vid.height);
  glVertex2f (0, vid.height);

  glEnd ();
#endif
  glColor4f (1,1,1,1);
  glEnable (GL_TEXTURE_2D);
  glDisable (GL_BLEND);

  Sbar_Changed();
}

//=============================================================================

/*
================
Draw_BeginDisc

Draws the little blue disc in the corner of the screen.
Call before beginning any disc IO.
================
*/
void Draw_BeginDisc (void)
{
  if (!draw_disc)
    return;
#ifdef USE_OPENGLES
  // !!! Implement this
#else
  glDrawBuffer  (GL_FRONT);
  Draw_Pic (vid.width - 24, 0, draw_disc);
  glDrawBuffer  (GL_BACK);
#endif
}


/*
================
Draw_EndDisc

Erases the disc icon.
Call after completing any disc IO
================
*/
void Draw_EndDisc (void)
{
}

/*
================
GL_Set2D

Setup as if the screen was 320*200
================
*/
void GL_Set2D (void)
{
  glViewport (glx, gly, glwidth, glheight);

  glMatrixMode(GL_PROJECTION);
    glLoadIdentity ();
#ifdef USE_OPENGLES
  glOrthof (0, vid.width, vid.height, 0, -99999, 99999);
#else
  glOrtho  (0, vid.width, vid.height, 0, -99999, 99999);
#endif

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity ();

  glDisable (GL_DEPTH_TEST);
  glDisable (GL_CULL_FACE);
  glDisable (GL_BLEND);
  glEnable (GL_ALPHA_TEST);
//	glDisable (GL_ALPHA_TEST);

  glColor4f (1,1,1,1);
}

//====================================================================

/*
================
GL_FindTexture
================
*/
int GL_FindTexture (const char *identifier)
{
  int		i;
  gltexture_t	*glt;

  for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
  {
    if (!strcmp (identifier, glt->identifier))
      return gltextures[i].texnum;
  }

  return -1;
}

/*
================
GL_ResampleTexture
================
*/
void GL_ResampleTexture (unsigned *in, int inwidth, int inheight, unsigned *out,  int outwidth, int outheight)
{
  int		i, j;
  unsigned	*inrow;
  unsigned	frac, fracstep;

  fracstep = inwidth*0x10000/outwidth;
  for (i=0 ; i<outheight ; i++, out += outwidth)
  {
    inrow = in + inwidth*(i*inheight/outheight);
    frac = fracstep >> 1;
    for (j=0 ; j<outwidth ; j+=4)
    {
      out[j] = inrow[frac>>16];
      frac += fracstep;
      out[j+1] = inrow[frac>>16];
      frac += fracstep;
      out[j+2] = inrow[frac>>16];
      frac += fracstep;
      out[j+3] = inrow[frac>>16];
      frac += fracstep;
    }
  }
}

/*
================
GL_Resample8BitTexture -- JACK
================
*/
void GL_Resample8BitTexture (unsigned char *in, int inwidth, int inheight, unsigned char *out,  int outwidth, int outheight)
{
  int		i, j;
  unsigned	char *inrow;
  unsigned	frac, fracstep;

  fracstep = inwidth*0x10000/outwidth;
  for (i=0 ; i<outheight ; i++, out += outwidth)
  {
    inrow = in + inwidth*(i*inheight/outheight);
    frac = fracstep >> 1;
    for (j=0 ; j<outwidth ; j+=4)
    {
      out[j] = inrow[frac>>16];
      frac += fracstep;
      out[j+1] = inrow[frac>>16];
      frac += fracstep;
      out[j+2] = inrow[frac>>16];
      frac += fracstep;
      out[j+3] = inrow[frac>>16];
      frac += fracstep;
    }
  }
}


/*
================
GL_MipMap

Operates in place, quartering the size of the texture
================
*/
void GL_MipMap (byte *in, int width, int height)
{
  int		i, j;
  byte	*out;

  width <<=2;
  height >>= 1;
  out = in;
  for (i=0 ; i<height ; i++, in+=width)
  {
    for (j=0 ; j<width ; j+=8, out+=4, in+=8)
    {
      out[0] = (in[0] + in[4] + in[width+0] + in[width+4])>>2;
      out[1] = (in[1] + in[5] + in[width+1] + in[width+5])>>2;
      out[2] = (in[2] + in[6] + in[width+2] + in[width+6])>>2;
      out[3] = (in[3] + in[7] + in[width+3] + in[width+7])>>2;
    }
  }
}

#ifdef SUPPORT_8BIT_MIPMAPGENERATION
/*
================
GL_MipMap8Bit

Mipping for 8 bit textures

The "in" and "out" arguments can point to the same buffer if desired
================
*/
void GL_MipMap8Bit (byte *in, byte* out, int width, int height)
{
  int		i, j;
  unsigned short     r,g,b;
  byte	*at1, *at2, *at3, *at4;

//	width <<=2;
  height >>= 1;
  for (i=0 ; i<height ; i++, in+=width)
  {
    for (j=0 ; j<width ; j+=2, out+=1, in+=2)
    {
      at1 = (byte *) (d_8to24table + in[0]);
      at2 = (byte *) (d_8to24table + in[1]);
      at3 = (byte *) (d_8to24table + in[width+0]);
      at4 = (byte *) (d_8to24table + in[width+1]);

       r = (at1[0]+at2[0]+at3[0]+at4[0]); r>>=5;
       g = (at1[1]+at2[1]+at3[1]+at4[1]); g>>=5;
       b = (at1[2]+at2[2]+at3[2]+at4[2]); b>>=5;

      out[0] = d_15to8table[(r<<0) + (g<<5) + (b<<10)];
    }
  }
}

#endif // SUPPORT_8BIT_MIPMAPGENERATION

void glTexImage2DHelper( GLenum target,
   GLint level,
   GLint internalformat,
   GLsizei width,
   GLsizei height,
   GLint border,
   GLenum format,
   GLenum type,
   const GLvoid *pixels )
{
  // In full OpenGL The internalformat can be 1..4, to indicate how many components of the data are valid.
  // OpenGL ES requires the internalformat argument match the format for glTexImage2D.

  glTexImage2D(target, level, format, width, height, border, format, type, pixels);
}


// Uncomment to enable manual MipMap generation
#define USE_MANUAL_MIPMAP_GEN

// Uncomment one of the following:

// #define USE_16BPP_WITH_8888_ALPHA
// #define USE_16BPP_WITH_5551_ALPHA // <--- This has bugs on the simulator and the device. (Device has all alpha images invisible.)
#define USE_16BPP_WITH_4444_ALPHA // <--- This has bugs on the simulator, works in device
// #define USE_32BPP
// #define USE_32BPP_MANUAL_MIPMAP_GEN

#ifdef USE_MANUAL_MIPMAP_GEN

inline unsigned int average4(unsigned int a, unsigned int b,
        unsigned int c, unsigned int d,
        unsigned int shift, unsigned int mask) {
    unsigned int aElem = (a >> shift) & mask;
    unsigned int bElem = (b >> shift) & mask;
    unsigned int cElem = (c >> shift) & mask;
    unsigned int dElem = (d >> shift) & mask;
    unsigned int avgElem = ((aElem + bElem + cElem + dElem) >> 2) & mask;
    return avgElem << shift;
}

inline unsigned int average2(unsigned int a, unsigned int b,
        unsigned int shift, unsigned int mask) {
    unsigned int aElem = (a >> shift) & mask;
    unsigned int bElem = (b >> shift) & mask;
    unsigned int avgElem = ((aElem + bElem) >> 1) & mask;
    return avgElem << shift;
}

inline unsigned int average4444(unsigned int a, unsigned int b) {
    return
        average2(a,b,0,0xf) |
        average2(a,b,4,0xf) |
        average2(a,b,8,0xf) |
        average2(a,b,12,0xf);
}

inline unsigned int average565(unsigned int a, unsigned int b) {
    return
        average2(a,b,0,0x1f) |
        average2(a,b,5,0x3f) |
        average2(a,b,11,0x1f);
}

inline unsigned int average2_8888(unsigned int a, unsigned int b) {
    return
        average2(a,b,0,0xff) |
        average2(a,b,8,0xff) |
        average2(a,b,16,0xff) |
        average2(a,b,24,0xff);
}

inline unsigned int average4_8888(unsigned int a, unsigned int b,
        unsigned int c, unsigned int d) {
    return
        average4(a,b,c,d,0,0xff) |
        average4(a,b,c,d,8,0xff) |
        average4(a,b,c,d,16,0xff) |
        average4(a,b,c,d,24,0xff);
}

#endif

// pData is 8 bpp 32-bit color


void sendTexture(int mipLevel, int width, int height, unsigned int* pData, qboolean alpha) {
    if (alpha) {
#if defined(USE_16BPP_WITH_8888_ALPHA)
        // 8888
        glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#elif defined(USE_16BPP_WITH_5551_ALPHA)
        // 5551
        glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 0);
        glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#else
        // 4444
        glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 0);
        glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#endif
    }
    else {
#if 0
        // 8888
        glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData);
#else
        // 565
        static	unsigned short scaled[1024*512];	// [512*256];
        glTexImage2D(GL_TEXTURE_2D, mipLevel, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
        // Some OpenGL ES implementations do not have to be able to convert from GL_RGBA to GL_RGB format, so
        // we must do it manually here:
        unsigned char* pSrc = (unsigned char*) pData;
        unsigned short* pDest = scaled;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                *pDest++ = ((pSrc[0] >> 3) << 11) |
                    ((pSrc[1] >> 2) << 5) |
                    (pSrc[2] >> 3);
                pSrc += 4;
            }
        }
        glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, scaled);
#endif
    }
}

/*
===============
GL_Upload32
===============
*/
void GL_Upload32 (unsigned *data, int width, int height,  qboolean mipmap, qboolean alpha)
{
  int			samples;
  int			scaled_width, scaled_height;
  static	unsigned	scaled[1024*512];	// [512*256];

  for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
    ;
  for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
    ;

  scaled_width >>= (int)gl_picmip.value;
  scaled_height >>= (int)gl_picmip.value;

  if (scaled_width > gl_max_size.value)
    scaled_width = (int) gl_max_size.value;
  if (scaled_height > gl_max_size.value)
    scaled_height = (int) gl_max_size.value;

  if (scaled_width * scaled_height > (int) sizeof(scaled)/4)
    Sys_Error ("GL_LoadTexture: too big");

  samples = alpha ? gl_alpha_format : gl_solid_format;

    texels += scaled_width * scaled_height;

  if (scaled_width == width && scaled_height == height)
  {
#if 0 // Disable this optimization, we want to be able to easily switch texture formats
    if (!mipmap)
    {
      glTexImage2DHelper (GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
      goto done;
    }
#endif
    memcpy (scaled, data, width*height*4);
  }
  else
    GL_ResampleTexture (data, width, height, scaled, scaled_width, scaled_height);

#if defined(USE_16BPP_WITH_8888_ALPHA) || defined(USE_16BPP_WITH_5551_ALPHA) || defined(USE_16BPP_WITH_4444_ALPHA)
  // Upload as 16 bpp

#ifdef USE_MANUAL_MIPMAP_GEN
#else
  // Use automatic MIPMAP generation
  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);
  }
#endif

  sendTexture(0, scaled_width, scaled_height, scaled, alpha);

#ifdef USE_MANUAL_MIPMAP_GEN
  if (mipmap) {
      // Compute mip levels
      int mipWidth = scaled_width;
      int mipHeight = scaled_height;
      int mipLevel = 1;
      while (mipWidth > 1 || mipHeight > 1) {
          if (mipWidth > 1 && mipHeight > 1) {
              // Scale horizontally and vertically
              int srcWidth = mipWidth;
                mipWidth >>= 1;
                mipHeight >>= 1;
                const unsigned int* pIn = (const unsigned int*) scaled;
                unsigned int* pOut = (unsigned int*) scaled;
                for(int y = 0; y < mipHeight; y++) {
                    for (int x = 0; x < mipWidth; x++) {
                        *pOut++ = average4_8888(pIn[0], pIn[1],
                                pIn[srcWidth], pIn[srcWidth+1]);
                        pIn += 2;
                    }
                    pIn += srcWidth;
                }
          }
          else {
              // Scale horizontally:
              if (mipWidth > 1) {
                  mipWidth >>= 1;
                  const unsigned int* pIn = (const unsigned int*) scaled;
                  unsigned int* pOut = (unsigned int*) scaled;
                  unsigned int numTexels = mipHeight * mipWidth;
                  for(unsigned int i = 0; i < numTexels; i++) {
                      *pOut++ = average2_8888(pIn[0], pIn[1]);
                      pIn += 2;
                  }
              }
              // Scale vertically:
              if (mipHeight > 1) {
                  mipHeight >>= 1;
                  const unsigned int* pIn = (const unsigned int*) scaled;
                  unsigned int* pOut = (unsigned int*) scaled;
                  for(int y = 0; y < mipHeight; y++) {
                      for (int x = 0; x < mipWidth; x++) {
                          *pOut++ = average2_8888(pIn[0], pIn[mipWidth]);
                          pIn += 1;
                      }
                      pIn += mipWidth;
                  }
              }
          }

          sendTexture(mipLevel, mipWidth, mipHeight, scaled, alpha);
          mipLevel++;
      }
  }

#else
  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 0);
  }
#endif

#elif defined(USE_32BPP)
  // 8888
  // Use automatic MIPMAP generation
  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 1);
  }
  glTexImage2DHelper (GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, 0);
  }
#else
  glTexImage2DHelper (GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
  if (mipmap)
  {
    int		miplevel;

    miplevel = 0;
    while (scaled_width > 1 || scaled_height > 1)
    {
      GL_MipMap ((byte *)scaled, scaled_width, scaled_height);
      scaled_width >>= 1;
      scaled_height >>= 1;
      if (scaled_width < 1)
        scaled_width = 1;
      if (scaled_height < 1)
        scaled_height = 1;
      miplevel++;
      glTexImage2DHelper (GL_TEXTURE_2D, miplevel, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
    }
  }
#endif
done: ;

  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
  }
  else
  {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
  }
}

#ifdef USE_OPENGLES

void GL_Upload8_EXT (byte *data, int width, int height,  qboolean mipmap, qboolean alpha)
{
  int			i, s, bytesUsed;
  qboolean	noalpha;
  int			p;
  static unsigned j;
    static	unsigned char compressedTextureBuffer[1024*512];	// [512*256];
  unsigned char* pTex = compressedTextureBuffer;
  int			scaled_width, scaled_height;
  int miplevel = 0;

  int originalScaledWidth;
  int originalScaledHeight;

  s = width*height;
  // if there are no transparent pixels, make it a 3 component
  // texture even if it was specified as otherwise
  if (alpha)
  {
    noalpha = true;
    for (i=0 ; i<s ; i++)
    {
      if (data[i] == 255)
        noalpha = false;
    }

    if (alpha && noalpha)
      alpha = false;
  }
  for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
    ;
  for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
    ;

  scaled_width >>= (int)gl_picmip.value;
  scaled_height >>= (int)gl_picmip.value;

  if (scaled_width > gl_max_size.value)
    scaled_width = (int) gl_max_size.value;
  if (scaled_height > gl_max_size.value)
    scaled_height = (int) gl_max_size.value;

  if (scaled_width * scaled_height > ((int) (sizeof(compressedTextureBuffer) * 3 / 4)))
    Sys_Error ("GL_LoadTexture: too big");

  // Copy the palette

  int entrySize = alpha ? 4 : 3;
  int paletteSize = entrySize * 256;
  {
    byte* pDest = compressedTextureBuffer;
    const byte* pSrc = host_basepal;
    if(alpha)
    {
      for(int i = 0; i< 255; i++)
      {
        *pDest++ = *pSrc++;
        *pDest++ = *pSrc++;
        *pDest++ = *pSrc++;
        *pDest++ = 0xff;
      }
      // Entry 255 is transparent
      *pDest++ = 0x00;
      *pDest++ = 0x00;
      *pDest++ = 0x00;
      *pDest++ = 0x00;
    }
    else
    {
      memcpy(pDest, pSrc, paletteSize);
    }
  }

  bytesUsed = paletteSize;
  pTex += paletteSize;

  texels += scaled_width * scaled_height;

  if (scaled_width == width && scaled_height == height)
  {
    memcpy (pTex, data, scaled_width*scaled_height);
  }
  else
    GL_Resample8BitTexture (data, width, height, pTex, scaled_width, scaled_height);

  bytesUsed += scaled_width * scaled_height;

  miplevel = 0;

  originalScaledWidth = scaled_width;
  originalScaledHeight = scaled_height;

  if (mipmap)
  {
#ifdef SUPPORT_8BIT_MIPMAPGENERATION
        miplevel = 1;
    while (scaled_width > 1 || scaled_height > 1)
    {
      byte* pDest = (byte*) pTex + scaled_width * scaled_height;
      GL_MipMap8Bit ((byte *)pTex, pDest, scaled_width, scaled_height);
      pTex = pDest;
      scaled_width >>= 1;
      scaled_height >>= 1;
      if (scaled_width < 1)
        scaled_width = 1;
      if (scaled_height < 1)
        scaled_height = 1;
      bytesUsed += scaled_width * scaled_height;
      miplevel++;
    }
#else
  Sys_Error("Unsupported attempt to generate 8 bit mip mapped texture. #define SUPPORT_8BIT_MIPMAPGENERATION");
#endif
  }

  GLint internalFormat = alpha ? GL_PALETTE8_RGBA8_OES : GL_PALETTE8_RGB8_OES;
  glCompressedTexImage2D (GL_TEXTURE_2D, -miplevel, internalFormat,
            originalScaledWidth, originalScaledHeight,
            0, bytesUsed, compressedTextureBuffer);

  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
  }
  else
  {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
  }
}

#else

void GL_Upload8_EXT (byte *data, int width, int height,  qboolean mipmap, qboolean alpha)
{
  int			i, s;
  qboolean	noalpha;
  int			p;
  static unsigned j;
  int			samples;
    static	unsigned char scaled[1024*512];	// [512*256];
  int			scaled_width, scaled_height;

  s = width*height;
  // if there are no transparent pixels, make it a 3 component
  // texture even if it was specified as otherwise
  if (alpha)
  {
    noalpha = true;
    for (i=0 ; i<s ; i++)
    {
      if (data[i] == 255)
        noalpha = false;
    }

    if (alpha && noalpha)
      alpha = false;
  }
  for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
    ;
  for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
    ;

  scaled_width >>= (int)gl_picmip.value;
  scaled_height >>= (int)gl_picmip.value;

  if (scaled_width > gl_max_size.value)
    scaled_width = gl_max_size.value;
  if (scaled_height > gl_max_size.value)
    scaled_height = gl_max_size.value;

  if (scaled_width * scaled_height > (int) sizeof(scaled))
    Sys_Error ("GL_LoadTexture: too big");

  samples = 1; // alpha ? gl_alpha_format : gl_solid_format;

  texels += scaled_width * scaled_height;

  if (scaled_width == width && scaled_height == height)
  {
    if (!mipmap)
    {
      glTexImage2D (GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, scaled_width, scaled_height, 0, GL_COLOR_INDEX , GL_UNSIGNED_BYTE, data);
      goto done;
    }
    memcpy (scaled, data, width*height);
  }
  else
    GL_Resample8BitTexture (data, width, height, scaled, scaled_width, scaled_height);

  glCompressedTexImage2D (GL_TEXTURE_2D, 0, GL_PALETTE8_RGB8_OES, scaled_width, scaled_height, 0, s, scaled);
  if (mipmap)
  {
#ifdef SUPPORT_8BIT_MIPMAPGENERATION
    int		miplevel;

    miplevel = 0;
    while (scaled_width > 1 || scaled_height > 1)
    {
      GL_MipMap8Bit ((byte *)scaled, (byte*) scaled, scaled_width, scaled_height);
      scaled_width >>= 1;
      scaled_height >>= 1;
      if (scaled_width < 1)
        scaled_width = 1;
      if (scaled_height < 1)
        scaled_height = 1;
      miplevel++;
      glTexImage2D (GL_TEXTURE_2D, miplevel, GL_COLOR_INDEX8_EXT, scaled_width, scaled_height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, scaled);
    }
#else
  Sys_Error("Unsupported attept to generate 8 bit mip mapped texture.");
#endif
  }
done: ;


  if (mipmap)
  {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
  }
  else
  {
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
  }
}

#endif // ! OPENGL_ES

/*
===============
GL_Upload8
===============
*/
void GL_Upload8 (byte *data, int width, int height,  qboolean mipmap, qboolean alpha)
{
static	unsigned	trans[640*480];		// FIXME, temporary
  int			i, s;
  qboolean	noalpha;
  int			p;

  s = width*height;
  // if there are no transparent pixels, make it a 3 component
  // texture even if it was specified as otherwise
  if (alpha)
  {
    noalpha = true;
    for (i=0 ; i<s ; i++)
    {
      p = data[i];
      if (p == 255)
        noalpha = false;
      trans[i] = d_8to24table[p];
    }

    if (alpha && noalpha)
      alpha = false;
  }
  else
  {
    if (s&3)
      Sys_Error ("GL_Upload8: s&3");
    for (i=0 ; i<s ; i+=4)
    {
      trans[i] = d_8to24table[data[i]];
      trans[i+1] = d_8to24table[data[i+1]];
      trans[i+2] = d_8to24table[data[i+2]];
      trans[i+3] = d_8to24table[data[i+3]];
    }
  }

   if (VID_Is8bit() && (data!=scrap_texels[0])
#if !defined(USE_OPENGLES)
    && !alpha
#endif
  ) {
     GL_Upload8_EXT (data, width, height, mipmap, alpha);
     return;
  }
  GL_Upload32 (trans, width, height, mipmap, alpha);
}

/*
================
GL_LoadTexture
================
*/
int GL_LoadTexture (const char *identifier, int width, int height, byte *data, qboolean mipmap, qboolean alpha)
{
  qboolean	noalpha;
  int			i, p, s;
  gltexture_t	*glt;

  // see if the texture is allready present
  if (identifier[0])
  {
    for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
    {
      if (!strcmp (identifier, glt->identifier))
      {
        if (width != glt->width || height != glt->height)
          Sys_Error ("GL_LoadTexture: cache mismatch");
        return gltextures[i].texnum;
      }
    }
#ifdef USE_OPENGLES
    // Surely we want to remember this new texture.
    // Doing this costs 1% fps per timedemo on a DX7 PC,
    // probably because of the linear search through the
    // texture cache, but it saves 10 MB of VM growth per
    // level load. It also makes the GL_TEXTUREMODE
    // console command work correctly.
    numgltextures++;
#endif
  }
  else {
    glt = &gltextures[numgltextures];
    numgltextures++;
  }

  strcpy (glt->identifier, identifier);
  glt->texnum = texture_extension_number;
  glt->width = width;
  glt->height = height;
  glt->mipmap = mipmap;

    GL_Bind(texture_extension_number);

#ifdef USE_TEXTURE_STORE

  textureStore::get()->create(width, height, data, mipmap, alpha);

#else

    GL_Upload8 (data, width, height, mipmap, alpha);

#endif

  texture_extension_number++;
  return texture_extension_number-1;
}


/****************************************/

static GLenum oldtarget = TEXTURE0_SGIS;

void GL_SelectTexture (GLenum target)
{
  if (!gl_mtexable)
    return;
#ifdef USE_OPENGLES
  glActiveTexture(target);
#else
  qglSelectTextureSGIS(target);
#endif
  if (target == oldtarget)
    return;
  cnttextures[oldtarget-TEXTURE0_SGIS] = currenttexture;
  currenttexture = cnttextures[target-TEXTURE0_SGIS];
  oldtarget = target;
}

// OpenGL ES compatible DrawQuad utility

#define BEGIN_QUAD glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
#define END_QUAD glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

void DrawQuad_NoTex(float x, float y, float w, float h)
{
  BEGIN_QUAD

  float vertex[2*4] = {x,y,x+w,y, x+w, y+h, x, y+h};
  short index[4] = {0, 1, 2, 3};
  glVertexPointer( 2, GL_FLOAT, 0, vertex);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, index);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

  END_QUAD
}

void DrawQuad(float x, float y, float w, float h, float u, float v, float uw, float vh)
{
  BEGIN_QUAD

    float texcoord[2*4] = {u, v, u + uw, v, u + uw, v + vh, u, v + vh};
  float vertex[2*4] = {x,y,x+w,y, x+w, y+h, x, y+h};
  unsigned short index[4] = {0, 1, 2, 3};
  glTexCoordPointer( 2, GL_FLOAT, 0, texcoord);
  glVertexPointer( 2, GL_FLOAT, 0, vertex);
  glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, index);

  END_QUAD
}

#ifdef USE_OPENGLES

// Reimplementation of OpenGL functions that are missing in OpenGL ES

void glColor3f(GLfloat r, GLfloat g, GLfloat b)
{
  glColor4f(r, g, b, 1.0f);
}

void glColor4fv(GLfloat* pColor)
{
  glColor4f(pColor[0], pColor[1], pColor[2], pColor[3]);
}

float gVertexBuffer[VERTEXARRAYSIZE];
float gColorBuffer[VERTEXARRAYSIZE];
float gTexCoordBuffer[VERTEXARRAYSIZE];

// Called when we've lost the OpenGL context and have to recreate it.
extern void GL_Init();
extern void R_InitParticleTexture2();
extern void GL_UploadLightmaps();
extern void R_ReloadSky();

void GL_ReInit() {
  GL_Init();
  textureStore::get()->rebindAll();
  scrap_dirty = true;
  R_InitParticleTexture2();
  GL_UploadLightmaps();
  R_ReloadSky();
}

#endif

#ifdef DEBUG_OPENGL_CALLS
void checkGLImp(const char* state, const char* file, int line) {
    GLenum error = glGetError();
    if (error != GL_NO_ERROR) {
    	Sys_Error("%s: error 0x%04X at %s:%d\n", state, error, file, line);
    }
}

#endif
