/*
 * Copyright (c) 2010 VMware, Inc.
 *
 * 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 VMWARE 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.
 */

/**
 * @file
 * Tests GL_EXT_texture_integer texture formats.
 * simpler test modified for GLSL1.30 by airlied
 */

#include "piglit-util-gl.h"

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 char *TestName = "texture-integer";

static GLint TexWidth = 16, TexHeight = 16;
static GLuint Texture;

static GLint BiasUniform[2] = {-1, -1}, TexUniform[2] = {-1, -1};

struct format_info
{
	GLenum IntFormat, BaseFormat;
	GLuint BitsPerChannel;
	GLboolean Signed;
};

static const struct format_info Formats[] = {
	/*   { "GL_RGBA", GL_RGBA, GL_RGBA, 8, GL_FALSE },*/
	{ GL_RGBA8I_EXT,   GL_RGBA_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_RGBA8UI_EXT , GL_RGBA_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_RGBA16I_EXT,  GL_RGBA_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_RGBA16UI_EXT, GL_RGBA_INTEGER_EXT, 16, GL_FALSE },
	{ GL_RGBA32I_EXT,  GL_RGBA_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_RGBA32UI_EXT, GL_RGBA_INTEGER_EXT, 32, GL_FALSE },

	{ GL_RGBA8I_EXT,   GL_BGRA_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_RGBA8UI_EXT , GL_BGRA_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_RGBA16I_EXT,  GL_BGRA_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_RGBA16UI_EXT, GL_BGRA_INTEGER_EXT, 16, GL_FALSE },
	{ GL_RGBA32I_EXT,  GL_BGRA_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_RGBA32UI_EXT, GL_BGRA_INTEGER_EXT, 32, GL_FALSE },

	{ GL_RGB8I_EXT,   GL_RGB_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_RGB8UI_EXT , GL_RGB_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_RGB16I_EXT,  GL_RGB_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_RGB16UI_EXT, GL_RGB_INTEGER_EXT, 16, GL_FALSE },
	{ GL_RGB32I_EXT,  GL_RGB_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_RGB32UI_EXT, GL_RGB_INTEGER_EXT, 32, GL_FALSE },

	{ GL_ALPHA8I_EXT,   GL_ALPHA_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_ALPHA8UI_EXT , GL_ALPHA_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_ALPHA16I_EXT,  GL_ALPHA_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_ALPHA16UI_EXT, GL_ALPHA_INTEGER_EXT, 16, GL_FALSE },
	{ GL_ALPHA32I_EXT,  GL_ALPHA_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_ALPHA32UI_EXT, GL_ALPHA_INTEGER_EXT, 32, GL_FALSE },

	{ GL_LUMINANCE8I_EXT,   GL_LUMINANCE_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_LUMINANCE8UI_EXT , GL_LUMINANCE_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_LUMINANCE16I_EXT,  GL_LUMINANCE_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_LUMINANCE16UI_EXT, GL_LUMINANCE_INTEGER_EXT, 16, GL_FALSE },
	{ GL_LUMINANCE32I_EXT,  GL_LUMINANCE_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_LUMINANCE32UI_EXT, GL_LUMINANCE_INTEGER_EXT, 32, GL_FALSE },

	{ GL_LUMINANCE_ALPHA8I_EXT,   GL_LUMINANCE_ALPHA_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_LUMINANCE_ALPHA8UI_EXT , GL_LUMINANCE_ALPHA_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_LUMINANCE_ALPHA16I_EXT,  GL_LUMINANCE_ALPHA_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_LUMINANCE_ALPHA16UI_EXT, GL_LUMINANCE_ALPHA_INTEGER_EXT, 16, GL_FALSE },
	{ GL_LUMINANCE_ALPHA32I_EXT,  GL_LUMINANCE_ALPHA_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_LUMINANCE_ALPHA32UI_EXT, GL_LUMINANCE_ALPHA_INTEGER_EXT, 32, GL_FALSE },

	{ GL_INTENSITY8I_EXT,   GL_RED_INTEGER_EXT, 8,  GL_TRUE  },
	{ GL_INTENSITY8UI_EXT , GL_RED_INTEGER_EXT, 8,  GL_FALSE },
	{ GL_INTENSITY16I_EXT,  GL_RED_INTEGER_EXT, 16, GL_TRUE  },
	{ GL_INTENSITY16UI_EXT, GL_RED_INTEGER_EXT, 16, GL_FALSE },
	{ GL_INTENSITY32I_EXT,  GL_RED_INTEGER_EXT, 32, GL_TRUE  },
	{ GL_INTENSITY32UI_EXT, GL_RED_INTEGER_EXT, 32, GL_FALSE },

};

