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

/**
 * \file isinf-and-isnan.c
 *
 * Test that isinf() and isnan() built-in functions behave properly.
 *
 * The GLSL 1.30 spec does not define when an implementation is required to
 * generate infinite or NaN values; in fact, it explicitly allows for
 * implementations that do not even have a representation of infinity or Nan.
 * Therefore, we cannot check that infinities and NaNs are created when we
 * expect them.  However, we can test: (a) that isnan() and isinf() return
 * false for finite values, (b) that isinf() and isnan() behave consistently
 * with each other, and (c) that when a floating-point value is read out the
 * shader (using transform feedback or a floating point framebuffer) the
 * behavior of isnan() and isinf() behave consistently with the value that is
 * read out.
 *
 * This test operates by generating several expressions, some of which are
 * likely to produce infinities, some of which are likely to produce NaN, and
 * some of which are expected to produce finite values.  For each expression,
 * it does the following:
 * - evaluates isinf(value) in the shader
 * - evaluates isnan(value) in the shader
 * - evaluates sign(value) in the shader
 * - evaluates (value > 0) in the shader
 * - reads the value out of the shader (using transform feedback or a floating
 *   point framebuffer)
 * - feeds that value back into the shader (using a uniform); the shader
 *   subtracts this uniform from the originally computed value to produce a
 *   delta.
 *
 * And then it performs the following checks:
 * - If the value was expected to be finite, verifies that isinf() and isnan()
 *   returned false.
 * - If the value was expected to be +Inf or -Inf, verifies that the sign is
 *   correct, using both sign(value) and (value > 0).  This check is skipped
 *   if isnan(value) is true, since it's possible that a conformant
 *   implementation might generate NaN instead of infinity, and NaN does not
 *   have a well-defined sign.
 * - Checks that isinf() and isnan() didn't both return true.
 * - Checks that the C isinf() and isnan() functions give the same result as
 *   the shader's isinf() and isnan() functions.
 * - If the value is finite, checks that the delta is zero (to within
 *   tolerance).
 *
 * The last two checks are only performed when using a floating point
 * framebuffer or transform feedback, because those are the only ways to get
 * infinities and NaNs out of the shader and into C code.
 *
 * Note: the reason for the final check is to verify that a value claimed by
 * the shader to be finite is truly behaving like a finite number.  Without
 * it, an implementation could pass all these tests by simply having isinf()
 * and isnan() return false, and converting infinities and NaNs to finite
 * values when they exit the shader.
 *
 * The output of the test is a table whose columns are:
 * - The expression being tested (this expression may refer to the uniforms
 *   z=0.0, u_inf=+Inf, u_minus_inf=-Inf, and u_nan=NaN).
 * - The expected behavior of the expression ("finite", "+Inf", "-Inf", or
 *   "NaN", indicating how the expression would be expected to evaluate on a
 *   fully IEEE 754 compliant architecture)
 * - isinf(value), as computed by the shader
 * - isnan(value), as computed by the shader
 * - sign(value), as computed by the shader
 * - (value > 0), as computed by the shader
 * - value, as read out of the shader using transform feedback or a
 *   floating-point framebuffer
 * - delta, the difference between the computed value and the value that was
 *   fed back into the shader.
 * - A pass/fail indication.
 *
 * Note: the uniform z=0.0 is present to defeat constant folding and ensure
 * that the expression is evaluated during shader execution rather than during
 * compilation.  For example, "exp(1000.0)" might be evaluated at compile-time,
 * preventing us from exercising this test case in the GPU.  But
 * "exp(1000.0+z)" will definitely be evaluated on the GPU.
 *
 * The test must be invoked with one of the following command-line arguments:
 * - vs_basic: test the VS without reading values out of the shader.
 * - fs_basic: test the FS without reading values out of the shader.
 * - vs_fbo: test the VS, using a floating-point framebuffer to read values
 *   out of the shader.
 * - vs_xfb: test the VS, using transform feedback to read values out of the
 *   shader.
 * - fs_fbo: test the FS, using a floating-point framebuffer to read values
 *   out of the shader.
 */

