/* $Id: texobj.c,v 1.2 2000/10/27 15:21:40 mholst Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  2.2
 * Copyright (C) 1995-1997  Brian Paul
 *
 * 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.
 */

#include <assert.h>
#include <stdlib.h>
#include "context.h"
#include "macros.h"
#include "teximage.h"
#include "texobj.h"
#include "types.h"



/*
 * Allocate a new texture object structure.  The name and dimensionality are
 * set to zero here and must be initialized by the caller.
 */
struct gl_texture_object *gl_alloc_texture_object( void )
{
   struct gl_texture_object *obj;

   obj = (struct gl_texture_object *)
                     calloc(1,sizeof(struct gl_texture_object));
   if (obj) {
      /* init the non-zero fields */
      obj->WrapS = GL_REPEAT;
      obj->WrapT = GL_REPEAT;
      obj->MinFilter = GL_NEAREST_MIPMAP_LINEAR;
      obj->MagFilter = GL_LINEAR;
   }
   return obj;
}


/*
 * Append a gl_texture_object struct to a list of texture objects.
 */
static void append_texture_object( struct gl_texture_object *list,
                                   struct gl_texture_object *obj )
{
   struct gl_texture_object *t;

   t = list;
   while (t->Next) {
      t = t->Next;
   }
   t->Next = obj;
}



/*
 * Deallocate a texture object struct and all children structures
 * and image data.
 */
void gl_free_texture_object( struct gl_texture_object *t )
{
   GLuint i;

   for (i=0;i<MAX_TEXTURE_LEVELS;i++) {
      if (t->Image[i]) {
         gl_free_texture_image( t->Image[i] );
      }
   }
   free( t );
}


/*
 * Given a texture object name, return a pointer to the texture object.
 */
static struct gl_texture_object *
find_texture_object( GLcontext *ctx, GLuint name )
{
   struct gl_texture_object *t;

   assert( name>0 );
   t = ctx->Shared->TexObjectList;
   while (t) {
      if (t->Name == name) {
         return t;
      }
      t = t->Next;
   }
   return NULL;
}




void gl_GenTextures( GLcontext *ctx, GLsizei n, GLuint *textures )
{
   struct gl_texture_object *t;
   GLuint i, max;

   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glGenTextures" );
      return;
   }
   if (n<0) {
      gl_error( ctx, GL_INVALID_VALUE, "glGenTextures" );
      return;
   }

   /* Find maximum texture object name in use */
   t = ctx->Shared->TexObjectList;
   max = 0;
   while (t) {
      if (t->Name>max) {
         max = t->Name;
      }
      t = t->Next;
   }

   /* Return new texture names starting at max+1 */
   for (i=0;i<n;i++) {
      max++;
      textures[i] = max;
   }
}



void gl_DeleteTextures( GLcontext *ctx, GLsizei n, const GLuint *textures)
{
   GLuint i;

   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glAreTexturesResident" );
      return;
   }

   for (i=0;i<n;i++) {
      struct gl_texture_object *t, *tprev, *tcurr;
      if (textures[i]>0) {
         t = find_texture_object( ctx, textures[i] );
         if (t) {
            if (ctx->Texture.Current1D==t) {
               /* revert to default 1-D texture */
               ctx->Texture.Current1D = ctx->Shared->TexObjectList;
               t->RefCount--;
               assert( t->RefCount >= 0 );
            }
            else if (ctx->Texture.Current2D==t) {
               /* revert to default 2-D texture */
               ctx->Texture.Current2D = ctx->Shared->TexObjectList->Next;
               t->RefCount--;
               assert( t->RefCount >= 0 );
            }
            else if (ctx->Texture.Current3D==t) {
               /* revert to default 3-D texture */
               ctx->Texture.Current3D = ctx->Shared->TexObjectList->Next;
               t->RefCount--;
               assert( t->RefCount >= 0 );
            }
            if (t->RefCount==0) {
               /* remove texture object t from the linked list */
               tprev = NULL;
               tcurr = ctx->Shared->TexObjectList;
               while (tcurr) {
                  if (tcurr==t) {
                     assert( tprev );
                     tprev->Next = t->Next;
                     gl_free_texture_object( t );
                     break;
                  }
                  tprev = tcurr;
                  tcurr = tcurr->Next;
               }
            }

            /* tell device driver to delete texture */
            if (ctx->Driver.DeleteTexture) {
               (*ctx->Driver.DeleteTexture)( ctx, textures[i] );
            }
         }
      }
   }
}



