/*
 * Copyright 1998-2003 VIA Technologies, Inc. All Rights Reserved.
 * Copyright 2001-2003 S3 Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * VIA, S3 GRAPHICS, AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */


#include "main/context.h"
#include "main/mm.h"
#include "main/macros.h"
#include "main/texstore.h"
#include "main/texobj.h"
#include "main/convolve.h"
#include "main/colormac.h"
#include "main/simple_list.h"
#include "main/enums.h"

#include "swrast/swrast.h"

#include "savagecontext.h"
#include "savagetex.h"
#include "savagetris.h"
#include "savageioctl.h"
#include "savage_bci.h"

#include "xmlpool.h"

#define TILE_INDEX_DXT1 0
#define TILE_INDEX_8    1
#define TILE_INDEX_16   2
#define TILE_INDEX_DXTn 3
#define TILE_INDEX_32   4

/* On Savage4 the texure LOD-bias needs an offset of ~ 0.3 to get
 * somewhere close to software rendering.
 */
#define SAVAGE4_LOD_OFFSET 10

/* Tile info for S3TC formats counts in 4x4 blocks instead of texels.
 * In DXT1 each block is encoded in 64 bits. In DXT3 and 5 each block is
 * encoded in 128 bits. */

/* Size 1, 2 and 4 images are packed into the last subtile. Each image
 * is repeated to fill a 4x4 pixel area. The figure below shows the
 * layout of those 4x4 pixel areas in the 8x8 subtile.
 *
 *    4 2
 *    x 1
 *
 * Yuck! 8-bit texture formats use 4x8 subtiles. See below.
 */
static const savageTileInfo tileInfo_pro[5] = {
    {16, 16, 16, 8, 1, 2, {0x18, 0x10}}, /* DXT1 */
    {64, 32, 16, 4, 4, 8, {0x30, 0x20}}, /* 8-bit */
    {64, 16,  8, 2, 8, 8, {0x48, 0x08}}, /* 16-bit */
    {16,  8, 16, 4, 1, 2, {0x30, 0x20}}, /* DXT3, DXT5 */
    {32, 16,  4, 2, 8, 8, {0x90, 0x10}}, /* 32-bit */
};

/* Size 1, 2 and 4 images are packed into the last two subtiles. Each
 * image is repeated to fill a 4x4 pixel area. The figures below show
 * the layout of those 4x4 pixel areas in the two 4x8 subtiles.
 *
 * second last subtile: 4   last subtile: 2
 *                      x                 1
 */
static const savageTileInfo tileInfo_s3d_s4[5] = {
    {16, 16, 16, 8, 1, 2, {0x18, 0x10}}, /* DXT1 */
    {64, 32, 16, 4, 4, 8, {0x30, 0x20}}, /* 8-bit */
    {64, 16, 16, 2, 4, 8, {0x60, 0x40}}, /* 16-bit */
    {16,  8, 16, 4, 1, 2, {0x30, 0x20}}, /* DXT3, DXT5 */
    {32, 16,  8, 2, 4, 8, {0xc0, 0x80}}, /* 32-bit */
};

/** \brief Template for subtile uploads.
 * \param h   height in pixels
 * \param w   width in bytes
 */
#define SUBTILE_FUNC(w,h)					\
static INLINE GLubyte *savageUploadSubtile_##w##x##h		\
(GLubyte *dest, GLubyte *src, GLuint srcStride)			\
{								\
    GLuint y;							\
    for (y = 0; y < h; ++y) {					\
	memcpy (dest, src, w);					\
	src += srcStride;					\
	dest += w;						\
    }								\
    return dest;						\
}

SUBTILE_FUNC(2, 8) /* 4 bits per pixel, 4 pixels wide */
SUBTILE_FUNC(4, 8)
SUBTILE_FUNC(8, 8)
SUBTILE_FUNC(16, 8)
SUBTILE_FUNC(32, 8) /* 4 bytes per pixel, 8 pixels wide */

SUBTILE_FUNC(8, 2) /* DXT1 */
SUBTILE_FUNC(16, 2) /* DXT3 and DXT5 */

/** \brief Upload a complete tile from src (srcStride) to dest
 *
 * \param tileInfo     Pointer to tiling information
 * \param wInSub       Width of source/dest image in subtiles
 * \param hInSub       Height of source/dest image in subtiles
 * \param bpp          Bytes per pixel
 * \param src          Pointer to source data
 * \param srcStride    Byte stride of rows in the source data
 * \param dest         Pointer to destination
 *
 * Writes linearly to the destination memory in order to exploit write
 * combining.
 *
 * For a complete tile wInSub and hInSub are set to the same values as
 * in tileInfo. If the source image is smaller than a whole tile in
 * one or both dimensions then they are set to the values of the
 * source image. This only works as long as the source image is bigger
 * than 8x8 pixels.
 */
static void savageUploadTile (const savageTileInfo *tileInfo,
			      GLuint wInSub, GLuint hInSub, GLuint bpp,
			      GLubyte *src, GLuint srcStride, GLubyte *dest) {
    GLuint subStride = tileInfo->subWidth * bpp;
    GLubyte *srcSRow = src, *srcSTile = src;
    GLubyte *(*subtileFunc) (GLubyte *, GLubyte *, GLuint);
    GLuint sx, sy;
    switch (subStride) {
    case  2: subtileFunc = savageUploadSubtile_2x8; break;
    case  4: subtileFunc = savageUploadSubtile_4x8; break;
    case  8: subtileFunc = tileInfo->subHeight == 8 ?
		 savageUploadSubtile_8x8 : savageUploadSubtile_8x2; break;
    case 16: subtileFunc = tileInfo->subHeight == 8 ?
		 savageUploadSubtile_16x8 : savageUploadSubtile_16x2; break;
    case 32: subtileFunc = savageUploadSubtile_32x8; break;
    default: assert(0);
    }
    for (sy = 0; sy < hInSub; ++sy) {
	srcSTile = srcSRow;
	for (sx = 0; sx < wInSub; ++sx) {
	    src = srcSTile;
	    dest = subtileFunc (dest, src, srcStride);
	    srcSTile += subStride;
	}
	srcSRow += srcStride * tileInfo->subHeight;
    }
}

/** \brief Upload a image that is smaller than 8 pixels in either dimension.
 *
 * \param tileInfo    Pointer to tiling information
 * \param width       Width of the image
 * \param height      Height of the image
 * \param bpp         Bytes per pixel
 * \param src         Pointer to source data
 * \param dest        Pointer to destination
 *
 * This function handles all the special cases that need to be taken
 * care off. The caller may need to call this function multiple times
 * with the destination offset in different ways since small texture
 * images must be repeated in order to fill a whole tile (or 4x4 for
 * the last 3 levels).
 *
 * FIXME: Repeating inside this function would be more efficient.
 */
static void savageUploadTiny (const savageTileInfo *tileInfo,
			      GLuint pixWidth, GLuint pixHeight,
			      GLuint width, GLuint height, GLuint bpp,
			      GLubyte *src, GLubyte *dest) {
    GLuint size = MAX2(pixWidth, pixHeight);

    if (width > tileInfo->subWidth) { /* assert: height <= subtile height */
	GLuint wInSub = width / tileInfo->subWidth;
	GLuint srcStride = width * bpp;
	GLuint subStride = tileInfo->subWidth * bpp;
	GLuint subSkip = (tileInfo->subHeight - height) * subStride;
	GLubyte *srcSTile = src;
	GLuint sx, y;
	for (sx = 0; sx < wInSub; ++sx) {
	    src = srcSTile;
	    for (y = 0; y < height; ++y) {
		memcpy (dest, src, subStride);
		src += srcStride;
		dest += subStride;
	    }
	    dest += subSkip;
	    srcSTile += subStride;
	}
    } else if (size > 4) { /* a tile or less wide, except the last 3 levels */
	GLuint srcStride = width * bpp;
	GLuint subStride = tileInfo->subWidth * bpp;
	/* if the subtile width is 4 we have to skip every other subtile */
	GLuint subSkip = tileInfo->subWidth <= 4 ?
	    subStride * tileInfo->subHeight : 0;
	GLuint skipRemainder = tileInfo->subHeight - 1;
	GLuint y;
	for (y = 0; y < height; ++y) {
	    memcpy (dest, src, srcStride);
	    src += srcStride;
	    dest += subStride;
	    if ((y & skipRemainder) == skipRemainder)
		dest += subSkip;
	}
    } else { /* the last 3 mipmap levels */
	GLuint offset = (size <= 2 ? tileInfo->tinyOffset[size-1] : 0);
	GLuint subStride = tileInfo->subWidth * bpp;
	GLuint y;
	dest += offset;
	for (y = 0; y < height; ++y) {
	    memcpy (dest, src, bpp*width);
	    src += width * bpp;
	    dest += subStride;
	}
    }
}

/** \brief Upload an image from mesa's internal copy.
 */
