texmem.c   [plain text]


/*
 * Copyright 2000-2001 VA Linux Systems, Inc.
 * (C) Copyright IBM Corporation 2002, 2003
 * 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
 * on 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
 * VA LINUX SYSTEM, IBM AND/OR THEIR 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.
 *
 * Authors:
 *    Ian Romanick <idr@us.ibm.com>
 *    Keith Whitwell <keithw@tungstengraphics.com>
 *    Kevin E. Martin <kem@users.sourceforge.net>
 *    Gareth Hughes <gareth@nvidia.com>
 */
/* $XFree86: xc/lib/GL/mesa/src/drv/common/texmem.c,v 1.3 2003/12/02 13:02:37 alanh Exp $ */

/** \file texmem.c
 * Implements all of the device-independent texture memory management.
 * 
 * Currently, only a simple LRU texture memory management policy is
 * implemented.  In the (hopefully very near) future, better policies will be
 * implemented.  The idea is that the DRI should be able to run in one of two
 * modes.  In the default mode the DRI will dynamically attempt to discover
 * the best texture management policy for the running application.  In the
 * other mode, the user (via some sort of as yet TBD mechanism) will select
 * a texture management policy that is known to work well with the
 * application.
 */

#include "texmem.h"
#include "simple_list.h"
#include "imports.h"
#include "macros.h"

#include <assert.h>



static unsigned dummy_swap_counter;


/**
 * Calculate \f$\log_2\f$ of a value.  This is a particularly poor
 * implementation of this function.  However, since system performance is in
 * no way dependent on this function, the slowness of the implementation is
 * irrelevent.
 * 
 * \param n Value whose \f$\log_2\f$ is to be calculated
 */

static unsigned
driLog2( unsigned n )
{
   unsigned   log2;


   for ( log2 = 1 ; n > 1 ; log2++ ) {
      n >>= 1;
   }

   return log2;
}




/**
 * Determine if a texture is resident in textureable memory.  Depending on
 * the driver, this may or may not be on-card memory.  It could be AGP memory
 * or anyother type of memory from which the hardware can directly read
 * texels.
 * 
 * This function is intended to be used as the \c IsTextureResident function
 * in the device's \c dd_function_table.
 * 
 * \param ctx GL context pointer (currently unused)
 * \param texObj Texture object to be tested
 */

GLboolean
driIsTextureResident( GLcontext * ctx, 
		      struct gl_texture_object * texObj )
{
   driTextureObject * t;


   t = (driTextureObject *) texObj->DriverData;
   return( (t != NULL) && (t->memBlock != NULL) );
}




/**
 * (Re)initialize the global circular LRU list.  The last element
 * in the array (\a heap->nrRegions) is the sentinal.  Keeping it
 * at the end of the array allows the other elements of the array
 * to be addressed rationally when looking up objects at a particular
 * location in texture memory.
 * 
 * \param heap Texture heap to be reset
 */

static void resetGlobalLRU( driTexHeap * heap )
{
   drmTextureRegionPtr list = heap->global_regions;
   unsigned       sz = 1U << heap->logGranularity;
   unsigned       i;

   for (i = 0 ; (i+1) * sz <= heap->size ; i++) {
      list[i].prev = i-1;
      list[i].next = i+1;
      list[i].age = 0;
   }

   i--;
   list[0].prev = heap->nrRegions;
   list[i].prev = i-1;
   list[i].next = heap->nrRegions;
   list[heap->nrRegions].prev = i;
   list[heap->nrRegions].next = 0;
   heap->global_age[0] = 0;
}

/**
 * Print out debugging information about the local texture LRU.
 *
 * \param heap Texture heap to be printed
 * \param callername Name of calling function
 */
static void printLocalLRU( driTexHeap * heap, const char *callername  )
{
   driTextureObject *t;
   unsigned sz = 1U << heap->logGranularity;

   fprintf( stderr, "%s in %s:\nLocal LRU, heap %d:\n", 
	    __FUNCTION__, callername, heap->heapId );

   foreach ( t, &heap->texture_objects ) {
      if (!t->memBlock)
	 continue;
      if (!t->tObj) {
	 fprintf( stderr, "Placeholder (%p) %d at 0x%x sz 0x%x\n",
		  (void *)t,
		  t->memBlock->ofs / sz,
		  t->memBlock->ofs,
		  t->memBlock->size );
      } else {
	 fprintf( stderr, "Texture (%p) at 0x%x sz 0x%x\n",
		  (void *)t,
		  t->memBlock->ofs,
		  t->memBlock->size );
      }
   }
   foreach ( t, heap->swapped_objects ) {
      if (!t->tObj) {
	 fprintf( stderr, "Swapped Placeholder (%p)\n", (void *)t );
      } else {
	 fprintf( stderr, "Swapped Texture (%p)\n", (void *)t );
      }
   }

   fprintf( stderr, "\n" );
}

