/*  BEGIN_COPYRIGHT -*- glean -*-
 *
 *  Copyright (C) 1999  Allen Akin   All Rights Reserved.
 *  Copyright (C) 2014  Intel Corporation.
 *
 * 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 (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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 *  END_COPYRIGHT
 */

/** @file blend.c
 *
 *  Test blending functions.
 *
 *	This test checks all combinations of source and destination
 *	blend factors for the GL_FUNC_ADD blend equation.  It operates
 *	on all RGB or RGBA drawing surface configurations that support
 *	the creation of windows.
 *
 *	Note that a common cause of failures for this test is small errors
 *	introduced when an implementation scales color values incorrectly;
 *	for example, converting an 8-bit color value to float by
 *	dividing by 256 rather than 255, or computing a blending result
 *	by shifting a double-width intermediate value rather than scaling
 *	it.  Also, please note that the OpenGL spec requires that when
 *	converting from floating-point colors to integer form, the result
 *	must be rounded to the nearest integer, not truncated.
 *	[1.2.1, 2.13.9]
 *
 *	The test reports two error measurements.  The first (readback) is
 *	the error detected when reading back raw values that were written
 *	to the framebuffer.  The error in this case should be very close
 *	to zero, since the values are carefully constructed so that they
 *	can be represented accurately in the framebuffer.  The second
 *	(blending) is the error detected in the result of the blending
 *	computation.  For the test to pass, these errors must both be
 *	no greater than one least-significant bit in the framebuffer
 *	representation of a color.
 */

#include "piglit-util-gl.h"

#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

#define HUGE_STEP 1000

static int test_stride = 1;

/*
 * We will check each pair of blend factors
 * for each pixel in a square image of this
 * dimension, so if you make it too large,
 * the tests may take quite a while to run.
 */
#define drawing_size 32
#define img_width drawing_size
#define img_height drawing_size

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_visual = PIGLIT_GL_VISUAL_RGBA |
		PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const GLenum src_factors[] = {
	GL_ZERO,
	GL_ONE,
	GL_DST_COLOR,
	GL_ONE_MINUS_DST_COLOR,
	GL_SRC_ALPHA,
	GL_ONE_MINUS_SRC_ALPHA,
	GL_DST_ALPHA,
	GL_ONE_MINUS_DST_ALPHA,
	GL_SRC_ALPHA_SATURATE,
	GL_CONSTANT_COLOR,
	GL_ONE_MINUS_CONSTANT_COLOR,
	GL_CONSTANT_ALPHA,
	GL_ONE_MINUS_CONSTANT_ALPHA
};
static const GLenum dst_factors[] = {
	GL_ZERO,
	GL_ONE,
	GL_SRC_COLOR,
	GL_ONE_MINUS_SRC_COLOR,
	GL_SRC_ALPHA,
	GL_ONE_MINUS_SRC_ALPHA,
	GL_DST_ALPHA,
	GL_ONE_MINUS_DST_ALPHA,
	GL_CONSTANT_COLOR,
	GL_ONE_MINUS_CONSTANT_COLOR,
	GL_CONSTANT_ALPHA,
	GL_ONE_MINUS_CONSTANT_ALPHA
};

GLenum operators[5];

struct image {
	GLuint name;
	GLfloat *data;
};

static struct image dst_img;      /* Image #1 */
static struct image src_img;      /* Image #2 */
static struct image exp_img;      /* The expected blending result */

static bool have_sep_func, have_blend_equation;
static bool have_blend_equation_sep, have_blend_color;

/* A bright, semi-transparent blue */
static const GLfloat constant_color[4] = {0.25, 0.0, 1.0, 0.75};

/* Our random image data factory. */
GLfloat *
random_image_data(void)
{
	int i;
	GLfloat *img = malloc(4*img_width*img_height*sizeof(GLfloat));
	for (i = 0; i < 4*img_width*img_height; ++i) {
		img[i] = (float) rand() / RAND_MAX;
	}
	return img;
} /* random_image_data */