static const struct format_info rg_formats[] = {
	{ GL_RG8I,   GL_RG_INTEGER, 8,  GL_TRUE  },
	{ GL_RG8UI , GL_RG_INTEGER, 8,  GL_FALSE },
	{ GL_RG16I,  GL_RG_INTEGER, 16, GL_TRUE  },
	{ GL_RG16UI, GL_RG_INTEGER, 16, GL_FALSE },
	{ GL_RG32I,  GL_RG_INTEGER, 32, GL_TRUE  },
	{ GL_RG32UI, GL_RG_INTEGER, 32, GL_FALSE },
	{ GL_R8I,   GL_RED_INTEGER, 8,  GL_TRUE  },
	{ GL_R8UI , GL_RED_INTEGER, 8,  GL_FALSE },
	{ GL_R16I,  GL_RED_INTEGER, 16, GL_TRUE  },
	{ GL_R16UI, GL_RED_INTEGER, 16, GL_FALSE },
	{ GL_R32I,  GL_RED_INTEGER, 32, GL_TRUE  },
	{ GL_R32UI, GL_RED_INTEGER, 32, GL_FALSE },
};

/* the rgb10 formats overload the Signed TRUE/FALSE member to test
 * the _REV and non-REV component ordering.
 */
static const struct format_info rgb10_formats[] = {
	{ GL_RGB10_A2UI, GL_RGBA_INTEGER_EXT, 10, GL_FALSE },
	{ GL_RGB10_A2UI, GL_BGRA_INTEGER_EXT, 10, GL_FALSE },
	{ GL_RGB10_A2UI, GL_RGBA_INTEGER_EXT, 10, GL_TRUE },
	{ GL_RGB10_A2UI, GL_BGRA_INTEGER_EXT, 10, GL_TRUE },
};

/* The programs will be indexed by whether the texture is signed or not. */
static const char *FragShaderText[2] = {
	"#version 130\n"
	"uniform vec4 bias; \n"
	"uniform usampler2D tex; \n"
	"void main() \n"
	"{ \n"
	"   vec4 t = vec4(texture(tex, gl_TexCoord[0].xy)); \n"
	"   gl_FragColor = t + bias; \n"
	"} \n",

	"#version 130\n"
	"uniform vec4 bias; \n"
	"uniform isampler2D tex; \n"
	"void main() \n"
	"{ \n"
	"   vec4 t = vec4(texture(tex, gl_TexCoord[0].xy)); \n"
	"   gl_FragColor = t + bias; \n"
	"} \n",
};

static GLuint Program[2];

static int
get_max_val(const struct format_info *info)
{
	int max;

	switch (info->BitsPerChannel) {
	case 8:
		if (info->Signed)
			max = 127;
		else
			max = 255;
		break;
	case 10:
		max = 1023;
		break;
	case 16:
		if (info->Signed)
			max = 32767;
		else
			max = 65535;
		break;
	case 32:
		if (info->Signed)
			max = 10*1000; /* don't use 0x8fffffff to avoid overflow issues */
		else
			max = 20*1000;
		break;
	default:
		assert(0);
		max = 0;
	}

	return max;
}


static int
num_components(GLenum format)
{
	switch (format) {
	case GL_RGBA:
	case GL_RGBA_INTEGER_EXT:
	case GL_BGRA_INTEGER_EXT:
		return 4;
	case GL_RGB_INTEGER_EXT:
		return 3;
	case GL_RG_INTEGER:
		return 2;
	case GL_ALPHA_INTEGER_EXT:
		return 1;
	case GL_LUMINANCE_INTEGER_EXT:
		return 1;
	case GL_LUMINANCE_ALPHA_INTEGER_EXT:
		return 2;
	case GL_RED_INTEGER_EXT:
		return 1;
	default:
		assert(0);
		return 0;
	}
}