/**
 * Print out debugging information about the global texture LRU.
 *
 * \param heap Texture heap to be printed
 * \param callername Name of calling function
 */
static void printGlobalLRU( driTexHeap * heap, const char *callername )
{
   drmTextureRegionPtr list = heap->global_regions;
   int i, j;

   fprintf( stderr, "%s in %s:\nGlobal LRU, heap %d list %p:\n", 
	    __FUNCTION__, callername, heap->heapId, (void *)list );

   for ( i = 0, j = heap->nrRegions ; i < heap->nrRegions ; i++ ) {
      fprintf( stderr, "list[%d] age %d next %d prev %d in_use %d\n",
	       j, list[j].age, list[j].next, list[j].prev, list[j].in_use );
      j = list[j].next;
      if ( j == heap->nrRegions ) break;
   }

   if ( j != heap->nrRegions ) {
      fprintf( stderr, "Loop detected in global LRU\n" );
      for ( i = 0 ; i < heap->nrRegions ; i++ ) {
	 fprintf( stderr, "list[%d] age %d next %d prev %d in_use %d\n",
		  i, list[i].age, list[i].next, list[i].prev, list[i].in_use );
      }
   }

   fprintf( stderr, "\n" );
}


/**
 * Called by the client whenever it touches a local texture.
 * 
 * \param t Texture object that the client has accessed
 */

void driUpdateTextureLRU( driTextureObject * t )
{
   driTexHeap   * heap;
   drmTextureRegionPtr list;
   unsigned   shift;
   unsigned   start;
   unsigned   end;
   unsigned   i;


   heap = t->heap;
   if ( heap != NULL ) {
      shift = heap->logGranularity;
      start = t->memBlock->ofs >> shift;
      end = (t->memBlock->ofs + t->memBlock->size - 1) >> shift;


      heap->local_age = ++heap->global_age[0];
      list = heap->global_regions;


      /* Update the context's local LRU 
       */

      move_to_head( & heap->texture_objects, t );


      for (i = start ; i <= end ; i++) {
	 list[i].in_use = 1;
	 list[i].age = heap->local_age;

	 /* remove_from_list(i)
	  */
	 list[(unsigned)list[i].next].prev = list[i].prev;
	 list[(unsigned)list[i].prev].next = list[i].next;

	 /* insert_at_head(list, i)
	  */
	 list[i].prev = heap->nrRegions;
	 list[i].next = list[heap->nrRegions].next;
	 list[(unsigned)list[heap->nrRegions].next].prev = i;
	 list[heap->nrRegions].next = i;
      }

      if ( 0 ) {
	 printGlobalLRU( heap, __FUNCTION__ );
	 printLocalLRU( heap, __FUNCTION__ );
      }
   }
}




/**
 * Keep track of swapped out texture objects.
 * 
 * \param t Texture object to be "swapped" out of its texture heap
 */

void driSwapOutTextureObject( driTextureObject * t )
{
   unsigned   face;


   if ( t->memBlock != NULL ) {
      assert( t->heap != NULL );
      mmFreeMem( t->memBlock );
      t->memBlock = NULL;

      if (t->timestamp > t->heap->timestamp)
	 t->heap->timestamp = t->timestamp;

      t->heap->texture_swaps[0]++;
      move_to_tail( t->heap->swapped_objects, t );
      t->heap = NULL;
   }
   else {
      assert( t->heap == NULL );
   }


   for ( face = 0 ; face < 6 ; face++ ) {
      t->dirty_images[face] = ~0;
   }
}




/**
 * Destroy hardware state associated with texture \a t.  Calls the
 * \a destroy_texture_object method associated with the heap from which
 * \a t was allocated.
 * 
 * \param t Texture object to be destroyed
 */

void driDestroyTextureObject( driTextureObject * t )
{
   driTexHeap * heap;


   if ( 0 ) {
      fprintf( stderr, "[%s:%d] freeing %p (tObj = %p, DriverData = %p)\n",
	       __FILE__, __LINE__,
	       (void *)t,
	       (void *)((t != NULL) ? t->tObj : NULL),
	       (void *)((t != NULL && t->tObj != NULL) ? t->tObj->DriverData : NULL ));
   }

   if ( t != NULL ) {
      if ( t->memBlock ) {
	 heap = t->heap;
	 assert( heap != NULL );

	 heap->texture_swaps[0]++;

	 mmFreeMem( t->memBlock );
	 t->memBlock = NULL;

	 if (t->timestamp > t->heap->timestamp)
	    t->heap->timestamp = t->timestamp;

	 heap->destroy_texture_object( heap->driverContext, t );
	 t->heap = NULL;
      }

      if ( t->tObj != NULL ) {
	 assert( t->tObj->DriverData == t );
	 t->tObj->DriverData = NULL;
      }

      remove_from_list( t );
      FREE( t );
   }

   if ( 0 ) {
      fprintf( stderr, "[%s:%d] done freeing %p\n", __FILE__, __LINE__, (void *)t );
   }
}