static void savageUploadTexLevel( savageTexObjPtr t, int level )
{
    const struct gl_texture_image *image = t->base.tObj->Image[0][level];
    const savageTileInfo *tileInfo = t->tileInfo;
    GLuint pixWidth = image->Width2, pixHeight = image->Height2;
    GLuint bpp = t->texelBytes;
    GLuint width, height;

    /* FIXME: Need triangle (rather than pixel) fallbacks to simulate
     * this using normal textured triangles.
     *
     * DO THIS IN DRIVER STATE MANAGMENT, not hardware state.
     */
    if(image->Border != 0) 
	fprintf (stderr, "Not supported texture border %d.\n",
		 (int) image->Border);

    if (t->hwFormat == TFT_S3TC4A4Bit || t->hwFormat == TFT_S3TC4CA4Bit ||
	t->hwFormat == TFT_S3TC4Bit) {
	width = (pixWidth+3) / 4;
	height = (pixHeight+3) / 4;
    } else {
	width = pixWidth;
	height = pixHeight;
    }

    if (pixWidth >= 8 && pixHeight >= 8) {
	GLuint *dirtyPtr = t->image[level].dirtyTiles;
	GLuint dirtyMask = 1;

	if (width >= tileInfo->width && height >= tileInfo->height) {
	    GLuint wInTiles = width / tileInfo->width;
	    GLuint hInTiles = height / tileInfo->height;
	    GLubyte *srcTRow = image->Data, *src;
	    GLubyte *dest = (GLubyte *)(t->bufAddr + t->image[level].offset);
	    GLuint x, y;
	    for (y = 0; y < hInTiles; ++y) {
		src = srcTRow;
		for (x = 0; x < wInTiles; ++x) {
		    if (*dirtyPtr & dirtyMask) {
			savageUploadTile (tileInfo,
					  tileInfo->wInSub, tileInfo->hInSub,
					  bpp, src, width * bpp, dest);
		    }
		    src += tileInfo->width * bpp;
		    dest += 2048; /* tile size is always 2k */
		    if (dirtyMask == 1<<31) {
			dirtyMask = 1;
			dirtyPtr++;
		    } else
			dirtyMask <<= 1;
		}
		srcTRow += width * tileInfo->height * bpp;
	    }
	} else if (width >= tileInfo->width) {
	    GLuint wInTiles = width / tileInfo->width;
	    GLubyte *src = image->Data;
	    GLubyte *dest = (GLubyte *)(t->bufAddr + t->image[level].offset);
	    GLuint tileStride = tileInfo->width * bpp * height;
	    savageContextPtr imesa = (savageContextPtr)t->base.heap->driverContext;
	    GLuint x;
	    /* Savage3D-based chips seem so use a constant tile stride
	     * of 2048 for vertically incomplete tiles, but only if
	     * the color depth is 32bpp. Nobody said this was supposed
	     * to be logical!
	     */
	    if (bpp == 4 && imesa->savageScreen->chipset < S3_SAVAGE4)
		tileStride = 2048;
	    for (x = 0; x < wInTiles; ++x) {
		if (*dirtyPtr & dirtyMask) {
		    savageUploadTile (tileInfo,
				      tileInfo->wInSub,
				      height / tileInfo->subHeight,
				      bpp, src, width * bpp, dest);
		}
		src += tileInfo->width * bpp;
		dest += tileStride;
		if (dirtyMask == 1<<31) {
		    dirtyMask = 1;
		    dirtyPtr++;
		} else
		    dirtyMask <<= 1;
	    }
	} else {
	    savageUploadTile (tileInfo, width / tileInfo->subWidth,
			      height / tileInfo->subHeight, bpp,
			      image->Data, width * bpp,
			      (GLubyte *)(t->bufAddr+t->image[level].offset));
	}
    } else {
	GLuint minHeight, minWidth, hRepeat, vRepeat, x, y;
	if (t->hwFormat == TFT_S3TC4A4Bit || t->hwFormat == TFT_S3TC4CA4Bit ||
	    t->hwFormat == TFT_S3TC4Bit)
	    minWidth = minHeight = 1;
	else
	    minWidth = minHeight = 4;
	if (width > minWidth || height > minHeight) {
	    minWidth = tileInfo->subWidth;
	    minHeight = tileInfo->subHeight;
	}
	hRepeat = width  >= minWidth  ? 1 : minWidth  / width;
	vRepeat = height >= minHeight ? 1 : minHeight / height;
	for (y = 0; y < vRepeat; ++y) {
	    GLuint offset = y * tileInfo->subWidth*height * bpp;
	    for (x = 0; x < hRepeat; ++x) {
		savageUploadTiny (tileInfo, pixWidth, pixHeight,
				  width, height, bpp, image->Data,
				  (GLubyte *)(t->bufAddr +
					      t->image[level].offset+offset));
		offset += width * bpp;
	    }
	}
    }
}

/** \brief Compute the destination size of a texture image
 */
static GLuint savageTexImageSize (GLuint width, GLuint height, GLuint bpp) {
    /* full subtiles */
    if (width >= 8 && height >= 8)
	return width * height * bpp;
    /* special case for the last three mipmap levels: the hardware computes
     * the offset internally */
    else if (width <= 4 && height <= 4)
	return 0;
    /* partially filled sub tiles waste memory
     * on Savage3D and Savage4 with subtile width 4 every other subtile is
     * skipped if width < 8 so we can assume a uniform subtile width of 8 */
    else if (width >= 8)
	return width * 8 * bpp;
    else if (height >= 8)
	return 8 * height * bpp;
    else
	return 64 * bpp;
}

/** \brief Compute the destination size of a compressed texture image
 */
static GLuint savageCompressedTexImageSize (GLuint width, GLuint height,
					    GLuint bpp) {
    width = (width+3) / 4;
    height = (height+3) / 4;
    /* full subtiles */
    if (width >= 2 && height >= 2)
	return width * height * bpp;
    /* special case for the last three mipmap levels: the hardware computes
     * the offset internally */
    else if (width <= 1 && height <= 1)
	return 0;
    /* partially filled sub tiles waste memory
     * on Savage3D and Savage4 with subtile width 4 every other subtile is
     * skipped if width < 8 so we can assume a uniform subtile width of 8 */
    else if (width >= 2)
	return width * 2 * bpp;
    else if (height >= 2)
	return 2 * height * bpp;
    else
	return 4 * bpp;
}

/** \brief Compute the number of (partial) tiles of a texture image
 */
static GLuint savageTexImageTiles (GLuint width, GLuint height,
				   const savageTileInfo *tileInfo)
{
   return (width + tileInfo->width - 1) / tileInfo->width *
      (height + tileInfo->height - 1) / tileInfo->height;
}

/** \brief Mark dirty tiles
 *
 * Some care must be taken because tileInfo may not be set or not
 * up-to-date. So we check if tileInfo is initialized and if the number
 * of tiles in the bit vector matches the number of tiles computed from
 * the current tileInfo.
 */
static void savageMarkDirtyTiles (savageTexObjPtr t, GLuint level,
				  GLuint totalWidth, GLuint totalHeight,
				  GLint xoffset, GLint yoffset,
				  GLsizei width, GLsizei height)
{
   GLuint wInTiles, hInTiles;
   GLuint x0, y0, x1, y1;
   GLuint x, y;
   if (!t->tileInfo)
      return;
   wInTiles = (totalWidth + t->tileInfo->width - 1) / t->tileInfo->width;
   hInTiles = (totalHeight + t->tileInfo->height - 1) / t->tileInfo->height;
   if (wInTiles * hInTiles != t->image[level].nTiles)
      return;

   x0 = xoffset / t->tileInfo->width;
   y0 = yoffset / t->tileInfo->height;
   x1 = (xoffset + width - 1) / t->tileInfo->width;
   y1 = (yoffset + height - 1) / t->tileInfo->height;

   for (y = y0; y <= y1; ++y) {
      GLuint *ptr = t->image[level].dirtyTiles + (y * wInTiles + x0) / 32;
      GLuint mask = 1 << (y * wInTiles + x0) % 32;
      for (x = x0; x <= x1; ++x) {
	 *ptr |= mask;
	 if (mask == (1<<31)) {
	    ptr++;
	    mask = 1;
	 } else {
	    mask <<= 1;
	 }
      }
   }
}

/** \brief Mark all tiles as dirty
 */
static void savageMarkAllTiles (savageTexObjPtr t, GLuint level)
{
   GLuint words = (t->image[level].nTiles + 31) / 32;
   if (words)
      memset(t->image[level].dirtyTiles, ~0, words*sizeof(GLuint));
}


static void savageSetTexWrapping(savageTexObjPtr tex, GLenum s, GLenum t)
{
    tex->setup.sWrapMode = s;
    tex->setup.tWrapMode = t;
}

static void savageSetTexFilter(savageTexObjPtr t, GLenum minf, GLenum magf)
{
   t->setup.minFilter = minf;
   t->setup.magFilter = magf;
}


/* Need a fallback ?
 */
static void savageSetTexBorderColor(savageTexObjPtr t, const GLfloat color[4])
{
/*    t->Setup[SAVAGE_TEXREG_TEXBORDERCOL] =  */
    /*t->setup.borderColor = SAVAGEPACKCOLOR8888(color[0],color[1],color[2],color[3]); */
}



static savageTexObjPtr
savageAllocTexObj( struct gl_texture_object *texObj ) 
{
   savageTexObjPtr t;

   t = (savageTexObjPtr) calloc(1,sizeof(*t));
   texObj->DriverData = t;
   if ( t != NULL ) {
      GLuint i;

      /* Initialize non-image-dependent parts of the state:
       */
      t->base.tObj = texObj;
      t->base.dirty_images[0] = 0;
      t->dirtySubImages = 0;
      t->tileInfo = NULL;

      /* Initialize dirty tiles bit vectors
       */
      for (i = 0; i < SAVAGE_TEX_MAXLEVELS; ++i)
	 t->image[i].nTiles = 0;

      /* FIXME Something here to set initial values for other parts of
       * FIXME t->setup?
       */
  
      make_empty_list( &t->base );

      savageSetTexWrapping(t,texObj->WrapS,texObj->WrapT);
      savageSetTexFilter(t,texObj->MinFilter,texObj->MagFilter);
      savageSetTexBorderColor(t,texObj->BorderColor);
   }

   return t;
}

/* Mesa texture formats for alpha-images on Savage3D/IX/MX
 *
 * Promoting texture images to ARGB888 or ARGB4444 doesn't work
 * because we can't tell the hardware to ignore the color components
 * and only use the alpha component. So we define our own texture
 * formats that promote to ARGB8888 or ARGB4444 and set the color
 * components to white. This way we get the correct result.
 */

#if 0
/* Using MESA_FORMAT_RGBA8888 to store alpha-only textures should
 * work but is space inefficient.
 */

static GLboolean
_savage_texstore_a1114444(TEXSTORE_PARAMS);

static GLboolean
_savage_texstore_a1118888(TEXSTORE_PARAMS);

static struct gl_texture_format _savage_texformat_a1114444 = {
    MESA_FORMAT_ARGB4444,		/* MesaFormat */
    GL_RGBA,				/* BaseFormat */
    GL_UNSIGNED_NORMALIZED_ARB,		/* DataType */
    4,					/* RedBits */
    4,					/* GreenBits */
    4,					/* BlueBits */
    4,					/* AlphaBits */
    0,					/* LuminanceBits */
    0,					/* IntensityBits */
    0,					/* IndexBits */
    0,					/* DepthBits */
    0,					/* StencilBits */
    2,					/* TexelBytes */
    _savage_texstore_a1114444,		/* StoreTexImageFunc */
    NULL, NULL, NULL, NULL, NULL, NULL  /* FetchTexel* filled in by 
					 * savageDDInitTextureFuncs */
};
static struct gl_texture_format _savage_texformat_a1118888 = {
    MESA_FORMAT_ARGB8888,		/* MesaFormat */
    GL_RGBA,				/* BaseFormat */
    GL_UNSIGNED_NORMALIZED_ARB,		/* DataType */
    8,					/* RedBits */
    8,					/* GreenBits */
    8,					/* BlueBits */
    8,					/* AlphaBits */
    0,					/* LuminanceBits */
    0,					/* IntensityBits */
    0,					/* IndexBits */
    0,					/* DepthBits */
    0,					/* StencilBits */
    4,					/* TexelBytes */
    _savage_texstore_a1118888,		/* StoreTexImageFunc */
    NULL, NULL, NULL, NULL, NULL, NULL  /* FetchTexel* filled in by 
					 * savageDDInitTextureFuncs */
};


