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

/**
 * Verify error checking for compressed texture functions, using s3tc as
 * the specific compression formats.
 * Some rendering is also tested, but it's not the focus here.
 * Other compressed formats could be added as well (the test should probably
 * be renamed at that point.)
 *
 * Brian Paul
 * Sep 20, 2012
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

#ifdef PIGLIT_USE_OPENGL
    config.supports_gl_compat_version = 11;
#else // PIGLIT_USE_OPENGL_ES2
    config.supports_gl_es_version = 20;
#endif

    config.window_width = 200;
    config.window_height = 200;
    config.window_visual = PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_DOUBLE;
    config.khr_no_error_support = PIGLIT_HAS_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const float red[4] =   {1.0, 0.0, 0.0, 1.0};
static const float green[4] = {0.0, 1.0, 0.0, 1.0};
static const float blue[4] =  {0.0, 0.0, 1.0, 1.0};
static const float white[4] = {1.0, 1.0, 1.0, 1.0};

#ifdef  PIGLIT_USE_OPENGL_ES2

const char *vs_source =
	"#version 100\n"
	"attribute vec4 piglit_vertex;\n"
	"attribute vec2 piglit_texcoord;\n"
	"varying mediump vec2 tex_coord;\n"
	"uniform mat4 proj;\n"
	"\n"
	"void main()\n"
	"{\n"
	"        gl_Position = proj * piglit_vertex;\n"
	"        tex_coord = piglit_texcoord;\n"
	"}\n";

const char *fs_source =
	"#version 100\n"
	"varying mediump vec2 tex_coord;\n"
	"uniform sampler2D tex;\n"
	"\n"
	"void main()\n"
	"{\n"
	"        gl_FragColor = texture2D(tex, tex_coord);\n"
	"}\n";

#include "piglit-matrix.h"

GLint tex_program, proj_loc;

#endif

static const GLenum s3tc_formats[] = {
	GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
	GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
	GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
	GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
};


static bool
check_rendering_(int width, int height, int line)
{
	const int w = width / 2 - 2;
	const int h = height / 2 - 2;
	bool pass = true;

#ifdef PIGLIT_USE_OPENGL
	piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);
#else
	glUseProgram(tex_program);
	piglit_ortho_uniform(proj_loc, piglit_width, piglit_height);
#endif

	glClear(GL_COLOR_BUFFER_BIT);

#ifdef PIGLIT_USE_OPENGL
	glEnable(GL_TEXTURE_2D);
	glColor3f(1, 1, 1);
#endif

	/* draw the texture */
	piglit_draw_rect_tex(0, 0, width, height, 0, 0, 1, 1);

	/* NOTE: don't probe the border pixels of the quadrants just to
	 * avoid potential off-by one errors.
	 */

	/* lower-left red */
	pass = piglit_probe_rect_rgb(1, 1, w, h, red) && pass;

	/* lower-right green */
	pass = piglit_probe_rect_rgb(width/2 + 1, 1, w, h, green) && pass;

	/* upper-left blue */
	pass = piglit_probe_rect_rgb(1, height/2 + 1, w, h, blue) && pass;

	/* upper-right white */
	pass = piglit_probe_rect_rgb(width/2 + 1, height/2 + 1,
				     w, h, white) && pass;

	piglit_present_results();

	if (!pass) {
		printf("s3tc-errors failure at line %d\n", line);
	}

	return pass;
}


#define check_rendering(w, h) check_rendering_(w, h, __LINE__)


#ifdef PIGLIT_USE_OPENGL
/**
 * Check for either of two expected GL errors.
 * XXX this could be a piglit util function
 */
static bool
check_gl_error2_(GLenum err1, GLenum err2, int line)
{
	GLenum err = glGetError();
	if (err != err1 && err != err2) {
		printf("Unexpected error %s at %s:%d\n",
		       piglit_get_gl_error_name(err), __FILE__, line);
		return false;
	}
	return true;
}

#define check_gl_error2(err1, err2)  check_gl_error2_(err1, err2, __LINE__)
#endif


