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

#include "piglit-util-gl.h"

/**
 * @file getuniform.c
 *
 * Tests that glGetUniform* work on various scalar/vector types.
 */

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGBA;

	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

enum piglit_result
piglit_display(void)
{
	/* Not reached */
	return PIGLIT_FAIL;
}

static const char *source =
	"uniform vec4 u_vec4;\n"
	"uniform ivec4 u_ivec4;\n"
	"uniform bvec4 u_bvec4;\n"
	"uniform vec3 u_vec3;\n"
	"uniform ivec3 u_ivec3;\n"
	"uniform bvec3 u_bvec3;\n"
	"uniform vec2 u_vec2;\n"
	"uniform ivec2 u_ivec2;\n"
	"uniform bvec2 u_bvec2;\n"
	"uniform float u_float;\n"
	"uniform int u_int;\n"
	"uniform bool u_bool;\n"
	"\n"
	"void main()\n"
	"{"
	"	gl_Position = gl_Vertex;\n"
	"	gl_FrontColor =\n"
	"	    vec4(u_float + float(u_int) + float(u_bool),\n"
	"		 u_vec2.x + float(u_ivec2.x) + float(u_bvec2.x),\n"
	"		 u_vec3.x + float(u_ivec3.x) + float(u_bvec3.x),\n"
	"		 u_vec4.x + float(u_ivec4.x) + float(u_bvec4.x));\n"
	"}\n";

static GLuint prog;

static void
uniformiv_n(int loc, int count, int *values)
{
	switch (count) {
	case 1:
		glUniform1iv(loc, 1, values);
		break;
	case 2:
		glUniform2iv(loc, 1, values);
		break;
	case 3:
		glUniform3iv(loc, 1, values);
		break;
	case 4:
		glUniform4iv(loc, 1, values);
		break;
	default:
		abort();
	}
}

static void
uniformfv_n(int loc, int count, float *values)
{
	switch (count) {
	case 1:
		glUniform1fv(loc, 1, values);
		break;
	case 2:
		glUniform2fv(loc, 1, values);
		break;
	case 3:
		glUniform3fv(loc, 1, values);
		break;
	case 4:
		glUniform4fv(loc, 1, values);
		break;
	default:
		abort();
	}
}

static bool
check_f(const char *uniform_name, int chan, float expected, float result)
{
	if (expected == result)
		return true;

	fprintf(stderr, "%s.%c: expected %f, got %f\n",
		uniform_name + 2, "xyzw"[chan],
		expected, result);
	return false;
}

static bool
check_i(const char *uniform_name, int chan, int expected, int result)
{
	if (expected == result)
		return true;

	fprintf(stderr, "%s.%c: expected %d, got %d\n",
		uniform_name + 2, "xyzw"[chan],
		expected, result);

	return false;
}

static bool
test_bool_type(const char *name, int loc, int size)
{
	int tvals[4] = {1, 2, -3, -4};
	int fvals[4] = {0, 0, 0, 0};
	float retf[4];
	int reti[4];
	int i;

	/* Make the stack data have a consistent starting value, to
	 * distinguish unset out values.
	 */
	memset(retf, 0xd0, sizeof(retf));
	memset(reti, 0xd0, sizeof(reti));

	/* According to the ARB_shader_objects spec:
	 *
	 *     When loading values for a uniform declared as a Boolean, a
	 *     Boolean vector or an array of Booleans or an array of Boolean
	 *     vectors, both the Uniform*i{v} and Uniform*f{v} set of commands
	 *     can be used to load Boolean values. Type conversion is done by
	 *     the GL. The uniform is set to FALSE if the input value is 0 or
	 *     0.0f, and set to TRUE otherwise. The Uniform*ARB command used
	 *     must match the size of the uniform, as declared in the shader.
	 *
	 * We don't really care about loading of different types in this test,
	 * just getting types back out.
	 */

	uniformiv_n(loc, size, tvals);

	glGetUniformfvARB(prog, loc, retf);
	glGetUniformivARB(prog, loc, reti);
	for (i = 0; i < size; i++) {
		if (!check_f(name, i, 1.0, retf[i]))
			return false;

		if (!check_i(name, i, 1, reti[i]))
			return false;
	}

	uniformiv_n(loc, size, fvals);

	glGetUniformfvARB(prog, loc, retf);
	glGetUniformivARB(prog, loc, reti);
	for (i = 0; i < size; i++) {
		if (!check_f(name, i, 0.0, retf[i]))
			return false;

		if (!check_i(name, i, 0, reti[i]))
			return false;
	}

	return true;
}

