/* -*- mode: c; c-basic-offset: 3 -*-
 *
 * Copyright 2000 VA Linux Systems Inc., Fremont, California.
 *
 * 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 (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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * VA LINUX SYSTEMS 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.
 */
/* $XFree86: xc/lib/GL/mesa/src/drv/tdfx/tdfx_texman.c,v 1.5 2002/02/22 21:45:04 dawes Exp $ */

/*
 * Original rewrite:
 *	Gareth Hughes <gareth@valinux.com>, 29 Sep - 1 Oct 2000
 *
 * Authors:
 *	Gareth Hughes <gareth@valinux.com>
 *	Brian Paul <brianp@valinux.com>
 *
 */

#include "tdfx_context.h"
#include "tdfx_tex.h"
#include "tdfx_texman.h"


#define BAD_ADDRESS ((FxU32) -1)


#if 0 /* DEBUG use */
/*
 * Verify the consistancy of the texture memory manager.
 * This involves:
 *    Traversing all texture objects and computing total memory used.
 *    Traverse the free block list and computing total memory free.
 *    Compare the total free and total used amounts to the total memory size.
 *    Make various assertions about the results.
 */
static void
VerifyFreeList(tdfxContextPtr fxMesa, FxU32 tmu)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    tdfxMemRange *block;
    int prevStart = -1, prevEnd = -1;
    int totalFree = 0;
    int numObj = 0, numRes = 0;
    int totalUsed = 0;

    for (block = shared->tmFree[tmu]; block; block = block->next) {
       assert( block->endAddr > 0 );
       assert( block->startAddr <= shared->totalTexMem[tmu] );
       assert( block->endAddr <= shared->totalTexMem[tmu] );
       assert( (int) block->startAddr > prevStart );
       assert( (int) block->startAddr >= prevEnd );
       prevStart = (int) block->startAddr;
       prevEnd = (int) block->endAddr;
       totalFree += (block->endAddr - block->startAddr);
    }
    assert(totalFree == shared->freeTexMem[tmu]);

    {
       struct gl_texture_object *obj;
       for (obj = mesaShared->TexObjectList; obj; obj = obj->Next) {
          tdfxTexInfo *ti = TDFX_TEXTURE_DATA(obj);
          numObj++;
          if (ti) {
             if (ti->isInTM) {
                numRes++;
                assert(ti->tm[0]);
                if (ti->tm[tmu])
                   totalUsed += (ti->tm[tmu]->endAddr - ti->tm[tmu]->startAddr);
             }
             else {
                assert(!ti->tm[0]);
             }
          }
       }
    }

    printf("totalFree: %d  totalUsed: %d  totalMem: %d #objs=%d  #res=%d\n",
           shared->freeTexMem[tmu], totalUsed, shared->totalTexMem[tmu],
           numObj, numRes);

    assert(totalUsed + totalFree == shared->totalTexMem[tmu]);
}


static void
dump_texmem(tdfxContextPtr fxMesa)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    struct gl_texture_object *oldestObj, *obj, *lowestPriorityObj;
    tdfxMemRange *r;
    FxU32 prev;

    printf("DUMP Objects:\n");
    for (obj = mesaShared->TexObjectList; obj; obj = obj->Next) {
        tdfxTexInfo *info = TDFX_TEXTURE_DATA(obj);

        if (info && info->isInTM) {
        printf("Obj %8p: %4d  info = %p\n", obj, obj->Name, info);

           printf("  isInTM=%d  whichTMU=%d  lastTimeUsed=%d\n",
                  info->isInTM, info->whichTMU, info->lastTimeUsed);
           printf("    tm[0] = %p", info->tm[0]);
           assert(info->tm[0]);
           if (info->tm[0]) {
              printf("  tm startAddr = %d  endAddr = %d",
                     info->tm[0]->startAddr,
                     info->tm[0]->endAddr);
           }
           printf("\n");
           printf("    tm[1] = %p", info->tm[1]);
           if (info->tm[1]) {
              printf("  tm startAddr = %d  endAddr = %d",
                     info->tm[1]->startAddr,
                     info->tm[1]->endAddr);
           }
           printf("\n");
        }
    }

    VerifyFreeList(fxMesa, 0);
    VerifyFreeList(fxMesa, 1);

    printf("Free memory unit 0:  %d bytes\n", shared->freeTexMem[0]);
    prev = 0;
    for (r = shared->tmFree[0]; r; r = r->next) {
       printf("%8p:  start %8d  end %8d  size %8d  gap %8d\n", r, r->startAddr, r->endAddr, r->endAddr - r->startAddr, r->startAddr - prev);
       prev = r->endAddr;
    }

    printf("Free memory unit 1:  %d bytes\n", shared->freeTexMem[1]);
    prev = 0;
    for (r = shared->tmFree[1]; r; r = r->next) {
       printf("%8p:  start %8d  end %8d  size %8d  gap %8d\n", r, r->startAddr, r->endAddr, r->endAddr - r->startAddr, r->startAddr - prev);
       prev = r->endAddr;
    }

}
#endif



