/*
 * 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 builtin-varyings.c
 *
 * Verify that transform feedback can be used with built-in varying
 * variables, such as gl_FrontColor, gl_BackColor, etc.
 *
 * Note: gl_FrontColor and gl_BackColor are tested at the same time,
 * in order to verify that the implementation is able to distinguish
 * them.  Same for gl_FrontSecondaryColor and gl_BackSecondaryColor.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGB;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

#define MAX_EXPECTED_OUTPUT_COMPONENTS 8

/**
 * All possible tests to run.  Note that in each test, the input will
 * consist of 6 vertices, with attribute vertex_num running from 0
 * through 5, and attribute vertex_pos tracing out one front-facing
 * triangle and one back-facing triangle.
 *
 * The expected output of each test is the sequence of floating point
 * values (0.0, 1.0/256.0, 2.0/256.0, 3.0/256.0, ...).
 */
struct test_desc {
	const char *name;
	int version;
	const char *vs;
	unsigned num_varyings;
	const char *varyings[16];
	unsigned expected_num_output_components;
	GLenum expected_type;
	GLsizei expected_size;
} tests[] = {
	{
		"gl_Color", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  vec4 offset = vec4(0.0, 1.0, 2.0, 3.0);\n"
		"  float scale = 1.0/256.0;\n"
		"  gl_FrontColor = (offset + 8.0 * vertex_num) * scale;\n"
		"  gl_BackColor = (offset + 4.0 + 8.0 * vertex_num) * scale;\n"
		"}\n",

		2, /* num_varyings */
		{"gl_FrontColor", "gl_BackColor"}, /* varyings */

		8, /* expected_num_output_components */
		GL_FLOAT_VEC4, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_SecondaryColor", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  vec4 offset = vec4(0.0, 1.0, 2.0, 3.0);\n"
		"  float scale = 1.0/256.0;\n"
		"  gl_FrontSecondaryColor = (offset + 8.0 * vertex_num) * scale;\n"
		"  gl_BackSecondaryColor = (offset + 4.0 + 8.0 * vertex_num) * scale;\n"
		"}\n",

		2, /* num_varyings */
		{"gl_FrontSecondaryColor", "gl_BackSecondaryColor"}, /* varyings */

		8, /* expected_num_output_components */
		GL_FLOAT_VEC4, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_TexCoord", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  vec4 offset = vec4(0.0, 1.0, 2.0, 3.0);\n"
		"  float scale = 1.0/256.0;\n"
		"  gl_TexCoord[0] = (offset + 8.0 * vertex_num) * scale;\n"
		"  gl_TexCoord[1] = (offset + 4.0 + 8.0 * vertex_num) * scale;\n"
		"}\n",

		2, /* num_varyings */
		{"gl_TexCoord[0]", "gl_TexCoord[1]"}, /* varyings */

		8, /* expected_num_output_components */
		GL_FLOAT_VEC4, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_FogFragCoord", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  gl_FogFragCoord = vertex_num / 256.0;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_FogFragCoord"}, /* varyings */

		1, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_Position", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  vec4 offset = vec4(0.0, 1.0, 2.0, 3.0);\n"
		"  float scale = 1.0/256.0;\n"
		"  gl_Position = (offset + 4.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_Position"}, /* varyings */

		4, /* expected_num_output_components */
		GL_FLOAT_VEC4, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_PointSize", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  gl_PointSize = vertex_num / 256.0;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_PointSize"}, /* varyings */

		1, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_ClipVertex", /* name */
		110, /* version */

		"#version 110\n" /* vs */
		"attribute vec4 vertex_pos;\n"
		"attribute float vertex_num;\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  vec4 offset = vec4(0.0, 1.0, 2.0, 3.0);\n"
		"  float scale = 1.0/256.0;\n"
		"  gl_ClipVertex = (offset + 4.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipVertex"}, /* varyings */

		4, /* expected_num_output_components */
		GL_FLOAT_VEC4, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_ClipDistance", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[8];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 8; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 8.0 * vertex_num) * scale;\n"
		"}\n",

		8, /* num_varyings */
		{"gl_ClipDistance[0]", "gl_ClipDistance[1]", /* varyings */
		 "gl_ClipDistance[2]", "gl_ClipDistance[3]",
		 "gl_ClipDistance[4]", "gl_ClipDistance[5]",
		 "gl_ClipDistance[6]", "gl_ClipDistance[7]"},

		8, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_ClipDistance[1]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[1];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 1; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 1.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		1, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_ClipDistance[2]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[2];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 2; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 2.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		2, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		2, /* expected_size */
	},
	{
		"gl_ClipDistance[3]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[3];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 3; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 3.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		3, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		3, /* expected_size */
	},
	{
		"gl_ClipDistance[4]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[4];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 4; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 4.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		4, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		4, /* expected_size */
	},
	{
		"gl_ClipDistance[5]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[5];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 5; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 5.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		5, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		5, /* expected_size */
	},
	{
		"gl_ClipDistance[6]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[6];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 6; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 6.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		6, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		6, /* expected_size */
	},
	{
		"gl_ClipDistance[7]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[7];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 7; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 7.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		7, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		7, /* expected_size */
	},
	{
		"gl_ClipDistance[8]-no-subscript", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[8];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 8; ++i)\n"
		"    gl_ClipDistance[i] = (float(i) + 8.0 * vertex_num) * scale;\n"
		"}\n",

		1, /* num_varyings */
		{"gl_ClipDistance"}, /* varyings */

		8, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		8, /* expected_size */
	},
	{
		"gl_ClipDistance-second-half", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_ClipDistance[8];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 4; ++i)\n"
		"    gl_ClipDistance[4 + i] = (float(i) + 4.0 * vertex_num) * scale;\n"
		"}\n",

		4, /* num_varyings */
		{"gl_ClipDistance[4]", "gl_ClipDistance[5]",
		 "gl_ClipDistance[6]", "gl_ClipDistance[7]"},

		4, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		1, /* expected_size */
	},
	{
		"gl_CullDistance", /* name */
		130, /* version */

		"#version 130\n" /* vs */
		"#extension GL_ARB_cull_distance : enable\n" /* vs */
		"in vec4 vertex_pos;\n"
		"in float vertex_num;\n"
		"out float gl_CullDistance[8];\n"
		"void main() {\n"
		"  gl_Position = vertex_pos;\n"
		"  float scale = 1.0/256.0;\n"
		"  for(int i = 0; i < 8; ++i)\n"
		"    gl_CullDistance[i] = (float(i) + 8.0 * vertex_num) * scale;\n"
		"}\n",

		8, /* num_varyings */
		{"gl_CullDistance[0]", "gl_CullDistance[1]", /* varyings */
		 "gl_CullDistance[2]", "gl_CullDistance[3]",
		 "gl_CullDistance[4]", "gl_CullDistance[5]",
		 "gl_CullDistance[6]", "gl_CullDistance[7]"},

		8, /* expected_num_output_components */
		GL_FLOAT, /* expected_type */
		1, /* expected_size */
	},
};

