/*
 * Copyright 2000-2001 VA Linux Systems, Inc.
 * (c) Copyright IBM Corporation 2002
 * 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, IBM AND/OR THEIR 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:
 *    Ian Romanick <idr@us.ibm.com>
 *    Keith Whitwell <keithw@tungstengraphics.com>
 */

#include <stdlib.h>
#include "main/mm.h"
#include "mgacontext.h"
#include "mgatex.h"
#include "mgaregs.h"
#include "mgatris.h"
#include "mgaioctl.h"

#include "main/context.h"
#include "main/enums.h"
#include "main/macros.h"
#include "main/imports.h"

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

#define MGA_USE_TABLE_FOR_FORMAT
#ifdef MGA_USE_TABLE_FOR_FORMAT
#define TMC_nr_tformat (MESA_FORMAT_YCBCR_REV + 1)
static const unsigned TMC_tformat[ TMC_nr_tformat ] =
{
    [MESA_FORMAT_ARGB8888] = TMC_tformat_tw32,
    [MESA_FORMAT_RGB565]   = TMC_tformat_tw16,
    [MESA_FORMAT_ARGB4444] = TMC_tformat_tw12,
    [MESA_FORMAT_ARGB1555] = TMC_tformat_tw15,
    [MESA_FORMAT_AL88]     = TMC_tformat_tw8al,
    [MESA_FORMAT_I8]       = TMC_tformat_tw8a,
    [MESA_FORMAT_CI8]      = TMC_tformat_tw8 ,
    [MESA_FORMAT_YCBCR]     = TMC_tformat_tw422uyvy,
    [MESA_FORMAT_YCBCR_REV] = TMC_tformat_tw422,
};
#endif