#ifdef TEXSANITY
static void
fubar(void)
{
}

/*
 * Sanity Check
 */
static void
sanity(tdfxContextPtr fxMesa)
{
    tdfxMemRange *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





/*
 * Allocate and initialize a new MemRange struct.
 * Try to allocate it from the pool of free MemRange nodes rather than malloc.
 */
static tdfxMemRange *
NewRangeNode(tdfxContextPtr fxMesa, FxU32 start, FxU32 end)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    tdfxMemRange *result;

    _glthread_LOCK_MUTEX(mesaShared->Mutex);
    if (shared && shared->tmPool) {
        result = shared->tmPool;
        shared->tmPool = shared->tmPool->next;
    }
    else {
        result = MALLOC(sizeof(tdfxMemRange));

    }
    _glthread_UNLOCK_MUTEX(mesaShared->Mutex);

    if (!result) {
        /*fprintf(stderr, "fxDriver: out of memory!\n");*/
        return NULL;
    }

    result->startAddr = start;
    result->endAddr = end;
    result->next = NULL;

    return result;
}


/*
 * Initialize texture memory.
 * We take care of one or both TMU's here.
 */
void
tdfxTMInit(tdfxContextPtr fxMesa)
{
    if (!fxMesa->glCtx->Shared->DriverData) {
        const char *extensions;
        struct tdfxSharedState *shared = CALLOC_STRUCT(tdfxSharedState);
        if (!shared)
           return;

        LOCK_HARDWARE(fxMesa);
        extensions = fxMesa->Glide.grGetString(GR_EXTENSION);
        UNLOCK_HARDWARE(fxMesa);
        if (strstr(extensions, "TEXUMA")) {
            FxU32 start, end;
            shared->umaTexMemory = GL_TRUE;
            LOCK_HARDWARE(fxMesa);
            fxMesa->Glide.grEnable(GR_TEXTURE_UMA_EXT);
            start = fxMesa->Glide.grTexMinAddress(0);
            end = fxMesa->Glide.grTexMaxAddress(0);
            UNLOCK_HARDWARE(fxMesa);
            shared->totalTexMem[0] = end - start;
            shared->totalTexMem[1] = 0;
            shared->freeTexMem[0] = end - start;
            shared->freeTexMem[1] = 0;
            shared->tmFree[0] = NewRangeNode(fxMesa, start, end);
            shared->tmFree[1] = NULL;
            /*printf("UMA tex memory: %d\n", (int) (end - start));*/
        }
        else {
            const int numTMUs = fxMesa->haveTwoTMUs ? 2 : 1;
            int tmu;
            shared->umaTexMemory = GL_FALSE;
            LOCK_HARDWARE(fxMesa);
            for (tmu = 0; tmu < numTMUs; tmu++) {
                FxU32 start = fxMesa->Glide.grTexMinAddress(tmu);
                FxU32 end = fxMesa->Glide.grTexMaxAddress(tmu);
                shared->totalTexMem[tmu] = end - start;
                shared->freeTexMem[tmu] = end - start;
                shared->tmFree[tmu] = NewRangeNode(fxMesa, start, end);
                /*printf("Split tex memory: %d\n", (int) (end - start));*/
            }
            UNLOCK_HARDWARE(fxMesa);
        }

        shared->tmPool = NULL;
        fxMesa->glCtx->Shared->DriverData = shared;
        /*printf("Texture memory init UMA: %d\n", shared->umaTexMemory);*/
    }
}


/*
 * Clean-up texture memory before destroying context.
 */