static bool
test_format(int width, int height, GLfloat *image, GLenum requested_format)
{
#ifdef PIGLIT_USE_OPENGL
	GLubyte *compressed_image;
	GLenum format2;
#endif
	int x, y, w, h;
	GLuint tex;
	bool pass = true;
	GLuint expected_size;
	GLint is_compressed;
	GLint compressed_size;
	GLint format;

	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, width);

	/* Setup initial texture */
	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, requested_format, width, height, 0,
		     GL_RGBA, GL_FLOAT, image);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	pass = check_rendering(width, height) && pass;

	if (!piglit_is_gles() || piglit_get_gl_version() >= 31) {

		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED,
					 &is_compressed);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0,
					 GL_TEXTURE_INTERNAL_FORMAT, &format);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, 0,
					 GL_TEXTURE_COMPRESSED_IMAGE_SIZE,
					 &compressed_size);

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		if (!is_compressed) {
			printf("Image was not compressed\n");
			pass = false;
		}

		if (format != requested_format) {
			printf("Internal Format mismatch. Found: 0x%04x Expected: 0x%04x\n",
			       format, requested_format);
			pass = false;
		}

		expected_size = piglit_compressed_image_size(requested_format,
				width, height);

		if (compressed_size != expected_size) {
			printf("Compressed image size mismatch. Found: %u Expected: %u\n",
			       compressed_size, expected_size);
			pass = false;
		}
	}

#ifdef PIGLIT_USE_OPENGL
	/* Use GL_TEXTURE_COMPRESSED_IMAGE_SIZE even if it wasn't what we
	 * expected to avoid corruption due to under-allocated buffer.
	 */
	compressed_image = malloc(compressed_size);

	/* Read back the compressed image data */
	glGetCompressedTexImage(GL_TEXTURE_2D, 0, compressed_image);
#endif

	/* Try texsubimage on 4-texel boundary - should work */
	x = 20;
	y = 12;
	w = 16;
	h = 8;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glTexSubImage2D(GL_TEXTURE_2D, 0,
			x, y, w, h,
			GL_RGBA, GL_FLOAT, image);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	pass = check_rendering(width, height) && pass;

	/* Try texsubimage on non 4-texel boundary - should not work */
	x = 10;
	y = 11;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glTexSubImage2D(GL_TEXTURE_2D, 0,
			x, y, w, h,
			GL_RGBA, GL_FLOAT, image);

	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

#ifdef PIGLIT_USE_OPENGL
	/* Try compressed subimage on 4-texel boundary - should work */
	x = 12;
	y = 8;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, x, y));

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	pass = check_rendering(width, height) && pass;

	/* Try compressed subimage on non 4-texel boundary - should not work */
	x = 14;
	y = 9;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, 0, 0));

	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	/* Try compressed subimage with size not a multiple of 4 -
	 * should not work
	 */
	x = 8;
	y = 8;
	w = 14;
	h = 10;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, 4, 4),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, x, y));

	/* Note, we can get either of these errors depending on the order
	 * in which glCompressedTexSubImage parameters are checked.
	 * INVALID_OPERATION for the bad size or INVALID_VALUE for the
	 * wrong compressed image size.
	 */
	pass = check_gl_error2(GL_INVALID_OPERATION, GL_INVALID_VALUE) && pass;

	/* Try compressed subimage with invalid offset - should not work */
	x = -4;
	y = 8;
	w = 4;
	h = 4;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, 0, 0));

	pass = piglit_check_gl_error(GL_INVALID_VALUE) && pass;

	/* Try compressed subimage with too large of image - should not work */
	x = 16;
	y = 8;
	w = width * 2;
	h = height * 2;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, x, y));

	pass = piglit_check_gl_error(GL_INVALID_VALUE) && pass;

	/* Try compressed subimage with different format - should not work */
	if (format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT)
		format2 = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
	else
		format2 = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
	x = 4;
	y = 4;
	w = 4;
	h = 4;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format2,
				  piglit_compressed_image_size(format2, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format2, width, x, y));

	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	/* Try zero-sized subimage - should not be an error */
	x = 4;
	y = 4;
	w = 0;
	h = 0;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 0,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, x, y));

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;


	/* Try CompressedTexSubImage into level 1 (which is missing) */
	x = 0;
	y = 0;
	w = 4;
	h = 4;
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, x);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, y);
	glCompressedTexSubImage2D(GL_TEXTURE_2D, 1,
				  x, y, w, h,
				  format,
				  piglit_compressed_image_size(format, w, h),
				  compressed_image +
				  piglit_compressed_pixel_offset(format, width, x, y));

	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	/* Try CompressedTexImage of size zero - should not be an erorr */
	w = 0;
	h = 0;
	glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0,
			       piglit_compressed_image_size(format, w, h),
			       compressed_image);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	/* Try CompressedTexImage with size which is a not a multiple of the
         * block size - should not be an error
         */
	w = width - 1;
	h = height - 1;
	glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0,
			       piglit_compressed_image_size(format, w, h),
			       compressed_image);

	if (piglit_get_gl_version() < 20 &&
	    !piglit_is_extension_supported("GL_ARB_texture_non_power_of_two")) {
		pass = piglit_check_gl_error(GL_INVALID_VALUE) && pass;

		/* Reload the original into the texture. */
		w = width;
		h = height;
		glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0,
				       piglit_compressed_image_size(format, w, h),
				       compressed_image);
	}

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	pass = check_rendering(width, height) && pass;

	free(compressed_image);