static GLboolean
_savage_texstore_a1114444(TEXSTORE_PARAMS)
{
    const GLchan *tempImage = _mesa_make_temp_chan_image(ctx, dims,
                                                 baseInternalFormat,
                                                 baseInternalFormat,
                                                 srcWidth, srcHeight, srcDepth,
                                                 srcFormat, srcType, srcAddr,
                                                 srcPacking);
    const GLchan *src = tempImage;
    GLint img, row, col;

    ASSERT(dstFormat == &_savage_texformat_a1114444);
    ASSERT(baseInternalFormat == GL_ALPHA);

    if (!tempImage)
	return GL_FALSE;
    _mesa_adjust_image_for_convolution(ctx, dims, &srcWidth, &srcHeight);
    for (img = 0; img < srcDepth; img++) {
        GLuint texelBytes = _mesa_get_format_bytes(dstFormat);
        GLubyte *dstRow = (GLubyte *) dstAddr
           + dstImageOffsets[dstZoffset + img] * texelBytes
           + dstYoffset * dstRowStride
           + dstXoffset * texelBytes;
	for (row = 0; row < srcHeight; row++) {
            GLushort *dstUI = (GLushort *) dstRow;
	    for (col = 0; col < srcWidth; col++) {
		dstUI[col] = PACK_COLOR_4444( CHAN_TO_UBYTE(src[0]),
					      255, 255, 255 );
		src += 1;
            }
            dstRow += dstRowStride;
	}
    }
    _mesa_free((void *) tempImage);

    return GL_TRUE;
}


static GLboolean
_savage_texstore_a1118888(TEXSTORE_PARAMS)
{
    const GLchan *tempImage = _mesa_make_temp_chan_image(ctx, dims,
                                                 baseInternalFormat,
                                                 baseInternalFormat,
                                                 srcWidth, srcHeight, srcDepth,
                                                 srcFormat, srcType, srcAddr,
                                                 srcPacking);
    const GLchan *src = tempImage;
    GLint img, row, col;

    ASSERT(dstFormat == &_savage_texformat_a1118888);
    ASSERT(baseInternalFormat == GL_ALPHA);

    if (!tempImage)
	return GL_FALSE;
    _mesa_adjust_image_for_convolution(ctx, dims, &srcWidth, &srcHeight);
    for (img = 0; img < srcDepth; img++) {
        GLuint texelBytes = _mesa_get_format_bytes(dstFormat);
        GLubyte *dstRow = (GLubyte *) dstAddr
           + dstImageOffsets[dstZoffset + img] * texelBytes
           + dstYoffset * dstRowStride
           + dstXoffset * texelBytes;
	for (row = 0; row < srcHeight; row++) {
            GLuint *dstUI = (GLuint *) dstRow;
	    for (col = 0; col < srcWidth; col++) {
		dstUI[col] = PACK_COLOR_8888( CHAN_TO_UBYTE(src[0]),
					      255, 255, 255 );
		src += 1;
            }
            dstRow += dstRowStride;
	}
    }
    _mesa_free((void *) tempImage);

    return GL_TRUE;
}
#endif


/* Called by the _mesa_store_teximage[123]d() functions. */
static gl_format
savageChooseTextureFormat( GLcontext *ctx, GLint internalFormat,
			   GLenum format, GLenum type )
{
   savageContextPtr imesa = SAVAGE_CONTEXT(ctx);
   const GLboolean do32bpt =
       ( imesa->texture_depth == DRI_CONF_TEXTURE_DEPTH_32 );
   const GLboolean force16bpt =
       ( imesa->texture_depth == DRI_CONF_TEXTURE_DEPTH_FORCE_16 );
   const GLboolean isSavage4 = (imesa->savageScreen->chipset >= S3_SAVAGE4);
   (void) format;

   switch ( internalFormat ) {
   case 4:
   case GL_RGBA:
   case GL_COMPRESSED_RGBA:
      switch ( type ) {
      case GL_UNSIGNED_INT_10_10_10_2:
      case GL_UNSIGNED_INT_2_10_10_10_REV:
	 return do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB1555;
      case GL_UNSIGNED_SHORT_4_4_4_4:
      case GL_UNSIGNED_SHORT_4_4_4_4_REV:
	 return MESA_FORMAT_ARGB4444;
      case GL_UNSIGNED_SHORT_5_5_5_1:
      case GL_UNSIGNED_SHORT_1_5_5_5_REV:
	 return MESA_FORMAT_ARGB1555;
      default:
         return do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444;
      }

   case 3:
   case GL_RGB:
   case GL_COMPRESSED_RGB:
      switch ( type ) {
      case GL_UNSIGNED_SHORT_4_4_4_4:
      case GL_UNSIGNED_SHORT_4_4_4_4_REV:
	 return MESA_FORMAT_ARGB4444;
      case GL_UNSIGNED_SHORT_5_5_5_1:
      case GL_UNSIGNED_SHORT_1_5_5_5_REV:
	 return MESA_FORMAT_ARGB1555;
      case GL_UNSIGNED_SHORT_5_6_5:
      case GL_UNSIGNED_SHORT_5_6_5_REV:
	 return MESA_FORMAT_RGB565;
      default:
         return do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_RGB565;
      }

   case GL_RGBA8:
   case GL_RGBA12:
   case GL_RGBA16:
      return !force16bpt ?
	  MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444;

   case GL_RGB10_A2:
      return !force16bpt ?
	  MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB1555;

   case GL_RGBA4:
   case GL_RGBA2:
      return MESA_FORMAT_ARGB4444;

   case GL_RGB5_A1:
      return MESA_FORMAT_ARGB1555;

   case GL_RGB8:
   case GL_RGB10:
   case GL_RGB12:
   case GL_RGB16:
      return !force16bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_RGB565;

   case GL_RGB5:
   case GL_RGB4:
   case GL_R3_G3_B2:
      return MESA_FORMAT_RGB565;

   case GL_ALPHA:
   case GL_COMPRESSED_ALPHA:
#if 0
      return isSavage4 ? MESA_FORMAT_a8 : (
	 do32bpt ? &_savage_texformat_a1118888 : &_savage_texformat_a1114444);
#else
      if (isSavage4)
         return MESA_FORMAT_A8;
      else if (do32bpt)
         return MESA_FORMAT_ARGB8888;
      else
         return MESA_FORMAT_ARGB4444;
#endif
   case GL_ALPHA4:
#if 0
      return isSavage4 ? MESA_FORMAT_a8 : &_savage_texformat_a1114444;
#else
      if (isSavage4)
         return MESA_FORMAT_A8;
      else
         return MESA_FORMAT_ARGB4444;
#endif
   case GL_ALPHA8:
   case GL_ALPHA12:
   case GL_ALPHA16:
#if 0
      return isSavage4 ? MESA_FORMAT_a8 : (
	 !force16bpt ? &_savage_texformat_a1118888 : &_savage_texformat_a1114444);
#else
      if (isSavage4)
         return MESA_FORMAT_A8;
      else if (force16bpt)
         return MESA_FORMAT_ARGB4444;
      else
         return MESA_FORMAT_ARGB8888;
#endif
   case 1:
   case GL_LUMINANCE:
   case GL_COMPRESSED_LUMINANCE:
      /* no alpha, but use argb1555 in 16bit case to get pure grey values */
      return isSavage4 ? MESA_FORMAT_L8 : (
	 do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB1555);
   case GL_LUMINANCE4:
      return isSavage4 ? MESA_FORMAT_L8 : MESA_FORMAT_ARGB1555;
   case GL_LUMINANCE8:
   case GL_LUMINANCE12:
   case GL_LUMINANCE16:
      return isSavage4 ? MESA_FORMAT_L8 : (
	 !force16bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB1555);

   case 2:
   case GL_LUMINANCE_ALPHA:
   case GL_COMPRESSED_LUMINANCE_ALPHA:
      /* Savage4 has a al44 texture format. But it's not supported by Mesa. */
      return do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444;
   case GL_LUMINANCE4_ALPHA4:
   case GL_LUMINANCE6_ALPHA2:
      return MESA_FORMAT_ARGB4444;
   case GL_LUMINANCE8_ALPHA8:
   case GL_LUMINANCE12_ALPHA4:
   case GL_LUMINANCE12_ALPHA12:
   case GL_LUMINANCE16_ALPHA16:
      return !force16bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444;
#if 0
   /* TFT_I8 produces garbage on ProSavageDDR and subsequent texture
    * disable keeps rendering garbage. Disabled for now. */
   case GL_INTENSITY:
   case GL_COMPRESSED_INTENSITY:
      return isSavage4 ? MESA_FORMAT_i8 : (
	 do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444);
   case GL_INTENSITY4:
      return isSavage4 ? MESA_FORMAT_i8 : MESA_FORMAT_ARGB4444;
   case GL_INTENSITY8:
   case GL_INTENSITY12:
   case GL_INTENSITY16:
      return isSavage4 ? MESA_FORMAT_i8 : (
	 !force16bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444);
#else
   case GL_INTENSITY:
   case GL_COMPRESSED_INTENSITY:
      return do32bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444;
   case GL_INTENSITY4:
      return MESA_FORMAT_ARGB4444;
   case GL_INTENSITY8:
   case GL_INTENSITY12:
   case GL_INTENSITY16:
      return !force16bpt ? MESA_FORMAT_ARGB8888 : MESA_FORMAT_ARGB4444;
#endif

   case GL_RGB_S3TC:
   case GL_RGB4_S3TC:
   case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
      return MESA_FORMAT_RGB_DXT1;
   case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
      return MESA_FORMAT_RGBA_DXT1;

   case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
      return MESA_FORMAT_RGBA_DXT3;

   case GL_RGBA_S3TC:
   case GL_RGBA4_S3TC:
      if (!isSavage4)
	 /* Not the best choice but Savage3D/MX/IX don't support DXT3 or DXT5. */
	 return MESA_FORMAT_RGBA_DXT1;
      /* fall through */
   case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
      return MESA_FORMAT_RGBA_DXT5;

/*
   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:
      return &_mesa_texformat_ci8;
*/
   default:
      _mesa_problem(ctx, "unexpected texture format in %s", __FUNCTION__);
      return MESA_FORMAT_NONE;
   }
}