void
tdfxTMClose(tdfxContextPtr fxMesa)
{
    if (fxMesa->glCtx->Shared->RefCount == 1 && fxMesa->driDrawable) {
        /* refcount will soon go to zero, free our 3dfx stuff */
        struct tdfxSharedState *shared = (struct tdfxSharedState *) fxMesa->glCtx->Shared->DriverData;

        const int numTMUs = fxMesa->haveTwoTMUs ? 2 : 1;
        int tmu;
        tdfxMemRange *tmp, *next;

        /* Deallocate the pool of free tdfxMemRange nodes */
        tmp = shared->tmPool;
        while (tmp) {
            next = tmp->next;
            FREE(tmp);
            tmp = next;
        }

        /* Delete the texture memory block tdfxMemRange nodes */
        for (tmu = 0; tmu < numTMUs; tmu++) {
            tmp = shared->tmFree[tmu];
            while (tmp) {
                next = tmp->next;
                FREE(tmp);
                tmp = next;
            }
        }

        FREE(shared);
        fxMesa->glCtx->Shared->DriverData = NULL;
    }
}



/*
 * Delete a tdfxMemRange struct.
 * We keep a linked list of free/available tdfxMemRange structs to
 * avoid extra malloc/free calls.
 */
#if 0
static void
DeleteRangeNode_NoLock(struct TdfxSharedState *shared, tdfxMemRange *range)
{
    /* insert at head of list */
    range->next = shared->tmPool;
    shared->tmPool = range;
}
#endif

#define DELETE_RANGE_NODE(shared, range) \
    (range)->next = (shared)->tmPool;    \
    (shared)->tmPool = (range)



/*
 * When we've run out of texture memory we have to throw out an
 * existing texture to make room for the new one.  This function
 * determins the texture to throw out.
 */
static struct gl_texture_object *
FindOldestObject(tdfxContextPtr fxMesa, FxU32 tmu)
{
    const GLuint bindnumber = fxMesa->texBindNumber;
    struct gl_texture_object *oldestObj, *obj, *lowestPriorityObj;
    GLfloat lowestPriority;
    GLuint oldestAge;

    oldestObj = NULL;
    oldestAge = 0;

    lowestPriority = 1.0F;
    lowestPriorityObj = NULL;

    for (obj = fxMesa->glCtx->Shared->TexObjectList; obj; obj = obj->Next) {
        tdfxTexInfo *info = TDFX_TEXTURE_DATA(obj);

        if (info && info->isInTM &&
            ((info->whichTMU == tmu) || (info->whichTMU == TDFX_TMU_BOTH) ||
             (info->whichTMU == TDFX_TMU_SPLIT))) {
            GLuint age, lasttime;

            assert(info->tm[0]);
            lasttime = info->lastTimeUsed;

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

            if (age >= oldestAge) {
                oldestAge = age;
                oldestObj = obj;
            }

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

    if (lowestPriority < 1.0) {
        ASSERT(lowestPriorityObj);
        /*
        printf("discard %d pri=%f\n", lowestPriorityObj->Name, lowestPriority);
        */
        return lowestPriorityObj;
    }
    else {
        /*
        printf("discard %d age=%d\n", oldestObj->Name, oldestAge);
        */
        return oldestObj;
    }
}


#if 0
static void
FlushTexMemory(tdfxContextPtr fxMesa)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    struct gl_texture_object *obj;

    for (obj = mesaShared->TexObjectList; obj; obj = obj->Next) {
       if (obj->RefCount < 2) {
          /* don't flush currently bound textures */
          tdfxTMMoveOutTM_NoLock(fxMesa, obj);
       }
    }
}
#endif


/*
 * Find the address (offset?) at which we can store a new texture.
 * <tmu> is the texture unit.
 * <size> is the texture size in bytes.
 */
static FxU32
FindStartAddr(tdfxContextPtr fxMesa, FxU32 tmu, FxU32 size)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    tdfxMemRange *prev, *block;
    FxU32 result;
#if 0
    int discardedCount = 0;
#define MAX_DISCARDS 10
#endif

    if (shared->umaTexMemory) {
        assert(tmu == TDFX_TMU0);
    }

    _glthread_LOCK_MUTEX(mesaShared->Mutex);
    while (1) {
        prev = NULL;
        block = shared->tmFree[tmu];
        while (block) {
            if (block->endAddr - block->startAddr >= size) {
                /* The texture will fit here */
                result = block->startAddr;
                block->startAddr += size;
                if (block->startAddr == block->endAddr) {
                    /* Remove this node since it's empty */
                    if (prev) {
                        prev->next = block->next;
                    }
                    else {
                        shared->tmFree[tmu] = block->next;
                    }
                    DELETE_RANGE_NODE(shared, block);
                }
                shared->freeTexMem[tmu] -= size;
                _glthread_UNLOCK_MUTEX(mesaShared->Mutex);
                return result;
            }
            prev = block;
            block = block->next;
        }
        /* We failed to find a block large enough to accomodate <size> bytes.
         * Find the oldest texObject and free it.
         */
#if 0
        discardedCount++;
        if (discardedCount > MAX_DISCARDS + 1) {
            _mesa_problem(NULL, "%s: extreme texmem fragmentation", __FUNCTION__);
            _glthread_UNLOCK_MUTEX(mesaShared->Mutex);
            return BAD_ADDRESS;
        }
        else if (discardedCount > MAX_DISCARDS) {
            /* texture memory is probably really fragmented, flush it */
            FlushTexMemory(fxMesa);
        }
        else
#endif
        {
            struct gl_texture_object *obj = FindOldestObject(fxMesa, tmu);
            if (obj) {
                tdfxTMMoveOutTM_NoLock(fxMesa, obj);
                fxMesa->stats.texSwaps++;
            }
            else {
                _mesa_problem(NULL, "%s: extreme texmem fragmentation", __FUNCTION__);
                _glthread_UNLOCK_MUTEX(mesaShared->Mutex);
                return BAD_ADDRESS;
            }
        }
    }

    /* never get here, but play it safe */
    _glthread_UNLOCK_MUTEX(mesaShared->Mutex);
    return BAD_ADDRESS;
}


