/*
 * Copyright 2011 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
 * 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.
 */

/*
 * file copyteximage.c
 *
 * Test to verify functionality of  glCopyTexImage() with various texture
 * targets and texture internal formats.
 */


#include "piglit-util-gl.h"

#define IMAGE_SIZE 16
static const GLfloat fcolor[8][4] = {
	/* GL_RED */
	{0.5, 0.0, 0.0, 1.0},
	/* GL_RG */
	{0.5, 0.2, 0.0, 1.0},
	/* GL_RGB */
	{0.5, 0.2, 0.8, 1.0},
	/* GL_RGBA8, GL_RGBA16, GL_RGBA16F, GL_RGBA32F */
	{0.5, 0.2, 0.8, 0.4},
	/* GL_LUMINANCE */
	{0.5, 0.5, 0.5, 1},
	/* GL_LUMINANCE_ALPHA */
	{0.5, 0.5, 0.5, 0.4},
	/* GL_INTENSITY */
	{0.5, 0.5, 0.5, 0.5},
	/* GL_DEPTH_COMPONENT */
	{0.75, 0.75, 0.75, 1.0} };

static const struct {
	GLenum format;
	const GLvoid *expected;
} test_vectors[] = {
	{ GL_RED,		fcolor[0] },
	{ GL_RG,		fcolor[1] },

	/* Clamps the color values to [0, 1] */
	{ GL_RGB8,		fcolor[2] },
	{ GL_RGB16,		fcolor[2] },

	/* Don't clamp color values to [0, 1] */
	{ GL_RGB16F,		fcolor[2] },
	{ GL_RGB32F,		fcolor[2] },

	/* Clamps the color values to [0, 1] */
	{ GL_RGBA8,	  	fcolor[3] },
	{ GL_RGBA16,	  	fcolor[3] },

	/* Don't clamp color values to [0, 1] */
	{ GL_RGBA16F,	  	fcolor[3] },
	{ GL_RGBA32F,	  	fcolor[3] },

	{ GL_COMPRESSED_RED,	fcolor[0] },
	{ GL_COMPRESSED_RG,	fcolor[1] },
	{ GL_COMPRESSED_RGB,	fcolor[2] },
	{ GL_COMPRESSED_RGBA,	fcolor[3] },

	{ GL_LUMINANCE,	  	fcolor[4] },
	{ GL_LUMINANCE_ALPHA,	fcolor[5] },

	{ GL_INTENSITY,	  	fcolor[6] },

	{ GL_DEPTH_COMPONENT,  	fcolor[7] },
	{ GL_DEPTH_COMPONENT16, fcolor[7] },
	{ GL_DEPTH_COMPONENT24, fcolor[7] },
	{ GL_DEPTH_COMPONENT32F, fcolor[7] },

	{ GL_DEPTH24_STENCIL8,	fcolor[7] },
	{ GL_DEPTH32F_STENCIL8, fcolor[7] },
};

static const struct {
	GLenum target;
	const char *name;
	unsigned gl_version;
	const char *extension;
} target[] = {
	{GL_TEXTURE_1D,		"1D",		11, NULL},
	{GL_TEXTURE_2D,		"2D",		11, NULL},
	{GL_TEXTURE_3D,		"3D",		12, NULL},
	{GL_TEXTURE_CUBE_MAP,	"CUBE",		13, "GL_ARB_texture_cube_map"},
	{GL_TEXTURE_1D_ARRAY,	"1D_ARRAY",	30, "GL_EXT_texture_array"},
	{GL_TEXTURE_2D_ARRAY,	"2D_ARRAY",	30, "GL_EXT_texture_array"},
	{GL_TEXTURE_RECTANGLE,	"RECT",		31, "GL_ARB_texture_rectangle"}
};

static int test_target = -1;

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_width = IMAGE_SIZE*(ARRAY_SIZE(test_vectors)+1);
	config.window_height = IMAGE_SIZE;
	config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGBA |
			       PIGLIT_GL_VISUAL_DEPTH | PIGLIT_GL_VISUAL_STENCIL;
	config.khr_no_error_support = PIGLIT_HAS_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const char array1D_shader_text[] =
	"#extension GL_EXT_texture_array: require\n"
	"uniform sampler1DArray s;\n"
	"void main()\n"
	"{\n"
	"    gl_FragColor = texture1DArray(s, gl_TexCoord[0].xy);\n"
	"}\n"
	;