#endif

	glDeleteTextures(1, &tex);
	return pass;
}


static bool
test_small_mipmap_level(void)
{
	bool pass = true;
	GLuint tex;
	GLubyte buf[100];
	int width, height;
	int format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;

	memset(buf, 0, sizeof(buf));

	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);

	/* test sizes 1x1, 1x2, 2x1, .. 2x4, 4x4 */
	for (width = 1; width <= 4; width *= 2) {
		for (height = 1; height <= 4; height *= 2) {
			/* Initial image */
			glTexImage2D(GL_TEXTURE_2D, 0, format, width, height,
				     0, GL_RGBA, GL_UNSIGNED_BYTE, buf);

			pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

			/* Try TexSubImage of whole texture */
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
					GL_RGBA, GL_UNSIGNED_BYTE, buf);

			pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
		}
	}

	glDeleteTextures(1, &tex);

	return pass;
}


static bool
test_non_power_of_two(void)
{
	bool pass = true;

	if (piglit_is_extension_supported("GL_ARB_texture_non_power_of_two")) {
		GLuint tex;
		GLubyte buf[800];
		int width = 11, height = 14;
		int format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;

		memset(buf, 0, sizeof(buf));

		/* Setup initial texture */
		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
			     GL_RGBA, GL_UNSIGNED_BYTE, buf);

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		/* Try TexSubImage of partial block on right edge */
		glTexSubImage2D(GL_TEXTURE_2D, 0,
				width-3, 0,  /* position */
				3, 4,        /* size */
				GL_RGBA, GL_UNSIGNED_BYTE, buf);

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		/* Try TexSubImage of partial block on top edge */
		glTexSubImage2D(GL_TEXTURE_2D, 0,
				0, height-2,  /* position */
				4, 2,         /* size */
				GL_RGBA, GL_UNSIGNED_BYTE, buf);

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		/* Try TexSubImage of larger partial block on right edge */
		glTexSubImage2D(GL_TEXTURE_2D, 0,
				width-3-4, 0,  /* position */
				3+4, 4,        /* size */
				GL_RGBA, GL_UNSIGNED_BYTE, buf);

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		/* Try TexSubImage of larger partial block on top edge */
		glTexSubImage2D(GL_TEXTURE_2D, 0,
				0, height-2-4,  /* position */
				4, 2+4,         /* size */
				GL_RGBA, GL_UNSIGNED_BYTE, buf);

		pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

		glDeleteTextures(1, &tex);

	}

	return pass;
}


static bool
test_formats(void)
{
	const int num_formats = ARRAY_SIZE(s3tc_formats);
	const int width = 128, height = 64;
	GLfloat *image = piglit_rgbw_image(GL_RGBA, width, height,
					   GL_FALSE, /* alpha */
					   GL_UNSIGNED_NORMALIZED);
	int i;
	bool pass = true;

	for (i = 0; i < num_formats; i++) {
		const GLenum format = s3tc_formats[i];
		pass = test_format(width, height, image, format) && pass;
	}

	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);

	free(image);

	return pass;
}


enum piglit_result
piglit_display(void)
{
	bool pass = test_formats();
	pass = test_small_mipmap_level() && pass;
	pass = test_non_power_of_two() && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}


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

#ifdef PIGLIT_USE_OPENGL_ES2
	tex_program = piglit_build_simple_program(vs_source, fs_source);
	proj_loc = glGetUniformLocation(tex_program, "proj");
#endif
}