/*
 * Remove the given tdfxMemRange node from hardware texture memory.
 */
static void
RemoveRange_NoLock(tdfxContextPtr fxMesa, FxU32 tmu, tdfxMemRange *range)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    tdfxMemRange *block, *prev;

    if (shared->umaTexMemory) {
       assert(tmu == TDFX_TMU0);
    }

    if (!range)
        return;

    if (range->startAddr == range->endAddr) {
        DELETE_RANGE_NODE(shared, range);
        return;
    }
    shared->freeTexMem[tmu] += range->endAddr - range->startAddr;

    /* find position in linked list to insert this tdfxMemRange node */
    prev = NULL;
    block = shared->tmFree[tmu];
    while (block) {
        assert(range->startAddr != block->startAddr);
        if (range->startAddr > block->startAddr) {
            prev = block;
            block = block->next;
        }
        else {
            break;
        }
    }

    /* Insert the free block, combine with adjacent blocks when possible */
    range->next = block;
    if (block) {
        if (range->endAddr == block->startAddr) {
            /* Combine */
            block->startAddr = range->startAddr;
            DELETE_RANGE_NODE(shared, range);
            range = block;
        }
    }
    if (prev) {
        if (prev->endAddr == range->startAddr) {
            /* Combine */
            prev->endAddr = range->endAddr;
            prev->next = range->next;
            DELETE_RANGE_NODE(shared, range);
        }
        else {
            prev->next = range;
        }
    }
    else {
        shared->tmFree[tmu] = range;
    }
}


#if 0 /* NOT USED */
static void
RemoveRange(tdfxContextPtr fxMesa, FxU32 tmu, tdfxMemRange *range)
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    _glthread_LOCK_MUTEX(mesaShared->Mutex);
    RemoveRange_NoLock(fxMesa, tmu, range);
    _glthread_UNLOCK_MUTEX(mesaShared->Mutex);
}
#endif


/*
 * Allocate space for a texture image.
 * <tmu> is the texture unit
 * <texmemsize> is the number of bytes to allocate
 */
static tdfxMemRange *
AllocTexMem(tdfxContextPtr fxMesa, FxU32 tmu, FxU32 texmemsize)
{
    FxU32 startAddr;
    startAddr = FindStartAddr(fxMesa, tmu, texmemsize);
    if (startAddr == BAD_ADDRESS) {
        _mesa_problem(fxMesa->glCtx, "%s returned NULL!  tmu=%d texmemsize=%d",
               __FUNCTION__, (int) tmu, (int) texmemsize);
        return NULL;
    }
    else {
        tdfxMemRange *range;
        range = NewRangeNode(fxMesa, startAddr, startAddr + texmemsize);
        return range;
    }
}