static void savageSetTexImages( savageContextPtr imesa,
				const struct gl_texture_object *tObj )
{
   savageTexObjPtr t = (savageTexObjPtr) tObj->DriverData;
   struct gl_texture_image *image = tObj->Image[0][tObj->BaseLevel];
   GLuint offset, i, textureFormat, tileIndex, size;
   GLint firstLevel, lastLevel;

   assert(t);
   assert(image);

   switch (image->TexFormat) {
   case MESA_FORMAT_ARGB8888:
      textureFormat = TFT_ARGB8888;
      t->texelBytes = tileIndex = 4;
      break;
   case MESA_FORMAT_ARGB1555:
      textureFormat = TFT_ARGB1555;
      t->texelBytes = tileIndex = 2;
      break;
   case MESA_FORMAT_ARGB4444:
      textureFormat = TFT_ARGB4444;
      t->texelBytes = tileIndex = 2;
      break;
   case MESA_FORMAT_RGB565:
      textureFormat = TFT_RGB565;
      t->texelBytes = tileIndex = 2;
      break;
   case MESA_FORMAT_L8:
      textureFormat = TFT_L8;
      t->texelBytes = tileIndex = 1;
      break;
   case MESA_FORMAT_I8:
      textureFormat = TFT_I8;
      t->texelBytes = tileIndex = 1;
      break;
   case MESA_FORMAT_A8:
      textureFormat = TFT_A8;
      t->texelBytes = tileIndex = 1;
      break;
   case MESA_FORMAT_RGB_DXT1:
      textureFormat = TFT_S3TC4Bit;
      tileIndex = TILE_INDEX_DXT1;
      t->texelBytes = 8;
      break;
   case MESA_FORMAT_RGBA_DXT1:
      textureFormat = TFT_S3TC4Bit;
      tileIndex = TILE_INDEX_DXT1;
      t->texelBytes = 8;
      break;
   case MESA_FORMAT_RGBA_DXT3:
      textureFormat =  TFT_S3TC4A4Bit;
      tileIndex = TILE_INDEX_DXTn;
      t->texelBytes = 16;
      break;
   case MESA_FORMAT_RGBA_DXT5:
      textureFormat = TFT_S3TC4CA4Bit;
      tileIndex = TILE_INDEX_DXTn;
      t->texelBytes = 16;
      break;
   default:
      _mesa_problem(imesa->glCtx, "Bad texture format in %s", __FUNCTION__);
      return;
   }
   t->hwFormat = textureFormat;

   /* Select tiling format depending on the chipset and texture format */
   if (imesa->savageScreen->chipset <= S3_SAVAGE4)
       t->tileInfo = &tileInfo_s3d_s4[tileIndex];
   else
       t->tileInfo = &tileInfo_pro[tileIndex];

   /* Compute which mipmap levels we really want to send to the hardware.
    */
   driCalculateTextureFirstLastLevel( &t->base );
   firstLevel = t->base.firstLevel;
   lastLevel  = t->base.lastLevel;

   /* Figure out the size now (and count the levels).  Upload won't be
    * done until later. If the number of tiles changes, it means that
    * this function is called for the first time on this tex object or
    * the image or the destination color format changed. So all tiles
    * are marked as dirty.
    */ 
   offset = 0;
   size = 1;
   for ( i = firstLevel ; i <= lastLevel && tObj->Image[0][i] ; i++ ) {
      GLuint nTiles;
      nTiles = savageTexImageTiles (image->Width2, image->Height2, t->tileInfo);
      if (t->image[i].nTiles != nTiles) {
	 GLuint words = (nTiles + 31) / 32;
	 if (t->image[i].nTiles != 0) {
	    free(t->image[i].dirtyTiles);
	 }
	 t->image[i].dirtyTiles = malloc(words*sizeof(GLuint));
	 memset(t->image[i].dirtyTiles, ~0, words*sizeof(GLuint));
      }
      t->image[i].nTiles = nTiles;

      t->image[i].offset = offset;

      image = tObj->Image[0][i];
      if (t->texelBytes >= 8)
	 size = savageCompressedTexImageSize (image->Width2, image->Height2,
					      t->texelBytes);
      else
	 size = savageTexImageSize (image->Width2, image->Height2,
				    t->texelBytes);
      offset += size;
   }

   t->base.lastLevel = i-1;
   t->base.totalSize = offset;
   /* the last three mipmap levels don't add to the offset. They are packed
    * into 64 pixels. */
   if (size == 0)
       t->base.totalSize += (t->texelBytes >= 8 ? 4 : 64) * t->texelBytes;
   /* 2k-aligned (really needed?) */
   t->base.totalSize = (t->base.totalSize + 2047UL) & ~2047UL;
}

void savageDestroyTexObj(savageContextPtr imesa, savageTexObjPtr t)
{
    GLuint i;

    /* Free dirty tiles bit vectors */
    for (i = 0; i < SAVAGE_TEX_MAXLEVELS; ++i) {
	if (t->image[i].nTiles)
	    free (t->image[i].dirtyTiles);
    }

    /* See if it was the driver's current object.
     */
    if ( imesa != NULL )
    { 
	for ( i = 0 ; i < imesa->glCtx->Const.MaxTextureUnits ; i++ )
	{
	    if ( &t->base == imesa->CurrentTexObj[ i ] ) {
		assert( t->base.bound & (1 << i) );
		imesa->CurrentTexObj[ i ] = NULL;
	    }
	}
    }
}

/* Upload a texture's images to one of the texture heaps. May have to
 * eject our own and/or other client's texture objects to make room
 * for the upload.
 */
static void savageUploadTexImages( savageContextPtr imesa, savageTexObjPtr t )
{
   const GLint numLevels = t->base.lastLevel - t->base.firstLevel + 1;
   GLuint i;

   assert(t);

   LOCK_HARDWARE(imesa);
   
   /* Do we need to eject LRU texture objects?
    */
   if (!t->base.memBlock) {
      GLint heap;
      GLuint ofs;

      heap = driAllocateTexture(imesa->textureHeaps, imesa->lastTexHeap,
				(driTextureObject *)t);
      if (heap == -1) {
	  UNLOCK_HARDWARE(imesa);
	  return;
      }

      ofs = t->base.memBlock->ofs;
      t->setup.physAddr = imesa->savageScreen->textureOffset[heap] + ofs;
      t->bufAddr = (GLubyte *)imesa->savageScreen->texVirtual[heap] + ofs;
      imesa->dirty |= SAVAGE_UPLOAD_GLOBAL; /* FIXME: really needed? */
   }

   /* Let the world know we've used this memory recently.
    */
   driUpdateTextureLRU( &t->base );
   UNLOCK_HARDWARE(imesa);

   if (t->base.dirty_images[0] || t->dirtySubImages) {
      if (SAVAGE_DEBUG & DEBUG_VERBOSE_TEX)
	 fprintf(stderr, "Texture upload: |");

      /* Heap timestamps are only reliable with Savage DRM 2.3.x or
       * later. Earlier versions had only 16 bit time stamps which
       * would wrap too frequently. */
      if (imesa->savageScreen->driScrnPriv->drm_version.minor >= 3) {
	  unsigned int heap = t->base.heap->heapId;
	  LOCK_HARDWARE(imesa);
	  savageWaitEvent (imesa, imesa->textureHeaps[heap]->timestamp);
      } else {
	  savageFlushVertices (imesa);
	  LOCK_HARDWARE(imesa);
	  savageFlushCmdBufLocked (imesa, GL_FALSE);
	  WAIT_IDLE_EMPTY_LOCKED(imesa);
      }

      for (i = 0 ; i < numLevels ; i++) {
         const GLint j = t->base.firstLevel + i;  /* the texObj's level */
	 if (t->base.dirty_images[0] & (1 << j)) {
	    savageMarkAllTiles(t, j);
	    if (SAVAGE_DEBUG & DEBUG_VERBOSE_TEX)
		fprintf (stderr, "*");
	 } else if (SAVAGE_DEBUG & DEBUG_VERBOSE_TEX) {
	    if (t->dirtySubImages & (1 << j))
	       fprintf (stderr, ".");
	    else
	       fprintf (stderr, " ");
	 }
	 if ((t->base.dirty_images[0] | t->dirtySubImages) & (1 << j))
	    savageUploadTexLevel( t, j );
      }

      UNLOCK_HARDWARE(imesa);
      t->base.dirty_images[0] = 0;
      t->dirtySubImages = 0;

      if (SAVAGE_DEBUG & DEBUG_VERBOSE_TEX)
	 fprintf(stderr, "|\n");
   }
}


static void
savage4_set_wrap_mode( savageContextPtr imesa, unsigned unit,
		      GLenum s_mode, GLenum t_mode )
{
    switch( s_mode ) {
    case GL_REPEAT:
	imesa->regs.s4.texCtrl[ unit ].ni.uMode = TAM_Wrap;
	break;
    case GL_CLAMP:
    case GL_CLAMP_TO_EDGE:
	imesa->regs.s4.texCtrl[ unit ].ni.uMode = TAM_Clamp;
	break;
    case GL_MIRRORED_REPEAT:
	imesa->regs.s4.texCtrl[ unit ].ni.uMode = TAM_Mirror;
	break;
    }

    switch( t_mode ) {
    case GL_REPEAT:
	imesa->regs.s4.texCtrl[ unit ].ni.vMode = TAM_Wrap;
	break;
    case GL_CLAMP:
    case GL_CLAMP_TO_EDGE:
	imesa->regs.s4.texCtrl[ unit ].ni.vMode = TAM_Clamp;
	break;
    case GL_MIRRORED_REPEAT:
	imesa->regs.s4.texCtrl[ unit ].ni.vMode = TAM_Mirror;
	break;
    }
}


