/* -*- mode: C; tab-width:8;  -*-

             fxmesa1.c - 3Dfx VooDoo/Mesa interface
*/

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Brian Paul  Nov 13, 1997
 *         - fixed bug in read_color_span(), red/blue were swapped
 *
 * V0.20 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - written the closetmmanger() function in order to free all the memory
 *           allocated by the Texture Memory Manager (it will be useful
 *           when the support for multiple contexts/boards will be ready)
 *         - now the Voodoo driver runs without printing any information,
 *           define the env. var. MESA_FX_INFO if you want to read some
 *           information about the hardware and some statistic
 *         - written a small workaround for the "GLQuake multiplayer white box bug"
 *           in the setup_fx_units() functions. I'm already rewriting
 *           this function because it is the source of nearly all the current
 *           Voodoo driver problems
 *         - fixed the GLQuake texture misalignment problem (the texture
 *           coordinates must be scaled between 0.0 and 256.0 and not
 *           between 0.0 and 255.0)
 *         - written the support for the GL_EXT_shared_texture_palette
 *         - some small change for supporting the automatic bulding of the
 *           OpenGL32.dll under the Windows platform
 *         - the redefinition of a mipmap level is now a LOT faster. This path
 *           is used by GLQuake for dynamic lighting with some call to glTexSubImage2D()
 *         - the texture memory is now managed a set of 2MB blocks so
 *           texture maps can't be allocated on a 2MB boundary. The new Pure3D
 *           needs this kind of support (and probably any other Voodoo Graphics
 *           board with more than 2MB of texture memory)
 *
 *         Brian Paul (brianp@elastic.avid.com) Avid Technology
 *         - added write_monocolor_span(), fixed bug in write_color_span()
 *         - added test for stenciling in choosepoint/line/triangle functions
 *
 *         Joe Waters (falc@attila.aegistech.com) Aegis
 *         - written the support for the env. var. SST_SCREENREFRESH
 *
 * V0.19 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - written the 3Dfx Global Palette extension for GLQuake
 *         - written the support for the GL_EXT_paletted_texture (it works only with GL_RGBA
 *           palettes and the alpha value is ignored ... this is a limitation of the
 *           the current Glide version and Voodoo hardware)
 *         - fixed the amount of memory allocated for 8bit textures
 *         - merged the under construction v0.19 driver with the Mesa 2.5
 *         - finally written the support for deleting textures
 *         - introduced a new powerful texture memory manager: the texture memory
 *           is used as a cache of the set of all defined texture maps. You can
 *           now define several MB of texture maps also with a 2MB of texture memory
 *           (the texture memory manager will do automatically all the swap out/swap in
 *           work). The new texture memory manager has also
 *           solved a lot of other bugs/no specs compliance/problems
 *           related to the texture memory usage. The texture
 *           manager code is inside the new fxmesa3.c file
 *         - broken the fxmesa.c file in two files (fxmesa1.c and fxmesa2.c)
 *           and done some code cleanup
 *         - now is possible to redefine texture mipmap levels already defined
 *         - fixed a problem with the amount of texture memory allocated for textures
 *           with not all mipmap levels defined
 *         - fixed a small problem with single buffer rendering
 *
 *         Brian Paul (brianp@elastic.avid.com) Avid Technology
 *         - read/write_color_span() now use front/back buffer correctly
 *         - create GLvisual with 5,6,5 bits per pixel, not 8,8,8
 *         - removed a few ^M characters from fxmesa2.c file
 *
 * V0.18 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - the Mesa-2.4beta3 is finally using the driver quads support (the
 *           previous Mesa versions have never taken any advantage from the quads support !)
 *         - tested with the Glide 2.4 for Win
 *         - ported all asm code to Linux
 *         - ported the v0.18 to Linux (without asm code)
 *         - back to Linux !!!
 *         - optimized the SETUP macro (no more vertex snap for points and lines)
 *         - optimized the SETUP macro (added one argument)
 *         - the Mesa/Voodoo is now 20/30% for points, lines and small triangles !
 *         - performance improvement setting VBSIZE to 72 
 *         - the GrVertex texture code is now written in asm
 *         - the GrVertex zbuffer code is now written in asm
 *         - the GrVertex wbuffer code is now written in asm
 *         - the GrVertex gouraud code is now written in asm
 *         - the GrVertex snap code is now written in asm
 *         - changed the 8bit compressed texture maps in 8bit palette texture maps
 *           support (it has the some advantage of compressed texture maps without the
 *           problem of a fixed NCC table for all mipmap levels)
 *         - written the support for 8bit compressed texture maps (but texture maps with
 *           more than one mipmap level aren't working fine)
 *         - finnaly everthing is working fine in MesaQuake !
 *         - fixed a bug in the computation of texture mapping coordinates (I have found
 *           the bug thanks to MesaQuake !)
 *         - written the GL_REPLACE support (mainly for MesaQuake)
 *         - written the support for textures with not all mipmap levels defined
 *         - rewritten all the Texture memory stuff
 *         - written the MesaQuake support (define MESAQUAKE)
 *         - working with a ZBuffer if glOrtho or not int the default glDepthRange,
 *           otherwise working with the WBuffer
 *         written the glDepthRange support
 *
 *         Diego Picciani (d.picciani@novacomp.it) Nova Computer s.r.l.
 *         - written the fxCloseHardware() and the fxQuaryHardware() (mainly
 *           for the VoodooWGL emulator)
 *
 *         Brian Paul (brianp@elastic.avid.com) Avid Technology
 *         - implemented read/write_color_span() so glRead/DrawPixels() works
 *         - now needs Glide 2.3 or later.  Removed GLIDE_FULL_SCREEN and call to grSstOpen()
 *
 * V0.17 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - optimized the bitmap support (66% faster)
 *         - tested with the Mesa 2.3beta2
 *
 *         Diego Picciani (d.picciani@novacomp.it) Nova Computer s.r.l.
 *         - solved a problem with the drawbitmap() and the Voodoo Rush
 *           (GR_ORIGIN_LOWER_LEFT did not work with the Stingray)
 *
 *         Brian Paul (brianp@elastic.avid.com) Avid Technology
 *         - linux stuff
 *         - general code clean-up
 *         - added attribList parameter to fxMesaCreateContext()
 *         - single buffering works now
 *         - VB colors are now GLubytes, removed ColorShift stuff
 *
 *         Paul Metzger
 *         - linux stuff
 *
 * V0.16 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - written the quadfunc support (no performance improvement)
 *         - written the support for the new Mesa 2.3beta1 driver interface (Wow ! It is faaaster)
 *         - rewritten the glBitmap support for the Glide 2.3 (~35% slower !)
 *         - written the glBitmap support for the most common case (fonts)
 *
 *         Jack Palevich
 *         - Glide 2.3 porting
 *
 *         Diego Picciani (d.picciani@novacomp.it) Nova Computer s.r.l.
 *         - extended the fxMesaCreateContext() and fxMesaCreateBestContext()
 *           functions in order to support also the Voodoo Rush
 *         - tested with the Hercules Stingray 128/3D (The rendering in a window works !)
 *
 * V0.15 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - written the GL_LUMINANCE_ALPHA support
 *         - written the GL_ALPHA support
 *         - written the GL_LUMINANCE support
 *         - now SETUP correctly set color for mono color sequences
 *         - written the 9x1,10x1,...,1x9,1x10,... texture map ratio support
 *         - written the no square texture map support
 *         - the fog table is no more rebuilt inside setup_fx_units() each time
 *
 *         Henri Fousse (arnaud@pobox.oleane.com) Thomson Training & Simulation
 *         - written (not yet finished: no texture mapping) support for glOrtho
 *         - some change to setup functions
 *         - the fog support is now fully compatible with the standard OpenGL
 *         - rewritten several parts of the driver in order to take
 *           advantage of meshes (40% faster !!!)
 *
 * V0.14 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - now glAlphaFunc() works
 *         - now glDepthMask() works
 *         - solved a mipmap problem when using more than one texture
 *         - moved ti, texid and wscale inside the fxMesaContext (now we can
 *           easy support more ctx and more boards)
 *         - the management of the fxMesaContext was completly broken !
 *         - solved several problems about Alpha and texture Alpha
 *         - 4 (RGBA) texture channels supported
 *         - setting the default color to white
 *
 *         Henri Fousse (arnaud@pobox.oleane.com) Thomson Training & Simulation
 *         - small change to fxMesaCreateContext() and fxMesaMakeCurrent()
 *         - written the fog support
 *         - setting the default clear color to black
 *         - written cleangraphics() for the onexit() function
 *         - written fxMesaCreateBestContext()
 *
 * V0.13 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - now glBlendFunc() works for all glBlendFunc without DST_ALPHA
 *           (because the alpha buffer is not yet implemented) 
 *         - now fxMesaCreateContext() accept resolution and refresh rate
 *         - fixed a bug for texture mapping: the w (alias z) must be set
 *           also without depth buffer
 *         - fixed a bug for texture image with width!=256
 *         - written texparam()
 *         - written all point, line and triangle functions for all possible supported
 *           contexts and the driver is slight faster with points, lines and small triangles
 *         - fixed a small bug in fx/fxmesa.h (glOrtho)
 *
 * V0.12 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - glDepthFunc supported
 *         - introduced a trick to discover the far plane distance
 *           (see fxMesaSetFar and fx/fxmesa.h)
 *         - now the wbuffer works with homogeneous coordinate (and it
 *           doesn't work with a glOrtho projection :)
 *         - solved several problems with homogeneous coordinate and texture mapping
 *         - fixed a bug in all line functions
 *         - fixed a clear framebuffer bug
 *         - solved a display list/teximg problem (but use
 *           glBindTexture: it is several times faster)
 *
 * V0.11 - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - introduced texture mapping support (not yet finished !)
 *         - tested with Mesa2.2b6
 *         - the driver is faster 
 *         - written glFlush/glFinish
 *         - the driver print a lot of info about the Glide lib
 *
 * v0.1  - David Bucciarelli (tech.hmw@plus.it) Humanware s.r.l.
 *         - Initial revision
 *
 */

#if defined(FX)

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "fxdrv.h"

fxMesaContext CurrentfxMesaCtx=NULL;

/*
 * Status of 3Dfx hardware initialization
 */

static int glideInitialized = 0;
static int _3dfxPresent = 0;
static GrHwConfiguration hwconfig;


/**********************************************************************/
/*****                 Miscellaneous functions                    *****/
/**********************************************************************/

/* return buffer size information */
static void buffer_size(GLcontext *ctx, GLuint *width, GLuint *height)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: buffer_size(...)\n");
#endif

  *width=fxMesa->width;
  *height=fxMesa->height;
}


/* Set current drawing color */
static void set_color(GLcontext *ctx, GLubyte red, GLubyte green,
		      GLubyte blue, GLubyte alpha )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: set_color(%d,%d,%d,%d)\n",red,green,blue,alpha);
#endif

  fxMesa->color=FXCOLOR(red,green,blue,alpha);
}