#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 float gl_version;

static GLint stock_vs;
static GLint stock_fs;
static GLint main_vs;
static GLint main_fs;
static GLint do_test_vs;
static GLint do_test_fs;
static GLuint xfb_buffer;

/**
 * True if we are using a floating-point framebuffer to read data out of the
 * shader.
 */
static bool use_fbo = false;

/**
 * True if we are using transform feedback to read data out of the shader.
 */
static bool use_xfb = false;

/**
 * True if we are testing the fragment shader, false if we are testing the
 * vertex shader.
 */
static bool use_fs;

/**
 * True if we are reading data out of the shader using a mechanism that
 * preserves the full 32-bit floating point value, so we can do additional
 * checks.
 */
static bool precise;

enum modes
{
	/**
	 * Output = vec4(value, isinf(value), isnan(value),
	 *               (sign(value) + 1.0) / 2.0)
	 */
	MODE_VALUE_ISINF_ISNAN_SIGN = 0,

	/**
	 * Output = vec4(value > 0, value - ref, 0.0, 0.0)
	 */
	MODE_GTZERO_DELTA_ZERO_ZERO = 1,
};

static const char stock_vs_text[] =
	"#version 130\n"
	"void main()\n"
	"{\n"
	"  gl_Position = gl_Vertex;\n"
	"}\n";

static const char stock_fs_text[] =
	"#version 130\n"
	"flat in vec4 data;\n"
	"void main()\n"
	"{\n"
	"  gl_FragColor = data;\n"
	"}\n";

static const char main_vs_text[] =
	"#version 130\n"
	"flat out vec4 data;\n"
	"vec4 do_test();\n"
	"void main()\n"
	"{\n"
	"  gl_Position = gl_Vertex;\n"
	"  data = do_test();\n"
	"}\n";

static const char main_fs_text[] =
	"#version 130\n"
	"flat in vec4 data;\n"
	"vec4 do_test();\n"
	"void main()\n"
	"{\n"
	"  gl_FragColor = do_test();\n"
	"}\n";

static const char do_test_text[] =
	"#version 130\n"
	"uniform float ref;\n" /* Value fed back from C */
	"uniform int mode;\n" /* See enum modes */
	"float compute_value();\n"
	"vec4 do_test()\n"
	"{\n"
	"  float value = compute_value();\n"
	"  if (mode == 0) { /* MODE_VALUE_ISINF_ISNAN_SIGN */\n"
	"    return vec4(value,\n"
	"                isinf(value) ? 1 : 0,\n"
	"                isnan(value) ? 1 : 0,\n"
	"                (sign(value) + 1.0) / 2.0);\n"
	"  } else if (mode == 1) { /* MODE_GTZERO_DELTA_ZERO_ZERO */\n"
	"    return vec4(value > 0 ? 1 : 0,\n"
	"                value - ref,\n"
	"                0.0,\n"
	"                0.0);\n"
	"  } else { /* Unrecognized mode */\n"
	"    return vec4(0.0);\n"
	"  }\n"
	"}\n";

static void
setup_fbo()
{
	GLuint fb = 0;
	GLuint color_rb = 0;
	GLenum fb_status;

	glGenFramebuffers(1, &fb);
	glBindFramebuffer(GL_FRAMEBUFFER, fb);

	/* Bind color attachment. */
	glGenRenderbuffers(1, &color_rb);
	glBindRenderbuffer(GL_RENDERBUFFER, color_rb);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA32F,
			      piglit_width, piglit_height);
	glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
				  GL_RENDERBUFFER, color_rb);
	if (!piglit_check_gl_error(0))
		piglit_report_result(PIGLIT_FAIL);

	fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
		printf("error: FBO incomplete (status = 0x%04x)\n", fb_status);
		piglit_report_result(PIGLIT_SKIP);
	}

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb);
	glBindFramebuffer(GL_READ_FRAMEBUFFER, fb);
}

