/*
 * Copyright © 2013 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 primitive-restart-draw-mode.c
 *
 * Test proper functioning of primitive restart in all draw modes.  In
 * particular, verify that dangling vertices are properly discarded.
 *
 * The test operates as follows:
 *
 * - Choose a pattern of 8 vertices which will allow easy visual
 *   inspection of the rendered image.  For some primitive types, we
 *   arrange the 8 vertices in an octagon.  For others, we arrange
 *   them in two rows, with vertices alternating between the two rows.
 *
 * - Construct an index buffer consisting of the values 0 through 7,
 *   interrupted at some location by the primitive restart index, and
 *   draw using the resulting index buffer using glDrawElements().
 *   Seven images are drawn in a vertical array, one for each possible
 *   place where the primitive restart index might interrupt the
 *   indices.
 *
 * - To the right of each of the above images, use a pair of calls to
 *   glDrawArrays() to simulate the expected behaviour of primitive
 *   restart.
 *
 * - Compare the left and right halves of the resulting window to make
 *   sure they match.
 *
 * Note: for easier visual inspection of the result, the image under
 * test is drawn in blue, and the vertices are drawn in white using
 * GL_POINTS.
 */

#include "piglit-util-gl.h"


#define NUM_VERTICES 8
#define NUM_ROWS (NUM_VERTICES - 1)
#define NUM_COLS 2
#define PATTERN_SIZE 75

PIGLIT_GL_TEST_CONFIG_BEGIN
	config.supports_gl_compat_version = 10;
	config.window_width = PATTERN_SIZE * NUM_COLS;
	config.window_height = PATTERN_SIZE * NUM_ROWS;
	config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGBA;
PIGLIT_GL_TEST_CONFIG_END


enum vertex_pattern
{
	VERTEX_PATTERN_OCTAGON,
	VERTEX_PATTERN_2_ROWS,
	VERTEX_PATTERN_COUNT,
};


static const struct xy_coords
{
	GLfloat x;
	GLfloat y;
} vertex_patterns[VERTEX_PATTERN_COUNT][NUM_VERTICES] = {
	/* VERTEX_PATTERN_OCTAGON */
	{
		{ 27, 69 },
		{ 48, 69 },
		{ 69, 48 },
		{ 69, 27 },
		{ 48,  6 },
		{ 27,  6 },
		{  6, 27 },
		{  6, 48 },
	},
	/* VERTEX_PATTERN_2_ROWS */
	{
		{ 20, 63 },
		{ 55, 63 },
		{ 20, 46 },
		{ 55, 46 },
		{ 20, 29 },
		{ 55, 29 },
		{ 20, 12 },
		{ 55, 12 },
	},
};


static const struct test_desc
{
	const char *name;
	GLenum prim_type;
	enum vertex_pattern pattern;
} tests[] = {
	{ "points",         GL_POINTS,         VERTEX_PATTERN_OCTAGON },
	{ "lines",          GL_LINES,          VERTEX_PATTERN_OCTAGON },
	{ "line_loop",      GL_LINE_LOOP,      VERTEX_PATTERN_OCTAGON },
	{ "line_strip",     GL_LINE_STRIP,     VERTEX_PATTERN_OCTAGON },
	{ "triangles",      GL_TRIANGLES,      VERTEX_PATTERN_2_ROWS  },
	{ "triangle_strip", GL_TRIANGLE_STRIP, VERTEX_PATTERN_2_ROWS  },
	{ "triangle_fan",   GL_TRIANGLE_FAN,   VERTEX_PATTERN_OCTAGON },
	{ "quads",          GL_QUADS,          VERTEX_PATTERN_OCTAGON },
	{ "quad_strip",     GL_QUAD_STRIP,     VERTEX_PATTERN_2_ROWS  },
	{ "polygon",        GL_POLYGON,        VERTEX_PATTERN_OCTAGON },
};

static const struct test_desc *test = NULL;


static const char vs_text[] =
	"#version 110\n"
	"attribute vec2 vertex;\n"
	"uniform vec2 offset;\n"
	"uniform vec2 window_size;\n"
	"uniform vec4 color, bcolor;\n"
	"void main()\n"
	"{\n"
	"  gl_Position = vec4((vertex + offset) / window_size * 2.0 - 1.0,\n"
	"                     0.0, 1.0);\n"
	"  gl_FrontColor = color;\n"
	"  gl_BackColor = bcolor;\n"
	"}\n";


static GLuint prog;
static GLint vertex_attr;
static GLint window_size_loc;
static GLint offset_loc;
static GLint color_loc;
static GLint bcolor_loc;