/**
 * Sets the hardware bits for the specified GL texture filter modes.
 * 
 * \todo
 * Does the Savage4 have the ability to select the magnification filter?
 */
static void
savage4_set_filter_mode( savageContextPtr imesa, unsigned unit,
			 GLenum minFilter, GLenum magFilter )
{
    (void) magFilter;

    switch (minFilter) {
    case GL_NEAREST:
	imesa->regs.s4.texCtrl[ unit ].ni.filterMode   = TFM_Point;
	imesa->regs.s4.texCtrl[ unit ].ni.mipmapEnable = GL_FALSE;
	break;

    case GL_LINEAR:
	imesa->regs.s4.texCtrl[ unit ].ni.filterMode   = TFM_Bilin;
	imesa->regs.s4.texCtrl[ unit ].ni.mipmapEnable = GL_FALSE;
	break;

    case GL_NEAREST_MIPMAP_NEAREST:
	imesa->regs.s4.texCtrl[ unit ].ni.filterMode   = TFM_Point;
	imesa->regs.s4.texCtrl[ unit ].ni.mipmapEnable = GL_TRUE;
	break;

    case GL_LINEAR_MIPMAP_NEAREST:
	imesa->regs.s4.texCtrl[ unit ].ni.filterMode   = TFM_Bilin;
	imesa->regs.s4.texCtrl[ unit ].ni.mipmapEnable = GL_TRUE;
	break;

    case GL_NEAREST_MIPMAP_LINEAR:
    case GL_LINEAR_MIPMAP_LINEAR:
	imesa->regs.s4.texCtrl[ unit ].ni.filterMode   = TFM_Trilin;
	imesa->regs.s4.texCtrl[ unit ].ni.mipmapEnable = GL_TRUE;
	break;
    }
}