/**
 * Update the local heap's representation of texture memory based on
 * data in the SAREA.  This is done each time it is detected that some other
 * direct rendering client has held the lock.  This pertains to both our local
 * textures and the textures belonging to other clients.  Keep track of other
 * client's textures by pushing a placeholder texture onto the LRU list --
 * these are denoted by \a tObj being \a NULL.
 * 
 * \param heap Heap whose state is to be updated
 * \param offset Byte offset in the heap that has been stolen
 * \param size Size, in bytes, of the stolen block
 * \param in_use Non-zero if the block is in-use by another context
 */

static void driTexturesGone( driTexHeap * heap, int offset, int size, 
			     int in_use )
{
   driTextureObject * t;
   driTextureObject * tmp;


   foreach_s ( t, tmp, & heap->texture_objects ) {
      if ( (t->memBlock->ofs < (offset + size))
	   && ((t->memBlock->ofs + t->memBlock->size) > offset) ) {
	 /* It overlaps - kick it out.  If the texture object is just a
	  * place holder, then destroy it all together.  Otherwise, mark
	  * it as being swapped out.
	  */

	 if ( t->tObj != NULL ) {
	    driSwapOutTextureObject( t );
	 }
	 else {
	    if ( in_use && 
		 offset == t->memBlock->ofs && size == t->memBlock->size ) {
	       /* Matching placeholder already exists */
	       return;
	    } else {
	       driDestroyTextureObject( t );
	    }
	 }
      }
   }


   if ( in_use ) {
      t = (driTextureObject *) CALLOC( heap->texture_object_size );
      if ( t == NULL ) return;

      t->memBlock = mmAllocMem( heap->memory_heap, size, 0, offset );
      if ( t->memBlock == NULL ) {
	 fprintf( stderr, "Couldn't alloc placeholder: heap %u sz %x ofs %x\n", heap->heapId,
		  (int)size, (int)offset );
	 mmDumpMemInfo( heap->memory_heap );
	 return;
      }
      t->heap = heap;
      insert_at_head( & heap->texture_objects, t );
   }
}




/**
 * Called by the client on lock contention to determine whether textures have
 * been stolen.  If another client has modified a region in which we have
 * textures, then we need to figure out which of our textures have been
 * removed and update our global LRU.
 * 
 * \param heap Texture heap to be updated
 */

void driAgeTextures( driTexHeap * heap )
{
   drmTextureRegionPtr list = heap->global_regions;
   unsigned       sz = 1U << (heap->logGranularity);
   unsigned       i, nr = 0;


   /* Have to go right round from the back to ensure stuff ends up
    * LRU in the local list...  Fix with a cursor pointer.
    */

   for (i = list[heap->nrRegions].prev ; 
	i != heap->nrRegions && nr < heap->nrRegions ; 
	i = list[i].prev, nr++) {
      /* If switching texturing schemes, then the SAREA might not have been
       * properly cleared, so we need to reset the global texture LRU.
       */

      if ( (i * sz) > heap->size ) {
	 nr = heap->nrRegions;
	 break;
      }

      if (list[i].age > heap->local_age) 
	  driTexturesGone( heap, i * sz, sz, list[i].in_use); 
   }

   /* Loop or uninitialized heap detected.  Reset.
    */

   if (nr == heap->nrRegions) {
      driTexturesGone( heap, 0, heap->size, 0);
      resetGlobalLRU( heap );
   }

   if ( 0 ) {
      printGlobalLRU( heap, __FUNCTION__ );
      printLocalLRU( heap, __FUNCTION__ );
   }

   heap->local_age = heap->global_age[0];
}




/**
 * Allocate memory from a texture heap to hold a texture object.  This
 * routine will attempt to allocate memory for the texture from the heaps
 * specified by \c heap_array in order.  That is, first it will try to
 * allocate from \c heap_array[0], then \c heap_array[1], and so on.
 *
 * \param heap_array Array of pointers to texture heaps to use
 * \param nr_heaps Number of heap pointer in \a heap_array
 * \param t Texture object for which space is needed
 * \return The ID of the heap from which memory was allocated, or -1 if
 *         memory could not be allocated.
 *
 * \bug The replacement policy implemented by this function is horrible.
 */


