/*
 * Copyright © 2010 Luca Barbieri
 *
 * 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 spec/arb_color_buffer_float/common.h
 *
 * Common test framework for GL_ARB_color_buffer_float
 *
 * NOTE: both ATI and nVidia proprietary drivers are seriously broken, in
 * different ways!
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_width = 128;
	config.window_height = 128;
	config.window_visual = PIGLIT_GL_VISUAL_RGBA | PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

/* use small values for pixels[0..3], so that the 0.01 tolerance is met for fp16 */
static float pixels[] = {
	7, -2.75, -0.25, 0.75,
	0.0, 1.0, 2.0, -1.0,
	0.5, 9.0 / 8.0, -156, 390,
	234, -86, -21.5, 46.5,
};

static float clamped_pixels[16];
static float signed_clamped_pixels[16];
static float pixels_mul_2[16];
static float clamped_pixels_mul_2[16];
static float clamped_pixels_mul_2_signed_clamped[16];
static float signed_clamped_pixels_mul_2_signed_clamped[16];
static float pixels_plus_half[16];
static float clamped_pixels_plus_half[16];
static float clamped_pixels_plus_half_clamped[16];
static float clamped_pixels_plus_half_signed_clamped[16];
static float signed_clamped_pixels_plus_half_signed_clamped[16];

static const char* clamp_strings[] = {"TRUE ", "FIXED", "FALSE"};
static GLenum clamp_enums[] = {GL_TRUE, GL_FIXED_ONLY_ARB, GL_FALSE};

const char* mrt_mode_strings[] = {"single target", "homogeneous framebuffer", "dishomogeneous framebuffer"};

static float clamp(float f)
{
	if (f >= 0.0f && f <= 1.0f)
		return f;
	else if (f > 0.0f)
		return 1.0f;
	else
		return 0.0f;
}

static float signed_clamp(float f)
{
	if (f >= -1.0f && f <= 1.0f)
		return f;
	else if (f > -1.0f)
		return 1.0f;
	else
		return -1.0f;
}

static unsigned ati_driver;
static unsigned nvidia_driver;

static GLuint tex, tex1, fb;
static GLenum status;
static unsigned error;

static int fbo_width = 2;
static int fbo_height = 2;

enum test_mode_type {
	TEST_NO_RT,
	TEST_SRT,
	TEST_MRT,
	TEST_SRT_MRT
};

static enum test_mode_type test_mode;

static GLboolean sanity, test_fog;
static GLenum format;
static const char* format_name;
static GLboolean fixed, fixed_snorm;
static GLboolean fixed0;
static GLboolean fixed1;
static unsigned mrt_mode;

static GLboolean test();