/* Our solid color fill data factory. */
GLfloat *
color_fill_data(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
	int i, j;
	GLfloat *img = malloc(4*img_width*img_height*sizeof(GLfloat));
	for (j = 0; j < img_height; ++j) { /* j = vertical, i = horizontal */
		for (i = 0; i < img_width; ++i) {
			img[4*(img_width*j + i) + 0] = r;
			img[4*(img_width*j + i) + 1] = g;
			img[4*(img_width*j + i) + 2] = b;
			img[4*(img_width*j + i) + 3] = a;
		}
	}
	return img;
} /* color_fill_data */

static void
image_init(struct image *image)
{
	glGenTextures(1, &image->name);
	glBindTexture(GL_TEXTURE_2D, image->name);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
		     img_width, img_height, 0,
		     GL_RGBA, GL_FLOAT, image->data);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
} /* image_init */


void
piglit_init(int argc, char **argv)
{

	int rgb_tol = 0;
	int alpha_tol = 0;
	const char* blend_rgb_tol = getenv("PIGLIT_BLEND_RGB_TOLERANCE");
	const char* blend_alpha_tol = getenv("PIGLIT_BLEND_ALPHA_TOLERANCE");

	if (argc > 1 && strcmp(argv[1], "--quick") == 0) {
		/* By default we run 27552 tests which is time consuming.
		 * With --quick we run only 1/20 (5%) of the tests.
		 */
		test_stride = 20;
	}

	/* Hack: Make driver tests on incorrect hardware feasible
	 * We want to be able to perform meaningful tests
	 * even when the blend unit of a GPU simply doesn't have
	 * sufficient precision.
	 */
	if (blend_rgb_tol)
	{
		rgb_tol = atoi(blend_rgb_tol);
		printf("Note: RGB tolerance changed to %i %s.\n", rgb_tol,
			rgb_tol == 1 ? "bit" : "bits");
	}
	if (blend_alpha_tol)
	{
		alpha_tol = atoi(blend_alpha_tol);
		printf("Note: Alpha tolerance changed to %i %s.\n", alpha_tol,
			alpha_tol == 1 ? "bit" : "bits");
	}
	/* A 0 passed in yields the default tolerance of 3.0/(1 << 8) ~= 0.01. */
	piglit_set_tolerance_for_bits(rgb_tol, rgb_tol, rgb_tol, alpha_tol);

	/* Initialize random images. */
	srand(0);

	/* Fill src and dst with randomness. */
	dst_img.data = random_image_data();
	src_img.data = random_image_data();

	/* Fill exp_img with constant_color.
	 *
	 * You can use this as a check to make sure the test is working
	 * properly.
	 */
	exp_img.data = color_fill_data(constant_color[0],
				       constant_color[1],
				       constant_color[2],
				       constant_color[3]);

	image_init(&dst_img);
	image_init(&src_img);
	image_init(&exp_img);

} /* piglit_init */

bool
needs_blend_color(const GLenum func)
{
	switch (func) {
	case GL_CONSTANT_COLOR:
	case GL_ONE_MINUS_CONSTANT_COLOR:
	case GL_CONSTANT_ALPHA:
	case GL_ONE_MINUS_CONSTANT_ALPHA:
		return true;
	default:
		return false;
	}
}