static void
print_usage_and_exit(const char *prog_name)
{
	int i;
	printf("Usage: %s <subtest>\n"
	       "  where <subtest> is one of the following:\n", prog_name);
	for (i = 0; i < ARRAY_SIZE(tests); i++)
		printf("    %s\n", tests[i].name);
	piglit_report_result(PIGLIT_FAIL);
}


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

	/* Parse params */
	if (argc != 2)
		print_usage_and_exit(argv[0]);
	for (i = 0; i < ARRAY_SIZE(tests); i++) {
		if (strcmp(argv[1], tests[i].name) == 0) {
			test = &tests[i];
			break;
		}
	}
	if (test == NULL)
		print_usage_and_exit(argv[0]);

	piglit_require_GLSL_version(110);
	if (!piglit_is_extension_supported("GL_NV_primitive_restart") &&
	    piglit_get_gl_version() < 31) {
		printf("GL_NV_primitive_restart or GL 3.1 required\n");
		piglit_report_result(PIGLIT_SKIP);
	}

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, vs_text);
	prog = piglit_link_simple_program(vs, 0);
	if (!prog)
		piglit_report_result(PIGLIT_FAIL);
	glDeleteShader(vs);
	vertex_attr = glGetAttribLocation(prog, "vertex");
	window_size_loc = glGetUniformLocation(prog, "window_size");
	offset_loc = glGetUniformLocation(prog, "offset");
	color_loc = glGetUniformLocation(prog, "color");
	bcolor_loc = glGetUniformLocation(prog, "bcolor");

	glEnable(GL_VERTEX_PROGRAM_TWO_SIDE);
}


static void
draw_pattern(int restart_pos, bool use_primitive_restart)
{
	/* Draw front faces blue and back faces red. */
	glUniform4f(color_loc, 0.25, 0.25, 1.0, 1.0);
	glUniform4f(bcolor_loc, 0.7, 0.2, 0.2, 1.0);

	if (use_primitive_restart) {
		GLubyte index_buffer[NUM_VERTICES + 1];
		int i;

		for (i = 0; i < restart_pos; i++) {
			index_buffer[i] = i;
		}
		index_buffer[restart_pos] = 0xff;
		for (i = restart_pos + 1; i < ARRAY_SIZE(index_buffer); i++) {
			index_buffer[i] = i - 1;
		}
		if (piglit_get_gl_version() >= 31) {
			glEnable(GL_PRIMITIVE_RESTART);
			glPrimitiveRestartIndex(0xff);
		} else {
			glEnableClientState(GL_PRIMITIVE_RESTART_NV);
			glPrimitiveRestartIndexNV(0xff);
		}
		glDrawElements(test->prim_type, ARRAY_SIZE(index_buffer),
			       GL_UNSIGNED_BYTE, index_buffer);
		if (piglit_get_gl_version() >= 31)
			glDisable(GL_PRIMITIVE_RESTART);
		else
			glDisableClientState(GL_PRIMITIVE_RESTART_NV);
	} else {
		glDrawArrays(test->prim_type, 0, restart_pos);
		glDrawArrays(test->prim_type, restart_pos,
			     NUM_VERTICES - restart_pos);
	}

	if (test->prim_type != GL_POINTS) {
		/* Draw vertices in white */
		glUniform4f(color_loc, 1.0, 1.0, 1.0, 1.0);
		glDrawArrays(GL_POINTS, 0, NUM_VERTICES);
	}
}


enum piglit_result
piglit_display(void)
{
	int row, col;
	bool pass = true;

	glClear(GL_COLOR_BUFFER_BIT);
	glUseProgram(prog);
	glUniform2f(window_size_loc, piglit_width/2, piglit_height);
	glVertexAttribPointer(vertex_attr, 2, GL_FLOAT, GL_FALSE,
			      sizeof(vertex_patterns[test->pattern][0]),
			      vertex_patterns[test->pattern]);
	glEnableVertexAttribArray(vertex_attr);

	for (col = 0; col < NUM_COLS; col++) {
		if (col == 0)
			glViewport(0, 0, piglit_width/2, piglit_height);
		else
			glViewport(piglit_width/2, 0, piglit_width/2, piglit_height);

		for (row = 0; row < NUM_ROWS; row++) {
			glUniform2f(offset_loc, 0,
				    (NUM_ROWS - 1 - row) * PATTERN_SIZE);
			draw_pattern(row + 1, col == 0);
		}
	}

	glViewport(0, 0, piglit_width, piglit_height);
	pass = piglit_probe_rect_halves_equal_rgba(0, 0, piglit_width,
						   piglit_height) && pass;
	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	piglit_present_results();
	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}