/* implements glClearColor() */
static void clear_color(GLcontext *ctx, GLubyte red, GLubyte green,
			GLubyte blue, GLubyte alpha )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: clear_color(%d,%d,%d,%d)\n",red,green,blue,alpha);
#endif
 
  fxMesa->clearc=FXCOLOR(red,green,blue,255);
  fxMesa->cleara=alpha;
}


/* clear the frame buffer */
static void clear(GLcontext *ctx, GLboolean all,
		  GLint x, GLint y, GLint width, GLint height )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: clear(%d,%d,%d,%d)\n",x,y,width,height);
#endif

  grDepthMask(FXFALSE);

  grBufferClear(fxMesa->clearc,fxMesa->cleara,0);

  if(ctx->Depth.Test && ctx->Depth.Mask)
    grDepthMask(FXTRUE);
}


/*  set the buffer used in double buffering */
static GLboolean set_buffer(GLcontext *ctx, GLenum mode )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: set_buffer(%d)\n",mode);
#endif

  if (fxMesa->double_buffer) {
    if(mode==GL_FRONT)
      fxMesa->currentfb = GR_BUFFER_FRONTBUFFER;
    else if(mode==GL_BACK)
      fxMesa->currentfb = GR_BUFFER_BACKBUFFER;
    else
      return GL_FALSE;
  } else {
    if(mode==GL_FRONT)
      fxMesa->currentfb = GR_BUFFER_FRONTBUFFER;
    else
      return GL_FALSE;
  }

  grRenderBuffer(fxMesa->currentfb);
  return GL_TRUE;
}


static GLboolean drawbitmap(GLcontext *ctx, GLsizei width, GLsizei height,
                            GLfloat xorig, GLfloat yorig,
                            GLfloat xmove, GLfloat ymove,
                            const struct gl_image *bitmap)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  FxU16 *p;
  GrLfbInfo_t info;
  GLubyte *pb;
  int x,y;
  GLint r,g,b,a,px,py,scrwidth,scrheight,stride;
  FxU16 color;

#define ISCLIPPED(rx) ( ((rx)<0) || ((rx)>=scrwidth) )
#define DRAWBIT(i) {	   \
  if(!ISCLIPPED(x+px))	   \
    if( (*pb) & (1<<(i)) ) \
      (*p)=color;	   \
  p++;                     \
  x++;			   \
  if(x>=width) {           \
    pb++;                  \
    break;                 \
  }                        \
}

  scrwidth=fxMesa->width;
  scrheight=fxMesa->height;

  px=(GLint)((ctx->Current.RasterPos[0]-xorig)+0.0F);
  py=(GLint)((ctx->Current.RasterPos[1]-yorig)+0.0F);

  if((px>=scrwidth) || (px+width<=0) || (py>=scrheight) || (py+height<=0))
    return GL_TRUE;

  pb=(GLubyte *)bitmap->Data;

  if(py<0) {
    pb+=(bitmap->Height*(-py)) >> (3+1);
    height+=py;
    py=0;
  }

  if(py+height>=scrheight)
    height-=(py+height)-scrheight;

  info.size=sizeof(info);
  if(!grLfbLock(GR_LFB_WRITE_ONLY,
		fxMesa->currentfb,
		GR_LFBWRITEMODE_565,
		GR_ORIGIN_UPPER_LEFT,
		FXFALSE,
		&info)) {
#ifndef FX_SILENT
    fprintf(stderr,"fx Driver: error locking the linear frame buffer\n");
#endif
    return GL_TRUE;
  }

  r=(GLint)(ctx->Current.RasterColor[0]*ctx->Visual->RedScale);
  g=(GLint)(ctx->Current.RasterColor[1]*ctx->Visual->GreenScale);
  b=(GLint)(ctx->Current.RasterColor[2]*ctx->Visual->BlueScale);
  a=(GLint)(ctx->Current.RasterColor[3]*ctx->Visual->AlphaScale);
  color=(FxU16)
    ( ((FxU16)0xf8 & r) <<(11-3))  |
    ( ((FxU16)0xfc & g) <<(5-3+1)) |
    ( ((FxU16)0xf8 & b) >> 3);

  stride=info.strideInBytes>>1;

  /* This code is a bit slow... */

  for(y=0;y<height;y++) {
    p=((FxU16 *)info.lfbPtr)+px+((scrheight-(y+py))*stride);

    for(x=0;;) {
      DRAWBIT(7);	DRAWBIT(6);	DRAWBIT(5);	DRAWBIT(4);
      DRAWBIT(3);	DRAWBIT(2);	DRAWBIT(1);	DRAWBIT(0);
      pb++;
    }
  }

  grLfbUnlock(GR_LFB_WRITE_ONLY,fxMesa->currentfb);

#undef ISCLIPPED
#undef DRAWBIT

  return GL_TRUE;
}

/************************************************************************/
/**************** 3D depth buffer functions *****************************/
/************************************************************************/

/* this is a no-op, since the z-buffer is in hardware */
static void alloc_depth_buffer(GLcontext *ctx)
{
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: alloc_depth_buffer()\n");
#endif
}

static void clear_depth_buffer(GLcontext *ctx)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: clear_depth_buffer()\n");
#endif

  if(ctx->Depth.Test && ctx->Depth.Mask) {
    grDepthMask(FXTRUE);
    grColorMask(FXFALSE,FXFALSE);

    if(fxMesa->wbuffer)
      /* I don't know how to convert ctx->Depth.Clear */
      grBufferClear(fxMesa->clearc,fxMesa->cleara,GR_WDEPTHVALUE_FARTHEST);
    else
      grBufferClear(fxMesa->clearc,fxMesa->cleara,(FxU16)(ctx->Depth.Clear*0xffff));

    grColorMask(FXTRUE,FXFALSE);
  }
}

/************************************************************************/
/*****                Span functions                                *****/
/************************************************************************/

static void write_color_span( GLcontext *ctx, 
                              GLuint n, GLint x, GLint y,
                              const GLubyte red[], const GLubyte green[],
                              const GLubyte blue[], const GLubyte alpha[],
                              const GLubyte mask[] )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLubyte data[MAX_WIDTH][4];
  GLint i;
  GLint bottom = fxMesa->height-1;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: write_color_span()\n");
#endif

  grLfbWriteColorFormat(GR_COLORFORMAT_ABGR);

  if (mask) {
    /* this is really inefficient, but it works */
    for (i=0;i<n;i++) {
      if (mask[i]) {
        data[0][0] = red[i];
        data[0][1] = green[i];
        data[0][2] = blue[i];
        data[0][3] = alpha[i];
        grLfbWriteRegion( fxMesa->currentfb, x+i, bottom-y,
                          GR_LFB_SRC_FMT_8888, 1, 1, 0, data );
      }
    }
  }
  else {
    for (i=0;i<n;i++) {
      data[i][0] = red[i];
      data[i][1] = green[i];
      data[i][2] = blue[i];
      data[i][3] = alpha[i];
    }
    grLfbWriteRegion( fxMesa->currentfb, x, bottom-y, GR_LFB_SRC_FMT_8888,
                      n, 1, 0, data );
  }
}


static void write_monocolor_span( GLcontext *ctx, 
                                  GLuint n, GLint x, GLint y,
                                  const GLubyte mask[] )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLint i;
  GLint bottom = fxMesa->height-1;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: write_color_span()\n");
#endif

  grLfbWriteColorFormat(GR_COLORFORMAT_ABGR);

  if (mask) {
    /* this is really inefficient, but it works */
    for (i=0;i<n;i++) {
      if (mask[i]) {
        grLfbWriteRegion( fxMesa->currentfb, x+i, bottom-y,
                          GR_LFB_SRC_FMT_8888, 1, 1, 0,
                          (GLubyte *) &fxMesa->color );
      }
    }
  }
  else {
    GLuint data[MAX_WIDTH];
    for (i=0;i<n;i++) {
       data[i] = (GLuint) fxMesa->color;
    }
    grLfbWriteRegion( fxMesa->currentfb, x, bottom-y, GR_LFB_SRC_FMT_8888,
                      n, 1, 0, (GLubyte *) data );
  }
}