static void
mgaSetTexImages( mgaContextPtr mmesa,
		 const struct gl_texture_object * tObj )
{
    mgaTextureObjectPtr t = (mgaTextureObjectPtr) tObj->DriverData;
    struct gl_texture_image *baseImage = tObj->Image[0][ tObj->BaseLevel ];
    GLint totalSize;
    GLint width, height;
    GLint i;
    GLint numLevels;
    GLint log2Width, log2Height;
    GLuint txformat = 0;
    GLint ofs;

    /* Set the hardware texture format
     */
#ifndef MGA_USE_TABLE_FOR_FORMAT
    switch (baseImage->TexFormat->MesaFormat) {

	case MESA_FORMAT_ARGB8888: txformat = TMC_tformat_tw32;	break;
	case MESA_FORMAT_RGB565:   txformat = TMC_tformat_tw16; break;
	case MESA_FORMAT_ARGB4444: txformat = TMC_tformat_tw12;	break;
	case MESA_FORMAT_ARGB1555: txformat = TMC_tformat_tw15; break;
	case MESA_FORMAT_AL88:     txformat = TMC_tformat_tw8al; break;
	case MESA_FORMAT_I8:       txformat = TMC_tformat_tw8a; break;
	case MESA_FORMAT_CI8:      txformat = TMC_tformat_tw8;  break;
        case MESA_FORMAT_YCBCR:    txformat  = TMC_tformat_tw422uyvy; break;
        case MESA_FORMAT_YCBCR_REV: txformat = TMC_tformat_tw422; break;

	default:
	_mesa_problem(NULL, "unexpected texture format in %s", __FUNCTION__);
	return;
    }
#else
    if ( (baseImage->TexFormat->MesaFormat >= TMC_nr_tformat)
	 || (TMC_tformat[ baseImage->TexFormat->MesaFormat ] == 0) )
    {
	_mesa_problem(NULL, "unexpected texture format in %s", __FUNCTION__);
	return;
    }

    txformat = TMC_tformat[ baseImage->TexFormat->MesaFormat ];

#endif /* MGA_USE_TABLE_FOR_FORMAT */

   driCalculateTextureFirstLastLevel( (driTextureObject *) t );
   if (tObj->Target == GL_TEXTURE_RECTANGLE_NV) {
      log2Width = 0;
      log2Height = 0;
   } else {
      log2Width  = tObj->Image[0][t->base.firstLevel]->WidthLog2;
      log2Height = tObj->Image[0][t->base.firstLevel]->HeightLog2;
   }

   width = tObj->Image[0][t->base.firstLevel]->Width;
   height = tObj->Image[0][t->base.firstLevel]->Height;

   numLevels = MIN2( t->base.lastLevel - t->base.firstLevel + 1,
                     MGA_IS_G200(mmesa) ? G200_TEX_MAXLEVELS : G400_TEX_MAXLEVELS);


   totalSize = 0;
   for ( i = 0 ; i < numLevels ; i++ ) {
      const struct gl_texture_image * const texImage = 
	  tObj->Image[0][ i + t->base.firstLevel ];
      int size;

      if (texImage == NULL)
	 break;

      size = texImage->Width * texImage->Height *
         baseImage->TexFormat->TexelBytes;

      t->offsets[i] = totalSize;
      t->base.dirty_images[0] |= (1<<i);

      /* All mipmaps must be 32-byte aligned */
      totalSize += (size + 31) & ~31;

      /* Since G400 calculates the offsets in hardware
       * it can't handle more than one < 32 byte mipmap.
       *
       * Further testing has indicated that it can't
       * handle any < 32 byte mipmaps.
       */
      if (MGA_IS_G400( mmesa ) && size <= 32) {
         i++;
         break;
      }
   }

   /* save these values */
   numLevels = i;
   t->base.lastLevel = t->base.firstLevel + numLevels - 1;
   t->base.totalSize = totalSize;

   /* setup hardware register values */
   t->setup.texctl &= (TMC_tformat_MASK & TMC_tpitch_MASK 
		       & TMC_tpitchext_MASK);
   t->setup.texctl |= txformat;


   /* Set the texture width.  In order to support non-power of 2 textures and
    * textures larger than 1024 texels wide, "linear" pitch must be used.  For
    * the linear pitch, if the width is 2048, a value of zero is used.
    */

   t->setup.texctl |= TMC_tpitchlin_enable;
   t->setup.texctl |= MGA_FIELD( TMC_tpitchext, width & (2048 - 1) );


   /* G400 specifies the number of mip levels in a strange way.  Since there
    * are up to 11 levels, it requires 4 bits.  Three of the bits are at the
    * high end of TEXFILTER.  The other bit is in the middle.  Weird.
    */
   numLevels--;
   t->setup.texfilter &= TF_mapnb_MASK & TF_mapnbhigh_MASK & TF_reserved_MASK;
   t->setup.texfilter |= MGA_FIELD( TF_mapnb, numLevels & 0x7 );
   t->setup.texfilter |= MGA_FIELD( TF_mapnbhigh, (numLevels >> 3) & 0x1 );

   /* warp texture registers */
   ofs = MGA_IS_G200(mmesa) ? 28 : 11;

   t->setup.texwidth = (MGA_FIELD(TW_twmask, width - 1) |
			MGA_FIELD(TW_rfw, (10 - log2Width - 8) & 63 ) |
			MGA_FIELD(TW_tw, (log2Width + ofs ) | 0x40 ));

   t->setup.texheight = (MGA_FIELD(TH_thmask, height - 1) |
			 MGA_FIELD(TH_rfh, (10 - log2Height - 8) & 63 ) |
			 MGA_FIELD(TH_th, (log2Height + ofs ) | 0x40 ));

   mgaUploadTexImages( mmesa, t );
}


/* ================================================================
 * Texture unit state management
 */