/* Function that verifies GL's blending behavior. */
static void
apply_blend(GLenum src_factor_rgb, GLenum src_factor_a,
	    GLenum dst_factor_rgb, GLenum dst_factor_a,
	    GLenum op_rgb, GLenum op_a,
	    float* dst, const float* src,
	    const GLfloat constant_color[4])
{
	float sf[4] = { 0 };
	float df[4] = { 0 };

	if (op_rgb != GL_MIN && op_rgb != GL_MAX) {
		/* Src RGB term */
		switch (src_factor_rgb) {
		case GL_ZERO:
			sf[0] = sf[1] = sf[2] = 0.0;
			break;
		case GL_ONE:
			sf[0] = sf[1] = sf[2] = 1.0;
			break;
		case GL_DST_COLOR:
			sf[0] = dst[0];
			sf[1] = dst[1];
			sf[2] = dst[2];
			break;
		case GL_ONE_MINUS_DST_COLOR:
			sf[0] = 1.0 - dst[0];
			sf[1] = 1.0 - dst[1];
			sf[2] = 1.0 - dst[2];
			break;
		case GL_SRC_ALPHA:
			sf[0] = sf[1] = sf[2] = src[3];
			break;
		case GL_ONE_MINUS_SRC_ALPHA:
			sf[0] = sf[1] = sf[2] = 1.0 - src[3];
			break;
		case GL_DST_ALPHA:
			sf[0] = sf[1] = sf[2] = dst[3];
			break;
		case GL_ONE_MINUS_DST_ALPHA:
			sf[0] = sf[1] = sf[2] = 1.0 - dst[3];
			break;
		case GL_SRC_ALPHA_SATURATE: {
			float f = 1.0 - dst[3];
			if (src[3] < f)
				f = src[3];
			sf[0] = sf[1] = sf[2] = f;
			}
			break;
		case GL_CONSTANT_COLOR:
			sf[0] = constant_color[0];
			sf[1] = constant_color[1];
			sf[2] = constant_color[2];
			break;
		case GL_ONE_MINUS_CONSTANT_COLOR:
			sf[0] = 1.0 - constant_color[0];
			sf[1] = 1.0 - constant_color[1];
			sf[2] = 1.0 - constant_color[2];
			break;
		case GL_CONSTANT_ALPHA:
			sf[0] =
			sf[1] =
			sf[2] = constant_color[3];
			break;
		case GL_ONE_MINUS_CONSTANT_ALPHA:
			sf[0] =
			sf[1] =
			sf[2] = 1.0 - constant_color[3];
			break;
		default:
			sf[0] = sf[1] = sf[2] = 0.0;
			abort();
			break;
		}

		/* Dest RGB term */
		switch (dst_factor_rgb) {
		case GL_ZERO:
			df[0] = df[1] = df[2] = 0.0;
			break;
		case GL_ONE:
			df[0] = df[1] = df[2] = 1.0;
			break;
		case GL_SRC_COLOR:
			df[0] = src[0];
			df[1] = src[1];
			df[2] = src[2];
			break;
		case GL_ONE_MINUS_SRC_COLOR:
			df[0] = 1.0 - src[0];
			df[1] = 1.0 - src[1];
			df[2] = 1.0 - src[2];
			break;
		case GL_SRC_ALPHA:
			df[0] = df[1] = df[2] = src[3];
			break;
		case GL_ONE_MINUS_SRC_ALPHA:
			df[0] = df[1] = df[2] = 1.0 - src[3];
			break;
		case GL_DST_ALPHA:
			df[0] = df[1] = df[2] = dst[3];
			break;
		case GL_ONE_MINUS_DST_ALPHA:
			df[0] = df[1] = df[2] = 1.0 - dst[3];
			break;
		case GL_CONSTANT_COLOR:
			df[0] = constant_color[0];
			df[1] = constant_color[1];
			df[2] = constant_color[2];
			break;
		case GL_ONE_MINUS_CONSTANT_COLOR:
			df[0] = 1.0 - constant_color[0];
			df[1] = 1.0 - constant_color[1];
			df[2] = 1.0 - constant_color[2];
			break;
		case GL_CONSTANT_ALPHA:
			df[0] =
			df[1] =
			df[2] = constant_color[3];
			break;
		case GL_ONE_MINUS_CONSTANT_ALPHA:
			df[0] =
			df[1] =
			df[2] = 1.0 - constant_color[3];
			break;
		default:
			df[0] = df[1] = df[2] = 0.0;
			abort();
			break;
		}
	}

	if (op_a != GL_MIN && op_a != GL_MAX) {
		/* Src Alpha term */
		switch (src_factor_a) {
		case GL_ZERO:
			sf[3] = 0.0;
			break;
		case GL_ONE:
			sf[3] = 1.0;
			break;
		case GL_DST_COLOR:
			sf[3] = dst[3];
			break;
		case GL_ONE_MINUS_DST_COLOR:
			sf[3] = 1.0 - dst[3];
			break;
		case GL_SRC_ALPHA:
			sf[3] = src[3];
			break;
		case GL_ONE_MINUS_SRC_ALPHA:
			sf[3] = 1.0 - src[3];
			break;
		case GL_DST_ALPHA:
			sf[3] = dst[3];
			break;
		case GL_ONE_MINUS_DST_ALPHA:
			sf[3] = 1.0 - dst[3];
			break;
		case GL_SRC_ALPHA_SATURATE:
			sf[3] = 1.0;
			break;
		case GL_CONSTANT_COLOR:
			sf[3] = constant_color[3];
			break;
		case GL_ONE_MINUS_CONSTANT_COLOR:
			sf[3] = 1.0 - constant_color[3];
			break;
		case GL_CONSTANT_ALPHA:
			sf[3] = constant_color[3];
			break;
		case GL_ONE_MINUS_CONSTANT_ALPHA:
			sf[3] = 1.0 - constant_color[3];
			break;
		default:
			sf[3] = 0.0;
			abort();
			break;
		}

		/* Dst Alpha term */
		switch (dst_factor_a) {
		case GL_ZERO:
			df[3] = 0.0;
			break;
		case GL_ONE:
			df[3] = 1.0;
			break;
		case GL_SRC_COLOR:
			df[3] = src[3];
			break;
		case GL_ONE_MINUS_SRC_COLOR:
			df[3] = 1.0 - src[3];
			break;
		case GL_SRC_ALPHA:
			df[3] = src[3];
			break;
		case GL_ONE_MINUS_SRC_ALPHA:
			df[3] = 1.0 - src[3];
			break;
		case GL_DST_ALPHA:
			df[3] = dst[3];
			break;
		case GL_ONE_MINUS_DST_ALPHA:
			df[3] = 1.0 - dst[3];
			break;
		case GL_CONSTANT_COLOR:
			df[3] = constant_color[3];
			break;
		case GL_ONE_MINUS_CONSTANT_COLOR:
			df[3] = 1.0 - constant_color[3];
			break;
		case GL_CONSTANT_ALPHA:
			df[3] = constant_color[3];
			break;
		case GL_ONE_MINUS_CONSTANT_ALPHA:
			df[3] = 1.0 - constant_color[3];
			break;
		default:
			df[3] = 0.0;
			abort();
			break;
		}
	}

	switch (op_rgb) {
	case GL_FUNC_ADD:
		dst[0] = CLAMP(src[0] * sf[0] + dst[0] * df[0], 0.0f, 1.0f);
		dst[1] = CLAMP(src[1] * sf[1] + dst[1] * df[1], 0.0f, 1.0f);
		dst[2] = CLAMP(src[2] * sf[2] + dst[2] * df[2], 0.0f, 1.0f);
		break;
	case GL_FUNC_SUBTRACT:
		dst[0] = CLAMP(src[0] * sf[0] - dst[0] * df[0], 0.0f, 1.0f);
		dst[1] = CLAMP(src[1] * sf[1] - dst[1] * df[1], 0.0f, 1.0f);
		dst[2] = CLAMP(src[2] * sf[2] - dst[2] * df[2], 0.0f, 1.0f);
		break;
	case GL_FUNC_REVERSE_SUBTRACT:
		dst[0] = CLAMP(dst[0] * df[0] - src[0] * sf[0], 0.0f, 1.0f);
		dst[1] = CLAMP(dst[1] * df[1] - src[1] * sf[1], 0.0f, 1.0f);
		dst[2] = CLAMP(dst[2] * df[2] - src[2] * sf[2], 0.0f, 1.0f);
		break;
	case GL_MIN:
		dst[0] = MIN2(src[0], dst[0]);
		dst[1] = MIN2(src[1], dst[1]);
		dst[2] = MIN2(src[2], dst[2]);
		break;
	case GL_MAX:
		dst[0] = MAX2(src[0], dst[0]);
		dst[1] = MAX2(src[1], dst[1]);
		dst[2] = MAX2(src[2], dst[2]);
		break;
	default:
		assert(!"Invalid RGB function");
	}

	switch (op_a) {
	case GL_FUNC_ADD:
		dst[3] = CLAMP(src[3] * sf[3] + dst[3] * df[3], 0.0f, 1.0f);
		break;
	case GL_FUNC_SUBTRACT:
		dst[3] = CLAMP(src[3] * sf[3] - dst[3] * df[3], 0.0f, 1.0f);
		break;
	case GL_FUNC_REVERSE_SUBTRACT:
		dst[3] = CLAMP(dst[3] * df[3] - src[3] * sf[3], 0.0f, 1.0f);
		break;
	case GL_MIN:
		dst[3] = MIN2(src[3], dst[3]);
		break;
	case GL_MAX:
		dst[3] = MAX2(src[3], dst[3]);
		break;
	default:
		assert(!"Invalid alpha function");
	}
} /* apply_blend */