/*
 * Download (copy) the given texture data (all mipmap levels) into the
 * Voodoo's texture memory.
 * The texture memory must have already been allocated.
 */
void
tdfxTMDownloadTexture(tdfxContextPtr fxMesa, struct gl_texture_object *tObj)
{
    tdfxTexInfo *ti;
    GLint l;
    FxU32 targetTMU;

    assert(tObj);
    ti = TDFX_TEXTURE_DATA(tObj);
    assert(ti);
    targetTMU = ti->whichTMU;

    switch (targetTMU) {
    case TDFX_TMU0:
    case TDFX_TMU1:
        if (ti->tm[targetTMU]) {
            for (l = ti->minLevel; l <= ti->maxLevel
                    && tObj->Image[0][l]->Data; l++) {
                GrLOD_t glideLod = ti->info.largeLodLog2 - l + tObj->BaseLevel;
                fxMesa->Glide.grTexDownloadMipMapLevel(targetTMU,
                                                  ti->tm[targetTMU]->startAddr,
                                                  glideLod,
                                                  ti->info.largeLodLog2,
                                                  ti->info.aspectRatioLog2,
                                                  ti->info.format,
                                                  GR_MIPMAPLEVELMASK_BOTH,
                                                  tObj->Image[0][l]->Data);
            }
        }
        break;
    case TDFX_TMU_SPLIT:
        if (ti->tm[TDFX_TMU0] && ti->tm[TDFX_TMU1]) {
            for (l = ti->minLevel; l <= ti->maxLevel
                    && tObj->Image[0][l]->Data; l++) {
                GrLOD_t glideLod = ti->info.largeLodLog2 - l + tObj->BaseLevel;
                fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU0,
                                                  ti->tm[TDFX_TMU0]->startAddr,
                                                  glideLod,
                                                  ti->info.largeLodLog2,
                                                  ti->info.aspectRatioLog2,
                                                  ti->info.format,
                                                  GR_MIPMAPLEVELMASK_ODD,
                                                  tObj->Image[0][l]->Data);

                fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU1,
                                                  ti->tm[TDFX_TMU1]->startAddr,
                                                  glideLod,
                                                  ti->info.largeLodLog2,
                                                  ti->info.aspectRatioLog2,
                                                  ti->info.format,
                                                  GR_MIPMAPLEVELMASK_EVEN,
                                                  tObj->Image[0][l]->Data);
            }
        }
        break;
    case TDFX_TMU_BOTH:
        if (ti->tm[TDFX_TMU0] && ti->tm[TDFX_TMU1]) {
            for (l = ti->minLevel; l <= ti->maxLevel
                    && tObj->Image[0][l]->Data; l++) {
                GrLOD_t glideLod = ti->info.largeLodLog2 - l + tObj->BaseLevel;
                fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU0,
                                                  ti->tm[TDFX_TMU0]->startAddr,
                                                  glideLod,
                                                  ti->info.largeLodLog2,
                                                  ti->info.aspectRatioLog2,
                                                  ti->info.format,
                                                  GR_MIPMAPLEVELMASK_BOTH,
                                                  tObj->Image[0][l]->Data);

                fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU1,
                                                  ti->tm[TDFX_TMU1]->startAddr,
                                                  glideLod,
                                                  ti->info.largeLodLog2,
                                                  ti->info.aspectRatioLog2,
                                                  ti->info.format,
                                                  GR_MIPMAPLEVELMASK_BOTH,
                                                  tObj->Image[0][l]->Data);
            }
        }
        break;
    default:
        _mesa_problem(NULL, "%s: bad tmu (%d)", __FUNCTION__, (int)targetTMU);
        return;
    }
}