int
driAllocateTexture( driTexHeap * const * heap_array, unsigned nr_heaps,
		    driTextureObject * t )
{
   driTexHeap       * heap;
   driTextureObject * temp;
   driTextureObject * cursor;
   unsigned           id;


   /* In case it already has texture space, initialize heap.  This also
    * prevents GCC from issuing a warning that heap might be used
    * uninitialized.
    */

   heap = t->heap;


   /* Run through each of the existing heaps and try to allocate a buffer
    * to hold the texture.
    */

   for ( id = 0 ; (t->memBlock == NULL) && (id < nr_heaps) ; id++ ) {
      heap = heap_array[ id ];
      if ( heap != NULL ) {
	 t->memBlock = mmAllocMem( heap->memory_heap, t->totalSize, 
				   heap->alignmentShift, 0 );
      }
   }


   /* Kick textures out until the requested texture fits.
    */

   if ( t->memBlock == NULL ) {
      for ( id = 0 ; (t->memBlock == NULL) && (id < nr_heaps) ; id++ ) {
	 heap = heap_array[ id ];
	 if ( t->totalSize <= heap->size ) { 

	    for ( cursor = heap->texture_objects.prev, temp = cursor->prev;
		  cursor != &heap->texture_objects ; 
		  cursor = temp, temp = cursor->prev ) {
	       
	       /* The the LRU element.  If the texture is bound to one of
		* the texture units, then we cannot kick it out.
		*/
	       if ( cursor->bound /* || cursor->reserved */ ) {
		  continue;
	       }

	       /* If this is a placeholder, there's no need to keep it */
	       if (cursor->tObj)
		   driSwapOutTextureObject( cursor );
	       else
		   driDestroyTextureObject( cursor );

	       t->memBlock = mmAllocMem( heap->memory_heap, t->totalSize, 
					 heap->alignmentShift, 0 );

	       if (t->memBlock)
		  break;
	    }
	 }     /* if ( t->totalSize <= heap->size ) ... */
      }
   }


   if ( t->memBlock != NULL ) {
      /* id and heap->heapId may or may not be the same value here.
       */

      assert( heap != NULL );
      assert( (t->heap == NULL) || (t->heap == heap) );

      t->heap = heap;
      return heap->heapId;
   }
   else {
      assert( t->heap == NULL );

      fprintf( stderr, "[%s:%d] unable to allocate texture\n",
	       __FUNCTION__, __LINE__ );
      return -1;
   }
}






/**
 * Set the location where the texture-swap counter is stored.
 */

void
driSetTextureSwapCounterLocation( driTexHeap * heap, unsigned * counter )
{
   heap->texture_swaps = (counter == NULL) ? & dummy_swap_counter : counter;
}




/**
 * Create a new heap for texture data.
 * 
 * \param heap_id             Device-dependent heap identifier.  This value
 *                            will returned by driAllocateTexture when memory
 *                            is allocated from this heap.
 * \param context             Device-dependent driver context.  This is
 *                            supplied as the first parameter to the
 *                            \c destroy_tex_obj function.
 * \param size                Size, in bytes, of the texture region
 * \param alignmentShift      Alignment requirement for textures.  If textures 
 *                            must be allocated on a 4096 byte boundry, this
 *                            would be 12.
 * \param nr_regions          Number of regions into which this texture space
 *                            should be partitioned
 * \param global_regions      Array of \c drmTextureRegion structures in the SAREA
 * \param global_age          Pointer to the global texture age in the SAREA
 * \param swapped_objects     Pointer to the list of texture objects that are
 *                            not in texture memory (i.e., have been swapped
 *                            out).
 * \param texture_object_size Size, in bytes, of a device-dependent texture
 *                            object
 * \param destroy_tex_obj     Function used to destroy a device-dependent
 *                            texture object
 *
 * \sa driDestroyTextureHeap
 */

driTexHeap *
driCreateTextureHeap( unsigned heap_id, void * context, unsigned size,
		      unsigned alignmentShift, unsigned nr_regions,
		      drmTextureRegionPtr global_regions, unsigned * global_age,
		      driTextureObject * swapped_objects, 
		      unsigned texture_object_size,
		      destroy_texture_object_t * destroy_tex_obj
		    )
{
   driTexHeap * heap;
   unsigned     l;
    
    
   if ( 0 )
       fprintf( stderr, "%s( %u, %p, %u, %u, %u )\n",
		__FUNCTION__,
		heap_id, (void *)context, size, alignmentShift, nr_regions );

   heap = (driTexHeap *) CALLOC( sizeof( driTexHeap ) );
   if ( heap != NULL ) {
      l = driLog2( (size - 1) / nr_regions );
      if ( l < alignmentShift )
      {
	 l = alignmentShift;
      }

      heap->logGranularity = l;
      heap->size = size & ~((1L << l) - 1);

      heap->memory_heap = mmInit( 0, heap->size );
      if ( heap->memory_heap != NULL ) {
	 heap->heapId = heap_id;
	 heap->driverContext = context;

	 heap->alignmentShift = alignmentShift;
	 heap->nrRegions = nr_regions;
	 heap->global_regions = global_regions;
	 heap->global_age = global_age;
	 heap->swapped_objects = swapped_objects;
	 heap->texture_object_size = texture_object_size;
	 heap->destroy_texture_object = destroy_tex_obj;

	 /* Force global heap init */
	 if (heap->global_age == 0)
	     heap->local_age = ~0;
	 else
	     heap->local_age = 0;

	 make_empty_list( & heap->texture_objects );
	 driSetTextureSwapCounterLocation( heap, NULL );
      }
      else {
	 FREE( heap );
	 heap = NULL;
      }
   }


   if ( 0 )
       fprintf( stderr, "%s returning %p\n", __FUNCTION__, (void *)heap );

   return heap;
}




