/*
 * Mesa 3-D graphics library
 * Version:  4.0
 *
 * Copyright (C) 1999-2001  Brian Paul   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, sublicense,
 * 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * BRIAN PAUL 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.
 */

/* Authors:
 *    David Bucciarelli
 *    Brian Paul
 *    Daryll Strauss
 *    Keith Whitwell
 *    Daniel Borca
 *    Hiroshi Morii
 */

/* fxtexman.c - 3Dfx VooDoo texture memory functions */


#ifdef HAVE_CONFIG_H
#include "conf.h"
#endif

#if defined(FX)

#include "fxdrv.h"

int texSwaps = 0;
static FxU32 texBoundMask;

#define FX_2MB_SPLIT 0x200000

static struct gl_texture_object *fxTMFindOldestObject(fxMesaContext fxMesa,
						      int tmu);


#ifdef TEXSANITY
static void
fubar()
{
}

  /* Sanity Check */
static void
sanity(fxMesaContext fxMesa)
{
   MemRange *tmp, *prev, *pos;

   prev = 0;
   tmp = fxMesa->tmFree[0];
   while (tmp) {
      if (!tmp->startAddr && !tmp->endAddr) {
	 fprintf(stderr, "Textures fubar\n");
	 fubar();
      }
      if (tmp->startAddr >= tmp->endAddr) {
	 fprintf(stderr, "Node fubar\n");
	 fubar();
      }
      if (prev && (prev->startAddr >= tmp->startAddr ||
		   prev->endAddr > tmp->startAddr)) {
	 fprintf(stderr, "Sorting fubar\n");
	 fubar();
      }
      prev = tmp;
      tmp = tmp->next;
   }
   prev = 0;
   tmp = fxMesa->tmFree[1];
   while (tmp) {
      if (!tmp->startAddr && !tmp->endAddr) {
	 fprintf(stderr, "Textures fubar\n");
	 fubar();
      }
      if (tmp->startAddr >= tmp->endAddr) {
	 fprintf(stderr, "Node fubar\n");
	 fubar();
      }
      if (prev && (prev->startAddr >= tmp->startAddr ||
		   prev->endAddr > tmp->startAddr)) {
	 fprintf(stderr, "Sorting fubar\n");
	 fubar();
      }
      prev = tmp;
      tmp = tmp->next;
   }
}
#endif

static MemRange *
fxTMNewRangeNode(fxMesaContext fxMesa, FxU32 start, FxU32 end)
{
   MemRange *result = 0;

   if (fxMesa->tmPool) {
      result = fxMesa->tmPool;
      fxMesa->tmPool = fxMesa->tmPool->next;
   }
   else {
      if (!(result = MALLOC(sizeof(MemRange)))) {
	 fprintf(stderr, "fxTMNewRangeNode: ERROR: out of memory!\n");
	 fxCloseHardware();
	 exit(-1);
      }
   }
   result->startAddr = start;
   result->endAddr = end;
   return result;
}

#if 1
#define fxTMDeleteRangeNode(fxMesa, range) \
   do {                                    \
       range->next = fxMesa->tmPool;       \
       fxMesa->tmPool = range;             \
   } while (0);
#else
static void
fxTMDeleteRangeNode(fxMesaContext fxMesa, MemRange * range)
{
   range->next = fxMesa->tmPool;
   fxMesa->tmPool = range;
}
#endif

static void
fxTMUInit(fxMesaContext fxMesa, int tmu)
{
   MemRange *tmn, *last;
   FxU32 start, end, blockstart, blockend, chunk;

   start = grTexMinAddress(tmu);
   end = grTexMaxAddress(tmu);

   chunk = (fxMesa->type >= GR_SSTTYPE_Banshee) ? (end - start) : FX_2MB_SPLIT;
	
   if (fxMesa->verbose) {
      fprintf(stderr, "Voodoo TMU%d configuration:\n", tmu);
   }

   fxMesa->freeTexMem[tmu] = end - start;
   fxMesa->tmFree[tmu] = NULL;

   last = 0;
   blockstart = start;
   while (blockstart < end) {
      if (blockstart + chunk > end)
	 blockend = end;
      else
	 blockend = blockstart + chunk;

      if (fxMesa->verbose)
	 fprintf(stderr, "Voodoo   %08u-%08u\n",
		 (unsigned int) blockstart, (unsigned int) blockend);

      tmn = fxTMNewRangeNode(fxMesa, blockstart, blockend);
      tmn->next = NULL;


      if (last)
	 last->next = tmn;
      else
	 fxMesa->tmFree[tmu] = tmn;
      last = tmn;

      blockstart += chunk;
   }
}