static void mgaUpdateTextureEnvG200( GLcontext *ctx, GLuint unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT(ctx);
   struct gl_texture_object *tObj = ctx->Texture.Unit[0]._Current;
   mgaTextureObjectPtr t = (mgaTextureObjectPtr) tObj->DriverData;
   GLenum format = tObj->Image[0][tObj->BaseLevel]->_BaseFormat;

   if (tObj != ctx->Texture.Unit[0].CurrentTex[TEXTURE_2D_INDEX] &&
       tObj != ctx->Texture.Unit[0].CurrentTex[TEXTURE_RECT_INDEX])
      return;


   t->setup.texctl &= ~TMC_tmodulate_enable;
   t->setup.texctl2 &= ~(TMC_decalblend_enable |
                         TMC_idecal_enable |
                         TMC_decaldis_enable);

   switch (ctx->Texture.Unit[0].EnvMode) {
   case GL_REPLACE:
      if (format == GL_ALPHA)
         t->setup.texctl2 |= TMC_idecal_enable;

      if (format == GL_RGB || format == GL_LUMINANCE)
         mmesa->hw.alpha_sel = AC_alphasel_diffused;
      else
         mmesa->hw.alpha_sel = AC_alphasel_fromtex;
      break;

   case GL_MODULATE:
      t->setup.texctl |= TMC_tmodulate_enable;

      if (format == GL_ALPHA)
         t->setup.texctl2 |= (TMC_idecal_enable |
                              TMC_decaldis_enable);

      if (format == GL_RGB || format == GL_LUMINANCE)
         mmesa->hw.alpha_sel = AC_alphasel_diffused;
      else
         mmesa->hw.alpha_sel = AC_alphasel_modulated;
      break;

   case GL_DECAL:
      if (format == GL_RGB || format == GL_RGBA)
         t->setup.texctl2 |= TMC_decalblend_enable;
      else
         t->setup.texctl2 |= TMC_idecal_enable;

      mmesa->hw.alpha_sel = AC_alphasel_diffused;
      break;

   case GL_BLEND:
      if (format == GL_ALPHA) {
         t->setup.texctl2 |= TMC_idecal_enable;
         mmesa->hw.alpha_sel = AC_alphasel_modulated;
      } else {
         t->texenv_fallback = GL_TRUE;
      }
      break;

   default:
      break;
   }
}


#define MGA_REPLACE		0
#define MGA_MODULATE		1
#define MGA_DECAL		2
#define MGA_ADD			3
#define MGA_MAX_COMBFUNC	4

static const GLuint g400_color_combine[][MGA_MAX_COMBFUNC] =
{
   /* Unit 0:
    */
   {
      /* GL_REPLACE
       * Cv = Cs
       * Av = Af
       */
      (TD0_color_sel_arg1 |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_arg2),
      
      /* GL_MODULATE
       * Cv = Cf Cs
       * Av = Af
       */
      (TD0_color_arg2_diffuse |
       TD0_color_sel_mul |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_arg2),
      
      /* GL_DECAL
       * Cv = Cs
       * Av = Af
       */
      (TD0_color_sel_arg1 |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_arg2),
      
      /* GL_ADD
       * Cv = Cf + Cs
       * Av = Af
       */
      (TD0_color_arg2_diffuse |
       TD0_color_add_add |
       TD0_color_sel_add |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_arg2),
   },
   
   /* Unit 1:
    */
   {
      /* GL_REPLACE
       * Cv = Cs
       * Av = Ap
       */
      (TD0_color_sel_arg1 |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_arg2),
      
      /* GL_MODULATE
       * Cv = Cp Cs
       * Av = Ap
       */
      (TD0_color_arg2_prevstage |
       TD0_color_sel_mul |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_arg2),

      /* GL_DECAL
       * Cv = Cs
       * Av = Ap
       */
      (TD0_color_sel_arg1 |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_arg2),
      
      /* GL_ADD
       * Cv = Cp + Cs
       * Av = Ap
       */
      (TD0_color_arg2_prevstage |
       TD0_color_add_add |
       TD0_color_sel_add |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_arg2),
   },
};

static const GLuint g400_color_alpha_combine[][MGA_MAX_COMBFUNC] =
{
   /* Unit 0:
    */
   {
      /* GL_REPLACE
       * Cv = Cs
       * Av = As
       */
      (TD0_color_sel_arg1 |
       TD0_alpha_sel_arg1),
      
      /* GL_MODULATE
       * Cv = Cf Cs
       * Av = Af As
       */
      (TD0_color_arg2_diffuse |
       TD0_color_sel_mul |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_mul),
      
      /* GL_DECAL
       * tmp = Cf ( 1 - As )
       * Cv = tmp + Cs As
       * Av = Af
       */
      (TD0_color_arg2_diffuse |
       TD0_color_alpha_currtex |
       TD0_color_alpha1inv_enable |
       TD0_color_arg1mul_alpha1 |
       TD0_color_blend_enable |
       TD0_color_arg1add_mulout |
       TD0_color_arg2add_mulout |
       TD0_color_add_add |
       TD0_color_sel_add |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_arg2),

      /* GL_ADD
       * Cv = Cf + Cs
       * Av = Af As
       */
      (TD0_color_arg2_diffuse |
       TD0_color_add_add |
       TD0_color_sel_add |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_mul),
   },
   
   /* Unit 1:
    */
   {
      /* GL_REPLACE
       * Cv = Cs
       * Av = As
       */
      (TD0_color_sel_arg1 |
       TD0_alpha_sel_arg1),
      
      /* GL_MODULATE
       * Cv = Cp Cs
       * Av = Ap As
       */
      (TD0_color_arg2_prevstage |
       TD0_color_sel_mul |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_mul),

      /* GL_DECAL
       * tmp = Cp ( 1 - As )
       * Cv = tmp + Cs As
       * Av = Ap
       */
      (TD0_color_arg2_prevstage |
       TD0_color_alpha_currtex |
       TD0_color_alpha1inv_enable |
       TD0_color_arg1mul_alpha1 |
       TD0_color_blend_enable |
       TD0_color_arg1add_mulout |
       TD0_color_arg2add_mulout |
       TD0_color_add_add |
       TD0_color_sel_add |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_arg2),
      
      /* GL_ADD
       * Cv = Cp + Cs
       * Av = Ap As
       */
      (TD0_color_arg2_prevstage |
       TD0_color_add_add |
       TD0_color_sel_add |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_mul),
   },
};