/** Destroys a texture heap
 * 
 * \param heap Texture heap to be destroyed
 */

void
driDestroyTextureHeap( driTexHeap * heap )
{
   driTextureObject * t;
   driTextureObject * temp;


   if ( heap != NULL ) {
      foreach_s( t, temp, & heap->texture_objects ) {
	 driDestroyTextureObject( t );
      }
      foreach_s( t, temp, heap->swapped_objects ) {
	 driDestroyTextureObject( t );
      }

      mmDestroy( heap->memory_heap );
      FREE( heap );
   }
}




/****************************************************************************/
/**
 * Determine how many texels (including all mipmap levels) would be required
 * for a texture map of size \f$2^^\c base_size_log2\f$ would require.
 *
 * \param base_size_log2 \f$log_2\f$ of the size of a side of the texture
 * \param dimensions Number of dimensions of the texture.  Either 2 or 3.
 * \param faces Number of faces of the texture.  Either 1 or 6 (for cube maps).
 * \return Number of texels
 */

static unsigned
texels_this_map_size( int base_size_log2, unsigned dimensions, unsigned faces )
{
   unsigned  texels;


   assert( (faces == 1) || (faces == 6) );
   assert( (dimensions == 2) || (dimensions == 3) );

   texels = 0;
   if ( base_size_log2 >= 0 ) {
      texels = (1U << (dimensions * base_size_log2));

      /* See http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg03636.html
       * for the complete explaination of why this formulation is used.
       * Basically, the smaller mipmap levels sum to 0.333 the size of the
       * level 0 map.  The total size is therefore the size of the map
       * multipled by 1.333.  The +2 is there to round up.
       */

      texels = (texels * 4 * faces + 2) / 3;
   }

   return texels;
}




struct maps_per_heap {
   unsigned  c[32];
};

static void
fill_in_maximums( driTexHeap * const * heaps, unsigned nr_heaps,
		  unsigned max_bytes_per_texel, unsigned max_size,
		  unsigned mipmaps_at_once, unsigned dimensions,
		  unsigned faces, struct maps_per_heap * max_textures )
{
   unsigned   heap;
   unsigned   log2_size;
   unsigned   mask;


   /* Determine how many textures of each size can be stored in each
    * texture heap.
    */

   for ( heap = 0 ; heap < nr_heaps ; heap++ ) {
      if ( heaps[ heap ] == NULL ) {
	 (void) memset( max_textures[ heap ].c, 0, 
			sizeof( max_textures[ heap ].c ) );
	 continue;
      }

      mask = (1U << heaps[ heap ]->logGranularity) - 1;

      if ( 0 ) {
	 fprintf( stderr, "[%s:%d] heap[%u] = %u bytes, mask = 0x%08x\n",
		  __FILE__, __LINE__,
		  heap, heaps[ heap ]->size, mask );
      }

      for ( log2_size = max_size ; log2_size > 0 ; log2_size-- ) {
	 unsigned   total;


	 /* Determine the total number of bytes required by a texture of
	  * size log2_size.
	  */

	 total = texels_this_map_size( log2_size, dimensions, faces )
	     - texels_this_map_size( log2_size - mipmaps_at_once,
				     dimensions, faces );
	 total *= max_bytes_per_texel;
	 total = (total + mask) & ~mask;

	 /* The number of textures of a given size that will fit in a heap
	  * is equal to the size of the heap divided by the size of the
	  * texture.
	  */

	 max_textures[ heap ].c[ log2_size ] = heaps[ heap ]->size / total;

	 if ( 0 ) {
	    fprintf( stderr, "[%s:%d] max_textures[%u].c[%02u] "
		     "= 0x%08x / 0x%08x "
		     "= %u (%u)\n",
		     __FILE__, __LINE__,
		     heap, log2_size,
		     heaps[ heap ]->size, total,
		     heaps[ heap ]->size / total,
		     max_textures[ heap ].c[ log2_size ] );
	 }
      }
   }
}