static void savageUpdateTex0State_s4( GLcontext *ctx )
{
   savageContextPtr imesa = SAVAGE_CONTEXT(ctx);
   struct gl_texture_object	*tObj;
   struct gl_texture_image *image;
   savageTexObjPtr t;
   GLuint format;

   /* disable */
   imesa->regs.s4.texDescr.ni.tex0En = GL_FALSE;
   imesa->regs.s4.texBlendCtrl[0].ui = TBC_NoTexMap;
   imesa->regs.s4.texCtrl[0].ui = 0x20f040;
   if (ctx->Texture.Unit[0]._ReallyEnabled == 0)
      return;

   tObj = ctx->Texture.Unit[0]._Current;
   if ((ctx->Texture.Unit[0]._ReallyEnabled & ~(TEXTURE_1D_BIT|TEXTURE_2D_BIT))
       || tObj->Image[0][tObj->BaseLevel]->Border > 0) {
      /* 3D texturing enabled, or texture border - fallback */
      FALLBACK (ctx, SAVAGE_FALLBACK_TEXTURE, GL_TRUE);
      return;
   }

   /* Do 2D texture setup */

   t = tObj->DriverData;
   if (!t) {
      t = savageAllocTexObj( tObj );
      if (!t)
         return;
   }

   imesa->CurrentTexObj[0] = &t->base;
   t->base.bound |= 1;

   if (t->base.dirty_images[0] || t->dirtySubImages) {
       savageSetTexImages(imesa, tObj);
       savageUploadTexImages(imesa, t); 
   }
   
   driUpdateTextureLRU( &t->base );

   format = tObj->Image[0][tObj->BaseLevel]->_BaseFormat;

   switch (ctx->Texture.Unit[0].EnvMode) {
   case GL_REPLACE:
      imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_FALSE;
      switch(format)
      {
          case GL_LUMINANCE:
          case GL_RGB:
               imesa->regs.s4.texBlendCtrl[0].ui = TBC_Decal;
               break;

          case GL_LUMINANCE_ALPHA:
          case GL_RGBA:
          case GL_INTENSITY:
               imesa->regs.s4.texBlendCtrl[0].ui = TBC_Copy;
               break;

          case GL_ALPHA:
               imesa->regs.s4.texBlendCtrl[0].ui = TBC_CopyAlpha;
               break;
      }
       __HWEnvCombineSingleUnitScale(imesa, 0, 0,
				     &imesa->regs.s4.texBlendCtrl[0]);
      break;

    case GL_DECAL:
        imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_FALSE;
        switch (format)
        {
            case GL_RGB:
            case GL_LUMINANCE:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_Decal;
                break;

            case GL_RGBA:
            case GL_INTENSITY:
            case GL_LUMINANCE_ALPHA:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_DecalAlpha;
                break;

            /*
             GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_ALPHA, GL_INTENSITY
             are undefined with GL_DECAL
            */

            case GL_ALPHA:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_CopyAlpha;
                break;
        }
        __HWEnvCombineSingleUnitScale(imesa, 0, 0,
				      &imesa->regs.s4.texBlendCtrl[0]);
        break;

    case GL_MODULATE:
        imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_FALSE;
        imesa->regs.s4.texBlendCtrl[0].ui = TBC_ModulAlpha;
        __HWEnvCombineSingleUnitScale(imesa, 0, 0,
				      &imesa->regs.s4.texBlendCtrl[0]);
        break;

    case GL_BLEND:
	imesa->regs.s4.texBlendColor.ui = imesa->texEnvColor;

        switch (format)
        {
            case GL_ALPHA:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_ModulAlpha;
                imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_FALSE;
                break;

            case GL_LUMINANCE:
            case GL_RGB:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_Blend0;
                imesa->regs.s4.texDescr.ni.tex1En = GL_TRUE;
                imesa->regs.s4.texDescr.ni.texBLoopEn = GL_TRUE;
                imesa->regs.s4.texDescr.ni.tex1Width  =
		    imesa->regs.s4.texDescr.ni.tex0Width;
                imesa->regs.s4.texDescr.ni.tex1Height =
		    imesa->regs.s4.texDescr.ni.tex0Height;
                imesa->regs.s4.texDescr.ni.tex1Fmt =
		    imesa->regs.s4.texDescr.ni.tex0Fmt;

		imesa->regs.s4.texAddr[1].ui = imesa->regs.s4.texAddr[0].ui;
		imesa->regs.s4.texBlendCtrl[1].ui = TBC_Blend1;

                imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_TRUE;
                imesa->bTexEn1 = GL_TRUE;
                break;

            case GL_LUMINANCE_ALPHA:
            case GL_RGBA:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_BlendAlpha0;
                imesa->regs.s4.texDescr.ni.tex1En = GL_TRUE;
                imesa->regs.s4.texDescr.ni.texBLoopEn = GL_TRUE;
                imesa->regs.s4.texDescr.ni.tex1Width  =
		    imesa->regs.s4.texDescr.ni.tex0Width;
                imesa->regs.s4.texDescr.ni.tex1Height =
		    imesa->regs.s4.texDescr.ni.tex0Height;
                imesa->regs.s4.texDescr.ni.tex1Fmt =
		    imesa->regs.s4.texDescr.ni.tex0Fmt;

		imesa->regs.s4.texAddr[1].ui = imesa->regs.s4.texAddr[0].ui;
		imesa->regs.s4.texBlendCtrl[1].ui = TBC_BlendAlpha1;

                imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_TRUE;
                imesa->bTexEn1 = GL_TRUE;
                break;

            case GL_INTENSITY:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_BlendInt0;
                imesa->regs.s4.texDescr.ni.tex1En = GL_TRUE;
                imesa->regs.s4.texDescr.ni.texBLoopEn = GL_TRUE;
                imesa->regs.s4.texDescr.ni.tex1Width  =
		    imesa->regs.s4.texDescr.ni.tex0Width;
                imesa->regs.s4.texDescr.ni.tex1Height =
		    imesa->regs.s4.texDescr.ni.tex0Height;
                imesa->regs.s4.texDescr.ni.tex1Fmt =
		    imesa->regs.s4.texDescr.ni.tex0Fmt;

		imesa->regs.s4.texAddr[1].ui = imesa->regs.s4.texAddr[0].ui;
		imesa->regs.s4.texBlendCtrl[1].ui = TBC_BlendInt1;

                imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_TRUE;
                imesa->regs.s4.texCtrl[0].ni.alphaArg1Invert = GL_TRUE;
                imesa->bTexEn1 = GL_TRUE;
                break;
        }
        __HWEnvCombineSingleUnitScale(imesa, 0, 0,
				      &imesa->regs.s4.texBlendCtrl[0]);
        break;

    case GL_ADD:
        imesa->regs.s4.texCtrl[0].ni.clrArg1Invert = GL_FALSE;
        switch (format)
        {
            case GL_ALPHA:
                imesa->regs.s4.texBlendCtrl[0].ui = TBC_ModulAlpha;
		break;

            case GL_LUMINANCE:
            case GL_RGB:
		imesa->regs.s4.texBlendCtrl[0].ui = TBC_Add;
		break;

            case GL_LUMINANCE_ALPHA:
            case GL_RGBA:
		imesa->regs.s4.texBlendCtrl[0].ui = TBC_Add;
		break;

            case GL_INTENSITY:
		imesa->regs.s4.texBlendCtrl[0].ui = TBC_AddAlpha;
		break;
	}
        __HWEnvCombineSingleUnitScale(imesa, 0, 0,
				      &imesa->regs.s4.texBlendCtrl[0]);
        break;

#if GL_ARB_texture_env_combine
    case GL_COMBINE_ARB:
        __HWParseTexEnvCombine(imesa, 0, &imesa->regs.s4.texCtrl[0],
			       &imesa->regs.s4.texBlendCtrl[0]);
        break;
#endif

   default:
      fprintf(stderr, "unknown tex env mode");
      exit(1);
      break;			
   }

    savage4_set_wrap_mode( imesa, 0, t->setup.sWrapMode, t->setup.tWrapMode );
    savage4_set_filter_mode( imesa, 0, t->setup.minFilter, t->setup.magFilter );

    if((ctx->Texture.Unit[0].LodBias !=0.0F) ||
       (imesa->regs.s4.texCtrl[0].ni.dBias != 0))
    {
	int bias = (int)(ctx->Texture.Unit[0].LodBias * 32.0) +
	    SAVAGE4_LOD_OFFSET;
	if (bias < -256)
	    bias = -256;
	else if (bias > 255)
	    bias = 255;
	imesa->regs.s4.texCtrl[0].ni.dBias = bias & 0x1ff;
    }

    image = tObj->Image[0][tObj->BaseLevel];
    imesa->regs.s4.texDescr.ni.tex0En = GL_TRUE;
    imesa->regs.s4.texDescr.ni.tex0Width  = image->WidthLog2;
    imesa->regs.s4.texDescr.ni.tex0Height = image->HeightLog2;
    imesa->regs.s4.texDescr.ni.tex0Fmt = t->hwFormat;
    imesa->regs.s4.texCtrl[0].ni.dMax = t->base.lastLevel - t->base.firstLevel;

    if (imesa->regs.s4.texDescr.ni.tex1En)
        imesa->regs.s4.texDescr.ni.texBLoopEn = GL_TRUE;

    imesa->regs.s4.texAddr[0].ui = (uint32_t) t->setup.physAddr | 0x2;
    if(t->base.heap->heapId == SAVAGE_AGP_HEAP)
	imesa->regs.s4.texAddr[0].ui |= 0x1;
    
    return;
}
static void savageUpdateTex1State_s4( GLcontext *ctx )
{
   savageContextPtr imesa = SAVAGE_CONTEXT(ctx);
   struct gl_texture_object	*tObj;
   struct gl_texture_image *image;
   savageTexObjPtr t;
   GLuint format;

   /* disable */
   if(imesa->bTexEn1)
   {
       imesa->bTexEn1 = GL_FALSE;
       return;
   }

   imesa->regs.s4.texDescr.ni.tex1En = GL_FALSE;
   imesa->regs.s4.texBlendCtrl[1].ui = TBC_NoTexMap1;
   imesa->regs.s4.texCtrl[1].ui = 0x20f040;
   imesa->regs.s4.texDescr.ni.texBLoopEn = GL_FALSE;
   if (ctx->Texture.Unit[1]._ReallyEnabled == 0)
      return;

   tObj = ctx->Texture.Unit[1]._Current;

   if ((ctx->Texture.Unit[1]._ReallyEnabled & ~(TEXTURE_1D_BIT|TEXTURE_2D_BIT))
       || tObj->Image[0][tObj->BaseLevel]->Border > 0) {
      /* 3D texturing enabled, or texture border - fallback */
      FALLBACK (ctx, SAVAGE_FALLBACK_TEXTURE, GL_TRUE);
      return;
   }

   /* Do 2D texture setup */

   t = tObj->DriverData;
   if (!t) {
      t = savageAllocTexObj( tObj );
      if (!t)
         return;
   }
    
   imesa->CurrentTexObj[1] = &t->base;

   t->base.bound |= 2;

   if (t->base.dirty_images[0] || t->dirtySubImages) {
       savageSetTexImages(imesa, tObj);
       savageUploadTexImages(imesa, t);
   }
   
   driUpdateTextureLRU( &t->base );

   format = tObj->Image[0][tObj->BaseLevel]->_BaseFormat;

   switch (ctx->Texture.Unit[1].EnvMode) {
   case GL_REPLACE:
        imesa->regs.s4.texCtrl[1].ni.clrArg1Invert = GL_FALSE;
        switch (format)
        {
            case GL_LUMINANCE:
            case GL_RGB:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_Decal;
                break;

            case GL_LUMINANCE_ALPHA:
            case GL_INTENSITY:
            case GL_RGBA:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_Copy;
                break;

            case GL_ALPHA:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_CopyAlpha1;
                break;
        }
        __HWEnvCombineSingleUnitScale(imesa, 0, 1, &imesa->regs.s4.texBlendCtrl);
      break;
   case GL_MODULATE:
       imesa->regs.s4.texCtrl[1].ni.clrArg1Invert = GL_FALSE;
       imesa->regs.s4.texBlendCtrl[1].ui = TBC_ModulAlpha1;
       __HWEnvCombineSingleUnitScale(imesa, 0, 1, &imesa->regs.s4.texBlendCtrl);
       break;

    case GL_ADD:
        imesa->regs.s4.texCtrl[1].ni.clrArg1Invert = GL_FALSE;
        switch (format)
        {
            case GL_ALPHA:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_ModulAlpha1;
		break;

            case GL_LUMINANCE:
            case GL_RGB:
		imesa->regs.s4.texBlendCtrl[1].ui = TBC_Add1;
		break;

            case GL_LUMINANCE_ALPHA:
            case GL_RGBA:
		imesa->regs.s4.texBlendCtrl[1].ui = TBC_Add1;
		break;

            case GL_INTENSITY:
		imesa->regs.s4.texBlendCtrl[1].ui = TBC_AddAlpha1;
		break;
	}
        __HWEnvCombineSingleUnitScale(imesa, 0, 1, &imesa->regs.s4.texBlendCtrl);
        break;

#if GL_ARB_texture_env_combine
    case GL_COMBINE_ARB:
        __HWParseTexEnvCombine(imesa, 1, &texCtrl, &imesa->regs.s4.texBlendCtrl);
        break;
#endif

   case GL_DECAL:
        imesa->regs.s4.texCtrl[1].ni.clrArg1Invert = GL_FALSE;

        switch (format)
        {
            case GL_LUMINANCE:
            case GL_RGB:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_Decal1;
                break;
            case GL_LUMINANCE_ALPHA:
            case GL_INTENSITY:
            case GL_RGBA:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_DecalAlpha1;
                break;

                /*
                // GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_ALPHA, GL_INTENSITY
                // are undefined with GL_DECAL
                */
            case GL_ALPHA:
                imesa->regs.s4.texBlendCtrl[1].ui = TBC_CopyAlpha1;
                break;
        }
        __HWEnvCombineSingleUnitScale(imesa, 0, 1, &imesa->regs.s4.texBlendCtrl);
        break;

   case GL_BLEND:
        if (format == GL_LUMINANCE)
        {
            /*
            // This is a hack for GLQuake, invert.
            */
            imesa->regs.s4.texCtrl[1].ni.clrArg1Invert = GL_TRUE;
            imesa->regs.s4.texBlendCtrl[1].ui = 0;
        }
        __HWEnvCombineSingleUnitScale(imesa, 0, 1, &imesa->regs.s4.texBlendCtrl);
      break;

   default:
      fprintf(stderr, "unknown tex 1 env mode\n");
      exit(1);
      break;			
   }

    savage4_set_wrap_mode( imesa, 1, t->setup.sWrapMode, t->setup.tWrapMode );
    savage4_set_filter_mode( imesa, 1, t->setup.minFilter, t->setup.magFilter );

    if((ctx->Texture.Unit[1].LodBias !=0.0F) ||
       (imesa->regs.s4.texCtrl[1].ni.dBias != 0))
    {
	int bias = (int)(ctx->Texture.Unit[1].LodBias * 32.0) +
	    SAVAGE4_LOD_OFFSET;
	if (bias < -256)
	    bias = -256;
	else if (bias > 255)
	    bias = 255;
	imesa->regs.s4.texCtrl[1].ni.dBias = bias & 0x1ff;
    }

    image = tObj->Image[0][tObj->BaseLevel];
    imesa->regs.s4.texDescr.ni.tex1En = GL_TRUE;
    imesa->regs.s4.texDescr.ni.tex1Width  = image->WidthLog2;
    imesa->regs.s4.texDescr.ni.tex1Height = image->HeightLog2;
    imesa->regs.s4.texDescr.ni.tex1Fmt = t->hwFormat;
    imesa->regs.s4.texCtrl[1].ni.dMax = t->base.lastLevel - t->base.firstLevel;
    imesa->regs.s4.texDescr.ni.texBLoopEn = GL_TRUE;

    imesa->regs.s4.texAddr[1].ui = (uint32_t) t->setup.physAddr | 2;
    if(t->base.heap->heapId == SAVAGE_AGP_HEAP)
	imesa->regs.s4.texAddr[1].ui |= 0x1;
}
static void savageUpdateTexState_s3d( GLcontext *ctx )
{
    savageContextPtr imesa = SAVAGE_CONTEXT(ctx);
    struct gl_texture_object *tObj;
    struct gl_texture_image *image;
    savageTexObjPtr t;
    GLuint format;

    /* disable */
    imesa->regs.s3d.texCtrl.ui = 0;
    imesa->regs.s3d.texCtrl.ni.texEn = GL_FALSE;
    imesa->regs.s3d.texCtrl.ni.dBias = 0x08;
    imesa->regs.s3d.texCtrl.ni.texXprEn = GL_TRUE;
    if (ctx->Texture.Unit[0]._ReallyEnabled == 0)
	return;

    tObj = ctx->Texture.Unit[0]._Current;
    if ((ctx->Texture.Unit[0]._ReallyEnabled & ~(TEXTURE_1D_BIT|TEXTURE_2D_BIT))
	|| tObj->Image[0][tObj->BaseLevel]->Border > 0) {
	/* 3D texturing enabled, or texture border - fallback */
	FALLBACK (ctx, SAVAGE_FALLBACK_TEXTURE, GL_TRUE);
	return;
    }

    /* Do 2D texture setup */
    t = tObj->DriverData;
    if (!t) {
	t = savageAllocTexObj( tObj );
	if (!t)
	    return;
    }

    imesa->CurrentTexObj[0] = &t->base;
    t->base.bound |= 1;

    if (t->base.dirty_images[0] || t->dirtySubImages) {
	savageSetTexImages(imesa, tObj);
	savageUploadTexImages(imesa, t);
    }

    driUpdateTextureLRU( &t->base );

    format = tObj->Image[0][tObj->BaseLevel]->_BaseFormat;

    /* FIXME: copied from utah-glx, probably needs some tuning */
    switch (ctx->Texture.Unit[0].EnvMode) {
    case GL_DECAL:
	imesa->regs.s3d.drawCtrl.ni.texBlendCtrl = SAVAGETBC_DECALALPHA_S3D;
	break;
    case GL_REPLACE:
	switch (format) {
	case GL_ALPHA: /* FIXME */
	    imesa->regs.s3d.drawCtrl.ni.texBlendCtrl = 1;
	    break;
	case GL_LUMINANCE_ALPHA:
	case GL_RGBA:
	    imesa->regs.s3d.drawCtrl.ni.texBlendCtrl = 4;
	    break;
	case GL_RGB:
	case GL_LUMINANCE:
	    imesa->regs.s3d.drawCtrl.ni.texBlendCtrl = SAVAGETBC_DECAL_S3D;
	    break;
	case GL_INTENSITY:
	    imesa->regs.s3d.drawCtrl.ni.texBlendCtrl = SAVAGETBC_COPY_S3D;
	}
	break;
    case GL_BLEND: /* hardware can't do GL_BLEND */
	FALLBACK (ctx, SAVAGE_FALLBACK_TEXTURE, GL_TRUE);
	return;
    case GL_MODULATE:
	imesa->regs.s3d.drawCtrl.ni.texBlendCtrl = SAVAGETBC_MODULATEALPHA_S3D;
	break;
    default:
	fprintf(stderr, "unknown tex env mode\n");
	/*exit(1);*/
	break;			
    }

    /* The Savage3D can't handle different wrapping modes in s and t.
     * If they are not the same, fall back to software. */
    if (t->setup.sWrapMode != t->setup.tWrapMode) {
	FALLBACK (ctx, SAVAGE_FALLBACK_TEXTURE, GL_TRUE);
	return;
    }
    imesa->regs.s3d.texCtrl.ni.uWrapEn = 0;
    imesa->regs.s3d.texCtrl.ni.vWrapEn = 0;
    imesa->regs.s3d.texCtrl.ni.wrapMode =
	(t->setup.sWrapMode == GL_REPEAT) ? TAM_Wrap : TAM_Clamp;

    switch (t->setup.minFilter) {
    case GL_NEAREST:
	imesa->regs.s3d.texCtrl.ni.filterMode    = TFM_Point;
	imesa->regs.s3d.texCtrl.ni.mipmapDisable = GL_TRUE;
	break;

    case GL_LINEAR:
	imesa->regs.s3d.texCtrl.ni.filterMode    = TFM_Bilin;
	imesa->regs.s3d.texCtrl.ni.mipmapDisable = GL_TRUE;
	break;

    case GL_NEAREST_MIPMAP_NEAREST:
	imesa->regs.s3d.texCtrl.ni.filterMode    = TFM_Point;
	imesa->regs.s3d.texCtrl.ni.mipmapDisable = GL_FALSE;
	break;

    case GL_LINEAR_MIPMAP_NEAREST:
	imesa->regs.s3d.texCtrl.ni.filterMode    = TFM_Bilin;
	imesa->regs.s3d.texCtrl.ni.mipmapDisable = GL_FALSE;
	break;

    case GL_NEAREST_MIPMAP_LINEAR:
    case GL_LINEAR_MIPMAP_LINEAR:
	imesa->regs.s3d.texCtrl.ni.filterMode    = TFM_Trilin;
	imesa->regs.s3d.texCtrl.ni.mipmapDisable = GL_FALSE;
	break;
    }

    /* There is no way to specify a maximum mipmap level. We may have to
       disable mipmapping completely. */
    /*
    if (t->max_level < t->image[0].image->WidthLog2 ||
	t->max_level < t->image[0].image->HeightLog2) {
	texCtrl.ni.mipmapEnable = GL_TRUE;
	if (texCtrl.ni.filterMode == TFM_Trilin)
	    texCtrl.ni.filterMode = TFM_Bilin;
	texCtrl.ni.filterMode = TFM_Point;
    }
    */

    if((ctx->Texture.Unit[0].LodBias !=0.0F) ||
       (imesa->regs.s3d.texCtrl.ni.dBias != 0))
    {
	int bias = (int)(ctx->Texture.Unit[0].LodBias * 16.0);
	if (bias < -256)
	    bias = -256;
	else if (bias > 255)
	    bias = 255;
	imesa->regs.s3d.texCtrl.ni.dBias = bias & 0x1ff;
    }

    image = tObj->Image[0][tObj->BaseLevel];
    imesa->regs.s3d.texCtrl.ni.texEn = GL_TRUE;
    imesa->regs.s3d.texDescr.ni.texWidth  = image->WidthLog2;
    imesa->regs.s3d.texDescr.ni.texHeight = image->HeightLog2;
    assert (t->hwFormat <= 7);
    imesa->regs.s3d.texDescr.ni.texFmt = t->hwFormat;

    imesa->regs.s3d.texAddr.ui = (uint32_t) t->setup.physAddr | 2;
    if(t->base.heap->heapId == SAVAGE_AGP_HEAP)
	imesa->regs.s3d.texAddr.ui |= 0x1;
}