/* Test for one set of factor levels */
bool
run_factor_set(GLenum src_factor_rgb, GLenum src_factor_a,
	       GLenum dst_factor_rgb, GLenum dst_factor_a,
	       GLenum op_rgb, GLenum op_a,
	       const GLfloat constant_color[4])
{
	int i, j;
	bool pass = true, p;

	glDisable(GL_DITHER);
	glClear(GL_COLOR_BUFFER_BIT);

	/* Send dst image to the framebuffer. */
	glDisable(GL_BLEND);
	piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, dst_img.name);
	piglit_draw_rect_tex(0, 0, img_width, img_height, 0, 0, 1, 1);
	pass &= piglit_check_gl_error(GL_NO_ERROR);

	/* Read back the contents of the framebuffer, and measure any
	 * difference from what was actually written.  We can't tell
	 * whether errors occurred when writing or when reading back,
	 * but at least we can report anything unusual.
	 */
	pass &= piglit_probe_image_rgba(0, 0, img_width,
		img_height, dst_img.data);

	/*
	 * Now apply the blending
	 * operation to both the framebuffer and a copy in the image
	 * ``expected''.  Note that a fresh source alpha must be
	 * generated here, because the range of source alpha values is
	 * not limited by the range of alpha values that can be
	 * represented in the framebuffer.  Save the source pixels in
	 * the image ``src'' so we can diagnose any problems we find
	 * later.
	 */

	/* Configure the appropriate blending settings */
	if (have_sep_func)
		glBlendFuncSeparate(src_factor_rgb, dst_factor_rgb,
				    src_factor_a, dst_factor_a);
	else
		glBlendFunc(src_factor_rgb, dst_factor_rgb);

	if (have_blend_equation_sep)
		glBlendEquationSeparate(op_rgb, op_a);
	else if (have_blend_equation)
		glBlendEquation(op_rgb);


	/* Send src to the framebuffer and let GL blend it with dst */
	glEnable(GL_BLEND);
	glBindTexture(GL_TEXTURE_2D, src_img.name);
	piglit_draw_rect_tex(0, 0, img_width, img_height, 0, 0, 1, 1);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_BLEND);
	pass &= piglit_check_gl_error(GL_NO_ERROR);

	/* Compute the appropriate expected. */
	for (j = 0; j < img_height; ++j) {
		for (i = 0; i < img_width; ++i) {

			int idx = 4*(img_width*j + i);

			/* Initialize expected with dst data. */
			exp_img.data[idx + 0] = dst_img.data[idx + 0];
			exp_img.data[idx + 1] = dst_img.data[idx + 1];
			exp_img.data[idx + 2] = dst_img.data[idx + 2];
			exp_img.data[idx + 3] = dst_img.data[idx + 3];

			/* Apply the blending. */
			apply_blend(src_factor_rgb, src_factor_a,
				    dst_factor_rgb, dst_factor_a,
				    op_rgb, op_a,
				    &exp_img.data[idx], &src_img.data[idx],
				    constant_color);
		}
	}

	/* Compare the image in the framebuffer to the computed image
	 * (``expected'') to see if any pixels are outside the expected
	 * tolerance range.
	 */
	p = piglit_probe_image_rgba(0, 0, img_width, img_height,
		exp_img.data);
	if (!p) {
		printf("  Blend src factors: %s, %s\n",
		       piglit_get_gl_enum_name(src_factor_rgb),
		       piglit_get_gl_enum_name(src_factor_a));
		printf("  Blend dst factors: %s, %s\n",
		       piglit_get_gl_enum_name(dst_factor_rgb),
		       piglit_get_gl_enum_name(dst_factor_a));
		printf("  Blend ops: %s, %s\n",
		       piglit_get_gl_enum_name(op_rgb),
		       piglit_get_gl_enum_name(op_a));
		printf("  Blend color: %.3f, %.3f, %.3f, %.3f\n",
		       constant_color[0], constant_color[1],
		       constant_color[2], constant_color[3]);
		fflush(stdout);
	}

	pass &= p;

	return pass;
} /* run_factor_set */