static void read_color_span( GLcontext *ctx, 
                             GLuint n, GLint x, GLint y,
                             GLubyte red[], GLubyte green[],
                             GLubyte blue[], GLubyte alpha[] )
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GLushort data[MAX_WIDTH];
  GLint i;
  GLint bottom = fxMesa->height-1;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: read_color_span()\n");
#endif

  assert(n < MAX_WIDTH);
  grLfbWriteColorFormat(GR_COLORFORMAT_RGBA);  /* controls reading too! */
  grLfbReadRegion( fxMesa->currentfb, x, bottom-y, n, 1, 0, data);
  for (i=0;i<n;i++) {
    red[i]   = (data[i] & 0xf800) >> 8;
    green[i] = (data[i] & 0x07e0) >> 3;
    blue[i]  = (data[i] & 0x001f) << 3;
    alpha[i] = 255;
  }
}

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

static void finish(GLcontext *ctx)
{
  grSstIdle();
}

static void flush(GLcontext *ctx)
{
  grSstIdle();
}

/************************************************************************/
/*************************** Texture Mapping ****************************/
/************************************************************************/

static texinfo *alloctexobjdata(void)
{
  texinfo *ti;

  if(!(ti=malloc(sizeof(texinfo)))) {
    fprintf(stderr,"fx Driver: out of memory !\n");
    fxCloseHardware();
    exit(-1);
  }

  ti->valid=GL_FALSE;
  ti->tmi.isintexturememory=GL_FALSE;

  return ti;
}

static void texbind(GLcontext *ctx, GLenum target, struct gl_texture_object *tObj)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  texinfo *ti;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: texbind(%d,%x)\n",tObj->Name,tObj->DriverData);
#endif

  if(target!=GL_TEXTURE_2D) {
#ifndef FX_SILENT
    fprintf(stderr,"fx Driver: unsupported texture in texbind()\n");
#endif
    return;
  }

  if(!tObj->DriverData)
    tObj->DriverData=alloctexobjdata();

  ti=(texinfo *)tObj->DriverData;

  if(!ti->valid)
    return;

  fxMesa->texbindnumber++;
  ti->tmi.lasttimeused=fxMesa->texbindnumber;

  moveintexturememory(fxMesa,tObj);

  if((ti->info.format==GR_TEXFMT_P_8) && (!fxMesa->globalpalette_texture)) {
#if defined(DEBUG_FXMESA)
    fprintf(stderr,"fxmesa: uploading texture palette\n");
#endif
    grTexDownloadTable(GR_TMU0,GR_TEXTABLE_PALETTE,&(ti->palette));
  }

  grTexClampMode(GR_TMU0,ti->sclamp,ti->tclamp);
  grTexFilterMode(GR_TMU0,ti->minfilt,ti->maxfilt);
  grTexMipMapMode(GR_TMU0,ti->mmmode,FXFALSE);
  grTexSource(GR_TMU0,ti->tmi.tm->startadr,GR_MIPMAPLEVELMASK_BOTH,&(ti->info));
}

static void texenv(GLcontext *ctx, GLenum pname, const GLfloat *param)
{
  /* ;) */
}

static void texparam(GLcontext *ctx, GLenum target, struct gl_texture_object *tObj,
                     GLenum pname, const GLfloat *params)
{
  GLenum param=(GLenum)(GLint)params[0];
  texinfo *ti;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: texparam(%d,%x)\n",tObj->Name,tObj->DriverData);
#endif

  if(target!=GL_TEXTURE_2D) {
#ifndef FX_SILENT
    fprintf(stderr,"fx Driver: unsupported texture in texparam()\n");
#endif
    return;
  }

  if(!tObj->DriverData)
    tObj->DriverData=alloctexobjdata();

  ti=(texinfo *)tObj->DriverData;

  switch(pname) {

  case GL_TEXTURE_MIN_FILTER:
    switch(param) {
    case GL_NEAREST:
      ti->mmmode=GR_MIPMAP_DISABLE;
      ti->minfilt=GR_TEXTUREFILTER_POINT_SAMPLED;
      break;
    case GL_LINEAR:
      ti->mmmode=GR_MIPMAP_DISABLE;
      ti->minfilt=GR_TEXTUREFILTER_BILINEAR;
      break;
    case GL_NEAREST_MIPMAP_NEAREST:
      ti->mmmode=GR_MIPMAP_NEAREST;
      ti->minfilt=GR_TEXTUREFILTER_POINT_SAMPLED;
      break;
    case GL_NEAREST_MIPMAP_LINEAR:
      ti->mmmode=GR_MIPMAP_NEAREST;
      ti->minfilt=GR_TEXTUREFILTER_BILINEAR;
      break;
    case GL_LINEAR_MIPMAP_LINEAR:
      ti->mmmode=GR_MIPMAP_NEAREST_DITHER;
      ti->minfilt=GR_TEXTUREFILTER_BILINEAR;
      break;
    default:
      break;
    }
    break;

  case GL_TEXTURE_MAG_FILTER:
    switch(param) {
    case GL_NEAREST:
      ti->maxfilt=GR_TEXTUREFILTER_POINT_SAMPLED;
      break;
    case GL_LINEAR:
      ti->maxfilt=GR_TEXTUREFILTER_BILINEAR;
      break;
    default:
      break;
    }
    break;

  case GL_TEXTURE_WRAP_S:
    switch(param) {
    case GL_CLAMP:
      ti->sclamp=GR_TEXTURECLAMP_CLAMP;
      break;
    case GL_REPEAT:
      ti->sclamp=GR_TEXTURECLAMP_WRAP;
      break;
    default:
      break;
    }
    break;

  case GL_TEXTURE_WRAP_T:
    switch(param) {
    case GL_CLAMP:
      ti->tclamp=GR_TEXTURECLAMP_CLAMP;
      break;
    case GL_REPEAT:
      ti->tclamp=GR_TEXTURECLAMP_WRAP;
      break;
    default:
      break;
    }
    break;

  case GL_TEXTURE_BORDER_COLOR:
    /* TO DO */
    break;
  default:
    break;
  }

  texbind(ctx,target,tObj);
}

static void texdel(GLcontext *ctx, struct gl_texture_object *tObj)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  texinfo *ti=(texinfo *)tObj->DriverData;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: texdel(%d,%x)\n",tObj->Name,ti);
#endif

  if(!ti)
    return;

  freetexture(fxMesa,tObj);

  free(ti);
  tObj->DriverData=NULL;
}


static void texpalette(GLcontext *ctx, struct gl_texture_object *tObj)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  int i;
  FxU32 r,g,b,a;
  texinfo *ti;

  if(tObj) {  
#if defined(DEBUG_FXMESA)
    fprintf(stderr,"fxmesa: texpalette(%d,%x)\n",tObj->Name,tObj->DriverData);
#endif

    if(tObj->PaletteFormat!=GL_RGBA) {
#ifndef FX_SILENT
      fprintf(stderr,"fxmesa: unsupported palette format in texpalette()\n");
#endif
      return;
    }

    if(tObj->PaletteSize>256) {
#ifndef FX_SILENT
      fprintf(stderr,"fxmesa: unsupported palette size in texpalette()\n");
#endif
      return;
    }

    if(!tObj->DriverData)
      tObj->DriverData=alloctexobjdata();
  
    ti=(texinfo *)tObj->DriverData;

    for(i=0;i<tObj->PaletteSize;i++) {
      r=tObj->Palette[i*4];
      g=tObj->Palette[i*4+1];
      b=tObj->Palette[i*4+2];
      a=tObj->Palette[i*4+3];
      ti->palette.data[i]=(a<<24)|(r<<16)|(g<<8)|b;
    }
  
    texbind(ctx,GL_TEXTURE_2D,tObj);
  } else {
#if defined(DEBUG_FXMESA)
    fprintf(stderr,"fxmesa: texpalette(global)\n");
#endif
    if(ctx->Texture.PaletteFormat!=GL_RGBA) {
#ifndef FX_SILENT
      fprintf(stderr,"fxmesa: unsupported palette format in texpalette()\n");
#endif
      return;
    }

    if(ctx->Texture.PaletteSize>256) {
#ifndef FX_SILENT
      fprintf(stderr,"fxmesa: unsupported palette size in texpalette()\n");
#endif
      return;
    }

    for(i=0;i<ctx->Texture.PaletteSize;i++) {
      r=ctx->Texture.Palette[i*4];
      g=ctx->Texture.Palette[i*4+1];
      b=ctx->Texture.Palette[i*4+2];
      a=ctx->Texture.Palette[i*4+3];
      fxMesa->glbpalette.data[i]=(a<<24)|(r<<16)|(g<<8)|b;
    }

    grTexDownloadTable(GR_TMU0,GR_TEXTABLE_PALETTE,&(fxMesa->glbpalette));
  }
}

static void useglbtexpalette(GLcontext *ctx, GLboolean state)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: useglbtexpalette(%d)\n",state);
#endif

  if(state) {
    fxMesa->globalpalette_texture=1;

    grTexDownloadTable(GR_TMU0,GR_TEXTABLE_PALETTE,&(fxMesa->glbpalette));
  } else {
    fxMesa->globalpalette_texture=0;

    if((ctx->Texture.Current==ctx->Texture.Current2D) &&
       (ctx->Texture.Current2D!=NULL))
      texbind(ctx,GL_TEXTURE_2D,ctx->Texture.Current);
  }
}