static int
fxTMFindStartAddr(fxMesaContext fxMesa, GLint tmu, int size)
{
   MemRange *prev, *tmp;
   int result;
   struct gl_texture_object *obj;

   if (fxMesa->HaveTexUma) {
      tmu = FX_TMU0;
   }

   while (1) {
      prev = 0;
      tmp = fxMesa->tmFree[tmu];
      while (tmp) {
	 if (tmp->endAddr - tmp->startAddr >= size) {	/* Fits here */
	    result = tmp->startAddr;
	    tmp->startAddr += size;
	    if (tmp->startAddr == tmp->endAddr) {	/* Empty */
	       if (prev) {
		  prev->next = tmp->next;
	       }
	       else {
		  fxMesa->tmFree[tmu] = tmp->next;
	       }
	       fxTMDeleteRangeNode(fxMesa, tmp);
	    }
	    fxMesa->freeTexMem[tmu] -= size;
	    return result;
	 }
	 prev = tmp;
	 tmp = tmp->next;
      }
      /* No free space. Discard oldest */
      if (TDFX_DEBUG & VERBOSE_TEXTURE) {
	 fprintf(stderr, "fxTMFindStartAddr: No free space. Discard oldest\n");
      }
      obj = fxTMFindOldestObject(fxMesa, tmu);
      if (!obj) {
	 fprintf(stderr, "fxTMFindStartAddr: ERROR: No space for texture\n");
	 return -1;
      }
      fxTMMoveOutTM(fxMesa, obj);
      texSwaps++;
   }
}

int fxTMCheckStartAddr (fxMesaContext fxMesa, GLint tmu, tfxTexInfo *ti)
{
 MemRange *prev, *tmp;
 int size;
 struct gl_texture_object *obj;

 if (fxMesa->HaveTexUma) {
    return FXTRUE;
 }

 size = grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH, &(ti->info));

 tmp = fxMesa->tmFree[tmu];
 while (tmp) {
       if (tmp->endAddr - tmp->startAddr >= size) { /* Fits here */
          return FXTRUE;
       }
       tmp = tmp->next;
 }

 return FXFALSE;
}

static void
fxTMRemoveRange(fxMesaContext fxMesa, GLint tmu, MemRange * range)
{
   MemRange *tmp, *prev;

   if (fxMesa->HaveTexUma) {
      tmu = FX_TMU0;
   }

   if (range->startAddr == range->endAddr) {
      fxTMDeleteRangeNode(fxMesa, range);
      return;
   }
   fxMesa->freeTexMem[tmu] += range->endAddr - range->startAddr;
   prev = 0;
   tmp = fxMesa->tmFree[tmu];
   while (tmp) {
      if (range->startAddr > tmp->startAddr) {
	 prev = tmp;
	 tmp = tmp->next;
      }
      else
	 break;
   }
   /* When we create the regions, we make a split at the 2MB boundary.
      Now we have to make sure we don't join those 2MB boundary regions
      back together again. */
   range->next = tmp;
   if (tmp) {
      if (range->endAddr == tmp->startAddr
	  && tmp->startAddr & texBoundMask) {
	 /* Combine */
	 tmp->startAddr = range->startAddr;
	 fxTMDeleteRangeNode(fxMesa, range);
	 range = tmp;
      }
   }
   if (prev) {
      if (prev->endAddr == range->startAddr
	  && range->startAddr & texBoundMask) {
	 /* Combine */
	 prev->endAddr = range->endAddr;
	 prev->next = range->next;
	 fxTMDeleteRangeNode(fxMesa, range);
      }
      else
	 prev->next = range;
   }
   else {
      fxMesa->tmFree[tmu] = range;
   }
}