static void
fill_array(int comps, int texels, void *buf, int bpp, const int val[4])
{
	int i, j;

	switch (bpp) {
	case 8:
		{
			GLubyte *b = (GLubyte *) buf;
			for (i = 0; i < texels; i++) {
				for (j = 0; j < comps; j++) {
					b[i * comps + j] = val[j];
				}
			}
		}
		break;
	case 16:
		{
			GLushort *b = (GLushort *) buf;
			for (i = 0; i < texels; i++) {
				for (j = 0; j < comps; j++) {
					b[i * comps + j] = val[j];
				}
			}
		}
		break;
	case 32:
		{
			GLuint *b = (GLuint *) buf;
			for (i = 0; i < texels; i++) {
				for (j = 0; j < comps; j++) {
					b[i * comps + j] = val[j];
				}
			}
		}
		break;
	default:
		assert(0);
	}
}

static void
fill_array_rgb10(int comps, int texels, void *buf, int type,
	   const int val[4])
{
	int i;
	GLuint *b = (GLuint *)buf;

	if (type == GL_UNSIGNED_INT_2_10_10_10_REV) {
		for (i = 0; i < texels; i++) {
			b[i] = (val[0] & 0x3ff) << 0 |
				(val[1] & 0x3ff) << 10 |
				(val[2] & 0x3ff) << 20 |
				(val[3] & 0x3) << 30;
		}
	} else if (type == GL_UNSIGNED_INT_10_10_10_2) {
		for (i = 0; i < texels; i++) {
			b[i] = (val[3] & 0x3) << 0 |
			       (val[2] & 0x3ff) << 2 |
			       (val[1] & 0x3ff) << 12 |
			       (val[0] & 0x3ff) << 22;
		}
	}
}

static GLenum
get_datatype(const struct format_info *info)
{
	switch (info->BitsPerChannel) {
	case 8:
		return info->Signed ? GL_BYTE : GL_UNSIGNED_BYTE;
	case 10:
		return info->Signed ? GL_UNSIGNED_INT_10_10_10_2 : GL_UNSIGNED_INT_2_10_10_10_REV;
	case 16:
		return info->Signed ? GL_SHORT : GL_UNSIGNED_SHORT;
	case 32:
		return info->Signed ? GL_INT : GL_UNSIGNED_INT;
	default:
		assert(0);
		return 0;
	}
}


static GLboolean
check_error(const char *file, int line)
{
	GLenum err = glGetError();
	if (err) {
		fprintf(stderr, "%s: error 0x%x at %s:%d\n",
			TestName, err, file, line);
		return GL_TRUE;
	}
	return GL_FALSE;
}


