/*
 * Copyright (C) 1999  Allen Akin   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 the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL ALLEN AKIN 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.
 *
 */

/**
 * @file combine.c:  Test the GL_EXT_texture_env_combine extension
 * Author: Brian Paul (brianp@valinux.com)  September 2000
 *
 * GL_EXT_texture_env_dot3 extension test
 * Author: Gareth Hughes (gareth@valinux.com)  January 2001
 *
 * GL_ARB_texture_env_crossbar extension test
 * Author: Brian Paul (brian@tungstengraphics.com)  December 2002
 *
 * The challenge with testing this extension is dealing with combinatorial
 * explosion.  There are 16 state variables in this extension:
 *
 * GL_COMBINE_RGB_EXT which has 5 possible values
 * GL_COMBINE_ALPHA_EXT which has 5 possible values
 * GL_SOURCE0_RGB_EXT which has 4 possible values
 * GL_SOURCE1_RGB_EXT which has 4 possible values
 * GL_SOURCE2_RGB_EXT which has 4 possible values
 * GL_SOURCE0_ALPHA_EXT which has 4 possible values
 * GL_SOURCE1_ALPHA_EXT which has 4 possible values
 * GL_SOURCE2_ALPHA_EXT which has 4 possible values
 * GL_OPERAND0_RGB_EXT which has 4 possible values
 * GL_OPERAND1_RGB_EXT which has 4 possible values
 * GL_OPERAND2_RGB_EXT which has 2 possible values
 * GL_OPERAND0_ALPHA_EXT which has 2 possible values
 * GL_OPERAND1_ALPHA_EXT which has 2 possible values
 * GL_OPERAND2_ALPHA_EXT which has 1 possible value
 * GL_RGB_SCALE_EXT which has 3 possible values
 * GL_ALPHA_SCALE which has 3 possible values
 *
 * The product of those values is 117,964,800.  And that's just for one
 * texture unit!  If we wanted to fully exercise N texture units we'd
 * need to run 117,964,800 ^ N tests!  Ideally we'd also like to test
 * with a number of different fragment, texenv and texture colors.
 * Clearly we can't test everything.
 *
 * So, we've partitioned the combination space into subsets defined
 * by the replace_params[], add_params[], interpolate_params[], etc arrays.
 * For multitexture, we do an even more limited set of tests:  testing
 * all permutations of the 5 combine modes on all texture units.
 *
 * In the future we might look at programs that use the combine
 * extension to see which mode combination are important to them and
 * put them into this test.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 13;
	config.window_visual = PIGLIT_GL_VISUAL_RGBA;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;
	config.window_width = 2;
	config.window_height = 2;

PIGLIT_GL_TEST_CONFIG_END

#define MAX_TEX_UNITS 8

#define COPY4(DST, SRC)                                                      \
{                                                                            \
	(DST)[0] = (SRC)[0];                                                 \
	(DST)[1] = (SRC)[1];                                                 \
	(DST)[2] = (SRC)[2];                                                 \
	(DST)[3] = (SRC)[3];                                                 \
}

static bool have_dot3;
static bool have_crossbar;
static bool have_combine3;
static GLuint textures[MAX_TEX_UNITS];
static int test_stride = 1;
static int num_tex_units;

/* Our model of GL machine state */
static struct {
	GLenum COMBINE_RGB[MAX_TEX_UNITS];
	GLenum COMBINE_ALPHA[MAX_TEX_UNITS];
	GLenum SOURCE0_RGB[MAX_TEX_UNITS];
	GLenum SOURCE1_RGB[MAX_TEX_UNITS];
	GLenum SOURCE2_RGB[MAX_TEX_UNITS];
	GLenum SOURCE0_ALPHA[MAX_TEX_UNITS];
	GLenum SOURCE1_ALPHA[MAX_TEX_UNITS];
	GLenum SOURCE2_ALPHA[MAX_TEX_UNITS];
	GLenum OPERAND0_RGB[MAX_TEX_UNITS];
	GLenum OPERAND1_RGB[MAX_TEX_UNITS];
	GLenum OPERAND2_RGB[MAX_TEX_UNITS];
	GLenum OPERAND0_ALPHA[MAX_TEX_UNITS];
	GLenum OPERAND1_ALPHA[MAX_TEX_UNITS];
	GLenum OPERAND2_ALPHA[MAX_TEX_UNITS];
	float RGB_SCALE[MAX_TEX_UNITS];
	float ALPHA_SCALE[MAX_TEX_UNITS];
	float frag_color[4];		   /* fragment color */
	float env_color[MAX_TEX_UNITS][4]; /* texture env color */
	float tex_color[MAX_TEX_UNITS][4]; /* texture image color */
	GLenum tex_format[MAX_TEX_UNITS];  /* texture base format */
} machine;

/* describes possible state combinations */
struct test_param {
	GLenum target;
	GLenum valid_values[6];
};

/* These objects define the space of tex-env combinations that we exercise.
 * Each array element is { state-var, { list of possible values, 0 } }.
 */

