/*
 * Copyright 2000-2001 VA Linux Systems, Inc.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * 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 SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Keith Whitwell <keith@tungstengraphics.com>
 */
/* $XFree86: xc/lib/GL/mesa/src/drv/mga/mgatexmem.c,v 1.7 2002/10/30 12:51:36 alanh Exp $ */

#include <stdlib.h>
#include <stdio.h>
#include <GL/gl.h>

#include "mm.h"
#include "mgacontext.h"
#include "mgatex.h"
#include "mgaregs.h"
#include "mgaioctl.h"

#include "mem.h"
#include "simple_list.h"

static void
mgaSwapOutTexObj(mgaContextPtr mmesa, mgaTextureObjectPtr t)
{
   if (t->MemBlock) {
      mmFreeMem(t->MemBlock);
      t->MemBlock = 0;

      if (t->age > mmesa->dirtyAge)
	 mmesa->dirtyAge = t->age;
   }

   t->dirty_images = ~0;
   move_to_tail(&(mmesa->SwappedOut), t);
}

static void
mgaPrintLocalLRU( mgaContextPtr mmesa, int heap )
{
   mgaTextureObjectPtr t;
   int sz = 1 << (mmesa->mgaScreen->logTextureGranularity[heap]);

   fprintf(stderr, "\nLocal LRU, heap %d:\n", heap);

   foreach( t, &(mmesa->TexObjList[heap]) ) {
      if (!t->tObj)
	 fprintf(stderr, "Placeholder %d at %x sz %x\n",
		 t->MemBlock->ofs / sz,
		 t->MemBlock->ofs,
		 t->MemBlock->size);
      else
	 fprintf(stderr, "Texture (bound %d) at %x sz %x\n",
		 t->bound,
		 t->MemBlock->ofs,
		 t->MemBlock->size);
   }

   fprintf(stderr, "\n\n");
}

static void
mgaPrintGlobalLRU( mgaContextPtr mmesa, int heap )
{
   int i, j;
   drmTextureRegion *list = mmesa->sarea->texList[heap];

   fprintf(stderr, "\nGlobal LRU, heap %d list %p:\n", heap, list);

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

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

   fprintf(stderr, "\n\n");
}


static void mgaResetGlobalLRU( mgaContextPtr mmesa, GLuint heap )
{
   drmTextureRegion *list = mmesa->sarea->texList[heap];
   int sz = 1 << mmesa->mgaScreen->logTextureGranularity[heap];
   int i;

   mmesa->texAge[heap] = ++mmesa->sarea->texAge[heap];

   if (0) fprintf(stderr, "mgaResetGlobalLRU %d\n", (int)heap);

   /* (Re)initialize the global circular LRU list.  The last element
    * in the array (MGA_NR_TEX_REGIONS) is the sentinal.  Keeping it
    * at the end of the array allows it to be addressed rationally
    * when looking up objects at a particular location in texture
    * memory.
    */
   for (i = 0 ; (i+1) * sz <= mmesa->mgaScreen->textureSize[heap] ; i++) {
      list[i].prev = i-1;
      list[i].next = i+1;
      list[i].age = mmesa->sarea->texAge[heap];
   }

   i--;
   list[0].prev = MGA_NR_TEX_REGIONS;
   list[i].prev = i-1;
   list[i].next = MGA_NR_TEX_REGIONS;
   list[MGA_NR_TEX_REGIONS].prev = i;
   list[MGA_NR_TEX_REGIONS].next = 0;

}