/** \return GL_TRUE for pass, GL_FALSE for fail */
static GLboolean
test_format(const struct format_info *info)
{
	const int max = get_max_val(info);
	const int comps = num_components(info->BaseFormat);
	const int texels = TexWidth * TexHeight;
	const GLenum type = get_datatype(info);
	const int w = piglit_width / 10;
	const int h = piglit_height / 10;
	const float error = 2.0 / 255.0; /* XXX fix */
	GLfloat expected[4];
	void *buf;
	int value[4];
	GLfloat result[4], bias[4];
	GLint f;
	GLfloat temp;

	const char *suffix;
	char *name;
	if (info->IntFormat == GL_RGB10_A2UI) {
		if (info->BaseFormat == GL_BGRA_INTEGER) {
			if (info->Signed)
				suffix = " (rev bgra)";
			else
				suffix = " (bgra)";
		} else {
			if (info->Signed)
				suffix = " (rev)";
			else
				suffix = "";
		}
	} else if (info->BaseFormat == GL_BGRA_INTEGER)
		suffix = " (bgra)";
	else
		suffix = "";
	asprintf(&name, "%s%s:\n", piglit_get_gl_enum_name(info->IntFormat), suffix);

	/* pick random texture color */
	value[0] = rand() % max;
	value[1] = rand() % max;
	value[2] = rand() % max;
	value[3] = rand() % max;

	/* alloc, fill texture image */
	if (info->BitsPerChannel == 10) {
		value[3] = rand() % 3;
		buf = malloc(texels * 4);
		fill_array_rgb10(comps, texels, buf, type, value);
	} else {
		buf = malloc(comps * texels * info->BitsPerChannel / 8);
		fill_array(comps, texels, buf, info->BitsPerChannel, value);
	}

	glTexImage2D(GL_TEXTURE_2D, 0, info->IntFormat, TexWidth, TexHeight, 0,
		     info->BaseFormat, type, buf);

	if (check_error(__FILE__, __LINE__))
		return GL_FALSE;

	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT,
				 &f);
	/*
	  assert(f == info->IntFormat);
	*/

	/* setup expected polygon color */
	expected[0] = 0.25;
	expected[1] = 0.50;
	expected[2] = 0.75;
	expected[3] = 1.00;

	/* Need to swizzle things depending on texture format.
	 *
	 * Also, for texture formats with no storage for a particular
	 * channel, instead of reading the randomly-chosen value
	 * above, we need to expect to read a 0, (for Green or Blue
	 * channels), or a 1 (for Alpha).
	 *
	 * Note: The alpha value read is an integer 1, not a
	 * maximum-valued integer representing 1.0.
	 */
	switch (info->BaseFormat) {
	case GL_RGBA_INTEGER_EXT:
		/* nothing */
		break;
	case GL_BGRA_INTEGER_EXT:
		/* swap 0 and 2 */
		temp = expected[2];
		expected[2] = expected[0];
		expected[0] = temp;
		temp = value[2];
		value[2] = value[0];
		value[0] = temp;
		break;
	case GL_RGB_INTEGER_EXT:
		value[3] = 1;
		break;
	case GL_RG_INTEGER:
		value[2] = 0;
		value[3] = 1;
		break;
	case GL_ALPHA_INTEGER_EXT:
		expected[0] = expected[1] = expected[2] = 0.0;
		expected[3] = 0.25;
		value[3] = value[0];
		break;
	case GL_LUMINANCE_INTEGER_EXT:
		expected[0] = expected[1] = expected[2] = 0.25;
		expected[3] = 1.0;
		value[1] = value[2] = value[0];
		value[3] = 1.0;
		break;
	case GL_LUMINANCE_ALPHA_INTEGER_EXT:
		expected[0] = expected[1] = expected[2] = 0.25;
		value[3] = value[1];
		value[1] = value[2] = value[0];
		break;
	case GL_RED_INTEGER_EXT:
		if (info->IntFormat == GL_INTENSITY8I_EXT ||
		    info->IntFormat == GL_INTENSITY8UI_EXT ||
		    info->IntFormat == GL_INTENSITY16I_EXT ||
		    info->IntFormat == GL_INTENSITY16UI_EXT ||
		    info->IntFormat == GL_INTENSITY32I_EXT ||
		    info->IntFormat == GL_INTENSITY32UI_EXT) {
			expected[0] = expected[1] = expected[2] = expected[3] = 0.25;
			value[1] = value[2] = value[3] = value[0];
		} else {
			value[1] = value[2] = 0;
			value[3] = 1;
		}			
		break;
	default:
		;
	}

	glUseProgram(Program[info->Signed]);

	/* compute, set test bias */
	bias[0] = expected[0] - value[0];
	bias[1] = expected[1] - value[1];
	bias[2] = expected[2] - value[2];
	bias[3] = expected[3] - value[3];
	glUniform4fv(BiasUniform[info->Signed], 1, bias);

	/* draw */
	glClearColor(0, 1, 1, 0);
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_POLYGON);
	glTexCoord2f(0, 0);   glVertex2f(0, 0);
	glTexCoord2f(1, 0);   glVertex2f(w, 0);
	glTexCoord2f(1, 1);   glVertex2f(w, h);
	glTexCoord2f(0, 1);   glVertex2f(0, h);
	glEnd();

	if (check_error(__FILE__, __LINE__))
		return GL_FALSE;

	/* test */
	glReadPixels(w/2, h/2, 1, 1, GL_RGBA, GL_FLOAT, result);

	if (check_error(__FILE__, __LINE__))
		return GL_FALSE;

	if (fabsf(result[0] - expected[0]) > error ||
	    fabsf(result[1] - expected[1]) > error ||
	    fabsf(result[2] - expected[2]) > error ||
	    fabsf(result[3] - expected[3]) > error) {
		fprintf(stderr, "%s: failure with format %s:\n",
			TestName, name);
		fprintf(stderr, "  texture color = %d, %d, %d, %d\n",
			value[0], value[1], value[2], value[3]);
		fprintf(stderr, "  expected color = %g, %g, %g, %g\n",
			expected[0], expected[1], expected[2], expected[3]);
		fprintf(stderr, "  result color = %g, %g, %g, %g\n",
			result[0], result[1], result[2], result[3]);
		return GL_FALSE;
	}

	piglit_present_results();

	free(buf);
	free(name);

	return GL_TRUE;
}