static int logbase2(int n)
{
  GLint i = 1;
  GLint log2 = 0;

  if (n<0) {
    return -1;
  }

  while (n > i) {
    i *= 2;
    log2++;
  }
  if (i != n) {
    return -1;
  }
  else {
    return log2;
  }
}

int texgetinfo(int w, int h, GrLOD_t *lodlevel, GrAspectRatio_t *ar,
	       float *sscale, float *tscale,
	       int *wscale, int *hscale)
{
  static GrLOD_t lod[9]={GR_LOD_256,GR_LOD_128,GR_LOD_64,GR_LOD_32,
                         GR_LOD_16,GR_LOD_8,GR_LOD_4,GR_LOD_2,GR_LOD_1};
  int logw,logh,ws,hs;
  GrLOD_t l;
  GrAspectRatio_t aspectratio;
  float s,t;

  logw=logbase2(w);
  logh=logbase2(h);

  switch(logw-logh) {
  case 0:
    aspectratio=GR_ASPECT_1x1;
    l=lod[8-logw];
    s=t=256.0f;
    ws=hs=1;
    break;
  case 1:
    aspectratio=GR_ASPECT_2x1;
    l=lod[8-logw];
    s=256.0f;
    t=128.0f;
    ws=1;
    hs=1;
    break;
  case 2:
    aspectratio=GR_ASPECT_4x1;
    l=lod[8-logw];
    s=256.0f;
    t=64.0f;
    ws=1;
    hs=1;
    break;
  case 3:
    aspectratio=GR_ASPECT_8x1;
    l=lod[8-logw];
    s=256.0f;
    t=32.0f;
    ws=1;
    hs=1;
    break;
  case 4:
    aspectratio=GR_ASPECT_8x1;
    l=lod[8-logw];
    s=256.0f;
    t=32.0f;
    ws=1;
    hs=2;
    break;
  case 5:
    aspectratio=GR_ASPECT_8x1;
    l=lod[8-logw];
    s=256.0f;
    t=32.0f;
    ws=1;
    hs=4;
    break;
  case 6:
    aspectratio=GR_ASPECT_8x1;
    l=lod[8-logw];
    s=256.0f;
    t=32.0f;
    ws=1;
    hs=8;
    break;
  case 7:
    aspectratio=GR_ASPECT_8x1;
    l=lod[8-logw];
    s=256.0f;
    t=32.0f;
    ws=1;
    hs=16;
    break;
  case 8:
    aspectratio=GR_ASPECT_8x1;
    l=lod[8-logw];
    s=256.0f;
    t=32.0f;
    ws=1;
    hs=32;
    break;
  case -1:
    aspectratio=GR_ASPECT_1x2;
    l=lod[8-logh];
    s=128.0f;
    t=256.0f;
    ws=1;
    hs=1;
    break;
  case -2:
    aspectratio=GR_ASPECT_1x4;
    l=lod[8-logh];
    s=64.0f;
    t=256.0f;
    ws=1;
    hs=1;
    break;
  case -3:
    aspectratio=GR_ASPECT_1x8;
    l=lod[8-logh];
    s=32.0f;
    t=256.0f;
    ws=1;
    hs=1;
    break;
  case -4:
    aspectratio=GR_ASPECT_1x8;
    l=lod[8-logh];
    s=32.0f;
    t=256.0f;
    ws=2;
    hs=1;
    break;
  case -5:
    aspectratio=GR_ASPECT_1x8;
    l=lod[8-logh];
    s=32.0f;
    t=256.0f;
    ws=4;
    hs=1;
    break;
  case -6:
    aspectratio=GR_ASPECT_1x8;
    l=lod[8-logh];
    s=32.0f;
    t=256.0f;
    ws=8;
    hs=1;
    break;
  case -7:
    aspectratio=GR_ASPECT_1x8;
    l=lod[8-logh];
    s=32.0f;
    t=256.0f;
    ws=16;
    hs=1;
    break;
  case -8:
    aspectratio=GR_ASPECT_1x8;
    l=lod[8-logh];
    s=32.0f;
    t=256.0f;
    ws=32;
    hs=1;
    break;
  default:
    return 0;
    break;
  }

  if(lodlevel)
    (*lodlevel)=l;

  if(ar)
    (*ar)=aspectratio;

  if(sscale)
    (*sscale)=s;

  if(tscale)
    (*tscale)=t;

  if(wscale)
    (*wscale)=ws;

  if(hscale)
    (*hscale)=hs;

  return 1;
}

static GrTextureFormat_t texgetformat(GLcontext *ctx, GLenum glformat)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  GrTextureFormat_t format;

  switch(glformat) {
  case GL_INTENSITY:
  case GL_INTENSITY4:
  case GL_INTENSITY8:
  case GL_INTENSITY12:
  case GL_INTENSITY16:
    /* TO DO: check */
  case 1:
  case GL_LUMINANCE:
  case GL_LUMINANCE4:
  case GL_LUMINANCE8:
  case GL_LUMINANCE12:
  case GL_LUMINANCE16:
    format=GR_TEXFMT_INTENSITY_8;
    break;
  case 2:
  case GL_LUMINANCE_ALPHA:
  case GL_LUMINANCE4_ALPHA4:
  case GL_LUMINANCE6_ALPHA2:
  case GL_LUMINANCE8_ALPHA8:
  case GL_LUMINANCE12_ALPHA4:
  case GL_LUMINANCE12_ALPHA12:
  case GL_LUMINANCE16_ALPHA16:
    format=GR_TEXFMT_ALPHA_INTENSITY_88;
    break;
  case GL_ALPHA:
  case GL_ALPHA4:
  case GL_ALPHA8:
  case GL_ALPHA12:
  case GL_ALPHA16:
    format=GR_TEXFMT_ALPHA_8;
    break;
  case 3:
  case GL_RGB:
  case GL_R3_G3_B2:
  case GL_RGB4:
  case GL_RGB5:
  case GL_RGB8:
  case GL_RGB10:
  case GL_RGB12:
  case GL_RGB16:
    if(fxMesa->fixedpalette_texture)
      format=GR_TEXFMT_P_8;
    else
      format=GR_TEXFMT_RGB_565;
    break;
  case 4:
  case GL_RGBA:
  case GL_RGBA2:
  case GL_RGBA4:
  case GL_RGB5_A1:
  case GL_RGBA8:
  case GL_RGB10_A2:
  case GL_RGBA12:
  case GL_RGBA16:
    format=GR_TEXFMT_ARGB_4444;
    break;
  case GL_COLOR_INDEX:
  case GL_COLOR_INDEX1_EXT:
  case GL_COLOR_INDEX2_EXT:
  case GL_COLOR_INDEX4_EXT:
  case GL_COLOR_INDEX8_EXT:
  case GL_COLOR_INDEX12_EXT:
  case GL_COLOR_INDEX16_EXT:
    format=GR_TEXFMT_P_8;
    break;
  default:
    fprintf(stderr,"fx Driver: unsupported internalFormat in texgetformat()\n");
    fxCloseHardware();
    exit(-1);
    break;
  }

  return format;
}

static void texalloc(GLcontext *ctx, struct gl_texture_object *tObj,
		     GLenum glformat, int w, int h)
{
  GrTextureFormat_t format;
  GrLOD_t l;
  GrAspectRatio_t aspectratio;
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  texinfo *ti=(texinfo *)tObj->DriverData;
  int wscale,hscale;

  assert(ti);

  texgetinfo(w,h,&l,&aspectratio,&(ti->sscale),&(ti->tscale),&wscale,&hscale);

  format=texgetformat(ctx,glformat);

  ti->width=w;
  ti->height=h;
  ti->info.smallLod=l;
  ti->info.largeLod=l;
  ti->info.aspectRatio=aspectratio;
  ti->info.format=format;
  ti->info.data=NULL;

  ti->fixedpalette=fxMesa->fixedpalette_texture;

  ti->minfilt=GR_TEXTUREFILTER_BILINEAR;
  ti->maxfilt=GR_TEXTUREFILTER_BILINEAR;

  ti->sclamp=GR_TEXTURECLAMP_WRAP;
  ti->tclamp=GR_TEXTURECLAMP_WRAP;

  ti->mmmode=GR_MIPMAP_NEAREST;

  ti->levelsdefined=0;
}