static void savageTimestampTextures( savageContextPtr imesa )
{
   /* Timestamp current texture objects for texture heap aging.
    * Only useful with long-lived 32-bit event tags available
    * with Savage DRM 2.3.x or later. */
   if ((imesa->CurrentTexObj[0] || imesa->CurrentTexObj[1]) &&
       imesa->savageScreen->driScrnPriv->drm_version.minor >= 3) {
       unsigned int e;
       FLUSH_BATCH(imesa);
       e = savageEmitEvent(imesa, SAVAGE_WAIT_3D);
       if (imesa->CurrentTexObj[0])
	   imesa->CurrentTexObj[0]->timestamp = e;
       if (imesa->CurrentTexObj[1])
	   imesa->CurrentTexObj[1]->timestamp = e;
   }
}


static void savageUpdateTextureState_s4( GLcontext *ctx )
{
   savageContextPtr imesa = SAVAGE_CONTEXT(ctx);

   /* When a texture is about to change or be disabled, timestamp the
    * old texture(s). We'll have to wait for this time stamp before
    * uploading anything to the same texture heap.
    */
   if ((imesa->CurrentTexObj[0] && ctx->Texture.Unit[0]._ReallyEnabled &&
	ctx->Texture.Unit[0]._Current->DriverData != imesa->CurrentTexObj[0]) ||
       (imesa->CurrentTexObj[1] && ctx->Texture.Unit[1]._ReallyEnabled &&
	ctx->Texture.Unit[1]._Current->DriverData != imesa->CurrentTexObj[1]) ||
       (imesa->CurrentTexObj[0] && !ctx->Texture.Unit[0]._ReallyEnabled) ||
       (imesa->CurrentTexObj[1] && !ctx->Texture.Unit[1]._ReallyEnabled))
       savageTimestampTextures(imesa);

   if (imesa->CurrentTexObj[0]) imesa->CurrentTexObj[0]->bound &= ~1;
   if (imesa->CurrentTexObj[1]) imesa->CurrentTexObj[1]->bound &= ~2;
   imesa->CurrentTexObj[0] = 0;
   imesa->CurrentTexObj[1] = 0;   
   savageUpdateTex0State_s4( ctx );
   savageUpdateTex1State_s4( ctx );
   imesa->dirty |= (SAVAGE_UPLOAD_TEX0 | 
		    SAVAGE_UPLOAD_TEX1);
}
static void savageUpdateTextureState_s3d( GLcontext *ctx )
{
    savageContextPtr imesa = SAVAGE_CONTEXT(ctx);

   /* When a texture is about to change or be disabled, timestamp the
    * old texture(s). We'll have to wait for this time stamp before
    * uploading anything to the same texture heap.
    */
    if ((imesa->CurrentTexObj[0] && ctx->Texture.Unit[0]._ReallyEnabled &&
	 ctx->Texture.Unit[0]._Current->DriverData != imesa->CurrentTexObj[0]) ||
	(imesa->CurrentTexObj[0] && !ctx->Texture.Unit[0]._ReallyEnabled))
	savageTimestampTextures(imesa);

    if (imesa->CurrentTexObj[0]) imesa->CurrentTexObj[0]->bound &= ~1;
    imesa->CurrentTexObj[0] = 0;
    savageUpdateTexState_s3d( ctx );
    imesa->dirty |= (SAVAGE_UPLOAD_TEX0);
}
void savageUpdateTextureState( GLcontext *ctx)
{
    savageContextPtr imesa = SAVAGE_CONTEXT( ctx );
    FALLBACK (ctx, SAVAGE_FALLBACK_TEXTURE, GL_FALSE);
    FALLBACK(ctx, SAVAGE_FALLBACK_PROJ_TEXTURE, GL_FALSE);
    if (imesa->savageScreen->chipset >= S3_SAVAGE4)
	savageUpdateTextureState_s4 (ctx);
    else
	savageUpdateTextureState_s3d (ctx);
}



/*****************************************
 * DRIVER functions
 *****************************************/

static void savageTexEnv( GLcontext *ctx, GLenum target, 
			GLenum pname, const GLfloat *param )
{
   savageContextPtr imesa = SAVAGE_CONTEXT( ctx );

   if (pname == GL_TEXTURE_ENV_MODE) {

      imesa->new_state |= SAVAGE_NEW_TEXTURE;

   } else if (pname == GL_TEXTURE_ENV_COLOR) {

      struct gl_texture_unit *texUnit = 
	 &ctx->Texture.Unit[ctx->Texture.CurrentUnit];
      const GLfloat *fc = texUnit->EnvColor;
      GLuint r, g, b, a;
      CLAMPED_FLOAT_TO_UBYTE(r, fc[0]);
      CLAMPED_FLOAT_TO_UBYTE(g, fc[1]);
      CLAMPED_FLOAT_TO_UBYTE(b, fc[2]);
      CLAMPED_FLOAT_TO_UBYTE(a, fc[3]);

      imesa->texEnvColor = ((a << 24) | (r << 16) | 
			    (g <<  8) | (b <<  0));
    

   } 
}

/* Update the heap's time stamp, so the new image is not uploaded
 * while the old one is still in use. If the texture that is going to
 * be changed is currently bound, we need to timestamp the texture
 * first. */
static void savageTexImageChanged (savageTexObjPtr t) {
    if (t->base.heap) {
	if (t->base.bound)
	    savageTimestampTextures(
		(savageContextPtr)t->base.heap->driverContext);
	if (t->base.timestamp > t->base.heap->timestamp)
	    t->base.heap->timestamp = t->base.timestamp;
    }
}

static void savageTexImage1D( GLcontext *ctx, GLenum target, GLint level,
			      GLint internalFormat,
			      GLint width, GLint border,
			      GLenum format, GLenum type, const GLvoid *pixels,
			      const struct gl_pixelstore_attrib *packing,
			      struct gl_texture_object *texObj,
			      struct gl_texture_image *texImage )
{
   savageTexObjPtr t = (savageTexObjPtr) texObj->DriverData;
   if (t) {
      savageTexImageChanged (t);
   } else {
      t = savageAllocTexObj(texObj);
      if (!t) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glTexImage1D");
         return;
      }
   }
   _mesa_store_teximage1d( ctx, target, level, internalFormat,
			   width, border, format, type,
			   pixels, packing, texObj, texImage );
   t->base.dirty_images[0] |= (1 << level);
   SAVAGE_CONTEXT(ctx)->new_state |= SAVAGE_NEW_TEXTURE;
}