static const struct test_param replace_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_REPLACE, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_REPLACE, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 2, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 4, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param add_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_ADD, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_ADD, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 2, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 4, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param modulate_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_MODULATE, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_MODULATE, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 2, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 4, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param add_signed_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_ADD_SIGNED_EXT, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_ADD_SIGNED_EXT, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 2, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 4, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param interpolate_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_INTERPOLATE_EXT, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_INTERPOLATE_EXT, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_RGB_EXT, {GL_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_ALPHA_EXT, {GL_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param dot3_rgb_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_DOT3_RGB_EXT, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_MODULATE, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 2, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 4, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param dot3_rgba_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_DOT3_RGBA_EXT, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_MODULATE, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 2, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 4, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param modulate_add_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_MODULATE_ADD_ATI, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_MODULATE_ADD_ATI, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param modulate_signed_add_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_MODULATE_SIGNED_ADD_ATI, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_MODULATE_SIGNED_ADD_ATI, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static const struct test_param modulate_subtract_params[] = {
	{GL_COMBINE_RGB_EXT, {GL_MODULATE_SUBTRACT_ATI, 0}},
	{GL_COMBINE_ALPHA_EXT, {GL_MODULATE_SUBTRACT_ATI, 0}},
	{GL_SOURCE0_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_RGB_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_RGB_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE0_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_SOURCE1_ALPHA_EXT, {GL_TEXTURE, GL_CONSTANT_EXT, GL_PRIMARY_COLOR_EXT, GL_PREVIOUS_EXT, 0}},
	{GL_SOURCE2_ALPHA_EXT, {GL_TEXTURE, GL_PRIMARY_COLOR_EXT, 0}},
	{GL_OPERAND0_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_RGB_EXT, {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND0_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND1_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_OPERAND2_ALPHA_EXT, {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, 0}},
	{GL_RGB_SCALE_EXT, {1, 4, 0}},
	{GL_ALPHA_SCALE, {1, 2, 0}},
	{0, {0, 0, 0, 0, 0}}
};

static void
problem(const char *s)
{
	fprintf(stderr, "Problem in combine(): %s\n", s);
	assert(0);
}

/* Set machine parameters to default values. */
static void
reset_machine(void)
{
	for (int u = 0; u < MAX_TEX_UNITS; u++) {
		machine.COMBINE_RGB[u] = GL_MODULATE;
		machine.COMBINE_ALPHA[u] = GL_MODULATE;
		machine.SOURCE0_RGB[u] = GL_TEXTURE;
		machine.SOURCE1_RGB[u] = GL_PREVIOUS_EXT;
		machine.SOURCE2_RGB[u] = GL_CONSTANT_EXT;
		machine.SOURCE0_ALPHA[u] = GL_TEXTURE;
		machine.SOURCE1_ALPHA[u] = GL_PREVIOUS_EXT;
		machine.SOURCE2_ALPHA[u] = GL_CONSTANT_EXT;
		machine.OPERAND0_RGB[u] = GL_SRC_COLOR;
		machine.OPERAND1_RGB[u] = GL_SRC_COLOR;
		machine.OPERAND2_RGB[u] = GL_SRC_ALPHA;
		machine.OPERAND0_ALPHA[u] = GL_SRC_ALPHA;
		machine.OPERAND1_ALPHA[u] = GL_SRC_ALPHA;
		machine.OPERAND2_ALPHA[u] = GL_SRC_ALPHA;
		machine.RGB_SCALE[u] = 1.0;
		machine.ALPHA_SCALE[u] = 1.0;
		machine.tex_format[u] = GL_RGBA;
	}
}

/* This computes the expected texcombine result for one texture unit. */
static void
compute_tex_combine(int tex_unit, const float prev_color[4], float result[4])
{
	float term0[4], term1[4], term2[4], dot;
	const float *color_src0, *color_src1, *color_src2;
	const float *alpha_src0, *alpha_src1 = NULL, *alpha_src2 = NULL;
	const float *frag_color = machine.frag_color;
	const float *const_color = machine.env_color[tex_unit];
	const float *tex_color = machine.tex_color[tex_unit];
	int src_unit;

	switch (machine.SOURCE0_RGB[tex_unit]) {
	case GL_PRIMARY_COLOR_EXT:
		color_src0 = frag_color;
		break;
	case GL_TEXTURE:
		color_src0 = tex_color;
		break;
	case GL_CONSTANT_EXT:
		color_src0 = const_color;
		break;
	case GL_PREVIOUS_EXT:
		color_src0 = prev_color;
		break;
	case GL_TEXTURE0:
	case GL_TEXTURE1:
	case GL_TEXTURE2:
	case GL_TEXTURE3:
	case GL_TEXTURE4:
	case GL_TEXTURE5:
	case GL_TEXTURE6:
	case GL_TEXTURE7:
		/* GL_ARB_texture_env_crossbar */
		src_unit = machine.SOURCE0_RGB[tex_unit] - GL_TEXTURE0;
		color_src0 = machine.tex_color[src_unit];
		break;
	default:
		problem("bad rgbSource0");
		return;
	}

	switch (machine.SOURCE0_ALPHA[tex_unit]) {
	case GL_PRIMARY_COLOR_EXT:
		alpha_src0 = frag_color;
		break;
	case GL_TEXTURE:
		alpha_src0 = tex_color;
		break;
	case GL_CONSTANT_EXT:
		alpha_src0 = const_color;
		break;
	case GL_PREVIOUS_EXT:
		alpha_src0 = prev_color;
		break;
	case GL_TEXTURE0:
	case GL_TEXTURE1:
	case GL_TEXTURE2:
	case GL_TEXTURE3:
	case GL_TEXTURE4:
	case GL_TEXTURE5:
	case GL_TEXTURE6:
	case GL_TEXTURE7:
		/* GL_ARB_texture_env_crossbar */
		src_unit = machine.SOURCE0_ALPHA[tex_unit] - GL_TEXTURE0;
		alpha_src0 = machine.tex_color[src_unit];
		break;
	default:
		problem("bad alphaSource0");
		return;
	}

	switch (machine.SOURCE1_RGB[tex_unit]) {
	case GL_PRIMARY_COLOR_EXT:
		color_src1 = frag_color;
		break;
	case GL_TEXTURE:
		color_src1 = tex_color;
		break;
	case GL_CONSTANT_EXT:
		color_src1 = const_color;
		break;
	case GL_PREVIOUS_EXT:
		color_src1 = prev_color;
		break;
	case GL_TEXTURE0:
	case GL_TEXTURE1:
	case GL_TEXTURE2:
	case GL_TEXTURE3:
	case GL_TEXTURE4:
	case GL_TEXTURE5:
	case GL_TEXTURE6:
	case GL_TEXTURE7:
		/* GL_ARB_texture_env_crossbar */
		src_unit = machine.SOURCE1_RGB[tex_unit] - GL_TEXTURE0;
		color_src1 = machine.tex_color[src_unit];
		break;
	default:
		problem("bad rgbSource1");
		return;
	}

	switch (machine.SOURCE1_ALPHA[tex_unit]) {
	case GL_PRIMARY_COLOR_EXT:
		alpha_src1 = frag_color;
		break;
	case GL_TEXTURE:
		alpha_src1 = tex_color;
		break;
	case GL_CONSTANT_EXT:
		alpha_src1 = const_color;
		break;
	case GL_PREVIOUS_EXT:
		alpha_src1 = prev_color;
		break;
	case GL_TEXTURE0:
	case GL_TEXTURE1:
	case GL_TEXTURE2:
	case GL_TEXTURE3:
	case GL_TEXTURE4:
	case GL_TEXTURE5:
	case GL_TEXTURE6:
	case GL_TEXTURE7:
		/* GL_ARB_texture_env_crossbar */
		src_unit = machine.SOURCE1_ALPHA[tex_unit] - GL_TEXTURE0;
		alpha_src1 = machine.tex_color[src_unit];
		break;
	default:
		problem("bad alphaSource1");
		return;
	}

	switch (machine.SOURCE2_RGB[tex_unit]) {
	case GL_PRIMARY_COLOR_EXT:
		color_src2 = frag_color;
		break;
	case GL_TEXTURE:
		color_src2 = tex_color;
		break;
	case GL_CONSTANT_EXT:
		color_src2 = const_color;
		break;
	case GL_PREVIOUS_EXT:
		color_src2 = prev_color;
		break;
	case GL_TEXTURE0:
	case GL_TEXTURE1:
	case GL_TEXTURE2:
	case GL_TEXTURE3:
	case GL_TEXTURE4:
	case GL_TEXTURE5:
	case GL_TEXTURE6:
	case GL_TEXTURE7:
		/* GL_ARB_texture_env_crossbar */
		src_unit = machine.SOURCE2_RGB[tex_unit] - GL_TEXTURE0;
		color_src2 = machine.tex_color[src_unit];
		break;
	default:
		problem("bad rgbSource2");
		return;
	}

	switch (machine.SOURCE2_ALPHA[tex_unit]) {
	case GL_PRIMARY_COLOR_EXT:
		alpha_src2 = frag_color;
		break;
	case GL_TEXTURE:
		alpha_src2 = tex_color;
		break;
	case GL_CONSTANT_EXT:
		alpha_src2 = const_color;
		break;
	case GL_PREVIOUS_EXT:
		alpha_src2 = prev_color;
		break;
	case GL_TEXTURE0:
	case GL_TEXTURE1:
	case GL_TEXTURE2:
	case GL_TEXTURE3:
	case GL_TEXTURE4:
	case GL_TEXTURE5:
	case GL_TEXTURE6:
	case GL_TEXTURE7:
		/* GL_ARB_texture_env_crossbar */
		src_unit = machine.SOURCE2_ALPHA[tex_unit] - GL_TEXTURE0;
		alpha_src2 = machine.tex_color[src_unit];
		break;
	default:
		problem("bad alphaSource2");
		return;
	}

	switch (machine.OPERAND0_RGB[tex_unit]) {
	case GL_SRC_COLOR:
		term0[0] = color_src0[0];
		term0[1] = color_src0[1];
		term0[2] = color_src0[2];
		break;
	case GL_ONE_MINUS_SRC_COLOR:
		term0[0] = 1.0 - color_src0[0];
		term0[1] = 1.0 - color_src0[1];
		term0[2] = 1.0 - color_src0[2];
		break;
	case GL_SRC_ALPHA:
		term0[0] = color_src0[3];
		term0[1] = color_src0[3];
		term0[2] = color_src0[3];
		break;
	case GL_ONE_MINUS_SRC_ALPHA:
		term0[0] = 1.0 - color_src0[3];
		term0[1] = 1.0 - color_src0[3];
		term0[2] = 1.0 - color_src0[3];
		break;
	default:
		problem("bad rgbOperand0");
		return;
	}

	switch (machine.OPERAND0_ALPHA[tex_unit]) {
	case GL_SRC_ALPHA:
		term0[3] = alpha_src0[3];
		break;
	case GL_ONE_MINUS_SRC_ALPHA:
		term0[3] = 1.0 - alpha_src0[3];
		break;
	default:
		problem("bad alphaOperand0");
		return;
	}

	switch (machine.OPERAND1_RGB[tex_unit]) {
	case GL_SRC_COLOR:
		term1[0] = color_src1[0];
		term1[1] = color_src1[1];
		term1[2] = color_src1[2];
		break;
	case GL_ONE_MINUS_SRC_COLOR:
		term1[0] = 1.0 - color_src1[0];
		term1[1] = 1.0 - color_src1[1];
		term1[2] = 1.0 - color_src1[2];
		break;
	case GL_SRC_ALPHA:
		term1[0] = color_src1[3];
		term1[1] = color_src1[3];
		term1[2] = color_src1[3];
		break;
	case GL_ONE_MINUS_SRC_ALPHA:
		term1[0] = 1.0 - color_src1[3];
		term1[1] = 1.0 - color_src1[3];
		term1[2] = 1.0 - color_src1[3];
		break;
	default:
		problem("bad rgbOperand1");
		return;
	}

	switch (machine.OPERAND1_ALPHA[tex_unit]) {
	case GL_SRC_ALPHA:
		term1[3] = alpha_src1[3];
		break;
	case GL_ONE_MINUS_SRC_ALPHA:
		term1[3] = 1.0 - alpha_src1[3];
		break;
	default:
		problem("bad alphaOperand1");
		return;
	}

	switch (machine.OPERAND2_RGB[tex_unit]) {
	case GL_SRC_COLOR:
		term2[0] = color_src2[0];
		term2[1] = color_src2[1];
		term2[2] = color_src2[2];
		break;
	case GL_ONE_MINUS_SRC_COLOR:
		term2[0] = 1.0 - color_src2[0];
		term2[1] = 1.0 - color_src2[1];
		term2[2] = 1.0 - color_src2[2];
		break;
	case GL_SRC_ALPHA:
		term2[0] = color_src2[3];
		term2[1] = color_src2[3];
		term2[2] = color_src2[3];
		break;
	case GL_ONE_MINUS_SRC_ALPHA:
		term2[0] = 1.0 - color_src2[3];
		term2[1] = 1.0 - color_src2[3];
		term2[2] = 1.0 - color_src2[3];
		break;
	default:
		problem("bad rgbOperand2");
		return;
	}

	switch (machine.OPERAND2_ALPHA[tex_unit]) {
	case GL_SRC_ALPHA:
		term2[3] = alpha_src2[3];
		break;
	case GL_ONE_MINUS_SRC_ALPHA:
		term2[3] = 1.0 - alpha_src2[3];
		break;
	default:
		problem("bad alphaOperand2");
		return;
	}

	/* Final combine */
	switch (machine.COMBINE_RGB[tex_unit]) {
	case GL_REPLACE:
		result[0] = term0[0];
		result[1] = term0[1];
		result[2] = term0[2];
		break;
	case GL_MODULATE:
		result[0] = term0[0] * term1[0];
		result[1] = term0[1] * term1[1];
		result[2] = term0[2] * term1[2];
		break;
	case GL_ADD:
		result[0] = term0[0] + term1[0];
		result[1] = term0[1] + term1[1];
		result[2] = term0[2] + term1[2];
		break;
	case GL_ADD_SIGNED_EXT:
		result[0] = term0[0] + term1[0] - 0.5;
		result[1] = term0[1] + term1[1] - 0.5;
		result[2] = term0[2] + term1[2] - 0.5;
		break;
	case GL_INTERPOLATE_EXT:
		result[0] = term0[0] * term2[0] + term1[0] * (1.0 - term2[0]);
		result[1] = term0[1] * term2[1] + term1[1] * (1.0 - term2[1]);
		result[2] = term0[2] * term2[2] + term1[2] * (1.0 - term2[2]);
		break;
	case GL_DOT3_RGB_EXT:
	case GL_DOT3_RGBA_EXT:
		dot = ((term0[0] - 0.5) * (term1[0] - 0.5) +
		       (term0[1] - 0.5) * (term1[1] - 0.5) +
		       (term0[2] - 0.5) * (term1[2] - 0.5));
		result[0] = dot;
		result[1] = dot;
		result[2] = dot;
		if (machine.COMBINE_RGB[tex_unit] == GL_DOT3_RGBA_EXT)
			result[3] = dot;
		break;
	case GL_MODULATE_ADD_ATI:
		result[0] = term0[0] * term2[0] + term1[0];
		result[1] = term0[1] * term2[1] + term1[1];
		result[2] = term0[2] * term2[2] + term1[2];
		break;
	case GL_MODULATE_SIGNED_ADD_ATI:
		result[0] = term0[0] * term2[0] + term1[0] - 0.5;
		result[1] = term0[1] * term2[1] + term1[1] - 0.5;
		result[2] = term0[2] * term2[2] + term1[2] - 0.5;
		break;
	case GL_MODULATE_SUBTRACT_ATI:
		result[0] = term0[0] * term2[0] - term1[0];
		result[1] = term0[1] * term2[1] - term1[1];
		result[2] = term0[2] * term2[2] - term1[2];
		break;
	default:
		problem("bad rgbCombine");
		return;
	}

	switch (machine.COMBINE_ALPHA[tex_unit]) {
	case GL_REPLACE:
		result[3] = term0[3];
		break;
	case GL_MODULATE:
		result[3] = term0[3] * term1[3];
		break;
	case GL_ADD:
		result[3] = term0[3] + term1[3];
		break;
	case GL_ADD_SIGNED_EXT:
		result[3] = term0[3] + term1[3] - 0.5;
		break;
	case GL_INTERPOLATE_EXT:
		result[3] = term0[3] * term2[3] + term1[3] * (1.0 - term2[3]);
		break;
	case GL_MODULATE_ADD_ATI:
		result[3] = term0[3] * term2[3] + term1[3];
		break;
	case GL_MODULATE_SIGNED_ADD_ATI:
		result[3] = term0[3] * term2[3] + term1[3] - 0.5;
		break;
	case GL_MODULATE_SUBTRACT_ATI:
		result[3] = term0[3] * term2[3] - term1[3];
		break;
	default:
		problem("bad alphaCombine");
		return;
	}

	if (machine.COMBINE_RGB[tex_unit] == GL_DOT3_RGBA_EXT) {
		result[3] = result[0];
	}

	/* scaling
	 * GH: Remove this crud when the ARB extension is done.  It
	 * most likely won't have this scale factor restriction. */
	switch (machine.COMBINE_RGB[tex_unit]) {
	case GL_DOT3_RGB_EXT:
	case GL_DOT3_RGBA_EXT:
		result[0] *= 4.0;
		result[1] *= 4.0;
		result[2] *= 4.0;
		break;
	default:
		result[0] *= machine.RGB_SCALE[tex_unit];
		result[1] *= machine.RGB_SCALE[tex_unit];
		result[2] *= machine.RGB_SCALE[tex_unit];
		break;
	}
	switch (machine.COMBINE_RGB[tex_unit]) {
	case GL_DOT3_RGBA_EXT:
		result[3] *= 4.0;
		break;
	default:
		result[3] *= machine.ALPHA_SCALE[tex_unit];
		break;
	}

	/* final clamping */
	result[0] = CLAMP(result[0], 0.0, 1.0);
	result[1] = CLAMP(result[1], 0.0, 1.0);
	result[2] = CLAMP(result[2], 0.0, 1.0);
	result[3] = CLAMP(result[3], 0.0, 1.0);
}

/* Set the fragment, texenv (constant), and texture colors for all the
 * machine's texture units. */
static void
setup_colors(void)
{
	static const float frag_color[4] = {0.00, 0.25, 0.50, 0.75};
	static const float env_colors[][4] = {{0.25, 0.50, 0.75, 1.00},
					      {0.50, 0.75, 1.00, 0.00},
					      {0.75, 1.00, 0.00, 0.25},
					      {1.00, 0.00, 0.25, 0.50}};
	static const float tex_colors[][8] = {
		{1.00, 0.00, 0.25, 0.50},
		{0.75, 1.00, 0.00, 0.25},
		{0.50, 0.75, 1.00, 0.00},
		{0.25, 0.50, 0.75, 1.00},
		/* extra colors that'll only be used for crossbar test */
		{0.00, 0.00, 0.00, 0.00},
		{0.25, 0.50, 0.50, 0.00},
		{0.50, 0.25, 0.75, 0.25},
		{0.75, 1.00, 0.25, 0.00}};

	COPY4(machine.frag_color, frag_color);
	glColor4fv(frag_color);

	for (int u = 0; u < num_tex_units; u++) {
		if (num_tex_units > 1)
			glActiveTexture(GL_TEXTURE0 + u);
		glBindTexture(GL_TEXTURE_2D, textures[u]);
		glEnable(GL_TEXTURE_2D);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
				GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
				GL_NEAREST);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
			  GL_COMBINE_EXT);
		machine.env_color[u][0] = env_colors[u % 4][0];
		machine.env_color[u][1] = env_colors[u % 4][1];
		machine.env_color[u][2] = env_colors[u % 4][2];
		machine.env_color[u][3] = env_colors[u % 4][3];
		glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
			   env_colors[u % 4]);

		const float *tex_col = tex_colors[u % 8];

		/* Setup texture color, according to texture format */
		switch (machine.tex_format[u]) {
		case GL_RGBA:
			machine.tex_color[u][0] = tex_col[0];
			machine.tex_color[u][1] = tex_col[1];
			machine.tex_color[u][2] = tex_col[2];
			machine.tex_color[u][3] = tex_col[3];
			break;
		case GL_RGB:
			machine.tex_color[u][0] = tex_col[0];
			machine.tex_color[u][1] = tex_col[1];
			machine.tex_color[u][2] = tex_col[2];
			machine.tex_color[u][3] = 1.0;
			break;
		case GL_ALPHA:
			machine.tex_color[u][0] = 0.0;
			machine.tex_color[u][1] = 0.0;
			machine.tex_color[u][2] = 0.0;
			machine.tex_color[u][3] = tex_col[3];
			break;
		case GL_LUMINANCE:
			machine.tex_color[u][0] = tex_col[0];
			machine.tex_color[u][1] = tex_col[0];
			machine.tex_color[u][2] = tex_col[0];
			machine.tex_color[u][3] = 1.0;
			break;
		case GL_LUMINANCE_ALPHA:
			machine.tex_color[u][0] = tex_col[0];
			machine.tex_color[u][1] = tex_col[0];
			machine.tex_color[u][2] = tex_col[0];
			machine.tex_color[u][3] = tex_col[3];
			break;
		case GL_INTENSITY:
			machine.tex_color[u][0] = tex_col[0];
			machine.tex_color[u][1] = tex_col[0];
			machine.tex_color[u][2] = tex_col[0];
			machine.tex_color[u][3] = tex_col[0];
			break;
		default:
			problem("bad texture format");
			return;
		}

		/* Make a 4x4 solid color texture */
		float image[16][4];
		for (int i = 0; i < 16; i++) {
			image[i][0] = tex_colors[u % 8][0];
			image[i][1] = tex_colors[u % 8][1];
			image[i][2] = tex_colors[u % 8][2];
			image[i][3] = tex_colors[u % 8][3];
		}
		glTexImage2D(GL_TEXTURE_2D, 0, machine.tex_format[u], 4, 4, 0,
			     GL_RGBA, GL_FLOAT, image);
	}
}