static int istexsupported(GLenum target, GLint internalFormat,
                          const struct gl_texture_image *image)
{
  if(target!=GL_TEXTURE_2D)
    return GL_FALSE;

  switch(internalFormat) {
  case GL_INTENSITY:
  case GL_INTENSITY4:
  case GL_INTENSITY8:
  case GL_INTENSITY12:
  case GL_INTENSITY16:
  case 1:
  case GL_LUMINANCE:
  case GL_LUMINANCE4:
  case GL_LUMINANCE8:
  case GL_LUMINANCE12:
  case GL_LUMINANCE16:
  case 2:
  case GL_LUMINANCE_ALPHA:
  case GL_LUMINANCE4_ALPHA4:
  case GL_LUMINANCE6_ALPHA2:
  case GL_LUMINANCE8_ALPHA8:
  case GL_LUMINANCE12_ALPHA4:
  case GL_LUMINANCE12_ALPHA12:
  case GL_LUMINANCE16_ALPHA16:
  case GL_ALPHA:
  case GL_ALPHA4:
  case GL_ALPHA8:
  case GL_ALPHA12:
  case GL_ALPHA16:
  case 3:
  case GL_RGB:
  case GL_R3_G3_B2:
  case GL_RGB4:
  case GL_RGB5:
  case GL_RGB8:
  case GL_RGB10:
  case GL_RGB12:
  case GL_RGB16:
  case 4:
  case GL_RGBA:
  case GL_RGBA2:
  case GL_RGBA4:
  case GL_RGB5_A1:
  case GL_RGBA8:
  case GL_RGB10_A2:
  case GL_RGBA12:
  case GL_RGBA16:
  case GL_COLOR_INDEX:
  case GL_COLOR_INDEX1_EXT:
  case GL_COLOR_INDEX2_EXT:
  case GL_COLOR_INDEX4_EXT:
  case GL_COLOR_INDEX8_EXT:
  case GL_COLOR_INDEX12_EXT:
  case GL_COLOR_INDEX16_EXT:
    break;
  default:
    return GL_FALSE;
  }

  if(image->Width>256)
    return GL_FALSE;

  if(image->Height>256)
    return GL_FALSE;

  if(!texgetinfo(image->Width,image->Height,NULL,NULL,NULL,NULL,NULL,NULL))
    return GL_FALSE;

  return GL_TRUE;
}

static void texbuildimagemap(const struct gl_texture_image *image,
			     GLint internalFormat, GuTexPalette *palette,
			     GLint levelsdefined, unsigned short **newsrc)
{
  unsigned short *src,*srccpy;
  unsigned char r,g,b,a,l,*data,*srcb;
  int x,y,w,h,wscale,hscale,idx;

  texgetinfo(image->Width,image->Height,NULL,NULL,NULL,NULL,&wscale,&hscale);
  w=image->Width*wscale;
  h=image->Height*hscale;

  data=image->Data;
  switch(internalFormat) {
  case GL_INTENSITY:
  case GL_INTENSITY4:
  case GL_INTENSITY8:
  case GL_INTENSITY12:
  case GL_INTENSITY16:
  case 1:
  case GL_LUMINANCE:
  case GL_LUMINANCE4:
  case GL_LUMINANCE8:
  case GL_LUMINANCE12:
  case GL_LUMINANCE16:
  case GL_ALPHA:
  case GL_ALPHA4:
  case GL_ALPHA8:
  case GL_ALPHA12:
  case GL_ALPHA16:
  case GL_COLOR_INDEX:
  case GL_COLOR_INDEX1_EXT:
  case GL_COLOR_INDEX2_EXT:
  case GL_COLOR_INDEX4_EXT:
  case GL_COLOR_INDEX8_EXT:
  case GL_COLOR_INDEX12_EXT:
  case GL_COLOR_INDEX16_EXT:
    if(!(*newsrc)) {
      if(!((*newsrc)=srccpy=src=(unsigned short *)malloc(sizeof(unsigned char)*w*h))) {
        fprintf(stderr,"fx Driver: out of memory !\n");
        fxCloseHardware();
        exit(-1);
      }
    } else
      srccpy=src=(*newsrc);

    /* Optimized for GLQuake */

    if(wscale==hscale==1)
      memcpy(src,data,h*w);
    else {
      srcb=(unsigned char *)src;
      for(y=0;y<h;y++)
        for(x=0;x<w;x++) {
          idx=(x/wscale+(y/hscale)*(w/wscale));
          srcb[x+y*w]=data[idx];
        }
    }
    break;
  case 2:
  case GL_LUMINANCE_ALPHA:
  case GL_LUMINANCE4_ALPHA4:
  case GL_LUMINANCE6_ALPHA2:
  case GL_LUMINANCE8_ALPHA8:
  case GL_LUMINANCE12_ALPHA4:
  case GL_LUMINANCE12_ALPHA12:
  case GL_LUMINANCE16_ALPHA16:
    if(!(*newsrc)) {
      if(!((*newsrc)=srccpy=src=(unsigned short *)malloc(sizeof(unsigned short)*w*h))) {
        fprintf(stderr,"fx Driver: out of memory !\n");
        fxCloseHardware();
        exit(-1);
      }
    } else
      srccpy=src=(*newsrc);


    for(y=0;y<h;y++)
      for(x=0;x<w;x++) {
        idx=(x/wscale+(y/hscale)*(w/wscale))*2;
        l=data[idx];
        a=data[idx+1];

        src[x+y*w]=(unsigned short)
          (((unsigned short) a) << 8) |
          (((unsigned short) l));
      }
    break;
  case 3:
  case GL_RGB:
  case GL_R3_G3_B2:
  case GL_RGB4:
  case GL_RGB5:
  case GL_RGB8:
  case GL_RGB10:
  case GL_RGB12:
  case GL_RGB16:
    if(!(*newsrc)) {
      if(!((*newsrc)=srccpy=src=(unsigned short *)malloc(sizeof(unsigned short)*w*h))) {
        fprintf(stderr,"fx Driver: out of memory !\n");
        fxCloseHardware();
        exit(-1);
      }
    } else
      srccpy=src=(*newsrc);

    if(palette) {
      Gu3dfInfo info;
      int memreq;
      unsigned char *expdata;

      if(!(expdata=malloc(w*h*3))) {
        fprintf(stderr,"fx Driver: out of memory\n");
        fxCloseHardware();
        exit(-1);
      }

      for(y=0;y<h;y++)
        for(x=0;x<w;x++) {
          idx=(x/wscale+(y/hscale)*(w/wscale))*3;
          r=data[idx];
          g=data[idx+1];
          b=data[idx+2];

          expdata[(x+y*w)*3]=r;
          expdata[(x+y*w)*3+1]=g;
          expdata[(x+y*w)*3+2]=b;
        }

      memreq=txInit3dfInfo(&info,GR_TEXFMT_P_8,&w,&h,1,TX_AUTORESIZE_DISABLE);

      if(!(info.data=malloc(memreq))) {
        fprintf(stderr,"fx Driver: out of memory\n");
        fxCloseHardware();
        exit(-1);
      }

      if(!levelsdefined) {
        txConvert(&info,GR_TEXFMT_RGB_888,w,h,expdata,TX_DITHER_ERR,NULL);
        memcpy(palette,&info.table.palette,sizeof(GuTexPalette));
      } else
        txConvert(&info,GR_TEXFMT_RGB_888,w,h,expdata,TX_TARGET_PALNCC_SOURCE|
		  TX_DITHER_ERR,palette);

      memcpy(src,info.data,memreq);

      free(info.data);
      free(expdata);
    } else {
      for(y=0;y<h;y++)
        for(x=0;x<w;x++) {
          idx=(x/wscale+(y/hscale)*(w/wscale))*3;
          r=data[idx];
          g=data[idx+1];
          b=data[idx+2];

          src[x+y*w]=(unsigned short)
            (((unsigned short)0xf8 & r) <<(11-3))  |
            (((unsigned short)0xfc & g) <<(5-3+1)) |
            (((unsigned short)0xf8 & b) >> 3); 
        }
    }
    break;
  case 4:
  case GL_RGBA:
  case GL_RGBA2:
  case GL_RGBA4:
  case GL_RGB5_A1:
  case GL_RGBA8:
  case GL_RGB10_A2:
  case GL_RGBA12:
  case GL_RGBA16:
    if(!(*newsrc)) {
      if(!((*newsrc)=srccpy=src=(unsigned short *)malloc(sizeof(unsigned short)*w*h))) {
        fprintf(stderr,"fx Driver: out of memory !\n");
        fxCloseHardware();
        exit(-1);
      }
    } else
      srccpy=src=(*newsrc);

    for(y=0;y<h;y++)
      for(x=0;x<w;x++) {
        idx=(x/wscale+(y/hscale)*(w/wscale))*4;
        r=data[idx];
        g=data[idx+1];
        b=data[idx+2];
        a=data[idx+3];

        src[x+y*w]=(unsigned short)
          (((unsigned short)0xf0 & a) << 8) |
          (((unsigned short)0xf0 & r) << 4) |
          ((unsigned short)0xf0 & g)       |
          (((unsigned short)0xf0 & b) >> 4); 
      }
    break;
  default:
    fprintf(stderr,"fx Driver: wrong internalFormat in texbuildimagemap()\n");
    fxCloseHardware();
    exit(-1);
    break;
  }
}