static unsigned
get_max_size( unsigned nr_heaps,
	      unsigned texture_units,
	      unsigned max_size,
	      int all_textures_one_heap,
	      struct maps_per_heap * max_textures )
{
   unsigned   heap;
   unsigned   log2_size;


   /* Determine the largest texture size such that a texture of that size
    * can be bound to each texture unit at the same time.  Some hardware
    * may require that all textures be in the same texture heap for
    * multitexturing.
    */

   for ( log2_size = max_size ; log2_size > 0 ; log2_size-- ) {
      unsigned   total = 0;

      for ( heap = 0 ; heap < nr_heaps ; heap++ )
      {
	 total += max_textures[ heap ].c[ log2_size ];

	 if ( 0 ) {
	    fprintf( stderr, "[%s:%d] max_textures[%u].c[%02u] = %u, "
		     "total = %u\n", __FILE__, __LINE__, heap, log2_size,
		     max_textures[ heap ].c[ log2_size ], total );
	 }

	 if ( (max_textures[ heap ].c[ log2_size ] >= texture_units)
	      || (!all_textures_one_heap && (total >= texture_units)) ) {
	    /* The number of mipmap levels is the log-base-2 of the
	     * maximum texture size plus 1.  If the maximum texture size
	     * is 1x1, the log-base-2 is 0 and 1 mipmap level (the base
	     * level) is available.
	     */

	    return log2_size + 1;
	 }
      }
   }

   /* This should NEVER happen.  It should always be possible to have at
    * *least* a 1x1 texture in memory!
    */
   assert( log2_size != 0 );
   return 0;
}

#define SET_MAX(f,v) \
    do { if ( max_sizes[v] != 0 ) { limits-> f = max_sizes[v]; } } while( 0 )

#define SET_MAX_RECT(f,v) \
    do { if ( max_sizes[v] != 0 ) { limits-> f = 1 << max_sizes[v]; } } while( 0 )


/**
 * Given the amount of texture memory, the number of texture units, and the
 * maximum size of a texel, calculate the maximum texture size the driver can
 * advertise.
 * 
 * \param heaps Texture heaps for this card
 * \param nr_heap Number of texture heaps
 * \param limits OpenGL contants.  MaxTextureUnits must be set.
 * \param max_bytes_per_texel Maximum size of a single texel, in bytes
 * \param max_2D_size \f$\log_2\f$ of the maximum 2D texture size (i.e.,
 *     1024x1024 textures, this would be 10)
 * \param max_3D_size \f$\log_2\f$ of the maximum 3D texture size (i.e.,
 *     1024x1024x1024 textures, this would be 10)
 * \param max_cube_size \f$\log_2\f$ of the maximum cube texture size (i.e.,
 *     1024x1024 textures, this would be 10)
 * \param max_rect_size \f$\log_2\f$ of the maximum texture rectangle size
 *     (i.e., 1024x1024 textures, this would be 10).  This is a power-of-2
 *     even though texture rectangles need not be a power-of-2.
 * \param mipmaps_at_once Total number of mipmaps that can be used
 *     at one time.  For most hardware this will be \f$\c max_size + 1\f$.
 *     For hardware that does not support mipmapping, this will be 1.
 * \param all_textures_one_heap True if the hardware requires that all
 *     textures be in a single texture heap for multitexturing.
 */

void
driCalculateMaxTextureLevels( driTexHeap * const * heaps,
			      unsigned nr_heaps,
			      struct gl_constants * limits,
			      unsigned max_bytes_per_texel, 
			      unsigned max_2D_size,
			      unsigned max_3D_size,
			      unsigned max_cube_size,
			      unsigned max_rect_size,
			      unsigned mipmaps_at_once,
			      int all_textures_one_heap )
{
   struct maps_per_heap  max_textures[8];
   unsigned         i;
   const unsigned   dimensions[4] = { 2, 3, 2, 2 };
   const unsigned   faces[4]      = { 1, 1, 6, 1 };
   unsigned         max_sizes[4];
   unsigned         mipmaps[4];


   max_sizes[0] = max_2D_size;
   max_sizes[1] = max_3D_size;
   max_sizes[2] = max_cube_size;
   max_sizes[3] = max_rect_size;

   mipmaps[0] = mipmaps_at_once;
   mipmaps[1] = mipmaps_at_once;
   mipmaps[2] = 1;
   mipmaps[3] = mipmaps_at_once;


   /* Calculate the maximum number of texture levels in two passes.  The
    * first pass determines how many textures of each power-of-two size
    * (including all mipmap levels for that size) can fit in each texture
    * heap.  The second pass finds the largest texture size that allows
    * a texture of that size to be bound to every texture unit.
    */

   for ( i = 0 ; i < 4 ; i++ ) {
      if ( max_sizes[ i ] != 0 ) {
	 fill_in_maximums( heaps, nr_heaps, max_bytes_per_texel, 
			   max_sizes[ i ], mipmaps[ i ],
			   dimensions[ i ], faces[ i ],
			   max_textures );

	 max_sizes[ i ] = get_max_size( nr_heaps, 
					limits->MaxTextureUnits,
					max_sizes[ i ],
					all_textures_one_heap,
					max_textures );
      }
   }

   SET_MAX( MaxTextureLevels,        0 );
   SET_MAX( Max3DTextureLevels,      1 );
   SET_MAX( MaxCubeTextureLevels,    2 );
   SET_MAX_RECT( MaxTextureRectSize, 3 );
}