static const GLuint g400_alpha_combine[][MGA_MAX_COMBFUNC] =
{
   /* Unit 0:
    */
   {
      /* GL_REPLACE
       * Cv = Cf
       * Av = As
       */
      (TD0_color_arg2_diffuse |
       TD0_color_sel_arg2 |
       TD0_alpha_sel_arg1),
      
      /* GL_MODULATE
       * Cv = Cf
       * Av = Af As
       */
      (TD0_color_arg2_diffuse |
       TD0_color_sel_arg2 |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_mul),

      /* GL_DECAL (undefined)
       * Cv = Cf
       * Av = Af
       */
      (TD0_color_arg2_diffuse |
       TD0_color_sel_arg2 |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_arg2),

      /* GL_ADD
       * Cv = Cf
       * Av = Af As
       */
      (TD0_color_arg2_diffuse |
       TD0_color_sel_arg2 |
       TD0_alpha_arg2_diffuse |
       TD0_alpha_sel_mul),
   },

   /* Unit 1:
    */
   {
      /* GL_REPLACE
       * Cv = Cp
       * Av = As
       */
      (TD0_color_arg2_prevstage |
       TD0_color_sel_arg2 |
       TD0_alpha_sel_arg1),
      
      /* GL_MODULATE
       * Cv = Cp
       * Av = Ap As
       */
      (TD0_color_arg2_prevstage |
       TD0_color_sel_arg2 |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_mul),

      /* GL_DECAL (undefined)
       * Cv = Cp
       * Av = Ap
       */
      (TD0_color_arg2_prevstage |
       TD0_color_sel_arg2 |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_arg2),

      /* GL_ADD
       * Cv = Cp
       * Av = Ap As
       */
      (TD0_color_arg2_prevstage |
       TD0_color_sel_arg2 |
       TD0_alpha_arg2_prevstage |
       TD0_alpha_sel_mul),
   },
};

