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

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

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * See the file fxapi.c for more informations about authors
 *
 */

#if defined(FX)

#include "fxdrv.h"

static tfxTMFreeNode *fxTMNewTMFreeNode(FxU32 start, FxU32 end)
{
  tfxTMFreeNode *tmn;

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

  tmn->next=NULL;
  tmn->startAddress=start;
  tmn->endAddress=end;

  return tmn;
}

static void fxTMUInit(fxMesaContext fxMesa, int tmu)
{
  tfxTMFreeNode *tmn,*tmntmp;
  FxU32 start,end,blockstart,blockend;

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

  if(fxMesa->verbose) {
    fprintf(stderr,"%s configuration:",(tmu==FX_TMU0) ? "TMU0" : "TMU1");
    fprintf(stderr,"  Lower texture memory address (%u)\n",(unsigned int)start);
    fprintf(stderr,"  Higher texture memory address (%u)\n",(unsigned int)end);
    fprintf(stderr,"  Splitting Texture memory in 2Mb blocks:\n");
  }

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

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

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

    tmn=fxTMNewTMFreeNode(blockstart,blockend);

    if(fxMesa->tmFree[tmu]) {
      for(tmntmp=fxMesa->tmFree[tmu];tmntmp->next!=NULL;tmntmp=tmntmp->next);
      tmntmp->next=tmn;
    } else
      fxMesa->tmFree[tmu]=tmn;

    blockstart+=0x1fffff+1;
  }
}

void fxTMInit(fxMesaContext fxMesa)
{
  fxTMUInit(fxMesa,FX_TMU0);

  if(fxMesa->haveTwoTMUs)
    fxTMUInit(fxMesa,FX_TMU1);

  fxMesa->texBindNumber=0;
}

static struct gl_texture_object *fxTMFindOldestTMBlock(fxMesaContext fxMesa,
						       tfxTMAllocNode *tmalloc,
						       GLuint texbindnumber)
{
  GLuint age,oldestage,lasttimeused;
  struct gl_texture_object *oldesttexobj;

  oldesttexobj=tmalloc->tObj;
  oldestage=0;

  while(tmalloc) {
    lasttimeused=((tfxTexInfo *)(tmalloc->tObj->DriverData))->tmi.lastTimeUsed;

    if(lasttimeused>texbindnumber)
      age=texbindnumber+(UINT_MAX-lasttimeused+1); /* TO DO: check */
    else
      age=texbindnumber-lasttimeused;

    if(age>=oldestage) {
      oldestage=age;
      oldesttexobj=tmalloc->tObj;
    }

    tmalloc=tmalloc->next;
  }

  return oldesttexobj;
}

static GLboolean fxTMFreeOldTMBlock(fxMesaContext fxMesa, GLint tmu)
{
  struct gl_texture_object *oldesttexobj;

  if(!fxMesa->tmAlloc[tmu])
    return GL_FALSE;

  oldesttexobj=fxTMFindOldestTMBlock(fxMesa,fxMesa->tmAlloc[tmu],fxMesa->texBindNumber);

  fxTMMoveOutTM(fxMesa,oldesttexobj);

  return GL_TRUE;
}

static tfxTMFreeNode *fxTMExtractTMFreeBlock(tfxTMFreeNode *tmfree, int texmemsize,
					     GLboolean *success, FxU32 *startadr)
{
  int blocksize;

  /* TO DO: cut recursion */

  if(!tmfree) {
    *success=GL_FALSE;
    return NULL;
  }

  blocksize=tmfree->endAddress-tmfree->startAddress+1;

  if(blocksize==texmemsize) {
    tfxTMFreeNode *nexttmfree;

    *success=GL_TRUE;
    *startadr=tmfree->startAddress;

    nexttmfree=tmfree->next;
    free(tmfree);

    return nexttmfree;
  }

  if(blocksize>texmemsize) {
    *success=GL_TRUE;
    *startadr=tmfree->startAddress;

    tmfree->startAddress+=texmemsize;

    return tmfree;
  }

  tmfree->next=fxTMExtractTMFreeBlock(tmfree->next,texmemsize,success,startadr);

  return tmfree;
}