/* Examine a set of test params and compute the number of possible
 * state combinations. */
static int
count_test_combinations(const struct test_param test_params[])
{
	int num_tests = 1;
	for (int t = 0; test_params[t].target; t++) {
		int values = 0;
		for (int val = 0; test_params[t].valid_values[val]; val++) {
			values++;
		}
		num_tests *= values;
	}
	return num_tests / test_stride;
}

/* Setup the actual GL state and our internal simulated GL state. */
static void
tex_env(int tex_unit, GLenum target, GLenum value)
{
	if (num_tex_units > 1)
		glActiveTexture(GL_TEXTURE0 + tex_unit);

	glTexEnvi(GL_TEXTURE_ENV, target, value);
	piglit_check_gl_error(GL_NO_ERROR);

	switch (target) {
	case GL_COMBINE_RGB_EXT:
		machine.COMBINE_RGB[tex_unit] = value;
		break;
	case GL_COMBINE_ALPHA_EXT:
		machine.COMBINE_ALPHA[tex_unit] = value;
		break;
	case GL_SOURCE0_RGB_EXT:
		machine.SOURCE0_RGB[tex_unit] = value;
		break;
	case GL_SOURCE1_RGB_EXT:
		machine.SOURCE1_RGB[tex_unit] = value;
		break;
	case GL_SOURCE2_RGB_EXT:
		machine.SOURCE2_RGB[tex_unit] = value;
		break;
	case GL_SOURCE0_ALPHA_EXT:
		machine.SOURCE0_ALPHA[tex_unit] = value;
		break;
	case GL_SOURCE1_ALPHA_EXT:
		machine.SOURCE1_ALPHA[tex_unit] = value;
		break;
	case GL_SOURCE2_ALPHA_EXT:
		machine.SOURCE2_ALPHA[tex_unit] = value;
		break;
	case GL_OPERAND0_RGB_EXT:
		machine.OPERAND0_RGB[tex_unit] = value;
		break;
	case GL_OPERAND1_RGB_EXT:
		machine.OPERAND1_RGB[tex_unit] = value;
		break;
	case GL_OPERAND2_RGB_EXT:
		machine.OPERAND2_RGB[tex_unit] = value;
		break;
	case GL_OPERAND0_ALPHA_EXT:
		machine.OPERAND0_ALPHA[tex_unit] = value;
		break;
	case GL_OPERAND1_ALPHA_EXT:
		machine.OPERAND1_ALPHA[tex_unit] = value;
		break;
	case GL_OPERAND2_ALPHA_EXT:
		machine.OPERAND2_ALPHA[tex_unit] = value;
		break;
	case GL_RGB_SCALE_EXT:
		machine.RGB_SCALE[tex_unit] = value;
		break;
	case GL_ALPHA_SCALE:
		machine.ALPHA_SCALE[tex_unit] = value;
		break;
	}
}