static GLboolean mgaUpdateTextureEnvBlend( GLcontext *ctx, int unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT(ctx);
   const int source = mmesa->tmu_source[unit];
   const struct gl_texture_unit *texUnit = &ctx->Texture.Unit[source];
   const struct gl_texture_object *tObj = texUnit->_Current;
   GLuint *reg = ((GLuint *)&mmesa->setup.tdualstage0 + unit);
   GLenum format = tObj->Image[0][tObj->BaseLevel]->_BaseFormat;

   *reg = 0;

   if (format == GL_ALPHA) {
      /* Cv = Cf */
      *reg |= (TD0_color_arg2_diffuse |
               TD0_color_sel_arg2);
      /* Av = Af As */
      *reg |= (TD0_alpha_arg2_diffuse |
               TD0_alpha_sel_mul);
      return GL_TRUE;
   }

   /* C1 = Cf ( 1 - Cs ) */
   *reg |= (TD0_color_arg1_inv_enable |
            TD0_color_arg2_diffuse |
            TD0_color_sel_mul);

   if (format == GL_RGB || format == GL_LUMINANCE) {
      /* A1 = Af */
      *reg |= (TD0_alpha_arg2_diffuse |
               TD0_alpha_sel_arg2);
   } else
   if (format == GL_RGBA || format == GL_LUMINANCE_ALPHA) {
      /* A1 = Af As */
      *reg |= (TD0_alpha_arg2_diffuse |
               TD0_alpha_sel_mul);
   } else
   if (format == GL_INTENSITY) {
      /* A1 = Af ( 1 - As ) */
      *reg |= (TD0_alpha_arg1_inv_enable |
               TD0_alpha_arg2_diffuse |
               TD0_alpha_sel_mul);
   }
   
   if (RGB_ZERO(mmesa->envcolor[source]) &&
       (format != GL_INTENSITY || ALPHA_ZERO(mmesa->envcolor[source])))
      return GL_TRUE; /* all done */

   if (ctx->Texture._EnabledUnits == 0x03)
      return GL_FALSE; /* need both units */

   mmesa->force_dualtex = GL_TRUE;
   reg = &mmesa->setup.tdualstage1;
   *reg = 0;

   if (RGB_ZERO(mmesa->envcolor[source])) {
      /* Cv = C1 */
      *reg |= (TD0_color_arg2_prevstage |
               TD0_color_sel_arg2);
   } else
   if (RGB_ONE(mmesa->envcolor[source])) {
      /* Cv = C1 + Cs */
      *reg |= (TD0_color_arg2_prevstage |
               TD0_color_add_add |
               TD0_color_sel_add);
   } else
   if (RGBA_EQUAL(mmesa->envcolor[source])) {
      /* Cv = C1 + Cc Cs */
      *reg |= (TD0_color_arg2_prevstage |
               TD0_color_alpha_fcol |
               TD0_color_arg2mul_alpha2 |
               TD0_color_arg1add_mulout |
               TD0_color_add_add |
               TD0_color_sel_add);

      mmesa->setup.fcol = mmesa->envcolor[source];
   } else {
      return GL_FALSE;
   }

   if (format != GL_INTENSITY || ALPHA_ZERO(mmesa->envcolor[source])) {
      /* Av = A1 */
      *reg |= (TD0_alpha_arg2_prevstage |
               TD0_alpha_sel_arg2);
   } else
   if (ALPHA_ONE(mmesa->envcolor[source])) {
      /* Av = A1 + As */
      *reg |= (TD0_alpha_arg2_prevstage |
               TD0_alpha_add_enable |
               TD0_alpha_sel_add);
   } else {
      return GL_FALSE;
   }

   return GL_TRUE;
}