bool
proc_factors(int sf, int sfa, int df, int dfa, int *counter,
	     int op, int opa)
{
	GLenum src_rgb, src_a, dst_rgb, dst_a;

	if (have_sep_func) {
		src_rgb = src_factors[sf];
		src_a = src_factors[sfa];
		dst_rgb = dst_factors[df];
		dst_a = dst_factors[dfa];
	} else {
		src_rgb = src_a = src_factors[sf];
		dst_rgb = dst_a = dst_factors[df];
	}

	/* skip test if blend color used, but not supported. */
	if (!have_blend_color
	    && (needs_blend_color(src_rgb) ||
		needs_blend_color(src_a) ||
		needs_blend_color(dst_rgb) ||
		needs_blend_color(dst_a)))
		return true;

	/* Increment counter so that tests are numbered starting from 1. */
	(*counter)++;

#if 0
	/* For verification purposes, this prints every test configuration as
	 * it runs.
	 */
	printf("%i: %s, %s, %s, %s, %s, %s\n", *counter,
	       piglit_get_gl_enum_name(src_rgb),
	       piglit_get_gl_enum_name(src_a),
	       piglit_get_gl_enum_name(dst_rgb),
	       piglit_get_gl_enum_name(dst_a),
	       piglit_get_gl_enum_name(operators[op]),
	       piglit_get_gl_enum_name(operators[opa]));
#endif

	return run_factor_set(src_rgb, src_a, dst_rgb, dst_a,
			      operators[op], operators[opa], constant_color);
} /* proc_factors */