static void savageTexSubImage1D( GLcontext *ctx, 
				 GLenum target,
				 GLint level,	
				 GLint xoffset,
				 GLsizei width,
				 GLenum format, GLenum type,
				 const GLvoid *pixels,
				 const struct gl_pixelstore_attrib *packing,
				 struct gl_texture_object *texObj,
				 struct gl_texture_image *texImage )
{
   savageTexObjPtr t = (savageTexObjPtr) texObj->DriverData;
   assert( t ); /* this _should_ be true */
   if (t) {
      savageTexImageChanged (t);
      savageMarkDirtyTiles(t, level, texImage->Width2, 1,
			   xoffset, 0, width, 1);
   } else {
      t = savageAllocTexObj(texObj);
      if (!t) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glTexSubImage1D");
         return;
      }
      t->base.dirty_images[0] |= (1 << level);
   }
   _mesa_store_texsubimage1d(ctx, target, level, xoffset, width, 
			     format, type, pixels, packing, texObj,
			     texImage);
   t->dirtySubImages |= (1 << level);
   SAVAGE_CONTEXT(ctx)->new_state |= SAVAGE_NEW_TEXTURE;
}

static void savageTexImage2D( GLcontext *ctx, GLenum target, GLint level,
			      GLint internalFormat,
			      GLint width, GLint height, GLint border,
			      GLenum format, GLenum type, const GLvoid *pixels,
			      const struct gl_pixelstore_attrib *packing,
			      struct gl_texture_object *texObj,
			      struct gl_texture_image *texImage )
{
   savageTexObjPtr t = (savageTexObjPtr) texObj->DriverData;
   if (t) {
      savageTexImageChanged (t);
   } else {
      t = savageAllocTexObj(texObj);
      if (!t) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glTexImage2D");
         return;
      }
   }
   _mesa_store_teximage2d( ctx, target, level, internalFormat,
			   width, height, border, format, type,
			   pixels, packing, texObj, texImage );
   t->base.dirty_images[0] |= (1 << level);
   SAVAGE_CONTEXT(ctx)->new_state |= SAVAGE_NEW_TEXTURE;
}

static void savageTexSubImage2D( GLcontext *ctx, 
				 GLenum target,
				 GLint level,	
				 GLint xoffset, GLint yoffset,
				 GLsizei width, GLsizei height,
				 GLenum format, GLenum type,
				 const GLvoid *pixels,
				 const struct gl_pixelstore_attrib *packing,
				 struct gl_texture_object *texObj,
				 struct gl_texture_image *texImage )
{
   savageTexObjPtr t = (savageTexObjPtr) texObj->DriverData;
   assert( t ); /* this _should_ be true */
   if (t) {
      savageTexImageChanged (t);
      savageMarkDirtyTiles(t, level, texImage->Width2, texImage->Height2,
			   xoffset, yoffset, width, height);
   } else {
      t = savageAllocTexObj(texObj);
      if (!t) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glTexSubImage2D");
         return;
      }
      t->base.dirty_images[0] |= (1 << level);
   }
   _mesa_store_texsubimage2d(ctx, target, level, xoffset, yoffset, width, 
			     height, format, type, pixels, packing, texObj,
			     texImage);
   t->dirtySubImages |= (1 << level);
   SAVAGE_CONTEXT(ctx)->new_state |= SAVAGE_NEW_TEXTURE;
}

static void
savageCompressedTexImage2D( GLcontext *ctx, GLenum target, GLint level,
			    GLint internalFormat,
			    GLint width, GLint height, GLint border,
			    GLsizei imageSize, const GLvoid *data,
			    struct gl_texture_object *texObj,
			    struct gl_texture_image *texImage )
{
   savageTexObjPtr t = (savageTexObjPtr) texObj->DriverData;
   if (t) {
      savageTexImageChanged (t);
   } else {
      t = savageAllocTexObj(texObj);
      if (!t) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glCompressedTexImage2D");
         return;
      }
   }
   _mesa_store_compressed_teximage2d( ctx, target, level, internalFormat,
				      width, height, border, imageSize,
				      data, texObj, texImage );
   t->base.dirty_images[0] |= (1 << level);
   SAVAGE_CONTEXT(ctx)->new_state |= SAVAGE_NEW_TEXTURE;
}

static void
savageCompressedTexSubImage2D( GLcontext *ctx, 
			       GLenum target,
			       GLint level,	
			       GLint xoffset, GLint yoffset,
			       GLsizei width, GLsizei height,
			       GLenum format, GLsizei imageSize,
			       const GLvoid *data,
			       struct gl_texture_object *texObj,
			       struct gl_texture_image *texImage )
{
   savageTexObjPtr t = (savageTexObjPtr) texObj->DriverData;
   assert( t ); /* this _should_ be true */
   if (t) {
      savageTexImageChanged (t);
      savageMarkDirtyTiles(t, level, texImage->Width2, texImage->Height2,
			   xoffset, yoffset, width, height);
   } else {
      t = savageAllocTexObj(texObj);
      if (!t) {
         _mesa_error(ctx, GL_OUT_OF_MEMORY, "glTexSubImage2D");
         return;
      }
      t->base.dirty_images[0] |= (1 << level);
   }
   _mesa_store_compressed_texsubimage2d(ctx, target, level, xoffset, yoffset,
					width, height, format, imageSize,
					data, texObj, texImage);
   t->dirtySubImages |= (1 << level);
   SAVAGE_CONTEXT(ctx)->new_state |= SAVAGE_NEW_TEXTURE;
}

static void savageTexParameter( GLcontext *ctx, GLenum target,
			      struct gl_texture_object *tObj,
			      GLenum pname, const GLfloat *params )
{
   savageTexObjPtr t = (savageTexObjPtr) tObj->DriverData;
   savageContextPtr imesa = SAVAGE_CONTEXT( ctx );

   if (!t || (target != GL_TEXTURE_1D && target != GL_TEXTURE_2D))
      return;

   switch (pname) {
   case GL_TEXTURE_MIN_FILTER:
   case GL_TEXTURE_MAG_FILTER:
      savageSetTexFilter(t,tObj->MinFilter,tObj->MagFilter);
      break;

   case GL_TEXTURE_WRAP_S:
   case GL_TEXTURE_WRAP_T:
      savageSetTexWrapping(t,tObj->WrapS,tObj->WrapT);
      break;
  
   case GL_TEXTURE_BORDER_COLOR:
      savageSetTexBorderColor(t,tObj->BorderColor);
      break;

   default:
      return;
   }

   imesa->new_state |= SAVAGE_NEW_TEXTURE;
}

static void savageBindTexture( GLcontext *ctx, GLenum target,
			       struct gl_texture_object *tObj )
{
   savageContextPtr imesa = SAVAGE_CONTEXT( ctx );
   
   assert( (target != GL_TEXTURE_1D && target != GL_TEXTURE_2D) ||
	   (tObj->DriverData != NULL) );

   imesa->new_state |= SAVAGE_NEW_TEXTURE;
}

static void savageDeleteTexture( GLcontext *ctx, struct gl_texture_object *tObj )
{
   driTextureObject *t = (driTextureObject *)tObj->DriverData;
   savageContextPtr imesa = SAVAGE_CONTEXT( ctx );

   if (t) {
      if (t->bound)
	 savageTimestampTextures(imesa);

      driDestroyTextureObject(t);
   }
   /* Free mipmap images and the texture object itself */
   _mesa_delete_texture_object(ctx, tObj);
}


static struct gl_texture_object *
savageNewTextureObject( GLcontext *ctx, GLuint name, GLenum target )
{
    struct gl_texture_object *obj;
    obj = _mesa_new_texture_object(ctx, name, target);
    savageAllocTexObj( obj );

    return obj;
}

void savageDDInitTextureFuncs( struct dd_function_table *functions )
{
   functions->TexEnv = savageTexEnv;
   functions->ChooseTextureFormat = savageChooseTextureFormat;
   functions->TexImage1D = savageTexImage1D;
   functions->TexSubImage1D = savageTexSubImage1D;
   functions->TexImage2D = savageTexImage2D;
   functions->TexSubImage2D = savageTexSubImage2D;
   functions->CompressedTexImage2D = savageCompressedTexImage2D;
   functions->CompressedTexSubImage2D = savageCompressedTexSubImage2D;
   functions->BindTexture = savageBindTexture;
   functions->NewTextureObject = savageNewTextureObject;
   functions->DeleteTexture = savageDeleteTexture;
   functions->IsTextureResident = driIsTextureResident;
   functions->TexParameter = savageTexParameter;

   /* Texel fetching with our custom texture formats works just like
    * the standard argb formats. */
#if 0
   _savage_texformat_a1114444.FetchTexel1D = _mesa_texformat_argb4444.FetchTexel1D;
   _savage_texformat_a1114444.FetchTexel2D = _mesa_texformat_argb4444.FetchTexel2D;
   _savage_texformat_a1114444.FetchTexel3D = _mesa_texformat_argb4444.FetchTexel3D;
   _savage_texformat_a1114444.FetchTexel1Df= _mesa_texformat_argb4444.FetchTexel1Df;
   _savage_texformat_a1114444.FetchTexel2Df= _mesa_texformat_argb4444.FetchTexel2Df;
   _savage_texformat_a1114444.FetchTexel3Df= _mesa_texformat_argb4444.FetchTexel3Df;

   _savage_texformat_a1118888.FetchTexel1D = _mesa_texformat_argb8888.FetchTexel1D;
   _savage_texformat_a1118888.FetchTexel2D = _mesa_texformat_argb8888.FetchTexel2D;
   _savage_texformat_a1118888.FetchTexel3D = _mesa_texformat_argb8888.FetchTexel3D;
   _savage_texformat_a1118888.FetchTexel1Df= _mesa_texformat_argb8888.FetchTexel1Df;
   _savage_texformat_a1118888.FetchTexel2Df= _mesa_texformat_argb8888.FetchTexel2Df;
   _savage_texformat_a1118888.FetchTexel3Df= _mesa_texformat_argb8888.FetchTexel3Df;
#endif
}