static struct gl_texture_object *
fxTMFindOldestObject(fxMesaContext fxMesa, int tmu)
{
   GLuint age, old, lasttime, bindnumber;
   GLfloat lowestPriority;
   tfxTexInfo *info;
   struct gl_texture_object *obj, *tmp, *lowestPriorityObj;

   tmp = fxMesa->glCtx->Shared->TexObjectList;
   if (!tmp)
      return 0;
   obj = NULL;
   old = 0;

   lowestPriorityObj = NULL;
   lowestPriority = 1.0F;

   bindnumber = fxMesa->texBindNumber;
   while (tmp) {
      info = fxTMGetTexInfo(tmp);

      if (info && info->isInTM &&
	  ((info->whichTMU == tmu) ||
	   (info->whichTMU == FX_TMU_BOTH) ||
	   (info->whichTMU == FX_TMU_SPLIT) ||
	   fxMesa->HaveTexUma
	  )
	 ) {
	 lasttime = info->lastTimeUsed;

	 if (lasttime > bindnumber)
	    age = bindnumber + (UINT_MAX - lasttime + 1);	/* TO DO: check wrap around */
	 else
	    age = bindnumber - lasttime;

	 if (age >= old) {
	    old = age;
	    obj = tmp;
	 }

         /* examine priority */
         if (tmp->Priority < lowestPriority) {
            lowestPriority = tmp->Priority;
            lowestPriorityObj = tmp;
         }
      }
      tmp = tmp->Next;
   }

   if (lowestPriorityObj != NULL) {
       if (TDFX_DEBUG & VERBOSE_TEXTURE) {
          fprintf(stderr, "fxTMFindOldestObject: %d pri=%f\n", lowestPriorityObj->Name, lowestPriority);
       }
       return lowestPriorityObj;
   }
   else {
       if (TDFX_DEBUG & VERBOSE_TEXTURE) {
          if (obj != NULL) {
             fprintf(stderr, "fxTMFindOldestObject: %d age=%d\n", obj->Name, old);
          }
       }
       return obj;
   }
}

static MemRange *
fxTMAddObj(fxMesaContext fxMesa,
	   struct gl_texture_object *tObj, GLint tmu, int texmemsize)
{
   FxI32 startAddr;
   MemRange *range;

   startAddr = fxTMFindStartAddr(fxMesa, tmu, texmemsize);
   if (startAddr < 0)
      return 0;
   range = fxTMNewRangeNode(fxMesa, startAddr, startAddr + texmemsize);
   return range;
}

/* External Functions */