/* Make the glTexEnv calls to setup one particular set of test parameters
 * from <test_params>.
 * <test_num> must be between 0 and count_test_combinations(test_params)-1. */
static void
setup_test_env(int test_num, const struct test_param test_params[])
{
	int divisor = 1;
	for (int t = 0; test_params[t].target; t++) {
		int num_values = 0;
		for (int val = 0; test_params[t].valid_values[val]; val++) {
			num_values++;
		}
		assert(num_values > 0);
		int v = (test_num / divisor) % num_values;
		GLenum target = test_params[t].target;
		GLenum value = test_params[t].valid_values[v];
		tex_env(0, target, value);
		divisor *= num_values;
	}
}

static void
print_test_env(int test_num, const struct test_param test_params[])
{
	int divisor = 1;
	for (int t = 0; test_params[t].target; t++) {
		int num_values = 0;
		for (int val = 0; test_params[t].valid_values[val]; val++) {
			num_values++;
		}
		assert(num_values > 0);
		int v = (test_num / divisor) % num_values;
		GLenum target = test_params[t].target;
		GLenum value = test_params[t].valid_values[v];
		printf("%s %s\n", piglit_get_gl_enum_name(target),
		       piglit_get_gl_enum_name(value));
		divisor *= num_values;
	}
	printf("\n");
}