static tfxTMAllocNode *fxTMGetTMBlock(fxMesaContext fxMesa, struct gl_texture_object *tObj,
				      GLint tmu, int texmemsize)
{
  tfxTMFreeNode *newtmfree;
  tfxTMAllocNode *newtmalloc;
  GLboolean success;
  FxU32 startadr;

  for(;;) { /* TO DO: improve performaces */
    newtmfree=fxTMExtractTMFreeBlock(fxMesa->tmFree[tmu],texmemsize,&success,&startadr);

    if(success) {
      fxMesa->tmFree[tmu]=newtmfree;

      fxMesa->freeTexMem[tmu]-=texmemsize;

      if(!(newtmalloc=malloc(sizeof(tfxTMAllocNode)))) {
	fprintf(stderr,"fx Driver: out of memory !\n");
	fxCloseHardware();
	exit(-1);
      }
      
      newtmalloc->next=fxMesa->tmAlloc[tmu];
      newtmalloc->startAddress=startadr;
      newtmalloc->endAddress=startadr+texmemsize-1;
      newtmalloc->tObj=tObj;

      fxMesa->tmAlloc[tmu]=newtmalloc;

      return newtmalloc;
    }

    if(!fxTMFreeOldTMBlock(fxMesa,tmu)) {
      fprintf(stderr,"fx Driver: internal error in fxTMGetTMBlock()\n");
      fprintf(stderr,"           TMU: %d Size: %d\n",tmu,texmemsize);
    
      fxCloseHardware();
      exit(-1);
    }
  }
}

void fxTMMoveInTM(fxMesaContext fxMesa, struct gl_texture_object *tObj, GLint where)
{
  tfxTexInfo *ti=(tfxTexInfo *)tObj->DriverData;
  int i,l;
  int texmemsize;

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

  fxMesa->stats.reqTexUpload++;

  if(!ti->validated) {
    fprintf(stderr,"fx Driver: internal error in fxTMMoveInTM() -> not validated\n");
    fxCloseHardware();
    exit(-1);
  }

  if(ti->tmi.isInTM)
    return;

#if defined(DEBUG_FXMESA)
  fprintf(stderr,"fxmesa: downloading %x (%d) in texture memory in %d\n",tObj,tObj->Name,where);
#endif

  ti->tmi.whichTMU=where;

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

    for(i=ti->info.largeLod,l=ti->minLevel;i<=ti->info.smallLod;i++,l++)
      grTexDownloadMipMapLevel(where,
			       ti->tmi.tm[where]->startAddress,i,
			       ti->info.largeLod,ti->info.aspectRatio,
			       ti->info.format,GR_MIPMAPLEVELMASK_BOTH,
			       ti->tmi.mipmapLevel[l].data);
    break;
  case FX_TMU_SPLIT: /* TO DO: alternate even/odd TMU0/TMU1 */
    texmemsize=grTexTextureMemRequired(GR_MIPMAPLEVELMASK_ODD,&(ti->info));
    ti->tmi.tm[FX_TMU0]=fxTMGetTMBlock(fxMesa,tObj,FX_TMU0,texmemsize);
    fxMesa->stats.memTexUpload+=texmemsize;

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

    for(i=ti->info.largeLod,l=ti->minLevel;i<=ti->info.smallLod;i++,l++) {
      grTexDownloadMipMapLevel(GR_TMU0,ti->tmi.tm[FX_TMU0]->startAddress,i,
			       ti->info.largeLod,ti->info.aspectRatio,
			       ti->info.format,GR_MIPMAPLEVELMASK_ODD,
			       ti->tmi.mipmapLevel[l].data);

      grTexDownloadMipMapLevel(GR_TMU1,ti->tmi.tm[FX_TMU1]->startAddress,i,
			       ti->info.largeLod,ti->info.aspectRatio,
			       ti->info.format,GR_MIPMAPLEVELMASK_EVEN,
			       ti->tmi.mipmapLevel[l].data);
    }
    break;
  default:
    fprintf(stderr,"fx Driver: internal error in fxTMMoveInTM() -> wrong tmu (%d)\n",where);
    fxCloseHardware();
    exit(-1);
  }

  fxMesa->stats.texUpload++;

  ti->tmi.isInTM=GL_TRUE;
}