void
fxTMMoveInTM_NoLock(fxMesaContext fxMesa, struct gl_texture_object *tObj,
		    GLint where)
{
   tfxTexInfo *ti = fxTMGetTexInfo(tObj);
   int i, l;
   int texmemsize;

   if (TDFX_DEBUG & VERBOSE_DRIVER) {
      fprintf(stderr, "fxTMMoveInTM_NoLock(%d)\n", tObj->Name);
   }

   fxMesa->stats.reqTexUpload++;

   if (!ti->validated) {
      fprintf(stderr, "fxTMMoveInTM_NoLock: INTERNAL ERROR: not validated\n");
      fxCloseHardware();
      exit(-1);
   }

   if (ti->isInTM) {
      if (ti->whichTMU == where)
	 return;
      if (where == FX_TMU_SPLIT || ti->whichTMU == FX_TMU_SPLIT)
	 fxTMMoveOutTM_NoLock(fxMesa, tObj);
      else {
	 if (ti->whichTMU == FX_TMU_BOTH)
	    return;
	 where = FX_TMU_BOTH;
      }
   }

   if (TDFX_DEBUG & (VERBOSE_DRIVER | VERBOSE_TEXTURE)) {
      fprintf(stderr, "fxTMMoveInTM_NoLock: downloading %p (%d) in texture memory in %d\n",
	              (void *)tObj, tObj->Name, where);
   }

   ti->whichTMU = (FxU32) where;

   switch (where) {
   case FX_TMU0:
   case FX_TMU1:
      texmemsize = (int)grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH, &(ti->info));
      ti->tm[where] = fxTMAddObj(fxMesa, tObj, where, texmemsize);
      fxMesa->stats.memTexUpload += texmemsize;

      for (i = FX_largeLodValue(ti->info), l = ti->minLevel;
	   i <= FX_smallLodValue(ti->info); i++, l++) {
	 struct gl_texture_image *texImage = tObj->Image[0][l];
	 grTexDownloadMipMapLevel(where,
					    ti->tm[where]->startAddr,
					    FX_valueToLod(i),
					    FX_largeLodLog2(ti->info),
					    FX_aspectRatioLog2(ti->info),
					    ti->info.format,
					    GR_MIPMAPLEVELMASK_BOTH,
					    texImage->Data);
      }
      break;
   case FX_TMU_SPLIT:
      texmemsize = (int)grTexTextureMemRequired(GR_MIPMAPLEVELMASK_ODD, &(ti->info));
      ti->tm[FX_TMU0] = fxTMAddObj(fxMesa, tObj, FX_TMU0, texmemsize);
      fxMesa->stats.memTexUpload += texmemsize;

      texmemsize = (int)grTexTextureMemRequired(GR_MIPMAPLEVELMASK_EVEN, &(ti->info));
      ti->tm[FX_TMU1] = fxTMAddObj(fxMesa, tObj, FX_TMU1, texmemsize);
      fxMesa->stats.memTexUpload += texmemsize;

      for (i = FX_largeLodValue(ti->info), l = ti->minLevel;
	   i <= FX_smallLodValue(ti->info); i++, l++) {
	 struct gl_texture_image *texImage = tObj->Image[0][l];

	 grTexDownloadMipMapLevel(GR_TMU0,
					    ti->tm[FX_TMU0]->startAddr,
					    FX_valueToLod(i),
					    FX_largeLodLog2(ti->info),
					    FX_aspectRatioLog2(ti->info),
					    ti->info.format,
					    GR_MIPMAPLEVELMASK_ODD,
					    texImage->Data);

	 grTexDownloadMipMapLevel(GR_TMU1,
					    ti->tm[FX_TMU1]->startAddr,
					    FX_valueToLod(i),
					    FX_largeLodLog2(ti->info),
					    FX_aspectRatioLog2(ti->info),
					    ti->info.format,
					    GR_MIPMAPLEVELMASK_EVEN,
					    texImage->Data);
      }
      break;
   case FX_TMU_BOTH:
      texmemsize = (int)grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH, &(ti->info));
      ti->tm[FX_TMU0] = fxTMAddObj(fxMesa, tObj, FX_TMU0, texmemsize);
      fxMesa->stats.memTexUpload += texmemsize;

      /*texmemsize = (int)grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH, &(ti->info));*/
      ti->tm[FX_TMU1] = fxTMAddObj(fxMesa, tObj, FX_TMU1, texmemsize);
      fxMesa->stats.memTexUpload += texmemsize; /* ZZZ: required? */

      for (i = FX_largeLodValue(ti->info), l = ti->minLevel;
	   i <= FX_smallLodValue(ti->info); i++, l++) {
	 struct gl_texture_image *texImage = tObj->Image[0][l];
	 grTexDownloadMipMapLevel(GR_TMU0,
					    ti->tm[FX_TMU0]->startAddr,
					    FX_valueToLod(i),
					    FX_largeLodLog2(ti->info),
					    FX_aspectRatioLog2(ti->info),
					    ti->info.format,
					    GR_MIPMAPLEVELMASK_BOTH,
					    texImage->Data);

	 grTexDownloadMipMapLevel(GR_TMU1,
					    ti->tm[FX_TMU1]->startAddr,
					    FX_valueToLod(i),
					    FX_largeLodLog2(ti->info),
					    FX_aspectRatioLog2(ti->info),
					    ti->info.format,
					    GR_MIPMAPLEVELMASK_BOTH,
					    texImage->Data);
      }
      break;
   default:
      fprintf(stderr, "fxTMMoveInTM_NoLock: INTERNAL ERROR: wrong tmu (%d)\n", where);
      fxCloseHardware();
      exit(-1);
   }

   fxMesa->stats.texUpload++;

   ti->isInTM = GL_TRUE;
}


void
fxTMMoveInTM(fxMesaContext fxMesa, struct gl_texture_object *tObj,
	     GLint where)
{
   BEGIN_BOARD_LOCK();
   fxTMMoveInTM_NoLock(fxMesa, tObj, where);
   END_BOARD_LOCK();
}