#define RUN_SINGLE_TEXTURE_TEST(test) run_single_texture_test(test, #test)

/* Test texenv-combine with a single texture unit. */
static bool
run_single_texture_test(const struct test_param test_params[],
			const char *test_name)
{
	assert(num_tex_units == 1);
	setup_colors();

	const int num_tests = count_test_combinations(test_params);

	for (int test = 0; test < num_tests; test += test_stride) {
		/* 0. Setup state */
		reset_machine();
		setup_test_env(test, test_params);

		/* 1. Render with OpenGL */
		glTexCoord2f(0, 0); /* use texcoord (0,0) for all vertices */
		piglit_draw_rect(-1, -1, 2, 2);

		/* 2. Compute expected result */
		float expected[4];
		expected[3] = -1.0f;
		compute_tex_combine(0, machine.frag_color, expected);

		/* 3. Compare rendered result to expected result */
		if (!piglit_probe_pixel_rgba(0, 0, expected)) {
			printf("Single Texture Test %s %d\n", test_name,
			       test);
			print_test_env(test, test_params);
			return false;
		}
	}
	return true;
}

/* For each texture unit, test each texenv-combine mode.
 * That's 5 ^ num_tex_units combinations.
 * Or 7 ^ num_tex_units if DOT3 combine mode is supported */
