/*
 * 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>
 */

#include "main/glheader.h"

#include "main/mm.h"
#include "mgacontext.h"
#include "mgatex.h"
#include "mgaregs.h"
#include "mgaioctl.h"
#include "mga_xmesa.h"

#include "main/imports.h"
#include "main/simple_list.h"

/**
 * Destroy any device-dependent state associated with the texture.  This may
 * include NULLing out hardware state that points to the texture.
 */
void
mgaDestroyTexObj( mgaContextPtr mmesa, mgaTextureObjectPtr t )
{
    unsigned   i;


    /* See if it was the driver's current object.
     */

    if ( mmesa != NULL )
    { 
	if ( t->age > mmesa->dirtyAge )
	    mmesa->dirtyAge = t->age;

	for ( i = 0 ; i < mmesa->glCtx->Const.MaxTextureUnits ; i++ )
	{
	    if ( t == mmesa->CurrentTexObj[ i ] ) {
		mmesa->CurrentTexObj[ i ] = NULL;
	    }
	}
    }
}


/**
 * Upload a texture image from system memory to either on-card or AGP
 * memory.  Uploads to on-card memory are performed using an ILOAD operation.
 * This is used for both initial loading of the entire image, and texSubImage
 * updates.
 *
 * Performed with the hardware lock held.
 * 
 * Even though this function is named "upload subimage," the entire image
 * is uploaded.
 * 
 * \param mmesa  Driver context.
 * \param t      Texture to be uploaded.
 * \param hwlevel  Mipmap level of the texture to be uploaded.
 * 
 * \bug As mentioned above, this fuction actually copies the entier mipmap
 *      level.  There should be a version of this function that performs
 *      sub-rectangle uploads.  This will perform quite a bit better if only
 *      a small portion of a larger texture has been updated.  Care would
 *      need to be take with such an implementation once glCopyTexImage has
 *      been hardware accelerated.
 */