static void
setup_xfb()
{
	glGenBuffers(1, &xfb_buffer);
}

static void
print_usage_and_exit(char *prog_name)
{
	printf("Usage: %s <mode>\n"
	       "  where <mode> is one of:\n"
	       "    vs_basic\n"
	       "    fs_basic\n"
	       "    vs_fbo\n"
	       "    vs_xfb\n"
	       "    fs_fbo\n", prog_name);
	exit(1);
}

void
piglit_init(int argc, char **argv)
{
	if (argc != 2)
		print_usage_and_exit(argv[0]);
	if (strcmp(argv[1], "vs_basic") == 0) {
		use_fs = false;
	} else if (strcmp(argv[1], "fs_basic") == 0) {
		use_fs = true;
	} else if (strcmp(argv[1], "vs_fbo") == 0) {
		use_fs = false;
		use_fbo = true;
	} else if (strcmp(argv[1], "vs_xfb") == 0) {
		use_fs = false;
		use_xfb = true;
	} else if (strcmp(argv[1], "fs_fbo") == 0) {
		use_fs = true;
		use_fbo = true;
	} else {
		print_usage_and_exit(argv[0]);
	}
	precise = use_fbo || use_xfb;

	gl_version = strtod((char *) glGetString(GL_VERSION), NULL);

	piglit_require_GLSL();
	piglit_require_GLSL_version(130);

	if (piglit_is_extension_supported("GL_EXT_gpu_shader4")) {
		piglit_require_gl_version(21);
	} else {
		piglit_require_gl_version(30);
	}

	if (use_fbo) {
		setup_fbo();
	}
	if (use_xfb) {
		setup_xfb();
	}

	stock_vs = piglit_compile_shader_text(GL_VERTEX_SHADER,
					      stock_vs_text);
	stock_fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					      stock_fs_text);
	main_vs = piglit_compile_shader_text(GL_VERTEX_SHADER,
					     main_vs_text);
	main_fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					     main_fs_text);
	do_test_vs = piglit_compile_shader_text(GL_VERTEX_SHADER,
						do_test_text);
	do_test_fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
						do_test_text);
}

/**
 * enum indicating how the expression would be expected to behave on a fully
 * IEEE 754 compliant architecture.  Note: since OpenGL implementations are
 * not required to respect all of IEEE 754's rules for infinities and NaN's,
 * we don't necessarily check all of these behaviors.
 */
enum behavior
{
	B_NAN    = 0, /* Expected to evaluate to NaN */
	B_FINITE = 1, /* Expected to evaluate to a finite value */
	B_POSINF = 2, /* Expected to evaluate to +Infinity */
	B_NEGINF = 3, /* Expected to evaluate to -Infinity */
	B_FINITE_NAN_OK = 4, /* Expected finite value, but NaN ok */
};

struct expression_table_element
{
	char *expression;
	int expected_behavior;
};

static struct expression_table_element expressions[] = {
	{ "1000.0", B_FINITE },
	{ "1000.0+z", B_FINITE },
	{ "-1000.0", B_FINITE },
	{ "-1000.0+z", B_FINITE },
	{ "u_inf", B_POSINF },
	{ "exp(1000.0)", B_POSINF },
	{ "exp(1000.0+z)", B_POSINF },
	{ "u_minus_inf", B_NEGINF },
	{ "-exp(1000.0)", B_NEGINF },
	{ "-exp(1000.0+z)", B_NEGINF },
	{ "u_nan", B_NAN },
	{ "0.0/0.0", B_NAN },
	{ "z/z", B_NAN },
	{ "u_inf/u_minus_inf", B_NAN },
	{ "z*u_inf", B_NAN },
	{ "u_inf+u_minus_inf", B_NAN },
	{ "log(-1.0)", B_NAN },
	{ "log(-1.0+z)", B_NAN },
	{ "sqrt(-1.0)", B_NAN },
	{ "sqrt(-1.0+z)", B_NAN },
	{ "clamp(u_nan, 0.0, 1.0)", B_FINITE_NAN_OK },
	{ "min(u_two, u_nan)", B_FINITE_NAN_OK },
	{ "max(u_two, u_nan)", B_FINITE_NAN_OK },

};