static int
count_multi_texture_test_combinations()
{
	int num_tests = 1;
	for (int i = 0; i < num_tex_units; i++)
		num_tests *= (have_dot3 ? 7 : 5);

	return num_tests / test_stride;
}

/* Test texenv-combine with multiple texture units. */
static bool
run_multi_texture_test(void)
{
	static const GLenum combine_modes[10] = {
		GL_REPLACE,
		GL_ADD,
		GL_ADD_SIGNED_EXT,
		GL_MODULATE,
		GL_INTERPOLATE_EXT,
		GL_DOT3_RGB_EXT,
		GL_DOT3_RGBA_EXT,
		GL_MODULATE_ADD_ATI,
		GL_MODULATE_SIGNED_ADD_ATI,
		GL_MODULATE_SUBTRACT_ATI
	};
	const int num_modes = have_dot3 ? (have_combine3 ? 10 : 7) : 5;

	/* four texture units is enough to test */
	if (num_tex_units > 4)
		num_tex_units = 4;

	const int num_tests = count_multi_texture_test_combinations();

	setup_colors();
	for (int test_num = 0; test_num < num_tests;
	     test_num += test_stride) {
		/* 0. Set up texture units */
		reset_machine();
		int divisor = 1;
		for (int u = 0; u < num_tex_units; u++) {
			const int m = (test_num / divisor) % num_modes;
			const GLenum mode = combine_modes[m];

			/* Set GL_COMBINE_RGB_EXT and GL_COMBINE_ALPHA_EXT */
			tex_env(u, GL_COMBINE_RGB_EXT, mode);
			tex_env(u, GL_COMBINE_ALPHA_EXT,
				(mode == GL_DOT3_RGB_EXT ||
				 mode == GL_DOT3_RGBA_EXT)
					? GL_REPLACE
					: mode);
			tex_env(u, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
			tex_env(u, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT);
			tex_env(u, GL_SOURCE2_RGB_EXT, GL_TEXTURE);
			tex_env(u, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT);
			tex_env(u, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT);
			tex_env(u, GL_SOURCE2_ALPHA_EXT, GL_TEXTURE);
			tex_env(u, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
			tex_env(u, GL_OPERAND1_RGB_EXT,
				GL_ONE_MINUS_SRC_COLOR);
			tex_env(u, GL_OPERAND2_RGB_EXT, GL_SRC_ALPHA);
			tex_env(u, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA);
			tex_env(u, GL_OPERAND1_ALPHA_EXT,
				GL_ONE_MINUS_SRC_ALPHA);
			tex_env(u, GL_OPERAND2_ALPHA_EXT, GL_SRC_ALPHA);
			tex_env(u, GL_RGB_SCALE_EXT, 1);
			tex_env(u, GL_ALPHA_SCALE, 1);

			divisor *= num_modes;
		}

		/* 1. Render with OpenGL */
		/* use texcoord (0,0) for all vertices */
		for (int u = 0; u < num_tex_units; u++)
			glMultiTexCoord2f(GL_TEXTURE0 + u, 0, 0);
		piglit_draw_rect(-1, -1, 2, 2);

		/* 2. Compute expected result */
		float prev_color[4];
		float expected[4] = {0};
		COPY4(prev_color, machine.frag_color);
		for (int u = 0; u < num_tex_units; u++) {
			compute_tex_combine(u, prev_color, expected);
			COPY4(prev_color, expected);
		}

		/* 3. Compare rendered result to expected result */
		if (!piglit_probe_pixel_rgba(0, 0, expected)) {
			printf("Multi-texture test %d\n", test_num);
			return false;
		}
	}
	return true;
}

/* We do a really short, simple test for GL_ARB_texture_env_crossbar since the
 * preceding tests are pretty comprehensive and the crossbar feature is just
 * an incremental addition.  Basically, if we have N texture units we run N
 * tests.  For test [i] we set texture unit [i] to fetch the texture color
 * from unit [num_units - i - 1].  For units != i we use the constant color
 * (0,0,0,0).  We use GL_ADD mode to compute the sum over all units.  So
 * effectively, the result of texture combine is simply the incoming fragment
 * color plus unit [num_units - test - 1]'s texture color. */
static bool
run_crossbar_test()
{
	glGetIntegerv(GL_MAX_TEXTURE_UNITS, &num_tex_units);

	/* Set up constant texture state for all tests */
	setup_colors();
	reset_machine();
	for (int unit = 0; unit < num_tex_units; unit++) {
		tex_env(unit, GL_COMBINE_RGB_EXT, GL_ADD);
		tex_env(unit, GL_COMBINE_ALPHA_EXT, GL_ADD);
		tex_env(unit, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
		tex_env(unit, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT);
		/* SOURCE1_RGB/ALPHA is set below, per test */
		tex_env(unit, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
		tex_env(unit, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
		tex_env(unit, GL_OPERAND2_RGB_EXT, GL_SRC_ALPHA);
		tex_env(unit, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA);
		tex_env(unit, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA);
		tex_env(unit, GL_OPERAND2_ALPHA_EXT, GL_SRC_ALPHA);
		tex_env(unit, GL_RGB_SCALE_EXT, 1);
		tex_env(unit, GL_ALPHA_SCALE, 1);

		machine.env_color[unit][0] = 0.0;
		machine.env_color[unit][1] = 0.0;
		machine.env_color[unit][2] = 0.0;
		machine.env_color[unit][3] = 0.0;
		glActiveTexture(GL_TEXTURE0 + unit);
		glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
			   machine.env_color[unit]);
	}

	for (int test = 0; test < num_tex_units; test++) {
		/* 1. Set up texture state */
		for (int unit = 0; unit < num_tex_units; unit++) {
			if (unit == test) {
				const int revUnit = num_tex_units - unit - 1;
				tex_env(unit, GL_SOURCE1_RGB_EXT,
					GL_TEXTURE0 + revUnit);
				tex_env(unit, GL_SOURCE1_ALPHA_EXT,
					GL_TEXTURE0 + revUnit);
			} else {
				tex_env(unit, GL_SOURCE1_RGB_EXT,
					GL_CONSTANT_EXT);
				tex_env(unit, GL_SOURCE1_ALPHA_EXT,
					GL_CONSTANT_EXT);
			}
		}

		/* 2. Render with OpenGL */
		/* texcoord (0,) for all vertices is OK */
		for (int unit = 0; unit < num_tex_units; unit++)
			glMultiTexCoord2f(GL_TEXTURE0 + unit, 0, 0);
		piglit_draw_rect(-1, -1, 2, 2);

		/* 3. Compute expected result */
		float prev_color[4];
		float expected[4];
		COPY4(prev_color, machine.frag_color);
		for (int unit = 0; unit < num_tex_units; unit++) {
			compute_tex_combine(unit, prev_color, expected);
			COPY4(prev_color, expected);
		}

		/* 4. Compare rendered result to expected result */
		if (!piglit_probe_pixel_rgba(0, 0, expected)) {
			printf("Texture crossbar test %d\n", test);
			return false;
		}
	}
	return true;
}

enum piglit_result
piglit_display(void)
{
	bool pass = true;
	float old_tolerance[4];

	/* Do single texture unit tests first. */
	if (pass)
		pass = RUN_SINGLE_TEXTURE_TEST(replace_params);
	if (pass)
		pass = RUN_SINGLE_TEXTURE_TEST(add_params);
	if (pass)
		pass = RUN_SINGLE_TEXTURE_TEST(add_signed_params);
	if (pass)
		pass = RUN_SINGLE_TEXTURE_TEST(modulate_params);
	if (pass)
		pass = RUN_SINGLE_TEXTURE_TEST(interpolate_params);
	/* Some implementations have precision problems with the dot3
	 * instruction. */
	for (int i = 0; i < 4; ++i) {
		old_tolerance[i] = piglit_tolerance[i];
		piglit_tolerance[i] = MAX2(0.02, piglit_tolerance[i]);
	}
	if (pass && have_dot3)
		pass = RUN_SINGLE_TEXTURE_TEST(dot3_rgb_params);
	if (pass && have_dot3)
		pass = RUN_SINGLE_TEXTURE_TEST(dot3_rgba_params);
	for (int i = 0; i < 4; ++i)
		piglit_tolerance[i] = old_tolerance[i];
	if (pass && have_combine3)
		pass = RUN_SINGLE_TEXTURE_TEST(modulate_add_params);
	if (pass && have_combine3)
		pass = RUN_SINGLE_TEXTURE_TEST(modulate_signed_add_params);
	if (pass && have_combine3)
		pass = RUN_SINGLE_TEXTURE_TEST(modulate_subtract_params);

	/* Now do some multi-texture tests */
	glGetIntegerv(GL_MAX_TEXTURE_UNITS, &num_tex_units);
	if (pass && num_tex_units > 1) {
		pass = run_multi_texture_test();
	}

	/* Do crossbar tests */
	if (pass && have_crossbar) {
		pass = run_crossbar_test();
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

void
piglit_init(int argc, char **argv)
{
	piglit_require_extension("GL_EXT_texture_env_combine");

	/* Test the availability of the DOT3 extension */
	have_dot3 = piglit_is_extension_supported("GL_EXT_texture_env_dot3");

	have_crossbar =
		piglit_is_extension_supported("GL_ARB_texture_env_crossbar");

	have_combine3 =
		piglit_is_extension_supported("GL_ATI_texture_env_combine3");

	/* Allocate our textures */
	glGenTextures(MAX_TEX_UNITS, textures);

	reset_machine();
	num_tex_units = 1;

	for (int i = 1; i < argc; i++)
		if (!strcmp(argv[i], "--quick"))
			test_stride = 67; /* a prime number */
}