static void mgaUpdateTexLRU( mgaContextPtr mmesa, mgaTextureObjectPtr t )
{
   int i;
   int heap = t->heap;
   int logsz = mmesa->mgaScreen->logTextureGranularity[heap];
   int start = t->MemBlock->ofs >> logsz;
   int end = (t->MemBlock->ofs + t->MemBlock->size - 1) >> logsz;
   drmTextureRegion *list = mmesa->sarea->texList[heap];

   mmesa->texAge[heap] = ++mmesa->sarea->texAge[heap];

   if (!t->MemBlock) {
      fprintf(stderr, "no memblock\n\n");
      return;
   }

   /* Update our local LRU
    */
   move_to_head( &(mmesa->TexObjList[heap]), t );


   if (0)
      fprintf(stderr, "mgaUpdateTexLRU heap %d list %p\n", heap, list);


   /* Update the global LRU
    */
   for (i = start ; i <= end ; i++) {

      list[i].in_use = 1;
      list[i].age = mmesa->texAge[heap];

      /* 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 = MGA_NR_TEX_REGIONS;
      list[i].next = list[MGA_NR_TEX_REGIONS].next;
      list[(unsigned)list[MGA_NR_TEX_REGIONS].next].prev = i;
      list[MGA_NR_TEX_REGIONS].next = i;
   }

   if (0) {
      mgaPrintGlobalLRU(mmesa, t->heap);
      mgaPrintLocalLRU(mmesa, t->heap);
   }
}

/* Called for every shared texture region which has increased in age
 * since we last held the lock.
 *
 * Figures out which of our textures have been ejected by other clients,
 * and pushes a placeholder texture onto the LRU list to represent
 * the other client's textures.
 */
static void mgaTexturesGone( mgaContextPtr mmesa,
			     GLuint heap,
			     GLuint offset,
			     GLuint size,
			     GLuint in_use )
{
   mgaTextureObjectPtr t, tmp;



   foreach_s ( t, tmp, &(mmesa->TexObjList[heap]) ) {

      if (t->MemBlock->ofs >= offset + size ||
	  t->MemBlock->ofs + t->MemBlock->size <= offset)
	 continue;




      /* It overlaps - kick it off.  Need to hold onto the currently bound
       * objects, however.
       */
      if (t->bound)
	 mgaSwapOutTexObj( mmesa, t );
      else
	 mgaDestroyTexObj( mmesa, t );
   }


   if (in_use) {
      t = (mgaTextureObjectPtr) CALLOC(sizeof(*t));
      if (!t) return;

      t->heap = heap;
      t->MemBlock = mmAllocMem( mmesa->texHeap[heap], size, 0, offset);
      if (!t->MemBlock) {
	 fprintf(stderr, "Couldn't alloc placeholder sz %x ofs %x\n",
		 (int)size, (int)offset);
	 mmDumpMemInfo( mmesa->texHeap[heap]);
	 return;
      }
      insert_at_head( &(mmesa->TexObjList[heap]), t );
   }
}


void mgaAgeTextures( mgaContextPtr mmesa, int heap )
{
   MGASAREAPrivPtr sarea = mmesa->sarea;
   int sz = 1 << (mmesa->mgaScreen->logTextureGranularity[heap]);
   int idx, nr = 0;

   /* Have to go right round from the back to ensure stuff ends up
    * LRU in our local list...  Fix with a cursor pointer.
    */
   for (idx = sarea->texList[heap][MGA_NR_TEX_REGIONS].prev ;
	idx != MGA_NR_TEX_REGIONS && nr < MGA_NR_TEX_REGIONS ;
	idx = sarea->texList[heap][idx].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 ( idx * sz > mmesa->mgaScreen->textureSize[heap] ) {
         nr = MGA_NR_TEX_REGIONS;
         break;
      }

      if (sarea->texList[heap][idx].age > mmesa->texAge[heap]) {
        mgaTexturesGone(mmesa, heap, idx * sz, sz,
                        sarea->texList[heap][idx].in_use);
      }
   }

   if (nr == MGA_NR_TEX_REGIONS) {
      mgaTexturesGone(mmesa, heap, 0,
		      mmesa->mgaScreen->textureSize[heap], 0);
      mgaResetGlobalLRU( mmesa, heap );
   }


   if (0) {
      mgaPrintGlobalLRU( mmesa, heap );
      mgaPrintLocalLRU( mmesa, heap );
   }

   mmesa->texAge[heap] = sarea->texAge[heap];
   mmesa->dirty |= MGA_UPLOAD_TEX0IMAGE | MGA_UPLOAD_TEX1IMAGE;
}

/*
 * mgaUploadSubImageLocked
 *
 * Perform an iload based update of a resident buffer.  This is used for
 * both initial loading of the entire image, and texSubImage updates.
 *
 * Performed with the hardware lock held.
 */
void mgaUploadSubImageLocked( mgaContextPtr mmesa,
			      mgaTextureObjectPtr t,
			      int level,
			      int x, int y, int width, int height )
{
   int		x2;
   int		dwords;
   int		offset;
   struct gl_texture_image *image;
   int		texelBytes, texelsPerDword, texelMaccess, length;

   if ( level < 0 || level >= MGA_TEX_MAXLEVELS )
      return;

   image = t->tObj->Image[level];
   if ( !image ) return;


   if (image->Data == 0) {
      fprintf(stderr, "null texture image data tObj %p level %d\n",
	      t->tObj, level);
      return;
   }


   /* find the proper destination offset for this level */
   offset = (t->MemBlock->ofs +
	     t->offsets[level]);


   texelBytes = t->texelBytes;
   switch( texelBytes ) {
   case 1:
      texelsPerDword = 4;
      texelMaccess = 0;
      break;
   case 2:
      texelsPerDword = 2;
      texelMaccess = 1;
      break;
   case 4:
      texelsPerDword = 1;
      texelMaccess = 2;
      break;
   default:
      return;
   }


   /* We can't do a subimage update if pitch is < 32 texels due
    * to hardware XY addressing limits, so we will need to
    * linearly upload all modified rows.
    */
   if ( image->Width < 32 ) {
      x = 0;
      width = image->Width * height;
      height = 1;

      /* Assume that 1x1 textures aren't going to cause a
       * bus error if we read up to four texels from that
       * location:
       */
/*        if ( width < texelsPerDword ) { */
/*  	 width = texelsPerDword; */
/*        } */
   } else {
      /* pad the size out to dwords.  The image is a pointer
	 to the entire image, so we can safely reference
	 outside the x,y,width,height bounds if we need to */
      x2 = x + width;
      x2 = (x2 + (texelsPerDword-1)) & ~(texelsPerDword-1);
      x = (x + (texelsPerDword-1)) & ~(texelsPerDword-1);
      width = x2 - x;
   }

   /* we may not be able to upload the entire texture in one
      batch due to register limits or dma buffer limits.
      Recursively split it up. */
   while ( 1 ) {
      dwords = height * width / texelsPerDword;
      if ( dwords * 4 <= MGA_BUFFER_SIZE ) {
	 break;
      }

      mgaUploadSubImageLocked( mmesa, t, level, x, y,
			       width, height >> 1 );
      y += ( height >> 1 );
      height -= ( height >> 1 );
   }

   length = dwords * 4;

   /* Fill in the secondary buffer with properly converted texels
    * from the mesa buffer. */
   /* FIXME: the sync for direct copy reduces speed.. */
   if(t->heap == MGA_CARD_HEAP  ) {
      mgaGetILoadBufferLocked( mmesa );
      mgaConvertTexture( (GLuint *)mmesa->iload_buffer->address,
			 texelBytes, image, x, y, width, height );
      if(length < 64) length = 64;

      if (0)
	 fprintf(stderr, "TexelBytes : %d, offset: %d, length : %d\n",
		 texelBytes,
		 mmesa->mgaScreen->textureOffset[t->heap] +
		 offset +
		 y * width * 4/texelsPerDword,
		 length);

      mgaFireILoadLocked( mmesa,
			  mmesa->mgaScreen->textureOffset[t->heap] +
			  offset +
			  y * width * 4/texelsPerDword,
			  length);
   } else {
      /* This works, is slower for uploads to card space and needs
       * additional synchronization with the dma stream.
       */
       
      UPDATE_LOCK(mmesa, DRM_LOCK_FLUSH | DRM_LOCK_QUIESCENT);
      mgaConvertTexture( (GLuint *)
			 (mmesa->mgaScreen->texVirtual[t->heap] +
			  offset +
			  y * width * 4/texelsPerDword),
			 texelBytes, image, x, y, width, height );
   }
}


static void mgaUploadTexLevel( mgaContextPtr mmesa,
			       mgaTextureObjectPtr t,
			       int l )
{
   mgaUploadSubImageLocked( mmesa,
			    t,
			    l,
			    0, 0,
			    t->tObj->Image[l]->Width,
			    t->tObj->Image[l]->Height);
}




#if 0
static void mgaMigrateTexture( mgaContextPtr mmesa, mgaTextureObjectPtr t )
{
	/* NOT DONE */
}
#endif


static int mgaChooseTexHeap( mgaContextPtr mmesa, mgaTextureObjectPtr t )
{
   int freeagp, freecard;
   int fitincard, fitinagp;
   int totalcard, totalagp;
   TMemBlock *b;

   totalcard = totalagp = fitincard = fitinagp = freeagp = freecard = 0;

   b = mmesa->texHeap[0];
   while(b)
   {
     totalcard += b->size;
     if(b->free) if(t->totalSize <= b->size)fitincard = 1;
     b = b->next;
   }

   b = mmesa->texHeap[1];
   while(b)
   {
     totalagp += b->size;
     if(b->free)  if(t->totalSize <= b->size)fitinagp = 1;
     b = b->next;
   }

   if(fitincard)return 0;
   if(fitinagp)return 1;

   if(totalcard && totalagp)
   {
     int ages;
     int ratio = (totalcard > totalagp) ? totalcard / totalagp : totalagp / totalcard;
     ages = mmesa->sarea->texAge[0] + mmesa->sarea->texAge[1];
     if( (ages % ratio) == 0)return totalcard > totalagp ? 1 : 0;
     else return totalcard > totalagp ? 0 : 1;
   }

   if(totalagp) return 1;
   return 0;
}


int mgaUploadTexImages( mgaContextPtr mmesa, mgaTextureObjectPtr t )
{
   int heap;
   int i;
   int ofs;

   heap = t->heap = mgaChooseTexHeap( mmesa, t );

   /* Do we need to eject LRU texture objects?
    */
   if (!t->MemBlock) {
      while (1)
      {
	 mgaTextureObjectPtr tmp = mmesa->TexObjList[heap].prev;

	 t->MemBlock = mmAllocMem( mmesa->texHeap[heap],
				   t->totalSize,
				   6, 0 );
	 if (t->MemBlock)
	    break;

	 if (mmesa->TexObjList[heap].prev->bound) {
	    fprintf(stderr, "Hit bound texture in upload\n");
	    return -1;
	 }

	 if (mmesa->TexObjList[heap].prev ==
	     &(mmesa->TexObjList[heap]))
	 {
	    fprintf(stderr, "Failed to upload texture, sz %d\n", t->totalSize);
	    mmDumpMemInfo( mmesa->texHeap[heap] );
	    return -1;
	 }

	 mgaDestroyTexObj( mmesa, tmp );
      }

      ofs = t->MemBlock->ofs
	 + mmesa->mgaScreen->textureOffset[heap]
	 ;

      t->setup.texorg  = ofs;
      t->setup.texorg1 = ofs + t->offsets[1];
      t->setup.texorg2 = ofs + t->offsets[2];
      t->setup.texorg3 = ofs + t->offsets[3];
      t->setup.texorg4 = ofs + t->offsets[4];

      mmesa->dirty |= MGA_UPLOAD_CONTEXT;
   }

   /* Let the world know we've used this memory recently.
    */
   mgaUpdateTexLRU( mmesa, t );


   if (MGA_DEBUG&DEBUG_VERBOSE_LRU)
      fprintf(stderr, "dispatch age: %d age freed memory: %d\n",
	      GET_DISPATCH_AGE(mmesa), mmesa->dirtyAge);

   if (mmesa->dirtyAge >= GET_DISPATCH_AGE(mmesa))
      mgaWaitAgeLocked( mmesa, mmesa->dirtyAge );

   if (t->dirty_images) {
      if (MGA_DEBUG&DEBUG_VERBOSE_LRU)
	 fprintf(stderr, "*");

      for (i = 0 ; i <= t->lastLevel ; i++)
	 if (t->dirty_images & (1<<i))
	    mgaUploadTexLevel( mmesa, t, i );
   }


   t->dirty_images = 0;
   return 0;
}