static GLboolean run_test()
{
	GLboolean pass = GL_TRUE;
	fixed_snorm = format == GL_RGBA8_SNORM;
	fixed = fixed0 = format == GL_RGBA8 || fixed_snorm;
	fixed1 = -1;

	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, format,
		     2, 2, 0,
		     GL_RGBA, GL_FLOAT, pixels);
	error = glGetError();
	if (error)
	{
		printf("GL error after glTexImage2D 0x%04X\n", error);
		return GL_FALSE;
	}

	if (test_mode)
	{
		glBindTexture(GL_TEXTURE_2D, 0);

		glGenFramebuffersEXT(1, &fb);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
		glViewport(0, 0, fbo_width, fbo_height);

		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
					  GL_COLOR_ATTACHMENT0_EXT,
					  GL_TEXTURE_2D,
					  tex,
					  0);
		error = glGetError();
		if (error)
		{
			printf("GL error after FBO 0x%04X\n", error);
			return GL_FALSE;
		}

		status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
			fprintf(stderr, "fbo incomplete (status = 0x%04x)\n", status);
			piglit_report_result(PIGLIT_SKIP);
		}
	}

	if (test_mode <= TEST_SRT)
	{
		if (!sanity)
			glClampColorARB(GL_CLAMP_READ_COLOR_ARB, GL_FALSE);
		pass = test();
	}
	else
	{
		enum test_mode_type mrt_modes;
		enum test_mode_type first_mrt_mode = (test_mode == TEST_MRT) ? TEST_SRT : TEST_NO_RT;

		mrt_modes = TEST_SRT_MRT;
		if (!piglit_is_extension_supported("GL_ARB_texture_float"))
			mrt_modes = TEST_MRT;
		if (!piglit_is_extension_supported("GL_ARB_draw_buffers"))
			mrt_modes = TEST_SRT;
		else {
			GLint val;
			glGetIntegerv(GL_MAX_DRAW_BUFFERS, &val);
			if (val < 2)
				mrt_modes = TEST_SRT;
		}

		for (mrt_mode = first_mrt_mode; mrt_mode < mrt_modes; ++mrt_mode)
		{
			fixed1 = fixed;
			if (mrt_mode != TEST_NO_RT)
			{
				GLenum bufs[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
				unsigned format1;
				if (mrt_mode == TEST_SRT)
					format1 = format;
				else
				{
					format1 = fixed0 ? GL_RGBA32F_ARB : GL_RGBA8;
					fixed1 = !fixed0;
					fixed = GL_FALSE;
				}

				glGenTextures(1, &tex1);
				glBindTexture(GL_TEXTURE_2D, tex1);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				glTexImage2D(GL_TEXTURE_2D, 0, format1,
					     2, 2, 0,
					     GL_RGBA, GL_FLOAT, pixels);
				error = glGetError();
				if (error)
				{
					printf("GL error after second glTexImage2D 0x%04X\n", error);
					return GL_FALSE;
				}

				glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
							  GL_COLOR_ATTACHMENT1_EXT,
							  GL_TEXTURE_2D,
							  tex1,
							  0);

				status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
				if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
					if (mrt_mode == TEST_MRT)
						printf("Dishomogeneous framebuffer is incomplete, skipping dishomogeneous tests (status = 0x%04x)\n", status);
					else
					{
						printf("Framebuffer is incomplete (status = 0x%04x)\n", status);
						pass = GL_FALSE;
					}
					goto skip_mrt;
				}

				glDrawBuffers(2, bufs);

				error = glGetError();
				if (error)
				{
					printf("GL error after second glDrawBuffers 0x%04X\n", error);
					return GL_FALSE;
				}
			}

			if (!sanity)
				glClampColorARB(GL_CLAMP_READ_COLOR_ARB, GL_FALSE);
			pass = test() && pass;

	skip_mrt:
			if (mrt_mode != TEST_NO_RT)
			{
				glDrawBuffer(GL_COLOR_ATTACHMENT0);

				glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
						  GL_COLOR_ATTACHMENT1_EXT,
						  GL_TEXTURE_2D,
						  0,
						  0);
				glDeleteTextures(1, &tex1);
			}
		}
	}

	glDeleteTextures(1, &tex);
	tex = 0;
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, piglit_winsys_fbo);
	if (fb)
	{
		glDeleteFramebuffersEXT(1, &fb);
		fb = 0;
	}

	error = glGetError();
	if (error)
	{
		printf("GL error after test 0x%04X\n", error);
		return GL_FALSE;
	}

	return pass;
}