static void teximg(GLcontext *ctx, GLenum target,
                   struct gl_texture_object *tObj, GLint level, GLint internalFormat,
                   const struct gl_texture_image *image)
{
  GrLOD_t lodlev;
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;
  texinfo *ti;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: teximg(...,%d,%d,%d,%d...)\n",target,internalFormat,image->Width,image->Height);
#endif

  if(target!=GL_TEXTURE_2D) {
#ifndef FX_SILENT
    fprintf(stderr,"fx Driver: unsupported texture in teximg()\n");
#endif
    return;
  }

  if(!tObj->DriverData)
    tObj->DriverData=alloctexobjdata();

  ti=(texinfo *)tObj->DriverData;

  if(istexsupported(target,internalFormat,image)) {
    /* The following line isn't really right ... */

    if((!ti->valid) || (ti->info.format!=texgetformat(ctx,internalFormat)))
      texalloc(ctx,tObj,image->Format,image->Width,image->Height);

    if(ti->levelsdefined & (1<<level)) {
      if(ti->fixedpalette)
        texbuildimagemap(image,internalFormat,&ti->palette,
	                 ti->levelsdefined,&(ti->tmi.mipmaplevel[level]));
      else
        texbuildimagemap(image,internalFormat,NULL,0,&(ti->tmi.mipmaplevel[level]));

      reloadmipmaplevel(fxMesa,tObj,level);
    } else {
      moveouttexturememory(fxMesa,tObj); /* TO DO: SLOW but easy to write */

      ti->tmi.mipmaplevel[level]=NULL;

      if(ti->fixedpalette)
        texbuildimagemap(image,internalFormat,&ti->palette,
	                 ti->levelsdefined,&(ti->tmi.mipmaplevel[level]));
      else
        texbuildimagemap(image,internalFormat,NULL,0,&(ti->tmi.mipmaplevel[level]));

      texgetinfo(image->Width,image->Height,&lodlev,NULL,NULL,NULL,NULL,NULL);

      if(lodlev<ti->info.largeLod) /* A new larger mip map level */
        ti->info.largeLod=lodlev;
      else if(lodlev>ti->info.smallLod) /* A new smaller mip map level */
        ti->info.smallLod=lodlev;

      ti->levelsdefined|=(1<<level);
      ti->valid=GL_TRUE;

      texbind(ctx,target,tObj);
    }
  }
#ifndef FX_SILENT
  else
    fprintf(stderr,"fx Driver: unsupported texture in teximg()\n");
#endif
}

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

static void SetNearFar(GLcontext *ctx, GLfloat n, GLfloat f)
{
  fxMesaContext fxMesa = (fxMesaContext)ctx->DriverCtx;

  if(fxMesa) {
    fxMesa->nearval=fabs(n);
    fxMesa->farval=fabs(f);
    fxMesa->wscale=fxMesa->farval/65535.0;

    /*
     * We need to update fog table because it depends on w 
     * and w is adjusted to the maximum range.
     */
    if(ctx->Fog.Enabled && ctx->Hint.Fog==GL_NICEST) {
      switch(ctx->Fog.Mode)     {
      case GL_LINEAR:
        guFogGenerateLinear(fxMesa->fogtable,
                            ctx->Fog.Start/fxMesa->wscale,
                            ctx->Fog.End /fxMesa->wscale); /* works ? */
        break;
      case GL_EXP:
        guFogGenerateExp(fxMesa->fogtable,
                         ctx->Fog.Density*fxMesa->wscale);
        break;
      case GL_EXP2:
        guFogGenerateExp2(fxMesa->fogtable,
                          ctx->Fog.Density*fxMesa->wscale);
        break;
      default: /* That should never happen */
        break; 
      }
    }
  }
}

void fxMesaSetNearFar(GLfloat n, GLfloat f)
{
  if(CurrentfxMesaCtx)
    SetNearFar(CurrentfxMesaCtx->gl_ctx, n, f);
}


static void setup_fx_units(GLcontext *ctx)
{
  fxMesaContext fxMesa=(fxMesaContext)ctx->DriverCtx;

  if(ctx->Color.BlendEnabled) {
    GrAlphaBlendFnc_t sfact,dfact;

    if(ctx->Light.ShadeModel==GL_SMOOTH)
      guAlphaSource(GR_ALPHASOURCE_ITERATED_ALPHA);
    else
      guAlphaSource(GR_ALPHASOURCE_CC_ALPHA);

    switch(ctx->Color.BlendSrc) {
    case GL_ZERO:
      sfact=GR_BLEND_ZERO;
      break;
    case GL_ONE:
      sfact=GR_BLEND_ONE;
      break;
    case GL_DST_COLOR:
      sfact=GR_BLEND_DST_COLOR;
      break;
    case GL_ONE_MINUS_DST_COLOR:
      sfact=GR_BLEND_ONE_MINUS_DST_COLOR;
      break;
    case GL_SRC_ALPHA:
      sfact=GR_BLEND_SRC_ALPHA;
      break;
    case GL_ONE_MINUS_SRC_ALPHA:
      sfact=GR_BLEND_ONE_MINUS_SRC_ALPHA;
      break;
    case GL_SRC_ALPHA_SATURATE:
    case GL_SRC_COLOR:
    case GL_ONE_MINUS_SRC_COLOR:
    case GL_DST_ALPHA:
    case GL_ONE_MINUS_DST_ALPHA:
      /* USELESS or TO DO */
      sfact=GR_BLEND_ONE;
      break;
    default:
      break;
    }

    switch(ctx->Color.BlendDst) {
    case GL_ZERO:
      dfact=GR_BLEND_ZERO;
      break;
    case GL_ONE:
      dfact=GR_BLEND_ONE;
      break;
    case GL_SRC_COLOR:
      dfact=GR_BLEND_SRC_COLOR;
      break;
    case GL_ONE_MINUS_SRC_COLOR:
      dfact=GR_BLEND_ONE_MINUS_SRC_COLOR;
      break;
    case GL_SRC_ALPHA:
      dfact=GR_BLEND_SRC_ALPHA;
      break;
    case GL_ONE_MINUS_SRC_ALPHA:
      dfact=GR_BLEND_ONE_MINUS_SRC_ALPHA;
      break;
    case GL_SRC_ALPHA_SATURATE:
    case GL_DST_COLOR:
    case GL_ONE_MINUS_DST_COLOR:
    case GL_DST_ALPHA:
    case GL_ONE_MINUS_DST_ALPHA:
      /* USELESS or TO DO */
      sfact=GR_BLEND_ZERO;
      break;
    default:
      break;
    }

    grAlphaBlendFunction(sfact,dfact,GR_BLEND_ONE,GR_BLEND_ZERO);

    grColorMask(FXTRUE,FXFALSE);
  } else {
    if(ctx->Light.ShadeModel==GL_SMOOTH)
      guAlphaSource(GR_ALPHASOURCE_ITERATED_ALPHA);
    else
      guAlphaSource(GR_ALPHASOURCE_CC_ALPHA);

    grAlphaBlendFunction(GR_BLEND_ONE,GR_BLEND_ZERO,GR_BLEND_ONE,GR_BLEND_ZERO);
    grColorMask(FXTRUE,FXFALSE);
  }

  if(ctx->Color.AlphaEnabled) {
    switch(ctx->Color.AlphaFunc) {
    case GL_NEVER:
      grAlphaTestFunction(GR_CMP_NEVER);
      break;
    case GL_LESS:
      grAlphaTestFunction(GR_CMP_LESS);
      break;
    case GL_EQUAL:
      grAlphaTestFunction(GR_CMP_EQUAL);
      break;
    case GL_LEQUAL:
      grAlphaTestFunction(GR_CMP_LEQUAL);
      break;
    case GL_GREATER:
      grAlphaTestFunction(GR_CMP_GREATER);
      break;
    case GL_NOTEQUAL:
      grAlphaTestFunction(GR_CMP_NOTEQUAL);
      break;
    case GL_GEQUAL:
      grAlphaTestFunction(GR_CMP_GEQUAL);
      break;
    case GL_ALWAYS:
      grAlphaTestFunction(GR_CMP_ALWAYS);
      break;
    default:
      break;
    }
    grAlphaTestReferenceValue(ctx->Color.AlphaRefUbyte);
  } else
    grAlphaTestFunction(GR_CMP_ALWAYS);

  if(ctx->Texture.Enabled) {
    switch(ctx->Texture.EnvMode) {
    case GL_DECAL:
      guColorCombineFunction(GR_COLORCOMBINE_DECAL_TEXTURE);
      guTexCombineFunction(GR_TMU0,GR_TEXTURECOMBINE_DECAL);
      break;
    case GL_MODULATE:
      if(ctx->Color.BlendEnabled || ctx->Color.AlphaEnabled)
        guAlphaSource(GR_ALPHASOURCE_TEXTURE_ALPHA);

      if(ctx->Light.ShadeModel==GL_SMOOTH)
        guColorCombineFunction(GR_COLORCOMBINE_TEXTURE_TIMES_ITRGB);
      else
        guColorCombineFunction(GR_COLORCOMBINE_TEXTURE_TIMES_CCRGB);

      guTexCombineFunction(GR_TMU0,GR_TEXTURECOMBINE_DECAL);
      break;
    case GL_BLEND:
#ifndef FX_SILENT
      fprintf(stderr,"fx Driver: GL_BLEND not yet supported\n");
#endif
      /* TO DO */
      break;
    case GL_REPLACE:
      if(ctx->Color.BlendEnabled || ctx->Color.AlphaEnabled)
        guAlphaSource(GR_ALPHASOURCE_TEXTURE_ALPHA);

      guColorCombineFunction(GR_COLORCOMBINE_DECAL_TEXTURE);
      guTexCombineFunction(GR_TMU0,GR_TEXTURECOMBINE_DECAL);
      break;
    default:
#ifndef FX_SILENT
      fprintf(stderr,"fx Driver: %x Texture.EnvMode not yet supported\n",ctx->Texture.EnvMode);
#endif
      break;
    }
  } else {
    if(ctx->Light.ShadeModel==GL_SMOOTH)
      guColorCombineFunction(GR_COLORCOMBINE_ITRGB);
    else
      guColorCombineFunction(GR_COLORCOMBINE_CCRGB);
      
    grTexCombineFunction(GR_TMU0,GR_TEXTURECOMBINE_ONE);
  }

  if(ctx->Depth.Test) {
    if((ctx->ProjectionMatrix[15]==0.0f) &&
       (ctx->Viewport.Near==0.0) && (ctx->Viewport.Far==1.0)) {
      grDepthBufferMode(GR_DEPTHBUFFER_WBUFFER);
      fxMesa->wbuffer=1;
    } else {
      grDepthBufferMode(GR_DEPTHBUFFER_ZBUFFER); 
      fxMesa->wbuffer=0;
    }

    switch(ctx->Depth.Func) {
    case GL_NEVER:
      grDepthBufferFunction(GR_CMP_NEVER);
      break;
    case GL_LESS:
      grDepthBufferFunction(GR_CMP_LESS);
      break;
    case GL_GEQUAL:
      grDepthBufferFunction(GR_CMP_GEQUAL);
      break;
    case GL_LEQUAL:
      grDepthBufferFunction(GR_CMP_LEQUAL);
      break;
    case GL_GREATER:
      grDepthBufferFunction(GR_CMP_GREATER);
      break;
    case GL_NOTEQUAL:
      grDepthBufferFunction(GR_CMP_NOTEQUAL);
      break;
    case GL_EQUAL:
      grDepthBufferFunction(GR_CMP_EQUAL);
      break;
    case GL_ALWAYS:
      grDepthBufferFunction(GR_CMP_ALWAYS);
      break;
    default:
      break;
    }

    if(ctx->Depth.Mask)
      grDepthMask(FXTRUE);
    else
      grDepthMask(FXFALSE);
  } else {
    grDepthBufferFunction(GR_CMP_ALWAYS);
    grDepthMask(FXFALSE);
  }

  if(ctx->Fog.Enabled && ctx->Hint.Fog==GL_NICEST) {
    grFogMode(GR_FOG_WITH_TABLE);
    grFogColorValue FXCOLOR((unsigned int)(255*ctx->Fog.Color[0]), 
                            (unsigned int)(255*ctx->Fog.Color[1]),
                            (unsigned int)(255*ctx->Fog.Color[2]), 
                            (unsigned int)(255*ctx->Fog.Color[3]));

    if((fxMesa->fogtablemode!=ctx->Fog.Mode) ||
       (fxMesa->fogdensity!=ctx->Fog.Density)) {
      float wscale = ((fxMesaContext)ctx->DriverCtx)->wscale;
      switch(ctx->Fog.Mode) {
      case GL_LINEAR:
        guFogGenerateLinear(fxMesa->fogtable,
                            ctx->Fog.Start/wscale,
                            ctx->Fog.End/wscale);
        break;
      case GL_EXP:
        guFogGenerateExp(fxMesa->fogtable,ctx->Fog.Density*wscale);
        break;
      case GL_EXP2:
        guFogGenerateExp2(fxMesa->fogtable,ctx->Fog.Density*wscale);
        break;
      default: /* That should never happen */
        break; 
      }
         
      fxMesa->fogtablemode=ctx->Fog.Mode;
      fxMesa->fogdensity=ctx->Fog.Density;
    }
      
    grFogTable(fxMesa->fogtable);
  } else
    grFogMode(GR_FOG_DISABLE);
}