static const char array2D_shader_text[] =
	"#extension GL_EXT_texture_array: require\n"
	"uniform sampler2DArray s;\n"
	"void main()\n"
	"{\n"
	"    gl_FragColor = texture2DArray(s, gl_TexCoord[0].xyz);\n"
	"}\n"
	;


static GLboolean
is_compressed_format(GLenum format)
{
   switch (format) {
   case GL_COMPRESSED_RED:
   case GL_COMPRESSED_RG:
   case GL_COMPRESSED_RGB:
   case GL_COMPRESSED_RGBA:
      return GL_TRUE;
   default:
      return GL_FALSE;
   }
}

static bool
is_depth_format(GLenum format)
{
	switch (format) {
	case GL_DEPTH_COMPONENT:
	case GL_DEPTH_COMPONENT16:
	case GL_DEPTH_COMPONENT24:
	case GL_DEPTH_COMPONENT32F:
	case GL_DEPTH32F_STENCIL8:
	case GL_DEPTH24_STENCIL8:
		return true;
	default:
		return false;
	}
}

/** is the given texture internal format supported? */
static bool
supported_format(GLenum format)
{
	switch (format) {
	case GL_RGBA16F:
	case GL_RGBA32F:
	case GL_RGB16F:
	case GL_RGB32F:
		return piglit_is_extension_supported("GL_ARB_texture_float");
	case GL_RED:
	case GL_RG:
	case GL_COMPRESSED_RED:
	case GL_COMPRESSED_RG:
		return piglit_is_extension_supported("GL_ARB_texture_rg");
	case GL_DEPTH_COMPONENT:
	case GL_DEPTH_COMPONENT16:
	case GL_DEPTH_COMPONENT24:
		return piglit_is_extension_supported("GL_ARB_depth_texture");
	case GL_DEPTH24_STENCIL8:
		return piglit_is_extension_supported("GL_EXT_packed_depth_stencil") &&
			piglit_is_extension_supported("GL_ARB_depth_texture");
	case GL_DEPTH_COMPONENT32F:
	case GL_DEPTH32F_STENCIL8:
		return piglit_is_extension_supported("GL_ARB_depth_buffer_float");
	default:
		return true;
	}
}

/** is the texture format allowed for the texture target? */
static bool
supported_target_format(GLenum target, GLenum format)
{
	if (is_depth_format(format) && target == GL_TEXTURE_3D) {
		return false;
	}
	return true;
}

static bool
supported_target(unsigned i)
{
	/* Array targets are only supported if GLSL is available.
	 */
	if ((target[i].target == GL_TEXTURE_1D_ARRAY
	     || target[i].target == GL_TEXTURE_2D_ARRAY)
	    && (piglit_get_gl_version() < 20
		&& !piglit_is_extension_supported("GL_ARB_fragment_shader")))
		return false;

	return piglit_get_gl_version() >= target[i].gl_version ||
		(target[i].extension &&
		 piglit_is_extension_supported(target[i].extension));
}

static GLenum get_format(GLenum format)
{
	if (format == GL_DEPTH32F_STENCIL8 ||
	    format == GL_DEPTH24_STENCIL8)
		return GL_DEPTH_STENCIL;
	else if (is_depth_format(format))
		return GL_DEPTH_COMPONENT;
	else
		return GL_RGBA;
}

static GLenum get_type(GLenum format)
{
	switch (format) {
	case GL_DEPTH24_STENCIL8:
		return GL_UNSIGNED_INT_24_8;
	case GL_DEPTH32F_STENCIL8:
		return GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
	default:
		return GL_FLOAT;
	}
}

static void draw_pixels(float scale)
{
	GLfloat buf_fcolor[IMAGE_SIZE][IMAGE_SIZE][4];
	int i, j;

	/* Image data setup */
	for (i = 0; i < IMAGE_SIZE; i++) {
		for (j = 0; j < IMAGE_SIZE; j++) {
			buf_fcolor[i][j][0] = 0.5 * scale;
			buf_fcolor[i][j][1] = 0.2 * scale;
			buf_fcolor[i][j][2] = 0.8 * scale;
			buf_fcolor[i][j][3] = 0.4 * scale;
		}
	}

	glDrawPixels(IMAGE_SIZE, IMAGE_SIZE, GL_RGBA,
		     GL_FLOAT, buf_fcolor);
}

static void draw_depth(float scale)
{
	glClearDepth(0.75 * scale);
	glClear(GL_DEPTH_BUFFER_BIT);
}