/**
 * Perform initial binding of default textures objects on a per unit, per
 * texture target basis.
 *
 * \param ctx Current OpenGL context
 * \param swapped List of swapped-out textures
 * \param targets Bit-mask of value texture targets
 */

void driInitTextureObjects( GLcontext *ctx, driTextureObject * swapped,
			    GLuint targets )
{
   struct gl_texture_object *texObj;
   GLuint tmp = ctx->Texture.CurrentUnit;
   unsigned   i;


   for ( i = 0 ; i < ctx->Const.MaxTextureUnits ; i++ ) {
      ctx->Texture.CurrentUnit = i;

      if ( (targets & DRI_TEXMGR_DO_TEXTURE_1D) != 0 ) {
	 texObj = ctx->Texture.Unit[i].Current1D;
	 ctx->Driver.BindTexture( ctx, GL_TEXTURE_1D, texObj );
	 move_to_tail( swapped, (driTextureObject *) texObj->DriverData );
      }

      if ( (targets & DRI_TEXMGR_DO_TEXTURE_2D) != 0 ) {
	 texObj = ctx->Texture.Unit[i].Current2D;
	 ctx->Driver.BindTexture( ctx, GL_TEXTURE_2D, texObj );
	 move_to_tail( swapped, (driTextureObject *) texObj->DriverData );
      }

      if ( (targets & DRI_TEXMGR_DO_TEXTURE_3D) != 0 ) {
	 texObj = ctx->Texture.Unit[i].Current3D;
	 ctx->Driver.BindTexture( ctx, GL_TEXTURE_3D, texObj );
	 move_to_tail( swapped, (driTextureObject *) texObj->DriverData );
      }

      if ( (targets & DRI_TEXMGR_DO_TEXTURE_CUBE) != 0 ) {
	 texObj = ctx->Texture.Unit[i].CurrentCubeMap;
	 ctx->Driver.BindTexture( ctx, GL_TEXTURE_CUBE_MAP_ARB, texObj );
	 move_to_tail( swapped, (driTextureObject *) texObj->DriverData );
      }

      if ( (targets & DRI_TEXMGR_DO_TEXTURE_RECT) != 0 ) {
	 texObj = ctx->Texture.Unit[i].CurrentRect;
	 ctx->Driver.BindTexture( ctx, GL_TEXTURE_RECTANGLE_NV, texObj );
	 move_to_tail( swapped, (driTextureObject *) texObj->DriverData );
      }
   }

   ctx->Texture.CurrentUnit = tmp;
}




/**
 * Verify that the specified texture is in the specificed heap.
 * 
 * \param tex   Texture to be tested.
 * \param heap  Texture memory heap to be tested.
 * \return True if the texture is in the heap, false otherwise.
 */

static GLboolean
check_in_heap( const driTextureObject * tex, const driTexHeap * heap )
{
#if 1
   return tex->heap == heap;
#else
   driTextureObject * curr;

   foreach( curr, & heap->texture_objects ) {
      if ( curr == tex ) {
	 break;
      }
   }

   return curr == tex;
#endif
}



/****************************************************************************/
/**
 * Validate the consistency of a set of texture heaps.
 * Original version by Keith Whitwell in r200/r200_sanity.c.
 */