/**
 * Run the whole suite of blend tests
 * Not a full factorial test, that would take too long.
 * Tests all add blending permutations.
 * Tests about 1/3 of subtract blending.
 * Skips most max and min tests.
 */
bool
run_all_factor_sets(void)
{
	bool pass = true;
	int gl_version = piglit_get_gl_version();
	int counter = 0; /* Number of tests we have done. */
	int test_number = 0;
	int step;
	int op, opa;
	int sf, sfa, df, dfa;

	unsigned num_src_factors_sep, num_dst_factors_sep;
	unsigned num_operators;

	/* Find out what kind of GL blending capability we have. */
	if (gl_version >= 14) {
		have_blend_equation = true;
		have_blend_color = true;
		have_sep_func = true;
	} else {
		have_blend_equation =
			piglit_is_extension_supported("GL_EXT_blend_subtract") ||
			piglit_is_extension_supported("GL_EXT_blend_minmax");
		have_blend_color =
			piglit_is_extension_supported("GL_EXT_blend_color");
		have_sep_func =
			piglit_is_extension_supported("GL_EXT_blend_func_separate");
	}

	if (gl_version >= 20) {
		have_blend_equation_sep = true;
	} else {
		have_blend_equation_sep =
			piglit_is_extension_supported("GL_EXT_blend_equation_separate");
	}

	if (have_blend_color) {
		/* Just one blend color setting for all tests */
		/* A bright, semi-transparent blue */
		glBlendColor(constant_color[0], constant_color[1],
			     constant_color[2],
			     constant_color[3]);
	}

	if (have_sep_func) {
		num_src_factors_sep = ARRAY_SIZE(src_factors);
		num_dst_factors_sep = ARRAY_SIZE(dst_factors);
	}
	else {
		num_src_factors_sep = 1;
		num_dst_factors_sep = 1;
	}

	num_operators = 1;
	operators[0] = GL_FUNC_ADD;

	if (piglit_is_extension_supported("GL_EXT_blend_subtract")) {
		operators[num_operators++] = GL_FUNC_SUBTRACT;
		operators[num_operators++] = GL_FUNC_REVERSE_SUBTRACT;
	}

	if (piglit_is_extension_supported("GL_EXT_blend_minmax")) {
		operators[num_operators++] = GL_MIN;
		operators[num_operators++] = GL_MAX;
	}

	assert(num_operators <= ARRAY_SIZE(operators));

	for (op = 0; op < num_operators; ++op) {
		for (opa = 0; opa < num_operators; ++opa) {
			if (operators[op] == GL_FUNC_ADD &&
			    operators[opa] == GL_FUNC_ADD) {
				/* test _all_ blend term combinations */
				step = 1;
			}
			else if (operators[op] == GL_MIN ||
				 operators[op] == GL_MAX ||
				 operators[opa] == GL_MIN ||
				 operators[opa] == GL_MAX) {
				/* blend terms are N/A so only */
				/* do one iteration of loops */
				step = HUGE_STEP;
			}
			else {
				/* subtract modes: */
				/*do every 3rd blend term for speed */
				step = 3;
			}

			for (sf = 0;
			     sf < ARRAY_SIZE(src_factors);
			     sf += step) {
				for (sfa = 0;
				     sfa < num_src_factors_sep;
				     sfa += step) {
					for (df = 0;
					     df < ARRAY_SIZE(dst_factors);
					     df += step) {
						for (dfa = 0;
						     dfa < num_dst_factors_sep;
						     dfa += step) {
							if (test_number % test_stride == 0) {
								pass &= proc_factors(
									sf, sfa,
									df, dfa,
									&counter,
									op, opa);
							}
							test_number++;
						}
					}
				}
			}
		}
	}

	printf("\nRan %i tests.\n", counter);
	return pass;
} /* run_all_factor_sets */

enum piglit_result
piglit_display(void)
{
	bool pass = true;

	pass &= run_all_factor_sets();

	if (!piglit_automatic) {
		/* Draw our three images, separated by some space.  This will
		 * show only the results of the last test.
		 */

		/* Draw dst */
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, dst_img.name);
		piglit_draw_rect_tex(0, 0, img_width, img_height, 0, 0, 1, 1);

		/* Draw src */
		glBindTexture(GL_TEXTURE_2D, src_img.name);
		piglit_draw_rect_tex(img_width + 2, 0,
				     img_width, img_height, 0, 0, 1, 1);

		/* Draw exp */
		glBindTexture(GL_TEXTURE_2D, exp_img.name);
		/* Have to resend the texture to GL to update GL's copy */
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
			     img_width, img_height, 0,
			     GL_RGBA, GL_FLOAT, exp_img.data);
		piglit_draw_rect_tex(2*(img_width + 2), 0,
			img_width, img_height, 0, 0, 1, 1);
		glDisable(GL_TEXTURE_2D);

		piglit_present_results();
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
} /* piglit_display */
