/* $Id: texture.c,v 1.36 1997/11/17 02:04:06 brianp Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  2.5
 * 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.
 */


/*
 * $Log: texture.c,v $
 * Revision 1.36  1997/11/17 02:04:06  brianp
 * fixed a typo in texture code
 *
 * Revision 1.35  1997/11/13 02:17:32  brianp
 * added a few const keywords
 *
 * Revision 1.34  1997/10/16 01:59:08  brianp
 * added GL_EXT_shared_texture_palette extension
 *
 * Revision 1.33  1997/09/27 00:14:39  brianp
 * added GL_EXT_paletted_texture extension
 *
 * Revision 1.32  1997/08/14 01:03:12  brianp
 * fixed small bug in GL_SPHERE_MAP texgen (Petri Nordlund)
 *
 * Revision 1.31  1997/07/24 01:25:34  brianp
 * changed precompiled header symbol from PCH to PC_HEADER
 *
 * Revision 1.30  1997/07/22 01:23:48  brianp
 * fixed negative texture coord sampling problem (Magnus Lundin)
 *
 * Revision 1.29  1997/07/09 00:32:26  brianp
 * fixed bug involving sampling and incomplete texture objects
 *
 * Revision 1.28  1997/05/28 03:26:49  brianp
 * added precompiled header (PCH) support
 *
 * Revision 1.27  1997/05/17 03:51:10  brianp
 * fixed bug in PROD macro (Waldemar Celes)
 *
 * Revision 1.26  1997/05/03 00:53:56  brianp
 * all-new texture sampling via gl_texture_object's SampleFunc pointer
 *
 * Revision 1.25  1997/05/01 01:40:14  brianp
 * replaced sqrt() with GL_SQRT, use NORMALIZE_3FV instead of NORMALIZE_3V
 *
 * Revision 1.24  1997/04/28 02:04:18  brianp
 * GL_SPHERE_MAP texgen was wrong (Eamon O'Dea)
 *
 * Revision 1.23  1997/04/20 20:29:11  brianp
 * replaced abort() with gl_problem()
 *
 * Revision 1.22  1997/04/16 23:56:41  brianp
 * added a few #include files
 *
 * Revision 1.21  1997/04/14 02:02:39  brianp
 * moved many functions into new texstate.c file
 *
 * Revision 1.20  1997/04/01 04:19:14  brianp
 * call gl_analyze_modelview_matrix instead of gl_compute_modelview_inverse
 *
 * Revision 1.19  1997/03/04 19:55:58  brianp
 * small texture sampling optimizations.  better comments.
 *
 * Revision 1.18  1997/03/04 19:19:20  brianp
 * fixed a number of problems with texture borders
 *
 * Revision 1.17  1997/02/27 19:58:08  brianp
 * call gl_problem() instead of gl_warning()
 *
 * Revision 1.16  1997/02/09 19:53:43  brianp
 * now use TEXTURE_xD enable constants
 *
 * Revision 1.15  1997/02/09 18:53:14  brianp
 * added GL_EXT_texture3D support
 *
 * Revision 1.14  1997/01/30 21:06:03  brianp
 * added some missing glGetTexLevelParameter() GLenums
 *
 * Revision 1.13  1997/01/16 03:36:01  brianp
 * added calls to device driver TexParameter() and TexEnv() functions
 *
 * Revision 1.12  1997/01/09 19:48:30  brianp
 * better error checking
 * added gl_texturing_enabled()
 *
 * Revision 1.11  1996/12/20 20:22:30  brianp
 * linear interpolation between mipmap levels was reverse weighted
 * max mipmap level was incorrectly tested for
 *
 * Revision 1.10  1996/12/12 22:33:05  brianp
 * minor changes to gl_texgen()
 *
 * Revision 1.9  1996/12/07 10:35:41  brianp
 * implmented glGetTexGen*() functions
 *
 * Revision 1.8  1996/11/14 01:03:09  brianp
 * removed const's from gl_texgen() function to avoid VMS compiler warning
 *
 * Revision 1.7  1996/11/08 02:19:52  brianp
 * gl_do_texgen() replaced with gl_texgen()
 *
 * Revision 1.6  1996/10/26 17:17:30  brianp
 * glTexGen GL_EYE_PLANE vector now transformed by inverse modelview matrix
 *
 * Revision 1.5  1996/10/11 03:42:38  brianp
 * replaced old _EXT symbols
 *
 * Revision 1.4  1996/09/27 01:30:24  brianp
 * added missing default cases to switches
 *
 * Revision 1.3  1996/09/15 14:18:55  brianp
 * now use GLframebuffer and GLvisual
 *
 * Revision 1.2  1996/09/15 01:48:58  brianp
 * removed #define NULL 0
 *
 * Revision 1.1  1996/09/13 01:38:16  brianp
 * Initial revision
 *
 */


#ifdef PC_HEADER
#include "all.h"
#else
#include <math.h>
#include <stdlib.h>
#include "context.h"
#include "macros.h"
#include "mmath.h"
#include "pb.h"
#include "texture.h"
#include "types.h"
#endif



/*
 * Perform automatic texture coordinate generation.
 * Input:  ctx - the context
 *         n - number of texture coordinates to generate
 *         obj - array of vertexes in object coordinate system
 *         eye - array of vertexes in eye coordinate system
 *         normal - array of normal vectores in eye coordinate system
 * Output:  texcoord - array of resuling texture coordinates
 */
void gl_texgen( GLcontext *ctx, GLint n,
                GLfloat obj[][4], GLfloat eye[][4],
                GLfloat normal[][3], GLfloat texcoord[][4] )
{
   /* special case: S and T sphere mapping */
   if (ctx->Texture.TexGenEnabled==(S_BIT|T_BIT)
       && ctx->Texture.GenModeS==GL_SPHERE_MAP
       && ctx->Texture.GenModeT==GL_SPHERE_MAP) {
      GLint i;
      for (i=0;i<n;i++) {
         GLfloat u[3], two_nu, m, fx, fy, fz;
         COPY_3V( u, eye[i] );
         NORMALIZE_3FV( u );
         two_nu = 2.0F * DOT3(normal[i],u);
         fx = u[0] - normal[i][0] * two_nu;
         fy = u[1] - normal[i][1] * two_nu;
         fz = u[2] - normal[i][2] * two_nu;
         m = 2.0F * GL_SQRT( fx*fx + fy*fy + (fz+1.0F)*(fz+1.0F) );
         if (m==0.0F) {
            texcoord[i][0] = 0.5F;
            texcoord[i][1] = 0.5F;
         }
         else {
            GLfloat mInv = 1.0F / m;
            texcoord[i][0] = fx * mInv + 0.5F;
            texcoord[i][1] = fy * mInv + 0.5F;
         }
      }
      return;
   }

   /* general solution */
   if (ctx->Texture.TexGenEnabled & S_BIT) {
      GLint i;
      switch (ctx->Texture.GenModeS) {
         case GL_OBJECT_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][0] = DOT4( obj[i], ctx->Texture.ObjectPlaneS );
            }
            break;
         case GL_EYE_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][0] = DOT4( eye[i], ctx->Texture.EyePlaneS );
            }
            break;
         case GL_SPHERE_MAP:
            for (i=0;i<n;i++) {
               GLfloat u[3], two_nu, m, fx, fy, fz;
               COPY_3V( u, eye[i] );
               NORMALIZE_3FV( u );
               two_nu = 2.0*DOT3(normal[i],u);
               fx = u[0] - normal[i][0] * two_nu;
               fy = u[1] - normal[i][1] * two_nu;
               fz = u[2] - normal[i][2] * two_nu;
               m = 2.0F * GL_SQRT( fx*fx + fy*fy + (fz+1.0)*(fz+1.0) );
               if (m==0.0F) {
                  texcoord[i][0] = 0.5F;
               }
               else {
                  texcoord[i][0] = fx / m + 0.5F;
               }
            }
            break;
         default:
            gl_problem(ctx, "Bad S texgen");
            return;
      }
   }

   if (ctx->Texture.TexGenEnabled & T_BIT) {
      GLint i;
      switch (ctx->Texture.GenModeT) {
         case GL_OBJECT_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][1] = DOT4( obj[i], ctx->Texture.ObjectPlaneT );
            }
            break;
         case GL_EYE_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][1] = DOT4( eye[i], ctx->Texture.EyePlaneT );
            }
            break;
         case GL_SPHERE_MAP:
            for (i=0;i<n;i++) {
               GLfloat u[3], two_nu, m, fx, fy, fz;
               COPY_3V( u, eye[i] );
               NORMALIZE_3FV( u );
               two_nu = 2.0*DOT3(normal[i],u);
               fx = u[0] - normal[i][0] * two_nu;
               fy = u[1] - normal[i][1] * two_nu;
               fz = u[2] - normal[i][2] * two_nu;
               m = 2.0F * GL_SQRT( fx*fx + fy*fy + (fz+1.0)*(fz+1.0) );
               if (m==0.0F) {
                  texcoord[i][1] = 0.5F;
               }
               else {
                  texcoord[i][1] = fy / m + 0.5F;
               }
            }
            break;
         default:
            gl_problem(ctx, "Bad T texgen");
            return;
      }
   }

   if (ctx->Texture.TexGenEnabled & R_BIT) {
      GLint i;
      switch (ctx->Texture.GenModeR) {
         case GL_OBJECT_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][2] = DOT4( obj[i], ctx->Texture.ObjectPlaneR );
            }
            break;
         case GL_EYE_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][2] = DOT4( eye[i], ctx->Texture.EyePlaneR );
            }
            break;
         default:
            gl_problem(ctx, "Bad R texgen");
            return;
      }
   }

   if (ctx->Texture.TexGenEnabled & Q_BIT) {
      GLint i;
      switch (ctx->Texture.GenModeQ) {
         case GL_OBJECT_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][3] = DOT4( obj[i], ctx->Texture.ObjectPlaneQ );
            }
            break;
         case GL_EYE_LINEAR:
            for (i=0;i<n;i++) {
               texcoord[i][3] = DOT4( eye[i], ctx->Texture.EyePlaneQ );
            }
            break;
         default:
            gl_problem(ctx, "Bad Q texgen");
            return;
      }
   }
}