enum piglit_result
piglit_display(void)
{
	unsigned error;
	GLboolean pass = run_test();

	error = glGetError();
	if (error)
	{
		printf("GL error at end 0x%04X\n", error);
		return GL_FALSE;
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static unsigned init();

void
piglit_init(int argc, char **argv)
{
	int i;
	GLboolean distinguish_xfails = GL_FALSE;

	/* displaying thousands of single-pixel floating point results isn't really useful, or even doable */
	piglit_automatic = GL_TRUE;

	test_mode = init();

	if (test_mode != TEST_NO_RT)
		piglit_require_extension("GL_EXT_framebuffer_object");

	for (i = 1; i < argc; ++i)
	{
		if (!strcmp(argv[i], "-xfail")) {
			distinguish_xfails = GL_TRUE;
		} else if (!strcmp(argv[i], "sanity")) {
			sanity = GL_TRUE;
		} else if (!strcmp(argv[i], "fog")) {
			test_fog = GL_TRUE;
		} else if (!strcmp(argv[i], "GL_RGBA16F")) {
			piglit_require_extension("GL_ARB_texture_float");
			format = GL_RGBA16F;
			format_name = "f16";
			printf("\n\n\nTesting 16-bit floating-point FBO\n");
		} else if (!strcmp(argv[i], "GL_RGBA32F")) {
			piglit_require_extension("GL_ARB_texture_float");
			format = GL_RGBA32F;
			format_name = "f32";
			printf("\n\n\nTesting 32-bit floating-point FBO\n");
		} else if (!strcmp(argv[i], "GL_RGBA8_SNORM")) {
			piglit_require_extension("GL_EXT_texture_snorm");
			format = GL_RGBA8_SNORM;
			format_name = "sn8";
			printf("\n\n\nTesting 8-bit signed normalized fixed-point FBO\n");
		}
	}
	if (!format) {
		format = GL_RGBA8;
		format_name = "un8";
		printf("Testing 8-bit unsigned normalized fixed-point FBO\n");
	}

	if (sanity) {
		printf("Testing default clamping rules only. This is a sanity check. GL_ARB_color_buffer_float is not required.\n");
	} else {
		piglit_require_extension("GL_ARB_color_buffer_float");
	}

	/* current ATI drivers are broken */
	if (!strcmp((char*)glGetString(GL_VENDOR), "ATI Technologies Inc."))
		ati_driver = 1;

	/* current nVidia drivers are broken at least on GeForce 7xxx */
	if (!strcmp((char*)glGetString(GL_VENDOR), "NVIDIA Corporation"))
		nvidia_driver = 1;

	if (ati_driver || nvidia_driver)
	{
		/* print both so users don't think either driver is better */
		printf("Notice: the ATI proprietary driver does NOT conform to the GL_ARB_color_buffer_float specification! (tested version was 10.6 on cypress, on Linux x86)\n");
		printf("Notice: the nVidia proprietary driver does NOT conform to the GL_ARB_color_buffer_float specification! (tested version was 256.44 on nv49, on Linux x86)\n");
		printf("Notice: the nVidia and ATI proprietary drivers are both nonconformant, in different ways!\n\n\n");
	}

	if (!distinguish_xfails)
		ati_driver = nvidia_driver = 0;

	for (i = 0; i < sizeof(pixels) / sizeof(pixels[0]); ++i)
	{
		clamped_pixels[i] = clamp(pixels[i]);
		signed_clamped_pixels[i] = signed_clamp(pixels[i]);

		pixels_mul_2[i] = pixels[i] * 2.0f;
		clamped_pixels_mul_2[i] = clamped_pixels[i] * 2.0f;
		clamped_pixels_mul_2_signed_clamped[i] = signed_clamp(clamped_pixels_mul_2[i]);
		signed_clamped_pixels_mul_2_signed_clamped[i] = signed_clamp(signed_clamped_pixels[i] * 2.0f);

		pixels_plus_half[i] = pixels[i] + 0.5f;
		clamped_pixels_plus_half[i] = clamped_pixels[i] + 0.5f;
		clamped_pixels_plus_half_clamped[i] = clamp(clamped_pixels_plus_half[i]);
		clamped_pixels_plus_half_signed_clamped[i] = signed_clamp(clamped_pixels_plus_half[i]);
		signed_clamped_pixels_plus_half_signed_clamped[i] = signed_clamp(signed_clamped_pixels[i] + 0.5);
	}
}

GLboolean compare_arrays(float* expected, float* observed, int comps, int length)
{
	GLboolean pass = GL_TRUE;
	unsigned i, j, k;
	for (i = 0; i < length; ++i)
	{
		for (j = 0; j < comps; j++) {
			if (fabs(expected[i*comps+j] - observed[i*comps+j]) > 0.01)
			{
				printf(" At %i:\n  Expected: %f", i, expected[i*comps]);
				for (k = 1; k < comps; k++)
					printf(", %f", expected[i*comps+k]);
				printf("\n  Observed: %f", observed[i*comps]);
				for (k = 1; k < comps; k++)
					printf(", %f", observed[i*comps+k]);
				printf("\n");
				pass = GL_FALSE;
				break;
			}
		}
	}

	return pass;
}