static const char *renderer_string(void)
{
  return "Glide";
}


/*
 * The 3Dfx Global Palette extension for GLQuake.
 * More a trick than a real extesion, use the shared global
 * palette extension. 
 */
void APIENTRY gl3DfxSetPaletteEXT(GLuint *pal)
{
#if defined(DEBUG_FXMESA)
  int i;

  fprintf(stderr,"fxmesa: gl3DfxSetPaletteEXT()\n");

  for(i=0;i<256;i++)
    fprintf(stderr,"%x\n",pal[i]);
#endif

  if(CurrentfxMesaCtx) {
    CurrentfxMesaCtx->globalpalette_texture=1;
    grTexDownloadTable(GR_TMU0,GR_TEXTABLE_PALETTE,(GuTexPalette *)pal); 
  }
}


static void setup_dd_pointers(GLcontext *ctx)
{
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: fx_mesa_setup_dd_pointers()\n");
#endif

  ctx->Driver.UpdateState=setup_dd_pointers;
         
  ctx->Driver.RendererString=renderer_string;

  ctx->Driver.NearFar=SetNearFar;

  ctx->Driver.ClearIndex=NULL;
  ctx->Driver.ClearColor=clear_color;
  ctx->Driver.Clear=clear;

  ctx->Driver.Index=NULL;
  ctx->Driver.Color=set_color;

  ctx->Driver.SetBuffer=set_buffer;
  ctx->Driver.GetBufferSize=buffer_size;

  ctx->Driver.AllocDepthBuffer=alloc_depth_buffer;
  ctx->Driver.ClearDepthBuffer=clear_depth_buffer;

  /* acc. functions*/

  setup_fx_units(ctx);

  ctx->Driver.PointsFunc=choose_points_function(ctx);
  ctx->Driver.LineFunc=choose_line_function(ctx);
  ctx->Driver.TriangleFunc=choose_triangle_function(ctx);
  ctx->Driver.QuadFunc=choose_quad_function(ctx);
  ctx->Driver.RectFunc=NULL;

  ctx->Driver.Bitmap=drawbitmap;
  ctx->Driver.DrawPixels=NULL;

  ctx->Driver.RasterSetup=choose_setup_function(ctx);
  ctx->Driver.RenderVB=NULL;

  ctx->Driver.Finish=flush;
  ctx->Driver.Flush=finish;

  ctx->Driver.TexEnv=texenv;
  ctx->Driver.TexImage=teximg;
  ctx->Driver.TexParameter=texparam;
  ctx->Driver.BindTexture=texbind;
  ctx->Driver.DeleteTexture=texdel;
  ctx->Driver.UpdateTexturePalette=texpalette;
  ctx->Driver.UseGlobalTexturePalette=useglbtexpalette;

  ctx->Driver.WriteColorSpan      =write_color_span;
  ctx->Driver.WriteMonocolorSpan  =write_monocolor_span;
  ctx->Driver.WriteColorPixels    =NULL;
  ctx->Driver.WriteMonocolorPixels=NULL;
  ctx->Driver.WriteIndexSpan      =NULL;
  ctx->Driver.WriteMonoindexSpan  =NULL;
  ctx->Driver.WriteIndexPixels    =NULL;
  ctx->Driver.WriteMonoindexPixels=NULL;

  ctx->Driver.ReadIndexSpan=NULL;
  ctx->Driver.ReadColorSpan=read_color_span;
  ctx->Driver.ReadIndexPixels=NULL;
  ctx->Driver.ReadColorPixels=NULL;
}


#define NUM_RESOLUTIONS 3

static resolutions[NUM_RESOLUTIONS][3]={ 
  /*
    { 320, 200, GR_RESOLUTION_320x200 }
    { 320, 240, GR_RESOLUTION_320x240 },
    { 400, 256, GR_RESOLUTION_400x256 },
    { 512, 256, GR_RESOLUTION_512x256 },
  */
  { 512, 384, GR_RESOLUTION_512x384 },
  /*
  { 640, 200, GR_RESOLUTION_640x200 },
  { 640, 350, GR_RESOLUTION_640x350 },
  */
  { 640, 400, GR_RESOLUTION_640x400 },
  { 640, 480, GR_RESOLUTION_640x480 }
  /*,
    { 800, 600, GR_RESOLUTION_800x600 },
    { 856, 480, GR_RESOLUTION_856x480 },
    { 960, 720, GR_RESOLUTION_960x720 }
    */
};

GrScreenResolution_t bestResolution(int width, int height)
{
  int i;

  for(i=0;i<NUM_RESOLUTIONS;i++)
    if((width<=resolutions[i][0]) && (height<=resolutions[i][1]))
      return resolutions[i][2];
        
  return GR_RESOLUTION_640x480;
}


fxMesaContext fxMesaCreateBestContext(GLuint win,GLint width, GLint height,
                                      const GLint attribList[])
{
  GrScreenRefresh_t refresh;

  refresh=GR_REFRESH_75Hz;

  if (getenv("SST_SCREENREFRESH")) {
    if (!strcmp(getenv("SST_SCREENREFRESH"),"60"))
      refresh=GR_REFRESH_60Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"70"))
      refresh=GR_REFRESH_70Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"72"))
      refresh=GR_REFRESH_72Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"75"))
      refresh=GR_REFRESH_75Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"80"))
      refresh=GR_REFRESH_80Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"85"))
      refresh=GR_REFRESH_85Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"90"))
      refresh=GR_REFRESH_90Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"100"))
      refresh=GR_REFRESH_100Hz;
    if (!strcmp(getenv("SST_SCREENREFRESH"),"120"))
      refresh=GR_REFRESH_120Hz;
  }

  return fxMesaCreateContext(win,bestResolution(width,height),
                             refresh,attribList);
}

/*
 * Create a new FX/Mesa context and return a handle to it.
 */