void gl_BindTexture( GLcontext *ctx, GLenum target, GLuint texture )
{
   struct gl_texture_object *oldtexobj;
   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glAreTexturesResident" );
      return;
   }
   switch (target) {
      case GL_TEXTURE_1D:
         oldtexobj = ctx->Texture.Current1D;
         if (texture==0) {
            /* use default 1-D texture */
            ctx->Texture.Current1D = ctx->Shared->TexObjectList;
         }
         else {
            struct gl_texture_object *t;
            t = find_texture_object( ctx, texture );
            if (t) {
               if (t->Dimensions==1) {
                  /* success! */
                  ctx->Texture.Current1D = t;
               }
               else {
                  /* wrong dimensionality */
                  gl_error( ctx, GL_INVALID_OPERATION, "glBindTextureEXT" );
                  return;
               }
            }
            else {
               /* create new texture object */
               t = gl_alloc_texture_object();
               append_texture_object( ctx->Shared->TexObjectList, t );
               t->Name = texture;
               t->Dimensions = 1;
               ctx->Texture.Current1D = t;
            }
         }
         /* Tidy up reference counting */
         if (ctx->Texture.Current1D != oldtexobj && oldtexobj->Name>0) {
            /* decr reference count of the prev texture object */
            oldtexobj->RefCount--;
            assert( oldtexobj->RefCount >= 0 );
         }
         if (ctx->Texture.Current1D->Name>0) {
            ctx->Texture.Current1D->RefCount++;
         }
#ifdef FOO
         if (!ctx->Texture.Current1D->Complete) {
            /* re-examine texture completeness */
            gl_update_texture_state();
         }
#endif

         /* Check if we may have to use a new triangle rasterizer */
         if (oldtexobj->WrapS != ctx->Texture.Current1D->WrapS
             || oldtexobj->MinFilter != ctx->Texture.Current1D->MinFilter
             || oldtexobj->MagFilter != ctx->Texture.Current1D->MagFilter) {
            ctx->NewState |= NEW_RASTER_OPS;
         }

         /* The current 1D texture object can never be NULL! */
         assert(ctx->Texture.Current1D);
         break;

      case GL_TEXTURE_2D:
         oldtexobj = ctx->Texture.Current2D;
         if (texture==0) {
            /* use default 2-D texture */
            ctx->Texture.Current1D = ctx->Shared->TexObjectList->Next;
         }
         else {
            struct gl_texture_object *t;
            t = find_texture_object( ctx, texture );
            if (t) {
               if (t->Dimensions==2) {
                  /* success */
                  ctx->Texture.Current2D = t;
               }
               else {
                  /* wrong dimensionality */
                  gl_error( ctx, GL_INVALID_OPERATION, "glBindTexture" );
                  return;
               }
            }
            else {
               /* create new texture object */
               t = gl_alloc_texture_object();
               append_texture_object( ctx->Shared->TexObjectList, t );
               t->Name = texture;
               t->Dimensions = 2;
               ctx->Texture.Current2D = t;
            }
         }
         /* Tidy up reference counting */
         if (ctx->Texture.Current2D != oldtexobj && oldtexobj->Name>0) {
            /* decr reference count of the prev texture object */
            oldtexobj->RefCount--;
            assert( oldtexobj->RefCount >= 0 );
         }
         if (ctx->Texture.Current2D->Name>0) {
            ctx->Texture.Current2D->RefCount++;
         }
#ifdef FOO
         if (!ctx->Texture.Current2D->Complete) {
            /* re-examine texture completeness */
            gl_update_texture_state();
         }
#endif

         /* Check if we may have to use a new triangle rasterizer */
         if (oldtexobj->WrapS != ctx->Texture.Current1D->WrapS
             || oldtexobj->WrapT != ctx->Texture.Current1D->WrapT
             || oldtexobj->MinFilter != ctx->Texture.Current1D->MinFilter
             || oldtexobj->MagFilter != ctx->Texture.Current1D->MagFilter) {
            ctx->NewState |= NEW_RASTER_OPS;
         }

         /* The current 2D texture object can never be NULL! */
         assert(ctx->Texture.Current2D);
         break;

      case GL_TEXTURE_3D_EXT:
         oldtexobj = ctx->Texture.Current3D;
         if (texture==0) {
            /* use default 3-D texture */
            ctx->Texture.Current1D = ctx->Shared->TexObjectList->Next->Next;
         }
         else {
            struct gl_texture_object *t;
            t = find_texture_object( ctx, texture );
            if (t) {
               if (t->Dimensions==3) {
                  /* success */
                  ctx->Texture.Current3D = t;
               }
               else {
                  /* wrong dimensionality */
                  gl_error( ctx, GL_INVALID_OPERATION, "glBindTexture" );
                  return;
               }
            }
            else {
               /* create new texture object */
               t = gl_alloc_texture_object();
               append_texture_object( ctx->Shared->TexObjectList, t );
               t->Name = texture;
               t->Dimensions = 3;
               ctx->Texture.Current3D = t;
            }
         }
         /* Tidy up reference counting */
         if (ctx->Texture.Current3D != oldtexobj && oldtexobj->Name>0) {
            /* decr reference count of the prev texture object */
            oldtexobj->RefCount--;
            assert( oldtexobj->RefCount >= 0 );
         }
         if (ctx->Texture.Current3D->Name>0) {
            ctx->Texture.Current3D->RefCount++;
         }
#ifdef FOO
         if (!ctx->Texture.Current3D->Complete) {
            /* re-examine texture completeness */
            gl_update_texture_state();
         }
#endif

         /* Check if we may have to use a new triangle rasterizer */
         if (oldtexobj->WrapS != ctx->Texture.Current3D->WrapS
             || oldtexobj->WrapT != ctx->Texture.Current3D->WrapT
             || oldtexobj->MinFilter != ctx->Texture.Current3D->MinFilter
             || oldtexobj->MagFilter != ctx->Texture.Current3D->MagFilter) {
            ctx->NewState |= NEW_RASTER_OPS;
         }

         /* The current 3D texture object can never be NULL! */
         assert(ctx->Texture.Current3D);
         break;

      default:
         gl_error( ctx, GL_INVALID_ENUM, "glBindTexture" );
         return;
   }
 
   /* Pass BindTexture to device driver */
   if (ctx->Driver.BindTexture) {
      (*ctx->Driver.BindTexture)( ctx, target, texture );
   }
}