void
tdfxTMReloadMipMapLevel(GLcontext *ctx, struct gl_texture_object *tObj,
                        GLint level)
{
    tdfxContextPtr fxMesa = TDFX_CONTEXT(ctx);
    tdfxTexInfo *ti = TDFX_TEXTURE_DATA(tObj);
    GrLOD_t glideLod;
    FxU32 tmu;

    tmu = ti->whichTMU;
    glideLod =  ti->info.largeLodLog2 - level + tObj->BaseLevel;
    ASSERT(ti->isInTM);

    LOCK_HARDWARE(fxMesa);

    switch (tmu) {
    case TDFX_TMU0:
    case TDFX_TMU1:
        fxMesa->Glide.grTexDownloadMipMapLevel(tmu,
                                    ti->tm[tmu]->startAddr,
                                    glideLod,
                                    ti->info.largeLodLog2,
                                    ti->info.aspectRatioLog2,
                                    ti->info.format,
                                    GR_MIPMAPLEVELMASK_BOTH,
                                    tObj->Image[0][level]->Data);
        break;
    case TDFX_TMU_SPLIT:
        fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU0,
                                    ti->tm[GR_TMU0]->startAddr,
                                    glideLod,
                                    ti->info.largeLodLog2,
                                    ti->info.aspectRatioLog2,
                                    ti->info.format,
                                    GR_MIPMAPLEVELMASK_ODD,
                                    tObj->Image[0][level]->Data);

        fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU1,
                                    ti->tm[GR_TMU1]->startAddr,
                                    glideLod,
                                    ti->info.largeLodLog2,
                                    ti->info.aspectRatioLog2,
                                    ti->info.format,
                                    GR_MIPMAPLEVELMASK_EVEN,
                                    tObj->Image[0][level]->Data);
        break;
    case TDFX_TMU_BOTH:
        fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU0,
                                    ti->tm[GR_TMU0]->startAddr,
                                    glideLod,
                                    ti->info.largeLodLog2,
                                    ti->info.aspectRatioLog2,
                                    ti->info.format,
                                    GR_MIPMAPLEVELMASK_BOTH,
                                    tObj->Image[0][level]->Data);

        fxMesa->Glide.grTexDownloadMipMapLevel(GR_TMU1,
                                    ti->tm[GR_TMU1]->startAddr,
                                    glideLod,
                                    ti->info.largeLodLog2,
                                    ti->info.aspectRatioLog2,
                                    ti->info.format,
                                    GR_MIPMAPLEVELMASK_BOTH,
                                    tObj->Image[0][level]->Data);
        break;

    default:
        _mesa_problem(ctx, "%s: bad tmu (%d)", __FUNCTION__, (int)tmu);
        break;
    }
    UNLOCK_HARDWARE(fxMesa);
}


/*
 * Allocate space for the given texture in texture memory then
 * download (copy) it into that space.
 */
void
tdfxTMMoveInTM_NoLock( tdfxContextPtr fxMesa, struct gl_texture_object *tObj,
                       FxU32 targetTMU )
{
    tdfxTexInfo *ti = TDFX_TEXTURE_DATA(tObj);
    FxU32 texmemsize;

    fxMesa->stats.reqTexUpload++;

    if (ti->isInTM) {
        if (ti->whichTMU == targetTMU)
            return;
        if (targetTMU == TDFX_TMU_SPLIT || ti->whichTMU == TDFX_TMU_SPLIT) {
            tdfxTMMoveOutTM_NoLock(fxMesa, tObj);
        }
        else {
            if (ti->whichTMU == TDFX_TMU_BOTH)
                return;
            targetTMU = TDFX_TMU_BOTH;
        }
    }

    ti->whichTMU = targetTMU;

    switch (targetTMU) {
    case TDFX_TMU0:
    case TDFX_TMU1:
        texmemsize = fxMesa->Glide.grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH,
                                                       &(ti->info));
        ti->tm[targetTMU] = AllocTexMem(fxMesa, targetTMU, texmemsize);
        break;
    case TDFX_TMU_SPLIT:
        texmemsize = fxMesa->Glide.grTexTextureMemRequired(GR_MIPMAPLEVELMASK_ODD,
                                                       &(ti->info));
        ti->tm[TDFX_TMU0] = AllocTexMem(fxMesa, TDFX_TMU0, texmemsize);
        if (ti->tm[TDFX_TMU0])
           fxMesa->stats.memTexUpload += texmemsize;

        texmemsize = fxMesa->Glide.grTexTextureMemRequired(GR_MIPMAPLEVELMASK_EVEN,
                                                       &(ti->info));
        ti->tm[TDFX_TMU1] = AllocTexMem(fxMesa, TDFX_TMU1, texmemsize);
        break;
    case TDFX_TMU_BOTH:
        texmemsize = fxMesa->Glide.grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH,
                                                       &(ti->info));
        ti->tm[TDFX_TMU0] = AllocTexMem(fxMesa, TDFX_TMU0, texmemsize);
        if (ti->tm[TDFX_TMU0])
           fxMesa->stats.memTexUpload += texmemsize;

        texmemsize = fxMesa->Glide.grTexTextureMemRequired(GR_MIPMAPLEVELMASK_BOTH,
                                                       &(ti->info));
        ti->tm[TDFX_TMU1] = AllocTexMem(fxMesa, TDFX_TMU1, texmemsize);
        break;
    default:
        _mesa_problem(NULL, "%s: bad tmu (%d)", __FUNCTION__, (int)targetTMU);
        return;
    }

    ti->reloadImages = GL_TRUE;
    ti->isInTM = GL_TRUE;

    fxMesa->stats.texUpload++;
}