void
fxTMReloadMipMapLevel(fxMesaContext fxMesa, struct gl_texture_object *tObj,
		      GLint level)
{
   tfxTexInfo *ti = fxTMGetTexInfo(tObj);
   GrLOD_t lodlevel;
   GLint tmu;
   struct gl_texture_image *texImage = tObj->Image[0][level];
   tfxMipMapLevel *mml = FX_MIPMAP_DATA(texImage);

   if (TDFX_DEBUG & VERBOSE_TEXTURE) {
      fprintf(stderr, "fxTMReloadMipMapLevel(%p (%d), %d)\n", (void *)tObj, tObj->Name, level);
   }

   assert(mml);
   assert(mml->width > 0);
   assert(mml->height > 0);
   assert(mml->glideFormat > 0);

   if (!ti->validated) {
      fprintf(stderr, "fxTMReloadMipMapLevel: INTERNAL ERROR: not validated\n");
      fxCloseHardware();
      exit(-1);
   }

   tmu = (int) ti->whichTMU;
#if 0
   /* [dBorca]
    * We get here by (see Tex[Sub]Image2D), thus we are in TMU.
    * Also, we just set the correct TMU above. fxTMMoveInTM will
    * bail early, so don't bother...
    */
   fxTMMoveInTM(fxMesa, tObj, tmu);
#else
   fxMesa->stats.reqTexUpload++;
   fxMesa->stats.texUpload++;
#endif

   lodlevel =  ti->info.largeLodLog2 - (level - ti->minLevel);

   switch (tmu) {
   case FX_TMU0:
   case FX_TMU1:
      grTexDownloadMipMapLevel(tmu,
				  ti->tm[tmu]->startAddr,
				  lodlevel,
				  FX_largeLodLog2(ti->info),
				  FX_aspectRatioLog2(ti->info),
				  ti->info.format,
				  GR_MIPMAPLEVELMASK_BOTH, texImage->Data);
      break;
   case FX_TMU_SPLIT:
      grTexDownloadMipMapLevel(GR_TMU0,
				  ti->tm[GR_TMU0]->startAddr,
				  lodlevel,
				  FX_largeLodLog2(ti->info),
				  FX_aspectRatioLog2(ti->info),
				  ti->info.format,
				  GR_MIPMAPLEVELMASK_ODD, texImage->Data);

      grTexDownloadMipMapLevel(GR_TMU1,
				  ti->tm[GR_TMU1]->startAddr,
				  lodlevel,
				  FX_largeLodLog2(ti->info),
				  FX_aspectRatioLog2(ti->info),
				  ti->info.format,
				  GR_MIPMAPLEVELMASK_EVEN, texImage->Data);
      break;
   case FX_TMU_BOTH:
      grTexDownloadMipMapLevel(GR_TMU0,
				  ti->tm[GR_TMU0]->startAddr,
				  lodlevel,
				  FX_largeLodLog2(ti->info),
				  FX_aspectRatioLog2(ti->info),
				  ti->info.format,
				  GR_MIPMAPLEVELMASK_BOTH, texImage->Data);

      grTexDownloadMipMapLevel(GR_TMU1,
				  ti->tm[GR_TMU1]->startAddr,
				  lodlevel,
				  FX_largeLodLog2(ti->info),
				  FX_aspectRatioLog2(ti->info),
				  ti->info.format,
				  GR_MIPMAPLEVELMASK_BOTH, texImage->Data);
      break;

   default:
      fprintf(stderr, "fxTMReloadMipMapLevel: INTERNAL ERROR: wrong tmu (%d)\n", tmu);
      fxCloseHardware();
      exit(-1);
   }
}