/**
 * Draw using the shader, and then read back values using either (a) the
 * floating-point framebuffer, (b) transform feedback, or (c) pixel reads from
 * the window.  Note that pixel reads from the window are only accurate to one
 * part in 255, so the caller must be careful not to rely on high precision in
 * case (c).
 */
static void
draw_and_readback(float *readback)
{
	if (use_xfb) {
		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 4096, NULL,
			     GL_DYNAMIC_COPY);
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buffer);
		glEnable(GL_RASTERIZER_DISCARD);
		glBeginTransformFeedback(GL_TRIANGLES);
	}

	piglit_draw_rect(-1, -1, 2, 2);

	if (use_xfb) {
		glEndTransformFeedback();
		memcpy(readback,
		       glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_READ_ONLY),
		       4*sizeof(float));
		glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
	} else {
		glReadPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, readback);
	}
}

static void
set_uniform_float_if_present(GLint program, char *name, float value)
{
	GLint loc = glGetUniformLocation(program, name);
	if (loc != -1)
		glUniform1f(loc, value);
}

static void
set_uniform_int_if_present(GLint program, char *name, int value)
{
	GLint loc = glGetUniformLocation(program, name);
	if (loc != -1)
		glUniform1i(loc, value);
}

/**
 * Test the given expression, to make sure its behavior is self-consistent and
 * consistent with the expected behavior.
 */