/*
 * Move the given texture out of hardware texture memory.
 * This deallocates the texture's memory space.
 */
void
tdfxTMMoveOutTM_NoLock( tdfxContextPtr fxMesa, struct gl_texture_object *tObj )
{
    struct gl_shared_state *mesaShared = fxMesa->glCtx->Shared;
    struct tdfxSharedState *shared = (struct tdfxSharedState *) mesaShared->DriverData;
    tdfxTexInfo *ti = TDFX_TEXTURE_DATA(tObj);

    if (MESA_VERBOSE & VERBOSE_DRIVER) {
        fprintf(stderr, "fxmesa: %s(%p (%d))\n", __FUNCTION__, (void *)tObj, tObj->Name);
    }

    /*
    VerifyFreeList(fxMesa, 0);
    VerifyFreeList(fxMesa, 1);
    */

    if (!ti || !ti->isInTM)
        return;

    switch (ti->whichTMU) {
    case TDFX_TMU0:
    case TDFX_TMU1:
        RemoveRange_NoLock(fxMesa, ti->whichTMU, ti->tm[ti->whichTMU]);
        break;
    case TDFX_TMU_SPLIT:
    case TDFX_TMU_BOTH:
        assert(!shared->umaTexMemory);
        RemoveRange_NoLock(fxMesa, TDFX_TMU0, ti->tm[TDFX_TMU0]);
        RemoveRange_NoLock(fxMesa, TDFX_TMU1, ti->tm[TDFX_TMU1]);
        break;
    default:
        _mesa_problem(NULL, "%s: bad tmu (%d)", __FUNCTION__, (int)ti->whichTMU);
        return;
    }

    ti->isInTM = GL_FALSE;
    ti->tm[0] = NULL;
    ti->tm[1] = NULL;
    ti->whichTMU = TDFX_TMU_NONE;

    /*
    VerifyFreeList(fxMesa, 0);
    VerifyFreeList(fxMesa, 1);
    */
}


/*
 * Called via glDeleteTexture to delete a texture object.
 */
void
tdfxTMFreeTexture(tdfxContextPtr fxMesa, struct gl_texture_object *tObj)
{
    tdfxTexInfo *ti = TDFX_TEXTURE_DATA(tObj);
    if (ti) {
        tdfxTMMoveOutTM(fxMesa, tObj);
        FREE(ti);
        tObj->DriverData = NULL;
    }
    /*
    VerifyFreeList(fxMesa, 0);
    VerifyFreeList(fxMesa, 1);
    */
}



/*
 * After a context switch this function will be called to restore
 * texture memory for the new context.
 */
void tdfxTMRestoreTextures_NoLock( tdfxContextPtr fxMesa )
{
   GLcontext *ctx = fxMesa->glCtx;
   struct gl_texture_object *tObj;
   int i;

   for ( tObj = ctx->Shared->TexObjectList ; tObj ; tObj = tObj->Next ) {
      tdfxTexInfo *ti = TDFX_TEXTURE_DATA( tObj );
      if ( ti && ti->isInTM ) {
	 for ( i = 0 ; i < MAX_TEXTURE_UNITS ; i++ ) {
	    if ( ctx->Texture.Unit[i]._Current == tObj ) {
	       tdfxTMDownloadTexture( fxMesa, tObj );
	       break;
	    }
	 }
	 if ( i == MAX_TEXTURE_UNITS ) {
	    tdfxTMMoveOutTM_NoLock( fxMesa, tObj );
	 }
      }
   }
   /*
   VerifyFreeList(fxMesa, 0);
   VerifyFreeList(fxMesa, 1);
   */
}