static void mgaUploadSubImage( mgaContextPtr mmesa,
			       mgaTextureObjectPtr t, GLint hwlevel )
{
   struct gl_texture_image * texImage;
   unsigned     offset;
   unsigned     texelBytes;
   unsigned     length;
   const int level = hwlevel + t->base.firstLevel;


   if ( (hwlevel < 0) 
	|| (hwlevel >= (MGA_IS_G200(mmesa) 
		      ? G200_TEX_MAXLEVELS : G400_TEX_MAXLEVELS)) ) {
      fprintf( stderr, "[%s:%d] level = %d\n", __FILE__, __LINE__, level );
      return;
   }

   texImage = t->base.tObj->Image[0][level];
   if ( texImage == NULL ) {
      fprintf( stderr, "[%s:%d] Image[%d] = NULL\n", __FILE__, __LINE__,
	       level );
      return;
   }


   if (texImage->Data == NULL) {
      fprintf(stderr, "null texture image data tObj %p level %d\n",
	      (void *) t->base.tObj, level);
      return;
   }


   /* find the proper destination offset for this level */
   if ( MGA_IS_G200(mmesa) ) {
      offset = (t->base.memBlock->ofs + t->offsets[hwlevel]);
   }
   else {
      unsigned  i;

      offset = t->base.memBlock->ofs;
      for ( i = 0 ; i < hwlevel ; i++ ) {
	 offset += (t->offsets[1] >> (i * 2));
      }
   }


   /* Copy the texture from system memory to a memory space that can be
    * directly used by the hardware for texturing.
    */

   texelBytes = texImage->TexFormat->TexelBytes;
   length = texImage->Width * texImage->Height * texelBytes;
   if ( t->base.heap->heapId == MGA_CARD_HEAP ) {
      unsigned  tex_offset = 0;
      unsigned  to_copy;


      /* We may not be able to upload the entire texture in one batch due to
       * register limits or dma buffer limits.  Split the copy up into maximum
       * sized chunks.
       */

      offset += mmesa->mgaScreen->textureOffset[ t->base.heap->heapId ];
      while ( length != 0 ) {
	 mgaGetILoadBufferLocked( mmesa );

	 /* The kernel ILOAD ioctl requires that the lenght be an even multiple
	  * of MGA_ILOAD_ALIGN.
	  */
	 length = ((length) + MGA_ILOAD_MASK) & ~MGA_ILOAD_MASK;

	 to_copy = MIN2( length, MGA_BUFFER_SIZE );
	 (void) memcpy( mmesa->iload_buffer->address,
			(GLubyte *) texImage->Data + tex_offset, to_copy );

	 if ( MGA_DEBUG & DEBUG_VERBOSE_TEXTURE )
	     fprintf(stderr, "[%s:%d] address/size = 0x%08lx/%d\n",
		     __FILE__, __LINE__,
		     (long) (offset + tex_offset),
		     to_copy );

	 mgaFireILoadLocked( mmesa, offset + tex_offset, to_copy );
	 tex_offset += to_copy;
	 length -= to_copy;
      }
   } else {
      /* FIXME: the sync for direct copy reduces speed.. */
      /* 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);

      memcpy( mmesa->mgaScreen->texVirtual[t->base.heap->heapId] + offset,
	      texImage->Data, length );

      if ( MGA_DEBUG & DEBUG_VERBOSE_TEXTURE )
	 fprintf(stderr, "[%s:%d] address/size = 0x%08lx/%d\n",
		 __FILE__, __LINE__,
		 (long) (mmesa->mgaScreen->texVirtual[t->base.heap->heapId] 
			 + offset),
		 length);
   }
}


/**
 * Upload the texture images associated with texture \a t.  This might
 * require the allocation of texture memory.
 * 
 * \param mmesa Context pointer
 * \param t Texture to be uploaded
 */

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


   if ( (t == NULL) || (t->base.totalSize == 0) )
      return 0;

   LOCK_HARDWARE( mmesa );

   if (t->base.memBlock == NULL ) {
      int heap;

      heap = driAllocateTexture( mmesa->texture_heaps, mmesa->nr_heaps,
				 (driTextureObject *) t );
      if ( heap == -1 ) {
	 UNLOCK_HARDWARE( mmesa );
	 return -1;
      }

      ofs = mmesa->mgaScreen->textureOffset[ heap ]
	   + t->base.memBlock->ofs;

      if ( MGA_IS_G200(mmesa) ) {
	 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];
      }
      else {
	 t->setup.texorg  = ofs | TO_texorgoffsetsel;
	 t->setup.texorg1 = t->offsets[1];
	 t->setup.texorg2 = 0;
	 t->setup.texorg3 = 0;
	 t->setup.texorg4 = 0;
      }

      mmesa->dirty |= MGA_UPLOAD_CONTEXT;
   }

   /* Let the world know we've used this memory recently.
    */
   driUpdateTextureLRU( (driTextureObject *) t );

   if (MGA_DEBUG&DEBUG_VERBOSE_TEXTURE)
      fprintf(stderr, "[%s:%d] dispatch age: %d age freed memory: %d\n",
	      __FILE__, __LINE__,
	      GET_DISPATCH_AGE(mmesa), mmesa->dirtyAge);

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

   if (t->base.dirty_images[0]) {
      const int numLevels = t->base.lastLevel - t->base.firstLevel + 1;

      if (MGA_DEBUG&DEBUG_VERBOSE_TEXTURE)
	 fprintf(stderr, "[%s:%d] dirty_images[0] = 0x%04x\n",
		 __FILE__, __LINE__, t->base.dirty_images[0] );

      for (i = 0 ; i < numLevels ; i++) {
	 if ( (t->base.dirty_images[0] & (1U << i)) != 0 ) {
	    mgaUploadSubImage( mmesa, t, i );
	 }
      }
      t->base.dirty_images[0] = 0;
   }


   UNLOCK_HARDWARE( mmesa );

   return 0;
}