void
fxTMReloadSubMipMapLevel(fxMesaContext fxMesa,
			 struct gl_texture_object *tObj,
			 GLint level, GLint yoffset, GLint height)
{
   tfxTexInfo *ti = fxTMGetTexInfo(tObj);
   GrLOD_t lodlevel;
   unsigned short *data;
   GLint tmu;
   struct gl_texture_image *texImage = tObj->Image[0][level];
   tfxMipMapLevel *mml = FX_MIPMAP_DATA(texImage);

   assert(mml);

   if (!ti->validated) {
      fprintf(stderr, "fxTMReloadSubMipMapLevel: INTERNAL ERROR: not validated\n");
      fxCloseHardware();
      exit(-1);
   }

   tmu = (int) ti->whichTMU;
   fxTMMoveInTM(fxMesa, tObj, tmu);

   fxTexGetInfo(mml->width, mml->height,
		&lodlevel, NULL, NULL, NULL, NULL, NULL);

   if ((ti->info.format == GR_TEXFMT_INTENSITY_8) ||
       (ti->info.format == GR_TEXFMT_P_8) ||
       (ti->info.format == GR_TEXFMT_ALPHA_8))
	 data = (GLushort *) texImage->Data + ((yoffset * mml->width) >> 1);
   else
      data = (GLushort *) texImage->Data + yoffset * mml->width;

   switch (tmu) {
   case FX_TMU0:
   case FX_TMU1:
      grTexDownloadMipMapLevelPartial(tmu,
					 ti->tm[tmu]->startAddr,
					 FX_valueToLod(FX_lodToValue(lodlevel)
						       + level),
					 FX_largeLodLog2(ti->info),
					 FX_aspectRatioLog2(ti->info),
					 ti->info.format,
					 GR_MIPMAPLEVELMASK_BOTH, data,
					 yoffset, yoffset + height - 1);
      break;
   case FX_TMU_SPLIT:
      grTexDownloadMipMapLevelPartial(GR_TMU0,
					 ti->tm[FX_TMU0]->startAddr,
					 FX_valueToLod(FX_lodToValue(lodlevel)
						       + level),
					 FX_largeLodLog2(ti->info),
					 FX_aspectRatioLog2(ti->info),
					 ti->info.format,
					 GR_MIPMAPLEVELMASK_ODD, data,
					 yoffset, yoffset + height - 1);

      grTexDownloadMipMapLevelPartial(GR_TMU1,
					 ti->tm[FX_TMU1]->startAddr,
					 FX_valueToLod(FX_lodToValue(lodlevel)
						       + level),
					 FX_largeLodLog2(ti->info),
					 FX_aspectRatioLog2(ti->info),
					 ti->info.format,
					 GR_MIPMAPLEVELMASK_EVEN, data,
					 yoffset, yoffset + height - 1);
      break;
   case FX_TMU_BOTH:
      grTexDownloadMipMapLevelPartial(GR_TMU0,
					 ti->tm[FX_TMU0]->startAddr,
					 FX_valueToLod(FX_lodToValue(lodlevel)
						       + level),
					 FX_largeLodLog2(ti->info),
					 FX_aspectRatioLog2(ti->info),
					 ti->info.format,
					 GR_MIPMAPLEVELMASK_BOTH, data,
					 yoffset, yoffset + height - 1);

      grTexDownloadMipMapLevelPartial(GR_TMU1,
					 ti->tm[FX_TMU1]->startAddr,
					 FX_valueToLod(FX_lodToValue(lodlevel)
						       + level),
					 FX_largeLodLog2(ti->info),
					 FX_aspectRatioLog2(ti->info),
					 ti->info.format,
					 GR_MIPMAPLEVELMASK_BOTH, data,
					 yoffset, yoffset + height - 1);
      break;
   default:
      fprintf(stderr, "fxTMReloadSubMipMapLevel: INTERNAL ERROR: wrong tmu (%d)\n", tmu);
      fxCloseHardware();
      exit(-1);
   }
}

void
fxTMMoveOutTM(fxMesaContext fxMesa, struct gl_texture_object *tObj)
{
   tfxTexInfo *ti = fxTMGetTexInfo(tObj);

   if (TDFX_DEBUG & VERBOSE_DRIVER) {
      fprintf(stderr, "fxTMMoveOutTM(%p (%d))\n", (void *)tObj, tObj->Name);
   }

   if (!ti->isInTM)
      return;

   switch (ti->whichTMU) {
   case FX_TMU0:
   case FX_TMU1:
      fxTMRemoveRange(fxMesa, (int) ti->whichTMU, ti->tm[ti->whichTMU]);
      break;
   case FX_TMU_SPLIT:
   case FX_TMU_BOTH:
      fxTMRemoveRange(fxMesa, FX_TMU0, ti->tm[FX_TMU0]);
      fxTMRemoveRange(fxMesa, FX_TMU1, ti->tm[FX_TMU1]);
      break;
   default:
      fprintf(stderr, "fxTMMoveOutTM: INTERNAL ERROR: bad TMU (%ld)\n", ti->whichTMU);
      fxCloseHardware();
      exit(-1);
   }

   ti->isInTM = GL_FALSE;
   ti->whichTMU = FX_TMU_NONE;
}