/*
 * Paletted texture sampling.
 * Input:  tObj - the texture object
 *         index - the palette index (8-bit only)
 * Output:  red, green, blue, alpha - the texel color
 */
static void palette_sample(const struct gl_texture_object *tObj,
                           GLubyte index, GLubyte *red, GLubyte *green,
                           GLubyte *blue, GLubyte *alpha)
{
   GLcontext *ctx = gl_get_current_context();  /* THIS IS A HACK */
   GLint i = index;
   const GLubyte *palette;

   if (ctx->Texture.SharedPalette) {
      palette = ctx->Texture.Palette;
   }
   else {
      palette = tObj->Palette;
   }

   switch (tObj->PaletteFormat) {
      case GL_ALPHA:
         *alpha = tObj->Palette[index];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red = palette[index];
         return;
      case GL_LUMINANCE_ALPHA:
         *red   = palette[(index << 1) + 0];
         *alpha = palette[(index << 1) + 1];
         return;
      case GL_RGB:
         *red   = palette[index * 3 + 0];
         *green = palette[index * 3 + 1];
         *blue  = palette[index * 3 + 2];
         return;
      case GL_RGBA:
         *red   = palette[(i << 2) + 0];
         *green = palette[(i << 2) + 1];
         *blue  = palette[(i << 2) + 2];
         *alpha = palette[(i << 2) + 3];
         return;
      default:
         gl_problem(NULL, "Bad palette format in palette_sample");
   }
}




/**********************************************************************/
/*                    1-D Texture Sampling Functions                  */
/**********************************************************************/


/*
 * Return the fractional part of x.
 */
#define frac(x) ((GLfloat)(x)-floor((GLfloat)x))



/*
 * Given 1-D texture image and an (i) texel column coordinate, return the
 * texel color.
 */
static void get_1d_texel( const struct gl_texture_object *tObj,
                          const struct gl_texture_image *img, GLint i,
                          GLubyte *red, GLubyte *green, GLubyte *blue,
                          GLubyte *alpha )
{
   GLubyte *texel;

#ifdef DEBUG
   GLint width = img->Width;
   if (i<0 || i>=width)  abort();
#endif

   switch (img->Format) {
      case GL_COLOR_INDEX:
         {
            GLubyte index = img->Data[i];
            palette_sample(tObj, index, red, green, blue, alpha);
            return;
         }
         return;
      case GL_ALPHA:
         *alpha = img->Data[ i ];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red   = img->Data[ i ];
         return;
      case GL_LUMINANCE_ALPHA:
         texel = img->Data + i * 2;
         *red   = texel[0];
         *alpha = texel[1];
         return;
      case GL_RGB:
         texel = img->Data + i * 3;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         return;
      case GL_RGBA:
         texel = img->Data + i * 4;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         *alpha = texel[3];
         return;
      default:
         gl_problem(NULL, "Bad format in get_1d_texel");
         return;
   }
}



/*
 * Return the texture sample for coordinate (s) using GL_NEAREST filter.
 */
static void sample_1d_nearest( const struct gl_texture_object *tObj,
                               const struct gl_texture_image *img,
                               GLfloat s,
                               GLubyte *red, GLubyte *green,
                               GLubyte *blue, GLubyte *alpha )
{
   GLint width = img->Width2;  /* without border, power of two */
   GLint i;
   GLubyte *texel;

   /* Clamp/Repeat S and convert to integer texel coordinate */
   if (tObj->WrapS==GL_REPEAT) {
      /* s limited to [0,1) */
      /* i limited to [0,width-1] */
      i = (GLint) (s * width);
      if (s<0.0F)  i -= 1;
      i &= (width-1);
   }
   else {
      /* s limited to [0,1] */
      /* i limited to [0,width-1] */
      if (s<0.0F)        i = 0;
      else if (s>1.0F)   i = width-1;
      else               i = (GLint) (s * width);
   }

   /* skip over the border, if any */
   i += img->Border;

   /* Get the texel */
   switch (img->Format) {
      case GL_COLOR_INDEX:
         {
            GLubyte index = img->Data[i];
            palette_sample(tObj, index, red, green, blue, alpha);
            return;
         }
      case GL_ALPHA:
         *alpha = img->Data[i];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red   = img->Data[i];
         return;
      case GL_LUMINANCE_ALPHA:
         texel = img->Data + i * 2;
         *red   = texel[0];
         *alpha = texel[1];
         return;
      case GL_RGB:
         texel = img->Data + i * 3;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         return;
      case GL_RGBA:
         texel = img->Data + i * 4;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         *alpha = texel[3];
         return;
      default:
         gl_problem(NULL, "Bad format in sample_1d_nearest");
   }
}



/*
 * Return the texture sample for coordinate (s) using GL_LINEAR filter.
 */
static void sample_1d_linear( const struct gl_texture_object *tObj,
                              const struct gl_texture_image *img,
                              GLfloat s,
                              GLubyte *red, GLubyte *green,
                              GLubyte *blue, GLubyte *alpha )
{
   GLint width = img->Width2;
   GLint i0, i1;
   GLfloat u;
   GLint i0border, i1border;

   u = s * width;
   if (tObj->WrapS==GL_REPEAT) {
      i0 = ((GLint) floor(u - 0.5F)) % width;
      i1 = (i0 + 1) & (width-1);
      i0border = i1border = 0;
   }
   else {
      i0 = (GLint) floor(u - 0.5F);
      i1 = i0 + 1;
      i0border = (i0<0) | (i0>=width);
      i1border = (i1<0) | (i1>=width);
   }

   if (img->Border) {
      i0 += img->Border;
      i1 += img->Border;
      i0border = i1border = 0;
   }
   else {
      i0 &= (width-1);
   }

   {
      GLfloat a = frac(u - 0.5F);

      GLint w0 = (GLint) ((1.0F-a) * 256.0F);
      GLint w1 = (GLint) (      a  * 256.0F);

      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;

      if (i0border) {
         red0   = tObj->BorderColor[0];
         green0 = tObj->BorderColor[1];
         blue0  = tObj->BorderColor[2];
         alpha0 = tObj->BorderColor[3];
      }
      else {
         get_1d_texel( tObj, img, i0, &red0, &green0, &blue0, &alpha0 );
      }
      if (i1border) {
         red1   = tObj->BorderColor[0];
         green1 = tObj->BorderColor[1];
         blue1  = tObj->BorderColor[2];
         alpha1 = tObj->BorderColor[3];
      }
      else {
         get_1d_texel( tObj, img, i1, &red1, &green1, &blue1, &alpha1 );
      }

      *red   = (w0*red0   + w1*red1)   >> 8;
      *green = (w0*green0 + w1*green1) >> 8;
      *blue  = (w0*blue0  + w1*blue1)  >> 8;
      *alpha = (w0*alpha0 + w1*alpha1) >> 8;
   }
}


static void
sample_1d_nearest_mipmap_nearest( const struct gl_texture_object *tObj,
                                  GLfloat s, GLfloat lambda,
                                  GLubyte *red, GLubyte *green,
                                  GLubyte *blue, GLubyte *alpha )
{
   GLint level;
   if (lambda<=0.5F) {
      level = 0;
   }
   else {
      GLint widthlog2 = tObj->Image[0]->WidthLog2;
      level = (GLint) (lambda + 0.499999F);
      if (level>widthlog2 ) {
         level = widthlog2;
      }
   }
   sample_1d_nearest( tObj, tObj->Image[level],
                      s, red, green, blue, alpha );
}


static void
sample_1d_linear_mipmap_nearest( const struct gl_texture_object *tObj,
                                 GLfloat s, GLfloat lambda,
                                 GLubyte *red, GLubyte *green,
                                 GLubyte *blue, GLubyte *alpha )
{
   GLint level;
   if (lambda<=0.5F) {
      level = 0;
   }
   else {
      GLint widthlog2 = tObj->Image[0]->WidthLog2;
      level = (GLint) (lambda + 0.499999F);
      if (level>widthlog2 ) {
         level = widthlog2;
      }
   }
   sample_1d_linear( tObj, tObj->Image[level],
                     s, red, green, blue, alpha );
}