static void mgaUpdateTextureEnvG400( GLcontext *ctx, GLuint unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT( ctx );
   const int source = mmesa->tmu_source[unit];
   const struct gl_texture_unit *texUnit = &ctx->Texture.Unit[source];
   const struct gl_texture_object *tObj = texUnit->_Current;
   GLuint *reg = ((GLuint *)&mmesa->setup.tdualstage0 + unit);
   mgaTextureObjectPtr t = (mgaTextureObjectPtr) tObj->DriverData;
   GLenum format = tObj->Image[0][tObj->BaseLevel]->_BaseFormat;

   if (tObj != ctx->Texture.Unit[source].CurrentTex[TEXTURE_2D_INDEX] &&
       tObj != ctx->Texture.Unit[source].CurrentTex[TEXTURE_RECT_INDEX])
      return;

   switch (ctx->Texture.Unit[source].EnvMode) {
   case GL_REPLACE:
      if (format == GL_ALPHA) {
         *reg = g400_alpha_combine[unit][MGA_REPLACE];
      } else if (format == GL_RGB || format == GL_LUMINANCE) {
         *reg = g400_color_combine[unit][MGA_REPLACE];
      } else {
         *reg = g400_color_alpha_combine[unit][MGA_REPLACE];
      }
      break;

   case GL_MODULATE:
      if (format == GL_ALPHA) {
         *reg = g400_alpha_combine[unit][MGA_MODULATE];
      } else if (format == GL_RGB || format == GL_LUMINANCE) {
         *reg = g400_color_combine[unit][MGA_MODULATE];
      } else {
         *reg = g400_color_alpha_combine[unit][MGA_MODULATE];
      }
      break;

   case GL_DECAL:
      if (format == GL_RGB) {
         *reg = g400_color_combine[unit][MGA_DECAL];
      } else if (format == GL_RGBA) {
         *reg = g400_color_alpha_combine[unit][MGA_DECAL];
         if (ctx->Texture._EnabledUnits != 0x03) {
            /* Linear blending mode needs dual texturing enabled */
            *(reg+1) = (TD0_color_arg2_prevstage |
                        TD0_color_sel_arg2 |
                        TD0_alpha_arg2_prevstage |
                        TD0_alpha_sel_arg2);
            mmesa->force_dualtex = GL_TRUE;
         }
      } else {
         /* Undefined */
         *reg = g400_alpha_combine[unit][MGA_DECAL];
      }
      break;

   case GL_ADD:
      if (format == GL_ALPHA) {
         *reg = g400_alpha_combine[unit][MGA_ADD];
      } else if (format == GL_RGB || format == GL_LUMINANCE) {
         *reg = g400_color_combine[unit][MGA_ADD];
      } else if (format == GL_RGBA || format == GL_LUMINANCE_ALPHA) {
         *reg = g400_color_alpha_combine[unit][MGA_ADD];
      } else if (format == GL_INTENSITY) {
         /* Cv = Cf + Cs
          * Av = Af + As
          */
         if (unit == 0) {
            *reg = (TD0_color_arg2_diffuse |
                    TD0_color_add_add |
                    TD0_color_sel_add |
                    TD0_alpha_arg2_diffuse |
                    TD0_alpha_add_enable |
                    TD0_alpha_sel_add);
         } else {
            *reg = (TD0_color_arg2_prevstage |
                    TD0_color_add_add |
                    TD0_color_sel_add |
                    TD0_alpha_arg2_prevstage |
                    TD0_alpha_add_enable |
                    TD0_alpha_sel_add);
         }
      }
      break;

   case GL_BLEND:
      if (!mgaUpdateTextureEnvBlend(ctx, unit))
         t->texenv_fallback = GL_TRUE;
      break;

   case GL_COMBINE:
      if (!mgaUpdateTextureEnvCombine(ctx, unit))
         t->texenv_fallback = GL_TRUE;
      break;
   default:
      break;
   }
}

static void disable_tex( GLcontext *ctx, int unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT( ctx );

   /* Texture unit disabled */

   if ( mmesa->CurrentTexObj[unit] != NULL ) {
      /* The old texture is no longer bound to this texture unit.
       * Mark it as such.
       */

      mmesa->CurrentTexObj[unit]->base.bound &= ~(1UL << unit);
      mmesa->CurrentTexObj[unit] = NULL;
   }

   if ( unit != 0 && !mmesa->force_dualtex ) {
      mmesa->setup.tdualstage1 = mmesa->setup.tdualstage0;
   }

   if ( ctx->Texture._EnabledUnits == 0 ) {
      mmesa->setup.dwgctl &= DC_opcod_MASK;
      mmesa->setup.dwgctl |= DC_opcod_trap;
      mmesa->hw.alpha_sel = AC_alphasel_diffused;
   }

   mmesa->dirty |= MGA_UPLOAD_CONTEXT | (MGA_UPLOAD_TEX0 << unit);
}

static GLboolean enable_tex( GLcontext *ctx, int unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT(ctx);
   const int source = mmesa->tmu_source[unit];
   const struct gl_texture_unit *texUnit = &ctx->Texture.Unit[source];
   const struct gl_texture_object *tObj = texUnit->_Current;
   mgaTextureObjectPtr t = (mgaTextureObjectPtr) tObj->DriverData;

   /* Upload teximages (not pipelined)
    */
   if (t->base.dirty_images[0]) {
      FLUSH_BATCH( mmesa );
      mgaSetTexImages( mmesa, tObj );
      if ( t->base.memBlock == NULL ) {
	 return GL_FALSE;
      }
   }

   return GL_TRUE;
}