static bool
test_expr(char *expression, int expected_behavior)
{
	char compute_value_text[4096];
	GLint shader;
	GLint prog;
	float readback[4];
	float value;
	bool isinf_in_shader;
	bool isnan_in_shader;
	int sign_in_shader;
	float delta;
	bool greater_than_zero;
	bool pass = true;
	char *expected_behavior_string;

	/* Create and link a program specifically to test this expression */
	prog = glCreateProgram();
	sprintf(compute_value_text,
		"#version 130\n"
		"uniform float z = 0.0;\n" /* To defeat constant folding */
		"uniform float u_inf;\n" /* Always == +infinity */
		"uniform float u_minus_inf;\n" /* Always == -infinity */
		"uniform float u_nan;\n" /* Always == NaN */
		"uniform float u_two = 2.0;\n" /* To defeat constant folding */
		"float compute_value() {\n"
		"  return %s;\n"
		"}\n",
		expression);
	if (use_fs) {
		glAttachShader(prog, stock_vs);
		glAttachShader(prog, main_fs);
		glAttachShader(prog, do_test_fs);
		shader = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
						    compute_value_text);
		glAttachShader(prog, shader);
	} else {
		glAttachShader(prog, stock_fs);
		glAttachShader(prog, main_vs);
		glAttachShader(prog, do_test_vs);
		shader = piglit_compile_shader_text(GL_VERTEX_SHADER,
						    compute_value_text);
		glAttachShader(prog, shader);
	}
	if (use_xfb) {
		static const char *var_name = "data";
		glTransformFeedbackVaryings(prog, 1, &var_name,
					    GL_SEPARATE_ATTRIBS);
		glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfb_buffer);
	}
	glLinkProgram(prog);
	glDeleteShader(shader);
	glUseProgram(prog);

	/* Set up uniforms */
	set_uniform_float_if_present(prog, "u_inf", INFINITY);
	set_uniform_float_if_present(prog, "u_minus_inf", -INFINITY);
	set_uniform_float_if_present(prog, "u_nan", NAN);

	/* Use one draw call to read out value, isinf(value), isnan(value),
	 * and sign(value).
	 */
	set_uniform_float_if_present(prog, "ref", 0.0);
	set_uniform_int_if_present(prog, "mode", MODE_VALUE_ISINF_ISNAN_SIGN);
	draw_and_readback(readback);
	value = readback[0];
	isinf_in_shader = readback[1] > 0.5;
	isnan_in_shader = readback[2] > 0.5;
	sign_in_shader = (int) (2.0*readback[3] + 0.5) - 1;

	/* Use a second draw call to feed value back into the shader, and read
	 * out (value > 0) and delta.
	 */
	set_uniform_float_if_present(prog, "ref", value);
	set_uniform_int_if_present(prog, "mode", MODE_GTZERO_DELTA_ZERO_ZERO);
	draw_and_readback(readback);
	greater_than_zero = readback[0] > 0.5;
	delta = readback[1];

	/* Check that the behavior was as expected */
	switch (expected_behavior) {
	case B_FINITE:
		expected_behavior_string = "finite";
		if (isinf_in_shader || isnan_in_shader) {
			/* Expected finite, got Inf or NaN */
			pass = false;
		}
		break;
	case B_POSINF:
		expected_behavior_string = "+Inf";
		if (!isnan_in_shader && sign_in_shader != 1.0) {
			/* Expected positive or NaN, got <= 0 */
			pass = false;
		}
		break;
	case B_NEGINF:
		expected_behavior_string = "-Inf";
		if (!isnan_in_shader && sign_in_shader != -1.0) {
			/* Expected negative or NaN, got >= 0 */
			pass = false;
		}
		break;
	case B_FINITE_NAN_OK:
		expected_behavior_string = "finite";
		break;
	default:
		expected_behavior_string = "NaN";
		break;
	}

	/* Do other sanity checks */
	if (isnan_in_shader && isinf_in_shader) {
		/* No value can be simultaneously Inf and NaN */
		pass = false;
	}
	if (!isnan_in_shader) {
		if (sign_in_shader == -1 || sign_in_shader == 0) {
			if (greater_than_zero) {
				/* sign(value) inconsistent with (value>0) */
				pass = false;
			}
		} else if (sign_in_shader == 1) {
			if (!greater_than_zero) {
				/* sign(value) inconsistent with (value>0) */
				pass = false;
			}
		} else {
			/* Illegal return value for sign() */
			pass = false;
		}
	}

	/* If we are using a high-precision technique to read data out of the
	 * shader (fbo or xfb), check the behavior of isinf and isnan against
	 * their C counterparts, and verify that delta ~= 0 for finite values.
	 */
	if (precise) {
		bool isinf_in_c = !!isinf(value);
		bool isnan_in_c = !!isnan(value);
		if (isinf_in_shader != isinf_in_c ||
		    isnan_in_shader != isnan_in_c) {
			/* Result of isinf() and isnan() in the shader did not
			 * match the result in C code.
			 */
			pass = false;
		}
		if (!isinf_in_shader && !isnan_in_shader) {
			float threshold = fabs(value * 1e-6);
			if (isinf(delta) || isnan(delta) ||
			    fabs(delta) > threshold) {
				/* The shader and C code agree that the value
				 * was finite, but it isn't behaving as a nice
				 * finite value should.
				 */
				pass = false;
			}
		}
	}

	/* Output a line for the results table */
	printf("%17s %6s %5s %5s %4d %5s ",
	       expression,
	       expected_behavior_string,
	       isinf_in_shader ? "true" : "false",
	       isnan_in_shader ? "true" : "false",
	       sign_in_shader,
	       greater_than_zero ? "true" : "false");
	if (precise) {
		printf("%12g %12g ", value, delta);
	}
	printf("%s\n", pass ? "OK" : "FAIL");

	glUseProgram(0);
	glDeleteProgram(prog);

	return pass;
}

enum piglit_result
piglit_display()
{
	int i;
	bool pass = true;

	printf("    expression    expect isinf isnan sign  >0?");
	if (precise)
		printf("      value        delta");
	printf("\n");

	for (i = 0; i < sizeof(expressions)/sizeof(*expressions); ++i) {
		pass = test_expr(expressions[i].expression,
				 expressions[i].expected_behavior) && pass;
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}