static void
sample_1d_nearest_mipmap_linear( const struct gl_texture_object *tObj,
                                 GLfloat s, GLfloat lambda,
                                 GLubyte *red, GLubyte *green,
                                 GLubyte *blue, GLubyte *alpha )
{
   GLint max = tObj->Image[0]->MaxLog2;

   if (lambda>=max) {
      sample_1d_nearest( tObj, tObj->Image[max],
                         s, red, green, blue, alpha );
   }
   else {
      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;
      GLfloat f = frac(lambda);
      GLint level = (GLint) (lambda + 1.0F);
      level = CLAMP( level, 1, max );
      sample_1d_nearest( tObj, tObj->Image[level-1],
                         s, &red0, &green0, &blue0, &alpha0 );
      sample_1d_nearest( tObj, tObj->Image[level],
                         s, &red1, &green1, &blue1, &alpha1 );
      *red   = (1.0F-f)*red0   + f*red1;
      *green = (1.0F-f)*green0 + f*green1;
      *blue  = (1.0F-f)*blue0  + f*blue1;
      *alpha = (1.0F-f)*alpha0 + f*alpha1;
   }
}



static void
sample_1d_linear_mipmap_linear( const struct gl_texture_object *tObj,
                                GLfloat s, GLfloat lambda,
                                GLubyte *red, GLubyte *green,
                                GLubyte *blue, GLubyte *alpha )
{
   GLint max = tObj->Image[0]->MaxLog2;

   if (lambda>=max) {
      sample_1d_linear( tObj, tObj->Image[max],
                        s, red, green, blue, alpha );
   }
   else {
      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;
      GLfloat f = frac(lambda);
      GLint level = (GLint) (lambda + 1.0F);
      level = CLAMP( level, 1, max );
      sample_1d_linear( tObj, tObj->Image[level-1],
                        s, &red0, &green0, &blue0, &alpha0 );
      sample_1d_linear( tObj, tObj->Image[level],
                        s, &red1, &green1, &blue1, &alpha1 );
      *red   = (1.0F-f)*red0   + f*red1;
      *green = (1.0F-f)*green0 + f*green1;
      *blue  = (1.0F-f)*blue0  + f*blue1;
      *alpha = (1.0F-f)*alpha0 + f*alpha1;
   }
}



static void sample_nearest_1d( const struct gl_texture_object *tObj, GLuint n,
                               const GLfloat s[], const GLfloat t[],
                               const GLfloat u[], const GLfloat lambda[],
                               GLubyte red[], GLubyte green[], GLubyte blue[],
                               GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      sample_1d_nearest( tObj, tObj->Image[0], s[i],
                         &red[i], &green[i], &blue[i], &alpha[i]);
   }
}



static void sample_linear_1d( const struct gl_texture_object *tObj, GLuint n,
                              const GLfloat s[], const GLfloat t[],
                              const GLfloat u[], const GLfloat lambda[],
                              GLubyte red[], GLubyte green[], GLubyte blue[],
                              GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      sample_1d_linear( tObj, tObj->Image[0], s[i],
                        &red[i], &green[i], &blue[i], &alpha[i]);
   }
}


/*
 * Given an (s) texture coordinate and lambda (level of detail) value,
 * return a texture sample.
 *
 */