static void draw(GLenum format, float scale)
{
	if (is_depth_format(format))
		draw_depth(scale);
	else
		draw_pixels(scale);
}

static GLboolean probe_rect(int x, int y, int w, int h,
			    const float *expected, float scale)
{
	float expected_scaled[4];
	unsigned i;

	for (i = 0; i < 4; i++) {
		if (expected[i] == 1.0 || expected[i] == 0.0)
			expected_scaled[i] = expected[i];
		else
			expected_scaled[i] = expected[i] * scale;
	}
	return piglit_probe_rect_rgba(x, y, w, h, expected_scaled);
}


/**
 * Convenience function to draw an axis-aligned rectangle with 3 dimensional
 * texture coordinates where the third coordinate is constant.
 */
static GLvoid
draw_rect_tex_3d(float x, float y, float w, float h,
                 float tx, float ty, float tz, float tw, float th)
{
	float verts[4][2];
	float tex[4][3];

	verts[0][0] = x;
	verts[0][1] = y;
	tex[0][0] = tx;
	tex[0][1] = ty;
	tex[0][2] = tz;
	verts[1][0] = x + w;
	verts[1][1] = y;
	tex[1][0] = tx + tw;
	tex[1][1] = ty;
	tex[1][2] = tz;
	verts[2][0] = x;
	verts[2][1] = y + h;
	tex[2][0] = tx;
	tex[2][1] = ty + th;
	tex[2][2] = tz;
	verts[3][0] = x + w;
	verts[3][1] = y + h;
	tex[3][0] = tx + tw;
	tex[3][1] = ty + th;
	tex[3][2] = tz;

	glVertexPointer(2, GL_FLOAT, 0, verts);
	glTexCoordPointer(3, GL_FLOAT, 0, tex);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/**
 * Convenience function to draw an axis-aligned rectangle textured with one face
 * of a cube map.
 */
static GLvoid
draw_rect_tex_cube_face(float x, float y, float w, float h, int face)
{
	float verts[4][2];

	verts[0][0] = x;
	verts[0][1] = y;
	verts[1][0] = x + w;
	verts[1][1] = y;
	verts[2][0] = x + w;
	verts[2][1] = y + h;
	verts[3][0] = x;
	verts[3][1] = y + h;

	glVertexPointer(2, GL_FLOAT, 0, verts);
	glTexCoordPointer(3, GL_FLOAT, 0, cube_face_texcoords[face]);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glDrawArrays(GL_QUADS, 0, 4);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


/**
 * Test a specific texture target and format combination.
 */
static GLboolean
test_target_and_format(GLint x, GLint y, GLenum target, GLenum format,
		       const GLfloat *expected)
{
	GLboolean pass = GL_TRUE;
	GLuint k;
	GLuint prog = 0;

	printf("Texture target = %s, Internal format = %s",
	       piglit_get_gl_enum_name(target),
	       piglit_get_gl_enum_name(format));

	if (!supported_format(format) ||
	    !supported_target_format(target, format)) {
		printf(" - skipped\n");
		return GL_TRUE; /* not a failure */
	} else {
		printf("\n");
	}

	/* To avoid failures not related to this test case,
	 * loosen up the tolerence for compressed texture
	 * formats
	 */
	if (is_compressed_format(format))
		piglit_set_tolerance_for_bits(5, 5, 5, 5);
	else
		piglit_set_tolerance_for_bits(8, 8, 8, 8);

	switch(target) {

	case GL_TEXTURE_1D:
		draw(format, 1.0);
		glCopyTexImage1D(GL_TEXTURE_1D, 0,
				 format,
				 0, 0, IMAGE_SIZE, 0);
		pass = piglit_check_gl_error(GL_NO_ERROR)
			&& pass;

		glEnable(target);
		piglit_draw_rect_tex(x, y, IMAGE_SIZE, IMAGE_SIZE,
				      0, 0, 1, 1);
		pass = piglit_probe_rect_rgba(x, y, IMAGE_SIZE,
					      IMAGE_SIZE,
					      expected)
			&& pass;
		break;

	case GL_TEXTURE_2D:
		draw(format, 1.0);
		glCopyTexImage2D(GL_TEXTURE_2D, 0, format, 0, 0,
				 IMAGE_SIZE, IMAGE_SIZE, 0);
		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		glEnable(target);
		piglit_draw_rect_tex(x, y, IMAGE_SIZE, IMAGE_SIZE,
				      0, 0, 1, 1);
		pass = piglit_probe_rect_rgba(x, y, IMAGE_SIZE,
					      IMAGE_SIZE,
					      expected)
			&& pass;
		break;

	case GL_TEXTURE_3D:
		glTexImage3D(GL_TEXTURE_3D, 0, format, IMAGE_SIZE, IMAGE_SIZE, 4,
			     0, GL_RGBA, get_type(format), NULL);

		for (k = 0; k < 4; k++) {
			draw(format, 1.0 - k*0.2);
			glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, k,
					    0, 0, IMAGE_SIZE, IMAGE_SIZE);
		}

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		glEnable(target);

		for (k = 0; k < 4; k++) {
			const float tz = (k + 0.5) * 0.25;
			draw_rect_tex_3d(x, y, IMAGE_SIZE, IMAGE_SIZE,
					 0, 0, tz, 1, 1);
			pass = probe_rect(x, y, IMAGE_SIZE, IMAGE_SIZE,
					  expected, 1.0 - k*0.2) && pass;
		}
		break;

	case GL_TEXTURE_CUBE_MAP:
		for (k = 0; k < 6; k++) {
			draw(format, 1.0 - k*0.15);
			glCopyTexImage2D(cube_face_targets[k],
					 0, format, 0, 0,
					 IMAGE_SIZE, IMAGE_SIZE, 0);
		}

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		glEnable(target);

		for (k = 0; k < 6; k++) {
			draw_rect_tex_cube_face(x, y, IMAGE_SIZE, IMAGE_SIZE, k);
			pass = probe_rect(x, y, IMAGE_SIZE, IMAGE_SIZE,
					  expected, 1.0 - k*0.15) && pass;
		}
		break;

	case GL_TEXTURE_1D_ARRAY:
		glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, format, IMAGE_SIZE, 16,
			     0, get_format(format), get_type(format), NULL);

		for (k = 0; k < 4; k++) {
			draw(format, 1.0 - 0.2*k);
			glCopyTexSubImage2D(GL_TEXTURE_1D_ARRAY, 0, 0, 4*k,
					    0, 0, IMAGE_SIZE, 4);
		}

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		prog = piglit_build_simple_program(NULL, array1D_shader_text);
		glUseProgram(prog);
		glDeleteProgram(prog);

		for (k = 0; k < 16; k++) {
			piglit_draw_rect_tex(x, y, IMAGE_SIZE, IMAGE_SIZE,
					      0, k, 1, 0);
			pass = probe_rect(x, y, IMAGE_SIZE, IMAGE_SIZE,
					  expected, 1.0 - 0.2*(k/4)) && pass;
		}
		break;

	case GL_TEXTURE_2D_ARRAY:
		glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, format, IMAGE_SIZE, IMAGE_SIZE, 4,
			     0, get_format(format), get_type(format), NULL);

		for (k = 0; k < 4; k++) {
			draw(format, 1.0 - k*0.2);
			glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, k,
					    0, 0, IMAGE_SIZE, IMAGE_SIZE);
		}

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		prog = piglit_build_simple_program(NULL, array2D_shader_text);
		glUseProgram(prog);
		glDeleteProgram(prog);

		for (k = 0; k < 4; k++) {
			draw_rect_tex_3d(x, y, IMAGE_SIZE, IMAGE_SIZE,
					 0, 0, k, 1, 1);
			pass = probe_rect(x, y, IMAGE_SIZE, IMAGE_SIZE,
					  expected, 1.0 - k*0.2) && pass;
		}
		break;

	case GL_TEXTURE_RECTANGLE:
		draw(format, 1.0);
		glCopyTexImage2D(GL_TEXTURE_RECTANGLE, 0, format, 0, 0,
				 IMAGE_SIZE, IMAGE_SIZE, 0);
		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
		
		glEnable(target);
		piglit_draw_rect_tex(x, y, IMAGE_SIZE, IMAGE_SIZE,
				      0, 0, IMAGE_SIZE - 1, IMAGE_SIZE - 1);
		pass = piglit_probe_rect_rgba(x, y, IMAGE_SIZE,
					      IMAGE_SIZE,
					      expected)
			&& pass;
		break;
	}

	/* If a GLSL program is in use, then the preceeding code should not
	 * have called glEnable(target).  In that case, this code should not
	 * disable it.  For some targets, like GL_TEXTURE_1D_ARRAY,
	 * glDisable(target) will generate an error.
	 */
	if (prog == 0)
		glDisable(target);
	else
		glUseProgram(0);

	return pass;
}