static GLboolean update_tex_common( GLcontext *ctx, int unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT(ctx);
   const int source = mmesa->tmu_source[unit];
   const struct gl_texture_unit *texUnit = &ctx->Texture.Unit[source];
   struct gl_texture_object	*tObj = texUnit->_Current;
   mgaTextureObjectPtr t = (mgaTextureObjectPtr) tObj->DriverData;

   /* Fallback if there's a texture border */
   if ( tObj->Image[0][tObj->BaseLevel]->Border > 0 ) {
      return GL_FALSE;
   }


   /* Update state if this is a different texture object to last
    * time.
    */
   if ( mmesa->CurrentTexObj[unit] != t ) {
      if ( mmesa->CurrentTexObj[unit] != NULL ) {
	 /* The old texture is no longer bound to this texture unit.
	  * Mark it as such.
	  */

	 mmesa->CurrentTexObj[unit]->base.bound &= ~(1UL << unit);
      }

      mmesa->CurrentTexObj[unit] = t;
      t->base.bound |= (1UL << unit);

      driUpdateTextureLRU( (driTextureObject *) t ); /* done too often */
   }

   /* register setup */
   if ( unit == 1 ) {
      mmesa->setup.tdualstage1 = mmesa->setup.tdualstage0;
   }

   t->texenv_fallback = GL_FALSE;

   /* Set this before mgaUpdateTextureEnvG400() since
    * GL_ARB_texture_env_crossbar may have to disable texturing.
    */
   mmesa->setup.dwgctl &= DC_opcod_MASK;
   mmesa->setup.dwgctl |= DC_opcod_texture_trap;

   /* FIXME: The Radeon has some cached state so that it can avoid calling
    * FIXME: UpdateTextureEnv in some cases.  Is that possible here?
    */
   if (MGA_IS_G400(mmesa)) {
      /* G400: Regardless of texture env mode, we use the alpha from the
       * texture unit (AC_alphasel_fromtex) since it will have already
       * been modulated by the incoming fragment color, if needed.
       * We don't want (AC_alphasel_modulate) since that'll effectively
       * do the modulation twice.
       */
      mmesa->hw.alpha_sel = AC_alphasel_fromtex;

      mgaUpdateTextureEnvG400( ctx, unit );
   } else {
      mgaUpdateTextureEnvG200( ctx, unit );
   }

   t->setup.texctl2 &= TMC_dualtex_MASK;
   if (ctx->Texture._EnabledUnits == 0x03 || mmesa->force_dualtex) {
      t->setup.texctl2 |= TMC_dualtex_enable;
   }

   mmesa->dirty |= MGA_UPLOAD_CONTEXT | (MGA_UPLOAD_TEX0 << unit);

   FALLBACK( ctx, MGA_FALLBACK_BORDER_MODE, t->border_fallback );
   return !t->border_fallback && !t->texenv_fallback;
}


static GLboolean updateTextureUnit( GLcontext *ctx, int unit )
{
   mgaContextPtr mmesa = MGA_CONTEXT( ctx );
   const int source = mmesa->tmu_source[unit];
   const struct gl_texture_unit *texUnit = &ctx->Texture.Unit[source];


   if ( texUnit->_ReallyEnabled == TEXTURE_2D_BIT ||
        texUnit->_ReallyEnabled == TEXTURE_RECT_BIT ) {
      return(enable_tex( ctx, unit ) &&
	     update_tex_common( ctx, unit ));
   }
   else if ( texUnit->_ReallyEnabled ) {
      return GL_FALSE;
   }
   else {
      disable_tex( ctx, unit );
      return GL_TRUE;
   }
}

/* The G400 is now programmed quite differently wrt texture environment.
 */
void mgaUpdateTextureState( GLcontext *ctx )
{
   mgaContextPtr mmesa = MGA_CONTEXT( ctx );
   GLboolean ok;
   unsigned  i;

   mmesa->force_dualtex = GL_FALSE;
   mmesa->fcol_used = GL_FALSE;

   /* This works around a quirk with the MGA hardware.  If only OpenGL 
    * TEXTURE1 is enabled, then the hardware TEXTURE0 must be used.  The
    * hardware TEXTURE1 can ONLY be used when hardware TEXTURE0 is also used.
    */

   mmesa->tmu_source[0] = 0;
   mmesa->tmu_source[1] = 1;

   if ((ctx->Texture._EnabledUnits & 0x03) == 0x02) {
      /* only texture 1 enabled */
      mmesa->tmu_source[0] = 1;
      mmesa->tmu_source[1] = 0;
   }

   for ( i = 0, ok = GL_TRUE 
	 ; (i < ctx->Const.MaxTextureUnits) && ok
	 ; i++ ) {
      ok = updateTextureUnit( ctx, i );
   }

   FALLBACK( ctx, MGA_FALLBACK_TEXTURE, !ok );
}