static void sample_lambda_1d( const struct gl_texture_object *tObj, GLuint n,
                              const GLfloat s[], const GLfloat t[],
                              const GLfloat u[], const GLfloat lambda[],
                              GLubyte red[], GLubyte green[], GLubyte blue[],
                              GLubyte alpha[] )
{
   GLuint i;

   for (i=0;i<n;i++) {
      if (lambda[i] > tObj->MinMagThresh) {
         /* minification */
         switch (tObj->MinFilter) {
            case GL_NEAREST:
               sample_1d_nearest( tObj, tObj->Image[0], s[i],
                                  &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR:
               sample_1d_linear( tObj, tObj->Image[0], s[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_NEAREST_MIPMAP_NEAREST:
               sample_1d_nearest_mipmap_nearest( tObj, lambda[i], s[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR_MIPMAP_NEAREST:
               sample_1d_linear_mipmap_nearest( tObj, s[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_NEAREST_MIPMAP_LINEAR:
               sample_1d_nearest_mipmap_linear( tObj, s[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR_MIPMAP_LINEAR:
               sample_1d_linear_mipmap_linear( tObj, s[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            default:
               gl_problem(NULL, "Bad min filter in sample_1d_texture");
               return;
         }
      }
      else {
         /* magnification */
         switch (tObj->MagFilter) {
            case GL_NEAREST:
               sample_1d_nearest( tObj, tObj->Image[0], s[i],
                                  &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR:
               sample_1d_linear( tObj, tObj->Image[0], s[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            default:
               gl_problem(NULL, "Bad mag filter in sample_1d_texture");
               return;
         }
      }
   }
}




/**********************************************************************/
/*                    2-D Texture Sampling Functions                  */
/**********************************************************************/


/*
 * Given a texture image and an (i,j) integer texel coordinate, return the
 * texel color.
 */
static void get_2d_texel( const struct gl_texture_object *tObj,
                          const struct gl_texture_image *img, GLint i, GLint j,
                          GLubyte *red, GLubyte *green, GLubyte *blue,
                          GLubyte *alpha )
{
   GLint width = img->Width;    /* includes border */
   GLubyte *texel;

#ifdef DEBUG
   GLint height = img->Height;  /* includes border */
   if (i<0 || i>=width)  abort();
   if (j<0 || j>=height)  abort();
#endif

   switch (img->Format) {
      case GL_COLOR_INDEX:
         {
            GLubyte index = img->Data[ width *j + i ];
            palette_sample(tObj, index, red, green, blue, alpha);
            return;
         }
      case GL_ALPHA:
         *alpha = img->Data[ width * j + i ];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red   = img->Data[ width * j + i ];
         return;
      case GL_LUMINANCE_ALPHA:
         texel = img->Data + (width * j + i) * 2;
         *red   = texel[0];
         *alpha = texel[1];
         return;
      case GL_RGB:
         texel = img->Data + (width * j + i) * 3;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         return;
      case GL_RGBA:
         texel = img->Data + (width * j + i) * 4;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         *alpha = texel[3];
         return;
      default:
         gl_problem(NULL, "Bad format in get_2d_texel");
   }
}



/*
 * Return the texture sample for coordinate (s,t) using GL_NEAREST filter.
 */
static void sample_2d_nearest( const struct gl_texture_object *tObj,
                               const struct gl_texture_image *img,
                               GLfloat s, GLfloat t,
                               GLubyte *red, GLubyte *green,
                               GLubyte *blue, GLubyte *alpha )
{
   GLint imgWidth = img->Width;  /* includes border */
   GLint width = img->Width2;    /* without border, power of two */
   GLint height = img->Height2;  /* without border, power of two */
   GLint i, j;
   GLubyte *texel;

   /* Clamp/Repeat S and convert to integer texel coordinate */
   if (tObj->WrapS==GL_REPEAT) {
      /* s limited to [0,1) */
      /* i limited to [0,width-1] */
      i = (GLint) (s * width);
      if (s<0.0F)  i -= 1;
      i &= (width-1);
   }
   else {
      /* s limited to [0,1] */
      /* i limited to [0,width-1] */
      if (s<=0.0F)      i = 0;
      else if (s>1.0F)  i = width-1;
      else              i = (GLint) (s * width);
   }

   /* Clamp/Repeat T and convert to integer texel coordinate */
   if (tObj->WrapT==GL_REPEAT) {
      /* t limited to [0,1) */
      /* j limited to [0,height-1] */
      j = (GLint) (t * height);
      if (t<0.0F)  j -= 1;
      j &= (height-1);
   }
   else {
      /* t limited to [0,1] */
      /* j limited to [0,height-1] */
      if (t<=0.0F)      j = 0;
      else if (t>1.0F)  j = height-1;
      else              j = (GLint) (t * height);
   }

   /* skip over the border, if any */
   i += img->Border;
   j += img->Border;

   switch (img->Format) {
      case GL_COLOR_INDEX:
         {
            GLubyte index = img->Data[ j * imgWidth + i ];
            palette_sample(tObj, index, red, green, blue, alpha);
            return;
         }
      case GL_ALPHA:
         *alpha = img->Data[ j * imgWidth + i ];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red   = img->Data[ j * imgWidth + i ];
         return;
      case GL_LUMINANCE_ALPHA:
         texel = img->Data + ((j * imgWidth + i) << 1);
         *red   = texel[0];
         *alpha = texel[1];
         return;
      case GL_RGB:
         texel = img->Data + (j * imgWidth + i) * 3;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         return;
      case GL_RGBA:
         texel = img->Data + ((j * imgWidth + i) << 2);
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         *alpha = texel[3];
         return;
      default:
         gl_problem(NULL, "Bad format in sample_2d_nearest");
   }
}



/*
 * Return the texture sample for coordinate (s,t) using GL_LINEAR filter.
 */
static void sample_2d_linear( const struct gl_texture_object *tObj,
                              const struct gl_texture_image *img,
                              GLfloat s, GLfloat t,
                              GLubyte *red, GLubyte *green,
                              GLubyte *blue, GLubyte *alpha )
{
   GLint width = img->Width2;
   GLint height = img->Height2;
   GLint i0, j0, i1, j1;
   GLint i0border, j0border, i1border, j1border;
   GLfloat u, v;

   u = s * width;
   if (tObj->WrapS==GL_REPEAT) {
      i0 = ((GLint) floor(u - 0.5F)) % width;
      i1 = (i0 + 1) & (width-1);
      i0border = i1border = 0;
   }
   else {
      i0 = (GLint) floor(u - 0.5F);
      i1 = i0 + 1;
      i0border = (i0<0) | (i0>=width);
      i1border = (i1<0) | (i1>=width);
   }

   v = t * height;
   if (tObj->WrapT==GL_REPEAT) {
      j0 = ((GLint) floor(v - 0.5F)) % height;
      j1 = (j0 + 1) & (height-1);
      j0border = j1border = 0;
   }
   else {
      j0 = (GLint) floor(v - 0.5F );
      j1 = j0 + 1;
      j0border = (j0<0) | (j0>=height);
      j1border = (j1<0) | (j1>=height);
   }

   if (img->Border) {
      i0 += img->Border;
      i1 += img->Border;
      j0 += img->Border;
      j1 += img->Border;
      i0border = i1border = 0;
      j0border = j1border = 0;
   }
   else {
      i0 &= (width-1);
      j0 &= (height-1);
   }

   {
      GLfloat a = frac(u - 0.5F);
      GLfloat b = frac(v - 0.5F);

      GLint w00 = (GLint) ((1.0F-a)*(1.0F-b) * 256.0F);
      GLint w10 = (GLint) (      a *(1.0F-b) * 256.0F);
      GLint w01 = (GLint) ((1.0F-a)*      b  * 256.0F);
      GLint w11 = (GLint) (      a *      b  * 256.0F);

      GLubyte red00, green00, blue00, alpha00;
      GLubyte red10, green10, blue10, alpha10;
      GLubyte red01, green01, blue01, alpha01;
      GLubyte red11, green11, blue11, alpha11;

      if (i0border | j0border) {
         red00   = tObj->BorderColor[0];
         green00 = tObj->BorderColor[1];
         blue00  = tObj->BorderColor[2];
         alpha00 = tObj->BorderColor[3];
      }
      else {
         get_2d_texel( tObj, img, i0, j0, &red00, &green00, &blue00, &alpha00);
      }
      if (i1border | j0border) {
         red10   = tObj->BorderColor[0];
         green10 = tObj->BorderColor[1];
         blue10  = tObj->BorderColor[2];
         alpha10 = tObj->BorderColor[3];
      }
      else {
         get_2d_texel( tObj, img, i1, j0, &red10, &green10, &blue10, &alpha10);
      }
      if (i0border | j1border) {
         red01   = tObj->BorderColor[0];
         green01 = tObj->BorderColor[1];
         blue01  = tObj->BorderColor[2];
         alpha01 = tObj->BorderColor[3];
      }
      else {
         get_2d_texel( tObj, img, i0, j1, &red01, &green01, &blue01, &alpha01);
      }
      if (i1border | j1border) {
         red11   = tObj->BorderColor[0];
         green11 = tObj->BorderColor[1];
         blue11  = tObj->BorderColor[2];
         alpha11 = tObj->BorderColor[3];
      }
      else {
         get_2d_texel( tObj, img, i1, j1, &red11, &green11, &blue11, &alpha11);
      }

      *red   = (w00*red00   + w10*red10   + w01*red01   + w11*red11  ) >> 8;
      *green = (w00*green00 + w10*green10 + w01*green01 + w11*green11) >> 8;
      *blue  = (w00*blue00  + w10*blue10  + w01*blue01  + w11*blue11 ) >> 8;
      *alpha = (w00*alpha00 + w10*alpha10 + w01*alpha01 + w11*alpha11) >> 8;
   }
}



static void
sample_2d_nearest_mipmap_nearest( const struct gl_texture_object *tObj,
                                  GLfloat s, GLfloat t, GLfloat lambda,
                                  GLubyte *red, GLubyte *green,
                                  GLubyte *blue, GLubyte *alpha )
{
   GLint level;
   if (lambda<=0.5F) {
      level = 0;
   }
   else {
      GLint max = tObj->Image[0]->MaxLog2;
      level = (GLint) (lambda + 0.499999F);
      if (level>max) {
         level = max;
      }
   }
   sample_2d_nearest( tObj, tObj->Image[level],
                      s, t, red, green, blue, alpha );
}



static void
sample_2d_linear_mipmap_nearest( const struct gl_texture_object *tObj,
                                 GLfloat s, GLfloat t, GLfloat lambda,
                                 GLubyte *red, GLubyte *green,
                                 GLubyte *blue, GLubyte *alpha )
{
   GLint level;
   if (lambda<=0.5F) {
      level = 0;
   }
   else {
      GLint max = tObj->Image[0]->MaxLog2;
      level = (GLint) (lambda + 0.499999F);
      if (level>max) {
         level = max;
      }
   }
   sample_2d_linear( tObj, tObj->Image[level],
                     s, t, red, green, blue, alpha );
}



static void
sample_2d_nearest_mipmap_linear( const struct gl_texture_object *tObj,
                                 GLfloat s, GLfloat t, GLfloat lambda,
                                 GLubyte *red, GLubyte *green,
                                 GLubyte *blue, GLubyte *alpha )
{
   GLint max = tObj->Image[0]->MaxLog2;

   if (lambda>=max) {
      sample_2d_nearest( tObj, tObj->Image[max],
                         s, t, red, green, blue, alpha );
   }
   else {
      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;
      GLfloat f = frac(lambda);
      GLint level = (GLint) (lambda + 1.0F);
      level = CLAMP( level, 1, max );
      sample_2d_nearest( tObj, tObj->Image[level-1], s, t,
                         &red0, &green0, &blue0, &alpha0 );
      sample_2d_nearest( tObj, tObj->Image[level], s, t,
                         &red1, &green1, &blue1, &alpha1 );
      *red   = (1.0F-f)*red0   + f*red1;
      *green = (1.0F-f)*green0 + f*green1;
      *blue  = (1.0F-f)*blue0  + f*blue1;
      *alpha = (1.0F-f)*alpha0 + f*alpha1;
   }
}



static void
sample_2d_linear_mipmap_linear( const struct gl_texture_object *tObj,
                                GLfloat s, GLfloat t, GLfloat lambda,
                                GLubyte *red, GLubyte *green,
                                GLubyte *blue, GLubyte *alpha )
{
   GLint max = tObj->Image[0]->MaxLog2;

   if (lambda>=max) {
      sample_2d_linear( tObj, tObj->Image[max],
                        s, t, red, green, blue, alpha );
   }
   else {
      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;
      GLfloat f = frac(lambda);
      GLint level = (GLint) (lambda + 1.0F);
      level = CLAMP( level, 1, max );
      sample_2d_linear( tObj, tObj->Image[level-1], s, t,
                        &red0, &green0, &blue0, &alpha0 );
      sample_2d_linear( tObj, tObj->Image[level], s, t,
                        &red1, &green1, &blue1, &alpha1 );
      *red   = (1.0F-f)*red0   + f*red1;
      *green = (1.0F-f)*green0 + f*green1;
      *blue  = (1.0F-f)*blue0  + f*blue1;
      *alpha = (1.0F-f)*alpha0 + f*alpha1;
   }
}



static void sample_nearest_2d( const struct gl_texture_object *tObj, GLuint n,
                               const GLfloat s[], const GLfloat t[],
                               const GLfloat u[], const GLfloat lambda[],
                               GLubyte red[], GLubyte green[], GLubyte blue[],
                               GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      sample_2d_nearest( tObj, tObj->Image[0], s[i], t[i],
                         &red[i], &green[i], &blue[i], &alpha[i]);
   }
}



static void sample_linear_2d( const struct gl_texture_object *tObj, GLuint n,
                              const GLfloat s[], const GLfloat t[],
                              const GLfloat u[], const GLfloat lambda[],
                              GLubyte red[], GLubyte green[], GLubyte blue[],
                              GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      sample_2d_linear( tObj, tObj->Image[0], s[i], t[i],
                        &red[i], &green[i], &blue[i], &alpha[i]);
   }
}


/*
 * Given an (s,t) texture coordinate and lambda (level of detail) value,
 * return a texture sample.
 */
static void sample_lambda_2d( const struct gl_texture_object *tObj,
                              GLuint n,
                              const GLfloat s[], const GLfloat t[],
                              const GLfloat u[], const GLfloat lambda[],
                              GLubyte red[], GLubyte green[], GLubyte blue[],
                              GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      if (lambda[i] > tObj->MinMagThresh) {
         /* minification */
         switch (tObj->MinFilter) {
            case GL_NEAREST:
               sample_2d_nearest( tObj, tObj->Image[0], s[i], t[i],
                                  &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR:
               sample_2d_linear( tObj, tObj->Image[0], s[i], t[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_NEAREST_MIPMAP_NEAREST:
               sample_2d_nearest_mipmap_nearest( tObj, s[i], t[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR_MIPMAP_NEAREST:
               sample_2d_linear_mipmap_nearest( tObj, s[i], t[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_NEAREST_MIPMAP_LINEAR:
               sample_2d_nearest_mipmap_linear( tObj, s[i], t[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR_MIPMAP_LINEAR:
               sample_2d_linear_mipmap_linear( tObj, s[i], t[i], lambda[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            default:
               gl_problem(NULL, "Bad min filter in sample_2d_texture");
               return;
         }
      }
      else {
         /* magnification */
         switch (tObj->MagFilter) {
            case GL_NEAREST:
               sample_2d_nearest( tObj, tObj->Image[0], s[i], t[i],
                                  &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR:
               sample_2d_linear( tObj, tObj->Image[0], s[i], t[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            default:
               gl_problem(NULL, "Bad mag filter in sample_2d_texture");
         }
      }
   }
}


/*
 * Optimized 2-D texture sampling:
 *    S and T wrap mode == GL_REPEAT
 *    No border
 *    Format = GL_RGB
 */
static void opt_sample_rgb_2d( const struct gl_texture_object *tObj,
                               GLuint n, const GLfloat s[], const GLfloat t[],
                               const GLfloat u[], const GLfloat lamda[],
                               GLubyte red[], GLubyte green[],
                               GLubyte blue[], GLubyte alpha[] )
{
   const struct gl_texture_image *img = tObj->Image[0];
   GLfloat width = img->Width, height = img->Height;
   GLint colMask = img->Width-1, rowMask = img->Height-1;
   GLint shift = img->WidthLog2;
   GLuint k;

   ASSERT(tObj->WrapS==GL_REPEAT);
   ASSERT(tObj->WrapT==GL_REPEAT);
   ASSERT(img->Border==0);
   ASSERT(img->Format==GL_RGB);

   for (k=0;k<n;k++) {
      GLint i = (GLint) (s[k] * width) & colMask;
      GLint j = (GLint) (t[k] * height) & rowMask;
      GLint pos = (j << shift) | i;
      GLubyte *texel = img->Data + pos + pos + pos;  /* pos*3 */
      red[k]   = texel[0];
      green[k] = texel[1];
      blue[k]  = texel[2];
   }
}


/*
 * Optimized 2-D texture sampling:
 *    S and T wrap mode == GL_REPEAT
 *    No border
 *    Format = GL_RGBA
 */
static void opt_sample_rgba_2d( const struct gl_texture_object *tObj,
                                GLuint n, const GLfloat s[], const GLfloat t[],
                                const GLfloat u[], const GLfloat lamda[],
                                GLubyte red[], GLubyte green[],
                                GLubyte blue[], GLubyte alpha[] )
{
   const struct gl_texture_image *img = tObj->Image[0];
   GLfloat width = img->Width, height = img->Height;
   GLint colMask = img->Width-1, rowMask = img->Height-1;
   GLint shift = img->WidthLog2;
   GLuint k;

   ASSERT(tObj->WrapS==GL_REPEAT);
   ASSERT(tObj->WrapT==GL_REPEAT);
   ASSERT(img->Border==0);
   ASSERT(img->Format==GL_RGBA);

   for (k=0;k<n;k++) {
      GLint i = (GLint) (s[k] * width) & colMask;
      GLint j = (GLint) (t[k] * height) & rowMask;
      GLint pos = (j << shift) | i;
      GLubyte *texel = img->Data + (pos << 2);    /* pos*4 */
      red[k]   = texel[0];
      green[k] = texel[1];
      blue[k]  = texel[2];
      alpha[k] = texel[3];
   }
}



/**********************************************************************/
/*                    3-D Texture Sampling Functions                  */
/**********************************************************************/

/*
 * Given a texture image and an (i,j,k) integer texel coordinate, return the
 * texel color.
 */
static void get_3d_texel( const struct gl_texture_object *tObj,
                          const struct gl_texture_image *img,
                          GLint i, GLint j, GLint k,
                          GLubyte *red, GLubyte *green, GLubyte *blue,
                          GLubyte *alpha )
{
   GLint width = img->Width;    /* includes border */
   GLint height = img->Height;  /* includes border */
   GLint depth = img->Depth;    /* includes border */
   GLint rectarea;              /* = width * heigth */
   GLubyte *texel;

   rectarea = width*height;

#ifdef DEBUG
   if (i<0 || i>=width)  abort();
   if (j<0 || j>=height)  abort();
   if (k<0 || k>=depth)  abort();
#endif

   switch (img->Format) {
      case GL_COLOR_INDEX:
         {
            GLubyte index = img->Data[ rectarea * k +  width * j + i ];
            palette_sample(tObj, index, red, green, blue, alpha);
            return;
         }
      case GL_ALPHA:
         *alpha = img->Data[ rectarea * k +  width * j + i ];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red   = img->Data[ rectarea * k +  width * j + i ];
         return;
      case GL_LUMINANCE_ALPHA:
         texel = img->Data + ( rectarea * k + width * j + i) * 2;
         *red   = texel[0];
         *alpha = texel[1];
         return;
      case GL_RGB:
         texel = img->Data + (rectarea * k + width * j + i) * 3;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         return;
      case GL_RGBA:
         texel = img->Data + (rectarea * k + width * j + i) * 4;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         *alpha = texel[3];
         return;
      default:
         gl_problem(NULL, "Bad format in get_3d_texel");
   }
}


/*
 * Return the texture sample for coordinate (s,t,r) using GL_NEAREST filter.
 */
static void sample_3d_nearest( const struct gl_texture_object *tObj,
                               const struct gl_texture_image *img,
                               GLfloat s, GLfloat t, GLfloat r,
                               GLubyte *red, GLubyte *green,
                               GLubyte *blue, GLubyte *alpha )
{
   GLint imgWidth = img->Width;   /* includes border, if any */
   GLint imgHeight = img->Height; /* includes border, if any */
   GLint width = img->Width2;     /* without border, power of two */
   GLint height = img->Height2;   /* without border, power of two */
   GLint depth = img->Depth2;     /* without border, power of two */
   GLint rectarea;                /* = width * height */
   GLint i, j, k;
   GLubyte *texel;

   rectarea = imgWidth * imgHeight;

   /* Clamp/Repeat S and convert to integer texel coordinate */
   if (tObj->WrapS==GL_REPEAT) {
      /* s limited to [0,1) */
      /* i limited to [0,width-1] */
      i = (GLint) (s * width);
      if (s<0.0F)  i -= 1;
      i &= (width-1);
   }
   else {
      /* s limited to [0,1] */
      /* i limited to [0,width-1] */
      if (s<0.0F)       i = 0;
      else if (s>1.0F)  i = width-1;
      else              i = (GLint) (s * width);
   }

   /* Clamp/Repeat T and convert to integer texel coordinate */
   if (tObj->WrapT==GL_REPEAT) {
      /* t limited to [0,1) */
      /* j limited to [0,height-1] */
      j = (GLint) (t * height);
      if (t<0.0F)  j -= 1;
      j &= (height-1);
   }
   else {
      /* t limited to [0,1] */
      /* j limited to [0,height-1] */
      if (t<0.0F)       j = 0;
      else if (t>1.0F)  j = height-1;
      else              j = (GLint) (t * height);
   }

   /* Clamp/Repeat R and convert to integer texel coordinate */
   if (tObj->WrapR==GL_REPEAT) {
      /* r limited to [0,1) */
      /* k limited to [0,depth-1] */
      k = (GLint) (r * depth);
      if (r<0.0F)  k -= 1;
      k &= (depth-1);
   }
   else {
      /* r limited to [0,1] */
      /* k limited to [0,depth-1] */
      if (r<0.0F)       k = 0;
      else if (r>1.0F)  k = depth-1;
      else              k = (GLint) (r * depth);
   }

   switch (tObj->Image[0]->Format) {
      case GL_COLOR_INDEX:
         {
            GLubyte index = img->Data[ rectarea * k + j * imgWidth + i ];
            palette_sample(tObj, index, red, green, blue, alpha);
            return;
         }
      case GL_ALPHA:
         *alpha = img->Data[ rectarea * k + j * imgWidth + i ];
         return;
      case GL_LUMINANCE:
      case GL_INTENSITY:
         *red   = img->Data[ rectarea * k + j * imgWidth + i ];
         return;
      case GL_LUMINANCE_ALPHA:
         texel  = img->Data + ((rectarea * k + j * imgWidth + i) << 1);
         *red   = texel[0];
         *alpha = texel[1];
         return;
      case GL_RGB:
         texel = img->Data + ( rectarea * k + j * imgWidth + i) * 3;
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         return;
      case GL_RGBA:
         texel = img->Data + ((rectarea * k + j * imgWidth + i) << 2);
         *red   = texel[0];
         *green = texel[1];
         *blue  = texel[2];
         *alpha = texel[3];
         return;
      default:
         gl_problem(NULL, "Bad format in sample_3d_nearest");
   }
}


/*
 * Return the texture sample for coordinate (s,t,r) using GL_LINEAR filter.
 */
static void sample_3d_linear( const struct gl_texture_object *tObj,
                              const struct gl_texture_image *img,
                              GLfloat s, GLfloat t, GLfloat r,
                              GLubyte *red, GLubyte *green,
                              GLubyte *blue, GLubyte *alpha )
{
   GLint width = img->Width2;
   GLint height = img->Height2;
   GLint depth = img->Depth2;
   GLint i0, j0, k0, i1, j1, k1;
   GLint i0border, j0border, k0border, i1border, j1border, k1border;
   GLfloat u, v, w;

   u = s * width;
   if (tObj->WrapS==GL_REPEAT) {
      i0 = ((GLint) floor(u - 0.5F)) % width;
      i1 = (i0 + 1) & (width-1);
      i0border = i1border = 0;
   }
   else {
      i0 = (GLint) floor(u - 0.5F);
      i1 = i0 + 1;
      i0border = (i0<0) | (i0>=width);
      i1border = (i1<0) | (i1>=width);
   }

   v = t * height;
   if (tObj->WrapT==GL_REPEAT) {
      j0 = ((GLint) floor(v - 0.5F)) % height;
      j1 = (j0 + 1) & (height-1);
      j0border = j1border = 0;
   }
   else {
      j0 = (GLint) floor(v - 0.5F);
      j1 = j0 + 1;
      j0border = (j0<0) | (j0>=height);
      j1border = (j1<0) | (j1>=height);
   }

   w = r * depth;
   if (tObj->WrapR==GL_REPEAT) {
      k0 = ((GLint) floor(w - 0.5F)) % depth;
      k1 = (k0 + 1) & (depth-1);
      k0border = k1border = 0;
   }
   else {
      k0 = (GLint) floor(v - 0.5F);
      k1 = k0 + 1;
      k0border = (k0<0) | (k0>=depth);
      k1border = (k1<0) | (k1>=depth);
   }

   if (img->Border) {
      i0 += img->Border;
      i1 += img->Border;
      j0 += img->Border;
      j1 += img->Border;
      k0 += img->Border;
      k1 += img->Border;
      i0border = i1border = 0;
      j0border = j1border = 0;
      k0border = k1border = 0;
   }
   else {
      i0 &= (width-1);
      j0 &= (height-1);
      k0 &= (depth-1);
   }

   {
      GLfloat a = frac(u - 0.5F);
      GLfloat b = frac(v - 0.5F);
      GLfloat c = frac(w - 0.5F);

      GLint w000 = (GLint) ((1.0F-a)*(1.0F-b) * (1.0F-c) * 256.0F);
      GLint w010 = (GLint) (      a *(1.0F-b) * (1.0F-c) * 256.0F);
      GLint w001 = (GLint) ((1.0F-a)*      b  * (1.0F-c) * 256.0F);
      GLint w011 = (GLint) (      a *      b  * (1.0F-c) * 256.0F);
      GLint w100 = (GLint) ((1.0F-a)*(1.0F-b) * c * 256.0F);
      GLint w110 = (GLint) (      a *(1.0F-b) * c * 256.0F);
      GLint w101 = (GLint) ((1.0F-a)*      b  * c * 256.0F);
      GLint w111 = (GLint) (      a *      b  * c * 256.0F);


      GLubyte red000, green000, blue000, alpha000;
      GLubyte red010, green010, blue010, alpha010;
      GLubyte red001, green001, blue001, alpha001;
      GLubyte red011, green011, blue011, alpha011;
      GLubyte red100, green100, blue100, alpha100;
      GLubyte red110, green110, blue110, alpha110;
      GLubyte red101, green101, blue101, alpha101;
      GLubyte red111, green111, blue111, alpha111;

      if (k0border | i0border | j0border ) {
         red000   = tObj->BorderColor[0];
         green000 = tObj->BorderColor[1];
         blue000  = tObj->BorderColor[2];
         alpha000 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i0, j0, k0, &red000, &green000, &blue000, &alpha000 );
      }
      if (k0border | i1border | j0border) {
         red010   = tObj->BorderColor[0];
         green010 = tObj->BorderColor[1];
         blue010  = tObj->BorderColor[2];
         alpha010 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i1, j0, k0, &red010, &green010, &blue010, &alpha010 );
      }
      if (k0border | i0border | j1border) {
         red001   = tObj->BorderColor[0];
         green001 = tObj->BorderColor[1];
         blue001  = tObj->BorderColor[2];
         alpha001 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i0, j1, k0, &red001, &green001, &blue001, &alpha001 );
      }
      if (k0border | i1border | j1border) {
         red011   = tObj->BorderColor[0];
         green011 = tObj->BorderColor[1];
         blue011  = tObj->BorderColor[2];
         alpha011 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i1, j1, k0, &red011, &green011, &blue011, &alpha011 );
      }

      if (k1border | i0border | j0border ) {
         red100   = tObj->BorderColor[0];
         green100 = tObj->BorderColor[1];
         blue100  = tObj->BorderColor[2];
         alpha100 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i0, j0, k1, &red100, &green100, &blue100, &alpha100 );
      }
      if (k1border | i1border | j0border) {
         red110   = tObj->BorderColor[0];
         green110 = tObj->BorderColor[1];
         blue110  = tObj->BorderColor[2];
         alpha110 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i1, j0, k1, &red110, &green110, &blue110, &alpha110 );
      }
      if (k1border | i0border | j1border) {
         red101   = tObj->BorderColor[0];
         green101 = tObj->BorderColor[1];
         blue101  = tObj->BorderColor[2];
         alpha101 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i0, j1, k1, &red101, &green101, &blue101, &alpha101 );
      }
      if (k1border | i1border | j1border) {
         red111   = tObj->BorderColor[0];
         green111 = tObj->BorderColor[1];
         blue111  = tObj->BorderColor[2];
         alpha111 = tObj->BorderColor[3];
      }
      else {
         get_3d_texel( tObj, img, i1, j1, k1, &red111, &green111, &blue111, &alpha111 );
      }

      *red   = (w000*red000   + w010*red010   + w001*red001   + w011*red011 +
                w100*red100   + w110*red110   + w101*red101   + w111*red111  )
                >> 8;
      *green = (w000*green000 + w010*green010 + w001*green001 + w011*green011 +
                w100*green100 + w110*green110 + w101*green101 + w111*green111 )
                >> 8;
      *blue  = (w000*blue000  + w010*blue010  + w001*blue001  + w011*blue011 +
                w100*blue100  + w110*blue110  + w101*blue101  + w111*blue111 )
                >> 8;
      *alpha = (w000*alpha000 + w010*alpha010 + w001*alpha001 + w011*alpha011 +
                w100*alpha100 + w110*alpha110 + w101*alpha101 + w111*alpha111 )
                >> 8;
   }
}


static void
sample_3d_nearest_mipmap_nearest( const struct gl_texture_object *tObj,
                                  GLfloat s, GLfloat t, GLfloat r,
                                  GLfloat lambda,
                                  GLubyte *red, GLubyte *green,
                                  GLubyte *blue, GLubyte *alpha )
{
   GLint level;
   if (lambda<=0.5F) {
      level = 0;
   }
   else {
      GLint widthlog2 = tObj->Image[0]->WidthLog2;
      level = (GLint) (lambda + 0.499999F);
      if (level>widthlog2 ) {
         level = widthlog2;
      }
   }
   sample_3d_nearest( tObj, tObj->Image[level],
                      s, t, r, red, green, blue, alpha );
}


static void
sample_3d_linear_mipmap_nearest( const struct gl_texture_object *tObj,
                                 GLfloat s, GLfloat t, GLfloat r,
                                 GLfloat lambda,
                                 GLubyte *red, GLubyte *green,
                                 GLubyte *blue, GLubyte *alpha )
{
   GLint level;
   if (lambda<=0.5F) {
      level = 0;
   }
   else {
      GLint widthlog2 = tObj->Image[0]->WidthLog2;
      level = (GLint) (lambda + 0.499999F);
      if (level>widthlog2 ) {
         level = widthlog2;
      }
   }
   sample_3d_linear( tObj, tObj->Image[level],
                     s, t, r, red, green, blue, alpha );
}


static void
sample_3d_nearest_mipmap_linear( const struct gl_texture_object *tObj,
                                 GLfloat s, GLfloat t, GLfloat r,
                                 GLfloat lambda,
                                 GLubyte *red, GLubyte *green,
                                 GLubyte *blue, GLubyte *alpha )
{
   GLint max = tObj->Image[0]->MaxLog2;

   if (lambda>=max) {
      sample_3d_nearest( tObj, tObj->Image[max],
                         s, t, r, red, green, blue, alpha );
   }
   else {
      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;
      GLfloat f = frac(lambda);
      GLint level = (GLint) (lambda + 1.0F);
      level = CLAMP( level, 1, max );
      sample_3d_nearest( tObj, tObj->Image[level-1], s, t, r,
                         &red0, &green0, &blue0, &alpha0 );
      sample_3d_nearest( tObj, tObj->Image[level], s, t, r,
                         &red1, &green1, &blue1, &alpha1 );
      *red   = (1.0F-f)*red1   + f*red0;
      *green = (1.0F-f)*green1 + f*green0;
      *blue  = (1.0F-f)*blue1  + f*blue0;
      *alpha = (1.0F-f)*alpha1 + f*alpha0;
   }
}


static void
sample_3d_linear_mipmap_linear( const struct gl_texture_object *tObj,
                                GLfloat s, GLfloat t, GLfloat r,
                                GLfloat lambda,
                                GLubyte *red, GLubyte *green,
                                GLubyte *blue, GLubyte *alpha )
{
   GLint max = tObj->Image[0]->MaxLog2;

   if (lambda>=max) {
      sample_3d_linear( tObj, tObj->Image[max],
                        s, t, r, red, green, blue, alpha );
   }
   else {
      GLubyte red0, green0, blue0, alpha0;
      GLubyte red1, green1, blue1, alpha1;
      GLfloat f = frac(lambda);
      GLint level = (GLint) (lambda + 1.0F);
      level = CLAMP( level, 1, max );
      sample_3d_linear( tObj, tObj->Image[level-1], s, t, r,
                        &red0, &green0, &blue0, &alpha0 );
      sample_3d_linear( tObj, tObj->Image[level], s, t, r,
                        &red1, &green1, &blue1, &alpha1 );
      *red   = (1.0F-f)*red1   + f*red0;
      *green = (1.0F-f)*green1 + f*green0;
      *blue  = (1.0F-f)*blue1  + f*blue0;
      *alpha = (1.0F-f)*alpha1 + f*alpha0;
   }
}


static void sample_nearest_3d( const struct gl_texture_object *tObj, GLuint n,
                               const GLfloat s[], const GLfloat t[],
                               const GLfloat u[], const GLfloat lambda[],
                               GLubyte red[], GLubyte green[], GLubyte blue[],
                               GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      sample_3d_nearest( tObj, tObj->Image[0], s[i], t[i], u[i],
                         &red[i], &green[i], &blue[i], &alpha[i]);
   }
}



static void sample_linear_3d( const struct gl_texture_object *tObj, GLuint n,
                              const GLfloat s[], const GLfloat t[],
                              const GLfloat u[], const GLfloat lambda[],
                              GLubyte red[], GLubyte green[], GLubyte blue[],
                              GLubyte alpha[] )
{
   GLuint i;
   for (i=0;i<n;i++) {
      sample_3d_linear( tObj, tObj->Image[0], s[i], t[i], u[i],
                        &red[i], &green[i], &blue[i], &alpha[i]);
   }
}

/*
 * Given an (s,t,r) texture coordinate and lambda (level of detail) value,
 * return a texture sample.
 */
static void sample_lambda_3d( const struct gl_texture_object *tObj, GLuint n,
                              const GLfloat s[], const GLfloat t[],
                              const GLfloat u[], const GLfloat lambda[],
                              GLubyte red[], GLubyte green[],
                              GLubyte blue[], GLubyte alpha[] )
{
   GLuint i;

   for (i=0;i<n;i++) {

      if (lambda[i] > tObj->MinMagThresh) {
         /* minification */
         switch (tObj->MinFilter) {
            case GL_NEAREST:
               sample_3d_nearest( tObj, tObj->Image[0], s[i], t[i], u[i],
                                  &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR:
               sample_3d_linear( tObj, tObj->Image[0], s[i], t[i], u[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_NEAREST_MIPMAP_NEAREST:
               sample_3d_nearest_mipmap_nearest( tObj, s[i], t[i], u[i],
                          lambda[i], &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR_MIPMAP_NEAREST:
               sample_3d_linear_mipmap_nearest( tObj, s[i], t[i], u[i],
                          lambda[i], &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_NEAREST_MIPMAP_LINEAR:
               sample_3d_nearest_mipmap_linear( tObj, s[i], t[i], u[i],
                          lambda[i], &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR_MIPMAP_LINEAR:
               sample_3d_linear_mipmap_linear( tObj, s[i], t[i], u[i],
                          lambda[i], &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            default:
               gl_problem(NULL, "Bad min filterin sample_3d_texture");
         }
      }
      else {
         /* magnification */
         switch (tObj->MagFilter) {
            case GL_NEAREST:
               sample_3d_nearest( tObj, tObj->Image[0], s[i], t[i], u[i],
                                  &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            case GL_LINEAR:
               sample_3d_linear( tObj, tObj->Image[0], s[i], t[i], u[i],
                                 &red[i], &green[i], &blue[i], &alpha[i] );
               break;
            default:
               gl_problem(NULL, "Bad mag filterin sample_3d_texture");
         }
      }
   }
}



/**********************************************************************/
/*                       Texture Sampling Setup                       */
/**********************************************************************/


/*
 * Setup the texture sampling function for this texture object.
 */
void gl_set_texture_sampler( struct gl_texture_object *t )
{
   if (!t->Complete) {
      t->SampleFunc = NULL;
   }
   else {
      GLboolean needLambda = (t->MinFilter != t->MagFilter);

      if (needLambda) {
         /* Compute min/mag filter threshold */
         if (t->MagFilter==GL_LINEAR
             && (t->MinFilter==GL_NEAREST_MIPMAP_NEAREST ||
                 t->MinFilter==GL_LINEAR_MIPMAP_NEAREST)) {
            t->MinMagThresh = 0.5F;
         }
         else {
            t->MinMagThresh = 0.0F;
         }
      }

      switch (t->Dimensions) {
         case 1:
            if (needLambda) {
               t->SampleFunc = sample_lambda_1d;
            }
            else if (t->MinFilter==GL_LINEAR) {
               t->SampleFunc = sample_linear_1d;
            }
            else {
               ASSERT(t->MinFilter==GL_NEAREST);
               t->SampleFunc = sample_nearest_1d;
            }
            break;
         case 2:
            if (needLambda) {
               t->SampleFunc = sample_lambda_2d;
            }
            else if (t->MinFilter==GL_LINEAR) {
               t->SampleFunc = sample_linear_2d;
            }
            else {
               ASSERT(t->MinFilter==GL_NEAREST);
               if (t->WrapS==GL_REPEAT && t->WrapT==GL_REPEAT
                   && t->Image[0]->Border==0 && t->Image[0]->Format==GL_RGB) {
                  t->SampleFunc = opt_sample_rgb_2d;
               }
               else if (t->WrapS==GL_REPEAT && t->WrapT==GL_REPEAT
                   && t->Image[0]->Border==0 && t->Image[0]->Format==GL_RGBA) {
                  t->SampleFunc = opt_sample_rgba_2d;
               }
               else
                  t->SampleFunc = sample_nearest_2d;
            }
            break;
         case 3:
            if (needLambda) {
               t->SampleFunc = sample_lambda_3d;
            }
            else if (t->MinFilter==GL_LINEAR) {
               t->SampleFunc = sample_linear_3d;
            }
            else {
               ASSERT(t->MinFilter==GL_NEAREST);
               t->SampleFunc = sample_nearest_3d;
            }
            break;
         default:
            gl_problem(NULL, "invalid dimensions in gl_set_texture_sampler");
      }
   }
}



/**********************************************************************/
/*                      Texture Application                           */
/**********************************************************************/


/*
 * Combine incoming fragment color with texel color to produce output color.
 * Input:  n - number of fragments
 *         format - base internal texture format
 *         env_mode - texture environment mode
 *         Rt, Gt, Bt, At - array of texel colors
 * InOut:  red, green, blue, alpha - incoming fragment colors modified
 *                                   by texel colors according to the
 *                                   texture environment mode.
 */
static void apply_texture( GLcontext *ctx,
         GLuint n, GLint format, GLenum env_mode,
	 GLubyte red[], GLubyte green[], GLubyte blue[], GLubyte alpha[],
	 GLubyte Rt[], GLubyte Gt[], GLubyte Bt[], GLubyte At[] )
{
   GLuint i;
   GLint Rc, Gc, Bc, Ac;

   if (!ctx->Visual->EightBitColor) {
      /* This is a hack!  Rescale input colors from [0,scale] to [0,255]. */
      GLfloat rscale = 255.0 * ctx->Visual->InvRedScale;
      GLfloat gscale = 255.0 * ctx->Visual->InvGreenScale;
      GLfloat bscale = 255.0 * ctx->Visual->InvBlueScale;
      GLfloat ascale = 255.0 * ctx->Visual->InvAlphaScale;
      for (i=0;i<n;i++) {
	 red[i]   = (GLint) (red[i]   * rscale);
	 green[i] = (GLint) (green[i] * gscale);
	 blue[i]  = (GLint) (blue[i]  * bscale);
	 alpha[i] = (GLint) (alpha[i] * ascale);
      }
   }

/*
 * Use (A*(B+1)) >> 8 as a fast approximation of (A*B)/255 for A
 * and B in [0,255]
 */
#define PROD(A,B)   (((GLint)(A) * ((GLint)(B)+1)) >> 8)

   if (format==GL_COLOR_INDEX) {
      format = GL_RGBA;  /* XXXX a hack! */
   }

   switch (env_mode) {
      case GL_REPLACE:
	 switch (format) {
	    case GL_ALPHA:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf */
                  /* Av = At */
                  alpha[i] = At[i];
	       }
	       break;
	    case GL_LUMINANCE:
	       for (i=0;i<n;i++) {
		  /* Cv = Lt */
                  GLint Lt = Rt[i];
                  red[i] = green[i] = blue[i] = Lt;
                  /* Av = Af */
	       }
	       break;
	    case GL_LUMINANCE_ALPHA:
	       for (i=0;i<n;i++) {
                  GLint Lt = Rt[i];
		  /* Cv = Lt */
		  red[i] = green[i] = blue[i] = Lt;
		  /* Av = At */
		  alpha[i] = At[i];
	       }
	       break;
	    case GL_INTENSITY:
	       for (i=0;i<n;i++) {
		  /* Cv = It */
                  GLint It = Rt[i];
                  red[i] = green[i] = blue[i] = It;
                  /* Av = It */
                  alpha[i] = It;
	       }
	       break;
	    case GL_RGB:
	       for (i=0;i<n;i++) {
		  /* Cv = Ct */
		  red[i]   = Rt[i];
		  green[i] = Gt[i];
		  blue[i]  = Bt[i];
		  /* Av = Af */
	       }
	       break;
	    case GL_RGBA:
	       for (i=0;i<n;i++) {
		  /* Cv = Ct */
		  red[i]   = Rt[i];
		  green[i] = Gt[i];
		  blue[i]  = Bt[i];
		  /* Av = At */
		  alpha[i] = At[i];
	       }
	       break;
            default:
               gl_problem(ctx, "Bad format in apply_texture");
               return;
	 }
	 break;

      case GL_MODULATE:
         switch (format) {
	    case GL_ALPHA:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf */
		  /* Av = AfAt */
		  alpha[i] = PROD( alpha[i], At[i] );
	       }
	       break;
	    case GL_LUMINANCE:
	       for (i=0;i<n;i++) {
		  /* Cv = LtCf */
                  GLint Lt = Rt[i];
		  red[i]   = PROD( red[i],   Lt );
		  green[i] = PROD( green[i], Lt );
		  blue[i]  = PROD( blue[i],  Lt );
		  /* Av = Af */
	       }
	       break;
	    case GL_LUMINANCE_ALPHA:
	       for (i=0;i<n;i++) {
		  /* Cv = CfLt */
                  GLint Lt = Rt[i];
		  red[i]   = PROD( red[i],   Lt );
		  green[i] = PROD( green[i], Lt );
		  blue[i]  = PROD( blue[i],  Lt );
		  /* Av = AfAt */
		  alpha[i] = PROD( alpha[i], At[i] );
	       }
	       break;
	    case GL_INTENSITY:
	       for (i=0;i<n;i++) {
		  /* Cv = CfIt */
                  GLint It = Rt[i];
		  red[i]   = PROD( red[i],   It );
		  green[i] = PROD( green[i], It );
		  blue[i]  = PROD( blue[i],  It );
		  /* Av = AfIt */
		  alpha[i] = PROD( alpha[i], It );
	       }
	       break;
	    case GL_RGB:
	       for (i=0;i<n;i++) {
		  /* Cv = CfCt */
		  red[i]   = PROD( red[i],   Rt[i] );
		  green[i] = PROD( green[i], Gt[i] );
		  blue[i]  = PROD( blue[i],  Bt[i] );
		  /* Av = Af */
	       }
	       break;
	    case GL_RGBA:
	       for (i=0;i<n;i++) {
		  /* Cv = CfCt */
		  red[i]   = PROD( red[i],   Rt[i] );
		  green[i] = PROD( green[i], Gt[i] );
		  blue[i]  = PROD( blue[i],  Bt[i] );
		  /* Av = AfAt */
		  alpha[i] = PROD( alpha[i], At[i] );
	       }
	       break;
            default:
               gl_problem(ctx, "Bad format (2) in apply_texture");
               return;
	 }
	 break;

      case GL_DECAL:
         switch (format) {
            case GL_ALPHA:
            case GL_LUMINANCE:
            case GL_LUMINANCE_ALPHA:
            case GL_INTENSITY:
               /* undefined */
               break;
	    case GL_RGB:
	       for (i=0;i<n;i++) {
		  /* Cv = Ct */
		  red[i]   = Rt[i];
		  green[i] = Gt[i];
		  blue[i]  = Bt[i];
		  /* Av = Af */
	       }
	       break;
	    case GL_RGBA:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf(1-At) + CtAt */
		  GLint t = At[i], s = 255 - t;
		  red[i]   = PROD(red[i],  s) + PROD(Rt[i],t);
		  green[i] = PROD(green[i],s) + PROD(Gt[i],t);
		  blue[i]  = PROD(blue[i], s) + PROD(Bt[i],t);
		  /* Av = Af */
	       }
	       break;
            default:
               gl_problem(ctx, "Bad format (3) in apply_texture");
               return;
	 }
	 break;

      case GL_BLEND:
         Rc = (GLint) (ctx->Texture.EnvColor[0] * 255.0F);
         Gc = (GLint) (ctx->Texture.EnvColor[1] * 255.0F);
         Bc = (GLint) (ctx->Texture.EnvColor[2] * 255.0F);
         Ac = (GLint) (ctx->Texture.EnvColor[3] * 255.0F);
	 switch (format) {
	    case GL_ALPHA:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf */
		  /* Av = AfAt */
                  alpha[i] = PROD(alpha[i], At[i]);
	       }
	       break;
            case GL_LUMINANCE:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf(1-Lt) + CcLt */
		  GLint Lt = Rt[i], s = 255 - Lt;
		  red[i]   = PROD(red[i],  s) + PROD(Rc,  Lt);
		  green[i] = PROD(green[i],s) + PROD(Gc,Lt);
		  blue[i]  = PROD(blue[i], s) + PROD(Bc, Lt);
		  /* Av = Af */
	       }
	       break;
	    case GL_LUMINANCE_ALPHA:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf(1-Lt) + CcLt */
		  GLint Lt = Rt[i], s = 255 - Lt;
		  red[i]   = PROD(red[i],  s) + PROD(Rc,  Lt);
		  green[i] = PROD(green[i],s) + PROD(Gc,Lt);
		  blue[i]  = PROD(blue[i], s) + PROD(Bc, Lt);
		  /* Av = AfAt */
		  alpha[i] = PROD(alpha[i],At[i]);
	       }
	       break;
            case GL_INTENSITY:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf(1-It) + CcLt */
		  GLint It = Rt[i], s = 255 - It;
		  red[i]   = PROD(red[i],  s) + PROD(Rc,It);
		  green[i] = PROD(green[i],s) + PROD(Gc,It);
		  blue[i]  = PROD(blue[i], s) + PROD(Bc,It);
                  /* Av = Af(1-It) + Ac*It */
                  alpha[i] = PROD(alpha[i],s) + PROD(Ac,It);
               }
               break;
	    case GL_RGB:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf(1-Ct) + CcCt */
		  red[i]   = PROD(red[i],  (255-Rt[i])) + PROD(Rc,Rt[i]);
		  green[i] = PROD(green[i],(255-Gt[i])) + PROD(Gc,Gt[i]);
		  blue[i]  = PROD(blue[i], (255-Bt[i])) + PROD(Bc,Bt[i]);
		  /* Av = Af */
	       }
	       break;
	    case GL_RGBA:
	       for (i=0;i<n;i++) {
		  /* Cv = Cf(1-Ct) + CcCt */
		  red[i]   = PROD(red[i],  (255-Rt[i])) + PROD(Rc,Rt[i]);
		  green[i] = PROD(green[i],(255-Gt[i])) + PROD(Gc,Gt[i]);
		  blue[i]  = PROD(blue[i], (255-Bt[i])) + PROD(Bc,Bt[i]);
		  /* Av = AfAt */
		  alpha[i] = PROD(alpha[i],At[i]);
	       }
	       break;
	 }
	 break;

      default:
         gl_problem(ctx, "Bad env mode in apply_texture");
         return;
   }
#undef PROD

   if (!ctx->Visual->EightBitColor) {
      /* This is a hack!  Rescale input colors from [0,255] to [0,scale]. */
      GLfloat rscale = ctx->Visual->RedScale   * (1.0F/ 255.0F);
      GLfloat gscale = ctx->Visual->GreenScale * (1.0F/ 255.0F);
      GLfloat bscale = ctx->Visual->BlueScale  * (1.0F/ 255.0F);
      GLfloat ascale = ctx->Visual->AlphaScale * (1.0F/ 255.0F);
      for (i=0;i<n;i++) {
	 red[i]   = (GLint) (red[i]   * rscale);
	 green[i] = (GLint) (green[i] * gscale);
	 blue[i]  = (GLint) (blue[i]  * bscale);
	 alpha[i] = (GLint) (alpha[i] * ascale);
      }
   }
}



void gl_texture_pixels( GLcontext *ctx, GLuint n,
                        const GLfloat s[], const GLfloat t[],
                        const GLfloat r[], const GLfloat lambda[],
                        GLubyte red[], GLubyte green[],
                        GLubyte blue[], GLubyte alpha[] )
{
   GLubyte tred[PB_SIZE];
   GLubyte tgreen[PB_SIZE];
   GLubyte tblue[PB_SIZE];
   GLubyte talpha[PB_SIZE];

   if (!ctx->Texture.Current || !ctx->Texture.Current->SampleFunc)
      return;

   /* Sample the texture. */
   (*ctx->Texture.Current->SampleFunc)( ctx->Texture.Current, n,
                                        s, t, r, lambda,
                                        tred, tgreen, tblue, talpha );

   apply_texture( ctx, n,
                  ctx->Texture.Current->Image[0]->Format,
                  ctx->Texture.EnvMode,
                  red, green, blue, alpha,
                  tred, tgreen, tblue, talpha );
}