static GLuint
create_texture(GLenum target)
{
	GLuint tex;

	glGenTextures(1, &tex);
	glBindTexture(target, tex);
	glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(target,	GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(target, GL_GENERATE_MIPMAP, GL_FALSE);

	return tex;
}


enum piglit_result
piglit_display(void)
{
	GLint format;
	GLuint tex;
	GLboolean pass = GL_TRUE;
	const GLfloat *expected;
	int i, j;

	glClear(GL_COLOR_BUFFER_BIT);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	/* Do glCopyPixels and draw a textured rectangle for each format
	 * and each texture target
	 */
	for (j = 0; j < ARRAY_SIZE(target); j++) {
		if (test_target != -1 && test_target != j)
			continue;
		if (!supported_target(j))
			continue;

		printf("Testing %s\n", piglit_get_gl_enum_name(target[j].target));

		if (target[j].target == GL_TEXTURE_1D_ARRAY) {
			printf("NOTE: We use glCopyTexSubImage2D to set 4 texture layers at once.\n");
		}

		tex = create_texture(target[j].target);

		for (i = 0; i < ARRAY_SIZE(test_vectors); i++) {
			GLint x = IMAGE_SIZE * (i + 1);
			GLint y = 0;
			expected = (const float*)test_vectors[i].expected;

			if (!test_target_and_format(x, y, target[j].target,
						    test_vectors[i].format,
						    expected)) {
				pass = GL_FALSE;
			}
		}

		/* Test that internalformats 1, 2, 3 and 4 are not accepted
		 * as OpenGL 1.0 backwards-compatible aliases.
		 *
		 * The OpenGL 1.1 spec, section 3.8 ('Texturing') says the
		 * the following about glCopyTexImage2D (and similar for
		 * glCopyTexImage1D):
		 *
		 * Parameters level, internalformat, and border are specified
		 * using the same values, with the same meanings, as the
		 * equivalent arguments of TexImage2D, except that
		 * internalformat may not be specified as 1, 2, 3, or 4.
		 */
		for (format = 1; format <= 4; ++format) {
			switch (target[j].target) {
			case GL_TEXTURE_1D:
			case GL_TEXTURE_1D_ARRAY:
				glCopyTexImage1D(target[j].target, 0,
						 format,
						 0, 0, IMAGE_SIZE, 0);
				pass = piglit_check_gl_error(GL_INVALID_ENUM) && pass;
				break;

			case GL_TEXTURE_2D:
			case GL_TEXTURE_2D_ARRAY:
			case GL_TEXTURE_RECTANGLE:
				glCopyTexImage2D(target[j].target, 0, format,
						 0, 0, IMAGE_SIZE, IMAGE_SIZE,
						 0);
				pass = piglit_check_gl_error(GL_INVALID_ENUM) && pass;
				break;

			case GL_TEXTURE_CUBE_MAP:
				for (i = 0; i < 6; i++) {
					glCopyTexImage2D(cube_face_targets[i],
							 0, format, 0, 0,
							 IMAGE_SIZE,
							 IMAGE_SIZE, 0);
					pass = piglit_check_gl_error(GL_INVALID_ENUM) && pass;
				}
				break;

			case GL_TEXTURE_3D:
				/* There is no glCopyTexImage3D, and
				 * glCopyTexSubImage3D does not take an
				 * internalformat argument
                                 */
				continue;
			}
		}

		glDeleteTextures(1, &tex);
	}
	if (!piglit_automatic)
		piglit_present_results();
	return (pass ? PIGLIT_PASS : PIGLIT_FAIL);
}

void
piglit_init(int argc, char **argv)
{
	if (argc == 2) {
		unsigned i;
		for (i = 0; i < ARRAY_SIZE(target); i++) {
			if (strcmp(target[i].name, argv[1]) == 0) {
				test_target = i;

				if (!supported_target(i)) {
					printf("Test requires OpenGL %1.1f", target[i].gl_version * 0.1);
					if (target[i].extension)
						printf(" or %s", target[i].extension);
					printf(".\n");
					piglit_report_result(PIGLIT_SKIP);
				}
				break;
			}
		}
	}

	glClearColor(0.0, 0.0, 0.0, 1.0);
	piglit_ortho_projection(piglit_width, piglit_height, GL_TRUE);
}