GLboolean
driValidateTextureHeaps( driTexHeap * const * texture_heaps,
			 unsigned nr_heaps, const driTextureObject * swapped )
{
   driTextureObject *t;
   unsigned  i;

   for ( i = 0 ; i < nr_heaps ; i++ ) {
      int last_end = 0;
      unsigned textures_in_heap = 0;
      unsigned blocks_in_mempool = 0;
      const driTexHeap * heap = texture_heaps[i];
      const memHeap_t * p = heap->memory_heap;

      /* Check each texture object has a MemBlock, and is linked into
       * the correct heap.  
       *
       * Check the texobj base address corresponds to the MemBlock
       * range.  Check the texobj size (recalculate?) fits within
       * the MemBlock.
       *
       * Count the number of texobj's using this heap.
       */

      foreach ( t, &heap->texture_objects ) {
	 if ( !check_in_heap( t, heap ) ) {
	    fprintf( stderr, "%s memory block for texture object @ %p not "
		     "found in heap #%d\n",
		     __FUNCTION__, (void *)t, i );
	    return GL_FALSE;
	 }


	 if ( t->totalSize > t->memBlock->size ) {
	    fprintf( stderr, "%s: Memory block for texture object @ %p is "
		     "only %u bytes, but %u are required\n",
		     __FUNCTION__, (void *)t, t->totalSize, t->memBlock->size );
	    return GL_FALSE;
	 }

	 textures_in_heap++;
      }

      /* Validate the contents of the heap:
       *   - Ordering
       *   - Overlaps
       *   - Bounds
       */

      while ( p != NULL ) {
	 if (p->reserved) {
	    fprintf( stderr, "%s: Block (%08x,%x), is reserved?!\n",
		     __FUNCTION__, p->ofs, p->size );
	    return GL_FALSE;
	 }

	 if (p->ofs != last_end) {
	    fprintf( stderr, "%s: blocks_in_mempool = %d, last_end = %d, p->ofs = %d\n",
		     __FUNCTION__, blocks_in_mempool, last_end, p->ofs );
	    return GL_FALSE;
	 }

	 if (!p->reserved && !p->free) {
	    blocks_in_mempool++;
	 }

	 last_end = p->ofs + p->size;
	 p = p->next;
      }

      if (textures_in_heap != blocks_in_mempool) {
	 fprintf( stderr, "%s: Different number of textures objects (%u) and "
		  "inuse memory blocks (%u)\n", 
		  __FUNCTION__, textures_in_heap, blocks_in_mempool );
	 return GL_FALSE;
      }

#if 0
      fprintf( stderr, "%s: textures_in_heap = %u\n", 
	       __FUNCTION__, textures_in_heap );
#endif
   }


   /* Check swapped texobj's have zero memblocks
    */
   i = 0;
   foreach ( t, swapped ) {
      if ( t->memBlock != NULL ) {
	 fprintf( stderr, "%s: Swapped texobj %p has non-NULL memblock %p\n",
		  __FUNCTION__, (void *)t, (void *)t->memBlock );
	 return GL_FALSE;
      }
      i++;
   }

#if 0
   fprintf( stderr, "%s: swapped texture count = %u\n", i );
#endif

   return GL_TRUE;
}

/****************************************************************************/
/**
 * Compute which mipmap levels that really need to be sent to the hardware.
 * This depends on the base image size, GL_TEXTURE_MIN_LOD,
 * GL_TEXTURE_MAX_LOD, GL_TEXTURE_BASE_LEVEL, and GL_TEXTURE_MAX_LEVEL.
 */

void
driCalculateTextureFirstLastLevel( driTextureObject * t )
{
   struct gl_texture_object * const tObj = t->tObj;
   const struct gl_texture_image * const baseImage =
       tObj->Image[tObj->BaseLevel];

   /* These must be signed values.  MinLod and MaxLod can be negative numbers,
    * and having firstLevel and lastLevel as signed prevents the need for
    * extra sign checks.
    */
   int   firstLevel;
   int   lastLevel;

   /* Yes, this looks overly complicated, but it's all needed.
    */

   switch (tObj->Target) {
   case GL_TEXTURE_1D:
   case GL_TEXTURE_2D:
   case GL_TEXTURE_3D:
   case GL_TEXTURE_CUBE_MAP:
      if (tObj->MinFilter == GL_NEAREST || tObj->MinFilter == GL_LINEAR) {
         /* GL_NEAREST and GL_LINEAR only care about GL_TEXTURE_BASE_LEVEL.
          */

         firstLevel = lastLevel = tObj->BaseLevel;
      }
      else {
	 firstLevel = tObj->BaseLevel + (GLint)(tObj->MinLod + 0.5);
	 firstLevel = MAX2(firstLevel, tObj->BaseLevel);
	 lastLevel = tObj->BaseLevel + (GLint)(tObj->MaxLod + 0.5);
	 lastLevel = MAX2(lastLevel, t->tObj->BaseLevel);
	 lastLevel = MIN2(lastLevel, t->tObj->BaseLevel + baseImage->MaxLog2);
	 lastLevel = MIN2(lastLevel, t->tObj->MaxLevel);
	 lastLevel = MAX2(firstLevel, lastLevel); /* need at least one level */
      }
      break;
   case GL_TEXTURE_RECTANGLE_NV:
   case GL_TEXTURE_4D_SGIS:
      firstLevel = lastLevel = 0;
      break;
   default:
      return;
   }

   /* save these values */
   t->firstLevel = firstLevel;
   t->lastLevel = lastLevel;
}