const struct test_desc *test_to_run;

static GLuint xfb_buf;
static GLuint prog;
static GLuint query;
static GLboolean size_and_type_ok = GL_TRUE;

void
print_usage_and_exit(const char *prog_name)
{
	int i;

	printf("Usage: %s <test_name>\n"
	       "  where <test_name> is one of:\n", prog_name);
	for (i = 0; i < ARRAY_SIZE(tests); ++i)
		printf("    %s\n", tests[i].name);
	exit(0);
}

const struct test_desc *
find_matching_test(const char *prog_name, const char *test_name)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(tests); ++i) {
		if (strcmp(tests[i].name, test_name) == 0)
			return &tests[i];
	}
	print_usage_and_exit(prog_name);
	return NULL; /* won't actually be reached */
}

void
piglit_init(int argc, char **argv)
{
	GLuint vs;
	int i;

	/* Parse params. */
	if (argc != 2)
		print_usage_and_exit(argv[0]);
	test_to_run = find_matching_test(argv[0], argv[1]);

	/* Set up test */
	piglit_require_vertex_shader();
	piglit_require_GLSL_version(test_to_run->version);
	if (strcmp("gl_CullDistance", test_to_run->name) == 0) {
		piglit_require_extension("GL_ARB_cull_distance");
	}
	piglit_require_transform_feedback();
	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, test_to_run->vs);
	prog = glCreateProgram();
	glAttachShader(prog, vs);
	glBindAttribLocation(prog, 0, "vertex_pos");
	glBindAttribLocation(prog, 1, "vertex_num");
	glTransformFeedbackVaryings(prog, test_to_run->num_varyings,
				    (const char **) test_to_run->varyings,
				    GL_INTERLEAVED_ATTRIBS_EXT);
	glLinkProgram(prog);
	if (!piglit_link_check_status(prog)) {
		glDeleteProgram(prog);
		piglit_report_result(PIGLIT_FAIL);
	}

	/* Test that GetTransformFeedbackVarying reports the correct
	 * size and type for all of the varyings.
	 */
	for (i = 0; i < test_to_run->num_varyings; ++i) {
		GLsizei size;
		GLenum type;
		glGetTransformFeedbackVarying(prog, i, 0, NULL, &size,
					      &type, NULL);
		if (size != test_to_run->expected_size) {
			printf("For varying %i, expected size %i, got %i\n",
			       i, test_to_run->expected_size, size);
			size_and_type_ok = GL_FALSE;
		}
		if (type != test_to_run->expected_type) {
			printf("For varying %i, expected type %i, got %i\n",
			       i, test_to_run->expected_type, type);
			size_and_type_ok = GL_FALSE;
		}
	}

	glGenBuffers(1, &xfb_buf);
	glGenQueries(1, &query);
	glEnable(GL_VERTEX_PROGRAM_TWO_SIDE);
}