void
fxTMFreeTexture(fxMesaContext fxMesa, struct gl_texture_object *tObj)
{
   tfxTexInfo *ti = fxTMGetTexInfo(tObj);
   int i;

   if (TDFX_DEBUG & VERBOSE_TEXTURE) {
      fprintf(stderr, "fxTMFreeTexture(%p (%d))\n", (void *)tObj, tObj->Name);
   }

   fxTMMoveOutTM(fxMesa, tObj);

   for (i = 0; i < MAX_TEXTURE_LEVELS; i++) {
      struct gl_texture_image *texImage = tObj->Image[0][i];
      if (texImage) {
         if (texImage->DriverData) {
            FREE(texImage->DriverData);
            texImage->DriverData = NULL;
         }
      }
   }
   switch (ti->whichTMU) {
   case FX_TMU0:
   case FX_TMU1:
      fxTMDeleteRangeNode(fxMesa, ti->tm[ti->whichTMU]);
      break;
   case FX_TMU_SPLIT:
   case FX_TMU_BOTH:
      fxTMDeleteRangeNode(fxMesa, ti->tm[FX_TMU0]);
      fxTMDeleteRangeNode(fxMesa, ti->tm[FX_TMU1]);
      break;
   }
}

void
fxTMInit(fxMesaContext fxMesa)
{
   fxMesa->texBindNumber = 0;
   fxMesa->tmPool = 0;

   if (fxMesa->HaveTexUma) {
      grEnable(GR_TEXTURE_UMA_EXT);
   }

   fxTMUInit(fxMesa, FX_TMU0);

   if (!fxMesa->HaveTexUma && fxMesa->haveTwoTMUs)
      fxTMUInit(fxMesa, FX_TMU1);

   texBoundMask = (fxMesa->type >= GR_SSTTYPE_Banshee) ? -1 : (FX_2MB_SPLIT - 1);
}

void
fxTMClose(fxMesaContext fxMesa)
{
   MemRange *tmp, *next;

   tmp = fxMesa->tmPool;
   while (tmp) {
      next = tmp->next;
      FREE(tmp);
      tmp = next;
   }
   tmp = fxMesa->tmFree[FX_TMU0];
   while (tmp) {
      next = tmp->next;
      FREE(tmp);
      tmp = next;
   }
   if (fxMesa->haveTwoTMUs) {
      tmp = fxMesa->tmFree[FX_TMU1];
      while (tmp) {
	 next = tmp->next;
	 FREE(tmp);
	 tmp = next;
      }
   }
}

void
fxTMRestoreTextures_NoLock(fxMesaContext ctx)
{
   tfxTexInfo *ti;
   struct gl_texture_object *tObj;
   int i, where;

   tObj = ctx->glCtx->Shared->TexObjectList;
   while (tObj) {
      ti = fxTMGetTexInfo(tObj);
      if (ti && ti->isInTM) {
	 for (i = 0; i < MAX_TEXTURE_UNITS; i++)
	    if (ctx->glCtx->Texture.Unit[i]._Current == tObj) {
	       /* Force the texture onto the board, as it could be in use */
	       where = ti->whichTMU;
	       fxTMMoveOutTM_NoLock(ctx, tObj);
	       fxTMMoveInTM_NoLock(ctx, tObj, where);
	       break;
	    }
	 if (i == MAX_TEXTURE_UNITS)	/* Mark the texture as off the board */
	    fxTMMoveOutTM_NoLock(ctx, tObj);
      }
      tObj = tObj->Next;
   }
}

#else


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

extern int gl_fx_dummy_function_texman(void);
int
gl_fx_dummy_function_texman(void)
{
   return 0;
}

#endif /* FX */