static GLboolean
test_general_formats(void)
{
	int f, i;

	for (f = 0; f < ARRAY_SIZE(Formats); f++) {
		for (i = 0; i < 5; i++) {
			if (!test_format(&Formats[f]))
				return GL_FALSE;
		}
	}

	if (piglit_is_extension_supported("GL_ARB_texture_rg")) {
		for (f = 0; f < ARRAY_SIZE(rg_formats); f++) {
			for (i = 0; i < 5; i++) {
				if (!test_format(&rg_formats[f]))
					return GL_FALSE;
			}
		}
	}

	if (piglit_is_extension_supported("GL_ARB_texture_rgb10_a2ui")) {
		for (f = 0; f < ARRAY_SIZE(rgb10_formats); f++) {
			for (i = 0; i < 5; i++) {
				if (!test_format(&rgb10_formats[f]))
					return GL_FALSE;
			}
		}
	}
	return GL_TRUE;
}


static GLboolean
test_specific_formats(void)
{
	/* These format combinations should all work */
	struct {
		GLenum intFormat, srcFormat, srcType;
	} formats[] = {
		{ GL_RGBA8UI_EXT, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE },
		{ GL_RGBA8UI_EXT, GL_RGBA_INTEGER, GL_SHORT },
		{ GL_RGBA8UI_EXT, GL_RGBA_INTEGER, GL_UNSIGNED_INT_8_8_8_8 },
		{ GL_RGBA8UI_EXT, GL_BGRA_INTEGER, GL_UNSIGNED_INT_8_8_8_8 },
		{ GL_LUMINANCE8I_EXT, GL_RGBA_INTEGER, GL_UNSIGNED_INT_8_8_8_8 },
		{ GL_RGB16I_EXT, GL_RGB_INTEGER, GL_UNSIGNED_SHORT_5_6_5 },
		{ GL_RGB32I_EXT, GL_RGB_INTEGER, GL_UNSIGNED_SHORT_5_6_5 }
	};
	int i;
	GLenum err;
	GLboolean pass = GL_TRUE;

	while (glGetError() != GL_NO_ERROR)
		;

	/* All of the packed formats require GL_ARB_texture_rgb10_a2ui.
	 */
	if (!piglit_is_extension_supported("GL_ARB_texture_rgb10_a2ui")) {
		return GL_TRUE;
	}

	for (i = 0; i < ARRAY_SIZE(formats); i++) {
		glTexImage2D(GL_TEXTURE_2D, 0, formats[i].intFormat,
			     16, 16, 0,
			     formats[i].srcFormat, formats[i].srcType, NULL);
		err = glGetError();
		if (err != GL_NO_ERROR) {
			fprintf(stderr, "%s failure: glTexImage2D(0x%x, 0x%x, 0x%x) generated"
				" error 0x%x (case %d)\n",
				TestName, formats[i].intFormat,
				formats[i].srcFormat, formats[i].srcType, err, i);
			pass = GL_FALSE;
		}
	}

	return pass;
}

enum piglit_result
piglit_display(void)
{
	if (!test_general_formats())
		return PIGLIT_FAIL;

	if (!test_specific_formats())
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}


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

	piglit_require_extension("GL_EXT_texture_integer");
	piglit_require_GLSL_version(130);

	for (i = 0; i < 2; i++) {
		Program[i] = piglit_build_simple_program(NULL,
							 FragShaderText[i]);
		BiasUniform[i] = glGetUniformLocation(Program[i], "bias");
		TexUniform[i] = glGetUniformLocation(Program[i], "tex");
	}

	(void) check_error(__FILE__, __LINE__);

	glGenTextures(1, &Texture);
	glBindTexture(GL_TEXTURE_2D, Texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	(void) check_error(__FILE__, __LINE__);

	piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);
}