struct vertex_data {
	float vertex_pos[4];
	float vertex_num;
};

enum piglit_result
piglit_display(void)
{
	static const struct vertex_data vertex_input[6] = {
		/*  vertex_pos XYZW         vertex_num */
		{ { -1.0, -1.0, 0.0, 1.0 }, 0.0 },
		{ { -1.0,  1.0, 0.0, 1.0 }, 1.0 },
		{ {  1.0, -1.0, 0.0, 1.0 }, 2.0 },
		{ { -1.0,  1.0, 0.0, 1.0 }, 3.0 },
		{ {  1.0, -1.0, 0.0, 1.0 }, 4.0 },
		{ {  1.0,  1.0, 0.0, 1.0 }, 5.0 }
	};
	static float initial_xfb_data[MAX_EXPECTED_OUTPUT_COMPONENTS * 6];
	GLboolean pass = size_and_type_ok;
	int i;
	float *readback;
	GLuint query_result;

	glUseProgram(prog);

	/* Setup inputs */
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE,
			      sizeof(struct vertex_data),
			      &vertex_input[0].vertex_pos);
	glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE,
			      sizeof(struct vertex_data),
			      &vertex_input[0].vertex_num);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);

	/* Setup transform feedback */
	for (i = 0; i < ARRAY_SIZE(initial_xfb_data); ++i)
		initial_xfb_data[i] = 12345.0;
	glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfb_buf);
	glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(initial_xfb_data),
		     initial_xfb_data, GL_STREAM_READ);
	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buf);
	glBeginTransformFeedback(GL_TRIANGLES);
	glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);

	/* Draw */
	glDrawArrays(GL_TRIANGLES, 0, 6);

	/* Check that there was room in the buffer to write all
	 * transform feedback outputs.
	 */
	glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
	glGetQueryObjectuiv(query, GL_QUERY_RESULT, &query_result);
	if (query_result != 2) {
		printf("Expected 2 primitives written, got %u\n",
		       query_result);
		pass = GL_FALSE;
	}

	/* Check transform feedback output */
	glEndTransformFeedback();
	readback = glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_READ_ONLY);
	for (i = 0; i < ARRAY_SIZE(initial_xfb_data); ++i) {
		float expected =
			i < 6 * test_to_run->expected_num_output_components
			? i/256.0 : 12345.0;
		if (readback[i] != expected) {
			printf("Buffer[%i]=%f, expected=%f\n", i, readback[i],
			       expected);
			pass = GL_FALSE;
		}
	}
	glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);

	piglit_present_results();

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}