fxMesaContext fxMesaCreateContext(GLuint win,GrScreenResolution_t res,
                                  GrScreenRefresh_t ref,
                                  const GLint attribList[])
{
  fxMesaContext ctx;
  int i,type;
  GLboolean doubleBuffer=GL_FALSE;
  GLboolean alphaBuffer=GL_FALSE;
  GLboolean verbose=GL_FALSE;
  GLint depthSize=0;
  GLint stencilSize=0;
  GLint accumSize=0;

  if(getenv("MESA_FX_INFO"))
    verbose=GL_TRUE;

  i=0;
  while (attribList[i]!=FXMESA_NONE) {
    switch (attribList[i]) {
    case FXMESA_DOUBLEBUFFER:
      doubleBuffer = GL_TRUE;
      break;
    case FXMESA_ALPHA_SIZE:
      i++;
      alphaBuffer = attribList[i] > 0;
      break;
    case FXMESA_DEPTH_SIZE:
      i++;
      depthSize = attribList[i];
      break;
    case FXMESA_STENCIL_SIZE:
      i++;
      stencilSize = attribList[i];
      break;
    case FXMESA_ACCUM_SIZE:
      i++;
      accumSize = attribList[i];
      break;
    default:
      return NULL;
    }
    i++;
  }


#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: fxMesaCreateContext()\n");
#endif

  if(verbose)
    fprintf(stderr,"Mesa fx Voodoo Device Driver v0.20\nWritten by David Bucciarelli (tech.hmw@plus.it)\n");


  if((type=fxQueryHardware()) >= 0) {
    if(type==GR_SSTTYPE_VOODOO)
      win=0;

#if USE_GLIDE_FULLSCREEN
    if(!grSstOpen(res,                  /* resolution */
                  ref,                  /* refresh */
                  GR_COLORFORMAT_ABGR,  /* pixel format */
                  GR_ORIGIN_LOWER_LEFT, /* origin */
                  GR_SMOOTHING_ENABLE,  /* smoothing mode */
                  2))                   /* num buffers */
      return NULL;
#else   /* Glide in a window */
    if(!grSstWinOpen((FxU32)win,res,ref,
                     GR_COLORFORMAT_ABGR,GR_ORIGIN_LOWER_LEFT,2,1))
      return NULL;
#endif

    if(verbose)
      fprintf(stderr,"Glide screen size: %dx%d\n",
	      (int)grSstScreenWidth(),(int)grSstScreenHeight());
  } else {
    fprintf(stderr,"fxmesa: ERROR no Voodoo Graphics or Voodoo Rush !\n");
    return NULL;
  }

  ctx=(fxMesaContext)malloc(sizeof(struct fx_mesa_context));
  if(!ctx)
    return NULL;

  ctx->width=grSstScreenWidth();
  ctx->height=grSstScreenHeight();
  ctx->double_buffer=doubleBuffer;
  ctx->verbose=verbose;
  ctx->fixedpalette_texture=0;
  ctx->globalpalette_texture=0;

  ctx->color=FXCOLOR(255,255,255,255);
  ctx->clearc=0;
  ctx->cleara=0;

  ctx->stats.swapbuffer=0;
  ctx->stats.reqtexupload=0;
  ctx->stats.texupload=0;
  ctx->stats.memtexupload=0;

  inittmmanager(ctx);

  grColorMask(FXTRUE,FXFALSE);
  if (doubleBuffer) {
    ctx->currentfb=GR_BUFFER_BACKBUFFER;
    grRenderBuffer(GR_BUFFER_BACKBUFFER);
  } else {
    ctx->currentfb=GR_BUFFER_FRONTBUFFER;
    grRenderBuffer(GR_BUFFER_FRONTBUFFER);
  }

  ctx->gl_vis = gl_create_visual(GL_TRUE,     /* RGB mode */
                                 alphaBuffer,
                                 doubleBuffer,
                                 depthSize,   /* depth_size */
                                 stencilSize, /* stencil_size */
                                 accumSize,   /* accum_size */
                                 0,           /* index bits */
                                 255.0,       /* color scales */
                                 255.0,
                                 255.0,
                                 255.0,
                                 5, 6, 5, 0);

  ctx->gl_ctx = gl_create_context(ctx->gl_vis,
                                  NULL,  /* share list context */
                                  (void *) ctx);

  ctx->gl_buffer = gl_create_framebuffer(ctx->gl_vis);

  setup_dd_pointers(ctx->gl_ctx);

  SetNearFar(ctx->gl_ctx,1.0,100.0);

  return ctx;
}


/*
 * Destroy the given FX/Mesa context.
 */
void fxMesaDestroyContext(fxMesaContext ctx)
{
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: fxMesaDestroyContext()\n");
#endif

  if(ctx) {
    gl_destroy_visual(ctx->gl_vis);
    gl_destroy_context(ctx->gl_ctx);
    gl_destroy_framebuffer(ctx->gl_buffer);

    fxCloseHardware();

    if(ctx->verbose) {
      fprintf(stderr,"Misc Stats:\n");
      fprintf(stderr,"  # swap buffer: %u\n",ctx->stats.swapbuffer);

      if(!ctx->stats.swapbuffer)
	ctx->stats.swapbuffer=1;

      fprintf(stderr,"Textures Stats:\n");
      fprintf(stderr,"  # request to TMM to upload a texture objects: %u\n",
	      ctx->stats.reqtexupload);
      fprintf(stderr,"  # request to TMM to upload a texture objects per swapbuffer: %.2f\n",
	      ctx->stats.reqtexupload/(float)ctx->stats.swapbuffer);
      fprintf(stderr,"  # texture objects uploaded: %u\n",
	      ctx->stats.texupload);
      fprintf(stderr,"  # texture objects uploaded per swapbuffer: %.2f\n",
	      ctx->stats.texupload/(float)ctx->stats.swapbuffer);
      fprintf(stderr,"  # MBs uploaded to texture memory: %.2f\n",
	      ctx->stats.memtexupload/(float)(1<<20));
      fprintf(stderr,"  # MBs uploaded to texture memory per swapbuffer: %.2f\n",
	      (ctx->stats.memtexupload/(float)ctx->stats.swapbuffer)/(float)(1<<20));
    }

    closetmmanager(ctx);

    free(ctx);
  }

  if(ctx==CurrentfxMesaCtx)
    CurrentfxMesaCtx=NULL;
}

/*
 * Make the specified FX/Mesa context the current one.
 */
void fxMesaMakeCurrent(fxMesaContext ctx)
{
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: fxMesaMakeCurrent()\n");
#endif

  if(!ctx) {
    gl_make_current(NULL,NULL);
    CurrentfxMesaCtx=NULL;

    return;
  }

  CurrentfxMesaCtx=ctx;

  gl_make_current(ctx->gl_ctx,ctx->gl_buffer);

  setup_dd_pointers(ctx->gl_ctx);

  /* The first time we call MakeCurrent we set the initial viewport size */
  if (ctx->gl_ctx->Viewport.Width==0)
    gl_Viewport(ctx->gl_ctx,0,0,ctx->width,ctx->height);
}


/*
 * Swap front/back buffers for current context if double buffered.
 */
void fxMesaSwapBuffers(void)
{
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: ------------------------------- fxMesaSwapBuffers() -------------------------------\n");
#endif

  if(CurrentfxMesaCtx)
    if(CurrentfxMesaCtx->double_buffer) {
      grBufferSwap(1);

      CurrentfxMesaCtx->stats.swapbuffer++;
    }
}


/*
 * Activate/Deactivate Texture Palette
 */
void fxMesaTextureUsePalette(GLboolean v)
{
#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: fxMesaTextureUsePalette()\n");
#endif

  if(CurrentfxMesaCtx)
    CurrentfxMesaCtx->fixedpalette_texture=v;
}


#if defined(__WIN32__)
static int cleangraphics(void)
{
  fxMesaDestroyContext(CurrentfxMesaCtx);

  return 0;
}
#elif defined(__linux__)
static void cleangraphics(void)
{
  fxMesaDestroyContext(CurrentfxMesaCtx);
}
#endif


/*
 * Query 3Dfx hardware presence/kind
 */
int fxQueryHardware(void)
{
  if(!glideInitialized) {
      grGlideInit();
      if(grSstQueryHardware(&hwconfig)) {
	  grSstSelect(0);
	  _3dfxPresent=1;

	  if(getenv("MESA_FX_INFO")) {
	    char buf[80];
			
	    grGlideGetVersion(buf);
	    fprintf(stderr,"Using Glide V%s\nNumber of boards: %d\n",buf,hwconfig.num_sst);
	  }
      } else
	_3dfxPresent=0;

      glideInitialized=1;

#if defined(__WIN32__)
      onexit((_onexit_t)cleangraphics);
#elif defined(__linux__)
      atexit(cleangraphics);
#endif
    }

  if(!_3dfxPresent)
    return(-1);
  else
    return(hwconfig.SSTs[0].type);
}


/*
 * Shutdown Glide library
 */
int fxCloseHardware(void)
{
  if(glideInitialized) {
    if(getenv("MESA_FX_INFO")) {
      GrSstPerfStats_t		st;

      grSstPerfStats(&st);
      fprintf(stderr,"Pixels Stats:\n");
      fprintf(stderr,"  # pixels processed (minus buffer clears): %u\n",(unsigned)st.pixelsIn);
      fprintf(stderr,"  # pixels not drawn due to chroma key test failure: %u\n",(unsigned)st.chromaFail);
      fprintf(stderr,"  # pixels not drawn due to depth test failure: %u\n",(unsigned)st.zFuncFail);
      fprintf(stderr,"  # pixels not drawn due to alpha test failure: %u\n",(unsigned)st.aFuncFail);
      fprintf(stderr,"  # pixels drawn (including buffer clears and LFB writes): %u\n",(unsigned)st.pixelsOut);
    }

    grGlideShutdown();
    glideInitialized = 0;
  }

  return 0;
}


#else


/*
 * Need this to provide at least one external definition.
 */

int gl_fx_dummy_function1(void)
{
  return 0;
}

#endif  /* FX */