void gl_PrioritizeTextures( GLcontext *ctx,
                            GLsizei n, const GLuint *textures,
                            const GLclampf *priorities )
{
   GLuint i;

   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glAreTexturesResident" );
      return;
   }
   if (n<0) {
      gl_error( ctx, GL_INVALID_VALUE, "glAreTexturesResident(n)" );
      return;
   }

   for (i=0;i<n;i++) {
      struct gl_texture_object *t;
      if (textures[i]>0) {
         t = find_texture_object( ctx, textures[i] );
         if (t) {
            t->Priority = CLAMP( priorities[i], 0.0F, 1.0F );
         }
      }
   }
}



GLboolean gl_AreTexturesResident( GLcontext *ctx, GLsizei n,
                                  const GLuint *textures,
                                  GLboolean *residences )
{
   GLboolean resident = GL_TRUE;
   GLuint i;

   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glAreTexturesResident" );
      return GL_FALSE;
   }
   if (n<0) {
      gl_error( ctx, GL_INVALID_VALUE, "glAreTexturesResident(n)" );
      return GL_FALSE;
   }

   for (i=0;i<n;i++) {
      struct gl_texture_object *t;
      if (textures[i]==0) {
         gl_error( ctx, GL_INVALID_VALUE, "glAreTexturesResident(textures)" );
         return GL_FALSE;
      }
      t = find_texture_object( ctx, textures[i] );
      if (t) {
         /* we consider all valid texture objects to be resident */
         residences[i] = GL_TRUE;
      }
      else {
         gl_error( ctx, GL_INVALID_VALUE, "glAreTexturesResident(textures)" );
         return GL_FALSE;
      }
   }
   return resident;
}



GLboolean gl_IsTexture( GLcontext *ctx, GLuint texture )
{
   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glIsTextures" );
      return GL_FALSE;
   }
   if (texture>0 && find_texture_object(ctx,texture)) {
      return GL_TRUE;
   }
   else {
      return GL_FALSE;
   }
}