static bool
test_float_type(const char *name, int loc, int size)
{
	float vals[4] = {1.2, -3.9, 4.9, 0.0};
	float retf[4];
	int reti[4];
	int i;

	/* Make the stack data have a consistent starting value, to
	 * distinguish unset out values.
	 */
	memset(retf, 0xd0, sizeof(retf));
	memset(reti, 0xd0, sizeof(reti));

	/* According to the ARB_shader_objects spec:
	 *
	 *     For all other uniform types the Uniform*ARB command used must
	 *     match the size and type of the uniform, as declared in the
	 *     shader. No type conversions are done.
	 */

	uniformfv_n(loc, size, vals);

	glGetUniformfvARB(prog, loc, retf);
	glGetUniformivARB(prog, loc, reti);
	for (i = 0; i < size; i++) {
		if (!check_f(name, i, vals[i], retf[i]))
			return false;

		/* While the GL 3.2 core spec doesn't explicitly
		 * state how conversion of float uniforms to integer
		 * values works, in section 6.2 "State Tables" on
		 * page 267 it says:
		 *
		 *     "Unless otherwise specified, when floating
		 *      point state is returned as integer values or
		 *      integer state is returned as floating-point
		 *      values it is converted in the fashion
		 *      described in section 6.1.2"
		 *
		 * That section, on page 248, says:
		 *
		 *     "If GetIntegerv or GetInteger64v are called,
		 *      a floating-point value is rounded to the
		 *      nearest integer..."
		 *
		 * So we assume rounding.
		 */
		if (!check_i(name, i, round(vals[i]), reti[i]))
			return false;
	}

	return true;
}

static bool
test_int_type(const char *name, int loc, int size)
{
	int vals[4] = {0, 1, 20, -40};
	float retf[4];
	int reti[4];
	int i;

	/* Make the stack data have a consistent starting value, to
	 * distinguish unset out values.
	 */
	memset(retf, 0xd0, sizeof(retf));
	memset(reti, 0xd0, sizeof(reti));

	/* According to the ARB_shader_objects spec:
	 *
	 *     For all other uniform types the Uniform*ARB command used must
	 *     match the size and type of the uniform, as declared in the
	 *     shader. No type conversions are done.
	 */

	uniformiv_n(loc, size, vals);

	glGetUniformfvARB(prog, loc, retf);
	glGetUniformivARB(prog, loc, reti);
	for (i = 0; i < size; i++) {
		if (!check_f(name, i, vals[i], retf[i]))
			return false;
		if (!check_i(name, i, vals[i], reti[i]))
			return false;
	}

	return true;
}

static struct {
	const char *name;
	bool (*test_func)(const char *name, int loc, int size);
	int size;
} uniforms[] = {
	{ "u_vec4", test_float_type, 4 },
	{ "u_vec3", test_float_type, 3 },
	{ "u_vec2", test_float_type, 2 },
	{ "u_float", test_float_type, 1 },
	{ "u_ivec4", test_int_type, 4 },
	{ "u_ivec3", test_int_type, 3 },
	{ "u_ivec2", test_int_type, 2 },
	{ "u_int", test_int_type, 1 },
	{ "u_bvec4", test_bool_type, 4 },
	{ "u_bvec3", test_bool_type, 3 },
	{ "u_bvec2", test_bool_type, 2 },
	{ "u_bool", test_bool_type, 1 },
};

void
piglit_init(int argc, char **argv)
{
	int i;
	bool pass = true;

	piglit_require_vertex_shader();

	prog = piglit_build_simple_program(source, NULL);
	glUseProgram(prog);

	for (i = 0; i < ARRAY_SIZE(uniforms); i++) {
		const char *name = uniforms[i].name;
		int loc = glGetUniformLocation(prog, name);
		assert(loc != -1);

		pass = pass && uniforms[i].test_func(name, loc,
						     uniforms[i].size);
	}

	piglit_report_result(pass ? PIGLIT_PASS : PIGLIT_FAIL);
}