void fxTMReloadMipMapLevel(fxMesaContext fxMesa, struct gl_texture_object *tObj, GLint level)
{
  tfxTexInfo *ti=(tfxTexInfo *)tObj->DriverData;
  GrLOD_t lodlevel;
  GLint tmu;

  if(!ti->validated) {
    fprintf(stderr,"fx Driver: internal error in fxTMReloadMipMapLevel() -> not validated\n");
    fxCloseHardware();
    exit(-1);
  }

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

  fxTexGetInfo(ti->tmi.mipmapLevel[0].width,ti->tmi.mipmapLevel[0].height,
	       &lodlevel,NULL,NULL,NULL,NULL,NULL);

  switch(tmu) {
  case FX_TMU0:
  case FX_TMU1:
    grTexDownloadMipMapLevel(tmu,
			     ti->tmi.tm[tmu]->startAddress,lodlevel+level,
			     ti->info.largeLod,ti->info.aspectRatio,
			     ti->info.format,GR_MIPMAPLEVELMASK_BOTH,
			     ti->tmi.mipmapLevel[level].data);
    break;
  case FX_TMU_SPLIT: /* TO DO: alternate even/odd TMU0/TMU1 */
    grTexDownloadMipMapLevel(GR_TMU0,
			     ti->tmi.tm[GR_TMU0]->startAddress,lodlevel+level,
			     ti->info.largeLod,ti->info.aspectRatio,
			     ti->info.format,GR_MIPMAPLEVELMASK_ODD,
			     ti->tmi.mipmapLevel[level].data);
    
    grTexDownloadMipMapLevel(GR_TMU1,
			     ti->tmi.tm[GR_TMU1]->startAddress,lodlevel+level,
			     ti->info.largeLod,ti->info.aspectRatio,
			     ti->info.format,GR_MIPMAPLEVELMASK_EVEN,
			     ti->tmi.mipmapLevel[level].data);
    break;
  default:
    fprintf(stderr,"fx Driver: internal error in fxTMReloadMipMapLevel() -> 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=(tfxTexInfo *)tObj->DriverData;
  GrLOD_t lodlevel;
  unsigned short *data;
  GLint tmu;

  if(!ti->validated) {
    fprintf(stderr,"fx Driver: internal error in fxTMReloadSubMipMapLevel() -> not validated\n");
    fxCloseHardware();
    exit(-1);
  }

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

  fxTexGetInfo(ti->tmi.mipmapLevel[0].width,ti->tmi.mipmapLevel[0].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=ti->tmi.mipmapLevel[level].data+((yoffset*ti->tmi.mipmapLevel[level].width)>>1);
  else
    data=ti->tmi.mipmapLevel[level].data+yoffset*ti->tmi.mipmapLevel[level].width;

  switch(tmu) {
  case FX_TMU0:
  case FX_TMU1:
    grTexDownloadMipMapLevelPartial(tmu,
				    ti->tmi.tm[tmu]->startAddress,lodlevel+level,
				    ti->info.largeLod,ti->info.aspectRatio,
				    ti->info.format,GR_MIPMAPLEVELMASK_BOTH,
				    data,
				    yoffset,yoffset+height-1);
    break;
  case FX_TMU_SPLIT: /* TO DO: alternate even/odd TMU0/TMU1 */
    grTexDownloadMipMapLevelPartial(GR_TMU0,
				    ti->tmi.tm[FX_TMU0]->startAddress,lodlevel+level,
				    ti->info.largeLod,ti->info.aspectRatio,
				    ti->info.format,GR_MIPMAPLEVELMASK_ODD,
				    data,
				    yoffset,yoffset+height-1);

    grTexDownloadMipMapLevelPartial(GR_TMU1,
				    ti->tmi.tm[FX_TMU1]->startAddress,lodlevel+level,
				    ti->info.largeLod,ti->info.aspectRatio,
				    ti->info.format,GR_MIPMAPLEVELMASK_EVEN,
				    data,
				    yoffset,yoffset+height-1);
    break;
  default:
    fprintf(stderr,"fx Driver: internal error in fxTMReloadSubMipMapLevel() -> wrong tmu (%d)\n",tmu);
    fxCloseHardware();
    exit(-1);
  }
}

static tfxTMAllocNode *fxTMFreeTMAllocBlock(tfxTMAllocNode *tmalloc,
					    tfxTMAllocNode *tmunalloc)
{
  if(!tmalloc)
    return NULL;

  if(tmalloc==tmunalloc) {
    tfxTMAllocNode *newtmalloc;

    newtmalloc=tmalloc->next;
    free(tmalloc);

    return newtmalloc;
  }

  tmalloc->next=fxTMFreeTMAllocBlock(tmalloc->next,tmunalloc);

  return tmalloc;
}

static tfxTMFreeNode *fxTMAddTMFree(tfxTMFreeNode *tmfree, FxU32 startadr, FxU32 endadr)
{
  if(!tmfree)
    return fxTMNewTMFreeNode(startadr,endadr);

  if((endadr+1==tmfree->startAddress) && (tmfree->startAddress & 0x1fffff)) {
    tmfree->startAddress=startadr;

    return tmfree;
  }

  if((startadr-1==tmfree->endAddress) && (startadr & 0x1fffff)) {
    tmfree->endAddress=endadr;

    if((tmfree->next && (endadr+1==tmfree->next->startAddress) &&
        (tmfree->next->startAddress & 0x1fffff))) {
      tfxTMFreeNode *nexttmfree;

      tmfree->endAddress=tmfree->next->endAddress;

      nexttmfree=tmfree->next->next;
      free(tmfree->next);

      tmfree->next=nexttmfree;
    }


    return tmfree;
  }

  if(startadr<tmfree->startAddress) {
    tfxTMFreeNode *newtmfree;

    newtmfree=fxTMNewTMFreeNode(startadr,endadr);
    newtmfree->next=tmfree;

    return newtmfree;
  }

  tmfree->next=fxTMAddTMFree(tmfree->next,startadr,endadr);

  return tmfree;
}

static void fxTMFreeTMBlock(fxMesaContext fxMesa, GLint tmu, tfxTMAllocNode *tmalloc)
{
  FxU32 startadr,endadr;

  startadr=tmalloc->startAddress;
  endadr=tmalloc->endAddress;

  fxMesa->tmAlloc[tmu]=fxTMFreeTMAllocBlock(fxMesa->tmAlloc[tmu],tmalloc);

  fxMesa->tmFree[tmu]=fxTMAddTMFree(fxMesa->tmFree[tmu],startadr,endadr);

  fxMesa->freeTexMem[tmu]+=endadr-startadr+1;
}

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

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

  if(!ti->tmi.isInTM)
    return;

  switch(ti->tmi.whichTMU) {
  case FX_TMU0:
  case FX_TMU1:
    fxTMFreeTMBlock(fxMesa,ti->tmi.whichTMU,ti->tmi.tm[ti->tmi.whichTMU]);
    break;
  case FX_TMU_SPLIT:
    fxTMFreeTMBlock(fxMesa,FX_TMU0,ti->tmi.tm[FX_TMU0]);
    fxTMFreeTMBlock(fxMesa,FX_TMU1,ti->tmi.tm[FX_TMU1]);
    break;
  default:
    fprintf(stderr,"fx Driver: internal error in fxTMMoveOutTM()\n");
    fxCloseHardware();
    exit(-1);
  }

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

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

  fxTMMoveOutTM(fxMesa,tObj);

  for(i=0;i<MAX_TEXTURE_LEVELS;i++) {
    if(ti->tmi.mipmapLevel[i].used &&
       ti->tmi.mipmapLevel[i].translated)
      free(ti->tmi.mipmapLevel[i].data);

    ti->tmi.mipmapLevel[i].data;
  }
}

void fxTMFreeAllFreeNode(tfxTMFreeNode *fn)
{
  if(!fn)
    return;

  if(fn->next)
    fxTMFreeAllFreeNode(fn->next);

  free(fn);
}

void fxTMFreeAllAllocNode(tfxTMAllocNode *an)
{
  if(!an)
    return;

  if(an->next)
    fxTMFreeAllAllocNode(an->next);

  free(an);
}

void fxTMClose(fxMesaContext fxMesa)
{
  fxTMFreeAllFreeNode(fxMesa->tmFree[FX_TMU0]);
  fxTMFreeAllAllocNode(fxMesa->tmAlloc[FX_TMU0]);

  if(fxMesa->haveTwoTMUs) {
    fxTMFreeAllFreeNode(fxMesa->tmFree[FX_TMU1]);
    fxTMFreeAllAllocNode(fxMesa->tmAlloc[FX_TMU1]);
  }
}


#else


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

int gl_fx_dummy_function_texman(void)
{
  return 0;
}

#endif  /* FX */
