/*
 * Copyright 2016 VMware, Inc.
 *
 * 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.
 */

/**
 * Test rendering of GS adjacency primitives, with:
 * - First and last provoking vertex
 * - Front and back-face culling
 * - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
 * See code for command line arguments.
 *
 * Brian Paul
 * May 2016
 */


#include "piglit-util-gl.h"
#include "piglit-matrix.h"


PIGLIT_GL_TEST_CONFIG_BEGIN
	config.window_width = 800;
	config.window_height = 200;
	config.supports_gl_core_version = 32;
	config.window_visual = PIGLIT_GL_VISUAL_RGBA | PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;
PIGLIT_GL_TEST_CONFIG_END


#define VERTEX_SIZE (2 * sizeof(GLfloat))

static const float gray[4] = { 0.5, 0.5, 0.5, 1.0 };
static const float black[4] = { 0.0, 0.0, 0.0, 0.0 };

static const float colors[18][4] = {
	{1.0, 0.2, 0.2, 1.0},
	{0.2, 1.0, 0.2, 1.0},
	{0.2, 0.2, 1.0, 1.0},
	{1.0, 1.0, 1.0, 1.0},
	{0.2, 1.0, 1.0, 1.0},
	{1.0, 0.2, 1.0, 1.0},
	{1.0, 1.0, 0.2, 1.0},
	{0.5, 1.0, 1.0, 1.0},
	{1.0, 0.5, 1.0, 1.0},
	{1.0, 1.0, 0.5, 1.0},
	{0.7, 1.0, 1.0, 1.0},
	{1.0, 0.7, 1.0, 1.0},
	{1.0, 1.0, 0.7, 1.0},
	{1.0, 0.2, 0.2, 1.0},
	{0.2, 1.0, 0.2, 1.0},
	{0.2, 0.2, 1.0, 1.0},
	{1.0, 1.0, 1.0, 1.0},
	{0.5, 0.5, 0.5, 1.0}
};

static const float lines_adj_verts[8][2] = {
	// first line
	{-1, -.75},
	{-0.5, -0.25},
	{ 0.5, -0.25},
	{ 1.0, -.75},
	// second line
	{-1, 0.0},
	{-0.5, 0.5},
	{ 0.5, 0.5},
	{ 1.0, 0.0}
};

static const float line_strip_adj_verts[7][2] = {
	{-1.5, .3},
	{-1, -.3},
	{-0.5, .3},
	{ 0.0, -.3},
	{ 0.5, .3},
	{ 1.0, -.3},
	{ 1.5, .3},
};

static const float triangles_adj_verts[6][2] = {
	{0, -.5},
	{-1.2, 0},
	{-.75, 1},
	{0, 1.5},
	{0.75, 1},
	{1.2, 0},
};

static const float triangle_strip_adj_verts[][2] = {
	{-1.5, -0.5},  // 0
	{-1.9, 0.0},   // 1
	{-1.5, 0.5},   // 2
	{-1, -1},      // 3 *
	{-1, -.5},     // 4
	{-1.5, 1},     // 5 *
	{-1, 0.5},     // 6
	{-.5, -1},     // 7 *
	{-.5, -.5},    // 8
	{-1, 1},       // 9 *
	{-.5, .5},     // 10
	{0, -1},       // 11 *
	{0, -.5},      // 12
	{-.5, 1},      // 13 *
	{0, 0.5},      // 14
	{0.5, -1},     // 15 *
	{0.5, -.5},    // 16
	{1, 0},        // 17
};

#define NUM_VERTS(ARRAY)  (sizeof(ARRAY) / VERTEX_SIZE)

static GLfloat ortho_matrix[16];

static GLuint lines_adj_vao;
static GLuint line_strip_adj_vao;
static GLuint triangles_adj_vao;
static GLuint triangle_strip_adj_vao;

static GLenum polygon_mode = GL_FILL;
static GLenum cull_mode = GL_NONE;
static GLenum provoking_vertex = GL_LAST_VERTEX_CONVENTION;

static GLuint gs_lines_program;
static GLuint gs_line_strip_program;
static GLuint gs_triangles_program;
static GLuint gs_triangle_strip_program;
static GLuint xfb_buf;
static GLuint element_buf;
static GLuint ref_program;
static GLint colorUniform, modelViewProjUniform;

// if false, draw without GS, also draw the 'extra' lines/tris.  For debugging.
static bool draw_with_gs = true;
static bool draw_elements = false;


/**
 * Given a primitive type (adjacency type only), and the first/last provoking
 * vertex mode, and a primitive (line, triangle) index, return the index of
 * the vertex which will specify the primitive's flat-shaded color.
 */
static unsigned
provoking_vertex_index(GLenum prim_mode, GLenum pv_mode, unsigned one_based_prim_index)
{
	unsigned one_based_vert_index;

	switch (prim_mode) {
	case GL_LINES_ADJACENCY:
		if (pv_mode == GL_FIRST_VERTEX_CONVENTION)
			one_based_vert_index = one_based_prim_index * 4 - 2;
		else
			one_based_vert_index = one_based_prim_index * 4 - 1;
		break;
	case GL_LINE_STRIP_ADJACENCY:
		if (pv_mode == GL_FIRST_VERTEX_CONVENTION)
			one_based_vert_index = one_based_prim_index + 1;
		else
			one_based_vert_index = one_based_prim_index + 2;
		break;
	case GL_TRIANGLES_ADJACENCY:
		if (pv_mode == GL_FIRST_VERTEX_CONVENTION)
			one_based_vert_index = one_based_prim_index * 6 - 5;
		else
			one_based_vert_index = one_based_prim_index * 6 - 1;
		break;
	case GL_TRIANGLE_STRIP_ADJACENCY:
		if (pv_mode == GL_FIRST_VERTEX_CONVENTION) {
			one_based_vert_index = one_based_prim_index * 2 - 1;
		} else
			one_based_vert_index = one_based_prim_index * 2 + 3;
		break;
	default:
		assert(!"Unexpected prim_mode");
		return 0;
	}
	assert(one_based_vert_index > 0);
	return one_based_vert_index - 1;
}


/**
 * Given a primitive type and a primitive (line/triangle) index, return
 * the (x,y) screen coordinate for probing.
 */
static void
compute_probe_location(GLenum prim_mode, unsigned prim_index,
		       const float verts[][2],
		       int vp_x, int vp_y, int *x, int *y)
{
	int i0, i1, i2 = -1;
	float coord[4], ndc[4], win[3];

	switch (prim_mode) {
	case GL_LINES_ADJACENCY:
		i0 = prim_index * 4 + 1;
		i1 = prim_index * 4 + 2;
		break;
	case GL_LINE_STRIP_ADJACENCY:
		i0 = prim_index + 1;
		i1 = prim_index + 2;
		break;
	case GL_TRIANGLES_ADJACENCY:
		i0 = prim_index * 6 + 0;
		i1 = prim_index * 6 + 2;
		if (polygon_mode != GL_LINE)
			i2 = prim_index * 6 + 4;
		break;
	case GL_TRIANGLE_STRIP_ADJACENCY:
		if (prim_index & 1) {
			i0 = prim_index * 2;
			i1 = prim_index * 2 + 2;
		} else {
			i0 = prim_index * 2 + 2;
			i1 = prim_index * 2;
		}
		if (polygon_mode != GL_LINE)
			i2 = prim_index * 2 + 4;
		break;
	default:
		assert(!"Unexpected prim_mode");
		*x = *y = 0;
		return;
	}

	/* average of 2 or 3 points */
	if (i2 == -1) {
		coord[0] = (verts[i0][0] + verts[i1][0]) / 2.0;
		coord[1] = (verts[i0][1] + verts[i1][1]) / 2.0;
	} else {
		coord[0] = (verts[i0][0] + verts[i1][0] + verts[i2][0]) / 3.0;
		coord[1] = (verts[i0][1] + verts[i1][1] + verts[i2][1]) / 3.0;
	}
	coord[2] = 0.0;
	coord[3] = 1.0;

	piglit_matrix_mul_vector(ndc, ortho_matrix, coord);
	piglit_ndc_to_window(win, ndc, vp_x, vp_y,
			     piglit_width/4, piglit_height);

	*x = (int) win[0];
	*y = (int) win[1];
}


/**
 * Do the colors match, within an epsilon?
 */
static bool
colors_match(const float c1[4], const float c2[4])
{
	const float epsilon = 1.0 / 256.0;

	if (fabs(c1[0] - c2[0]) > epsilon ||
	    fabs(c1[1] - c2[1]) > epsilon ||
	    fabs(c1[2] - c2[2]) > epsilon ||
	    fabs(c1[3] - c2[3]) > epsilon)
		return false;
	else
		return true;
}


/**
 * Given a primitive type and a number of vertices, return the number of
 * primitives (lines/tris) that'll be drawn.
 */
static unsigned
num_gs_prims(GLenum prim_mode, unsigned num_verts)
{
	switch (prim_mode) {
	case GL_LINES_ADJACENCY:
		assert(num_verts % 4 == 0);
		return num_verts / 4;
	case GL_LINE_STRIP_ADJACENCY:
		assert(num_verts >= 4);
		return num_verts - 3;
	case GL_TRIANGLES_ADJACENCY:
		assert(num_verts % 6 == 0);
		return num_verts / 6;
	case GL_TRIANGLE_STRIP_ADJACENCY:
		assert(num_verts >= 6);
		return (num_verts - 4) / 2;
	default:
		assert(!"Unexpected prim_mode");
		return 0;
	}
}


/**
 * Check if a primitive strip was rendered correctly by doing color probing.
 * vp_pos is the viewport position (0..3).
 */
static bool
probe_prims(GLenum prim_mode, const float verts[][2], unsigned num_verts,
	    unsigned vp_pos)
{
	const int vp_w = piglit_width / 4;
	const unsigned num_prims = num_gs_prims(prim_mode, num_verts);
	unsigned prim;

	for (prim = 0; prim < num_prims; prim++) {
		bool pass = false;
		float expected_color[4];
		float bad_color[4] = { -1 };
		bool bad_color_found = false;
		int x, y, i;

		compute_probe_location(prim_mode, prim, verts,
				       vp_pos * vp_w, 0, &x, &y);

		if (cull_mode == GL_FRONT &&
		    (prim_mode == GL_TRIANGLES_ADJACENCY ||
		     prim_mode == GL_TRIANGLE_STRIP_ADJACENCY)) {
			// All triangles should be front facing.
			// With front culling, all should be discarded.
			// Region should be black.
			if (piglit_probe_rect_rgba(x-1, y-1, 3, 3, black)) {
				pass = true;
			}
		} else {
			GLfloat buf[9][4];
			unsigned pvi = provoking_vertex_index(prim_mode,
						     provoking_vertex, 1 + prim);
			memcpy(&expected_color, colors[pvi], sizeof(expected_color));
			if (prim_mode == GL_TRIANGLES_ADJACENCY ||
			    prim_mode == GL_TRIANGLE_STRIP_ADJACENCY) {
				expected_color[2] = pvi * (1.0 / 255);
				expected_color[3] = provoking_vertex == GL_FIRST_VERTEX_CONVENTION ? 0.0 : 1.0;
			}

			// Read a 3x3 region for line probing
			glReadPixels(x-1, y-1, 3, 3, GL_RGBA, GL_FLOAT, buf);

			// look for non-black pixel
			for (i = 0; i < 9; i++) {
				if (buf[i][0] != 0 || buf[i][1] != 0 ||
				    buf[i][2] != 0 || buf[i][3] != 0) {
					// check for expected color
					if (colors_match(expected_color, buf[i]))
						pass = true;
					else {
						bad_color_found = true;
						bad_color[0] = buf[i][0];
						bad_color[1] = buf[i][1];
						bad_color[2] = buf[i][2];
						bad_color[3] = buf[i][3];
					}
				}
			}
		}

		if (!pass) {
			printf("Failure for %s, "
			       "prim %u wrong color at (%d,%d)\n",
			       piglit_get_prim_name(prim_mode), prim, x, y);
			if (bad_color_found) {
				printf("Expected %g, %g, %g, %g\n",
				       expected_color[0],
				       expected_color[1],
				       expected_color[2],
				       expected_color[3]);
				printf("Found %g, %g, %g, %g\n",
				       bad_color[0],
				       bad_color[1],
				       bad_color[2],
				       bad_color[3]);
			}

			return false;
		}
	}

	return true;
}

static bool
probe_xfb(GLenum prim_mode, unsigned num_verts)
{
	bool pass = true;
	const unsigned num_prims = num_gs_prims(prim_mode, num_verts);
	const float *xfb_data = glMapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, GL_READ_ONLY);

	for (unsigned prim = 0; prim < num_prims; prim++) {
		const float *found_color;
		float expected_color[4];
		unsigned pvi = provoking_vertex_index(prim_mode,
						provoking_vertex, 1 + prim);

		memcpy(&expected_color, colors[pvi], sizeof(expected_color));
		expected_color[2] = pvi * (1.0 / 255);
		expected_color[3] = provoking_vertex == GL_FIRST_VERTEX_CONVENTION ? 0.0 : 1.0;

		found_color = xfb_data + 4 * (3 * prim + (provoking_vertex == GL_FIRST_VERTEX_CONVENTION ? 0 : 2));

		if (!colors_match(expected_color, found_color)) {
			printf("Transform Feedback Failure for %s, prim %u wrong color\n",
			       piglit_get_prim_name(prim_mode), prim);
			printf("Expected %g, %g, %g, %g\n",
			       expected_color[0],
			       expected_color[1],
			       expected_color[2],
			       expected_color[3]);
			printf("Found %g, %g, %g, %g\n",
			       found_color[0],
			       found_color[1],
			       found_color[2],
			       found_color[3]);

			pass = false;
		}
	}

	glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);

	return pass;
}


static GLuint
make_gs_program(GLenum input_prim)
{
	static const char *vs_text =
		"#version 150 \n"
		"in vec4 vertex; \n"
		"in vec4 color; \n"
		"uniform mat4 modelViewProj; \n"
		"out vec4 pos;\n"
		"out vec4 vs_gs_color; \n"
		"out int vs_gs_vertex_id; \n"
		"void main() \n"
		"{ \n"
		"   gl_Position = vertex * modelViewProj; \n"
		"   pos = vertex * modelViewProj; \n"
		"   vs_gs_color = color; \n"
		"   vs_gs_vertex_id = gl_VertexID; \n"
		"} \n";
	static const char *gs_text_lines =
		"#version 150 \n"
		"layout(lines_adjacency) in;\n"
		"layout(line_strip, max_vertices = 2) out;\n"
		"in vec4 pos[]; \n"
		"in vec4 vs_gs_color[4]; \n"
		"flat out vec4 gs_fs_color; \n"
		"void main() \n"
		"{ \n"
		"   gs_fs_color = vs_gs_color[1]; \n"
		"   gl_Position = pos[1]; \n"
		"   EmitVertex(); \n"
		"   gs_fs_color = vs_gs_color[2]; \n"
		"   gl_Position = pos[2]; \n"
		"   EmitVertex(); \n"
		"   EndPrimitive(); \n"
		"} \n";
	static const char *gs_text_triangles =
		"#version 150 \n"
		"layout(triangles_adjacency) in;\n"
		"layout(triangle_strip, max_vertices = 3) out;\n"
		"in vec4 pos[]; \n"
		"in vec4 vs_gs_color[6]; \n"
		"in int vs_gs_vertex_id[6]; \n"
		"flat out vec4 gs_fs_color; \n"
		"void main() \n"
		"{ \n"
		"   gs_fs_color = vs_gs_color[0]; \n"
		"   gs_fs_color.b = vs_gs_vertex_id[0] * (1. / 255.); \n"
		"   gs_fs_color.a = 0.0; \n"
		"   gl_Position = pos[0]; \n"
		"   EmitVertex(); \n"
		"   gs_fs_color = vs_gs_color[2]; \n"
		"   gs_fs_color.b = vs_gs_vertex_id[2] * (1. / 255.); \n"
		"   gs_fs_color.a = 0.5; \n"
		"   gl_Position = pos[2]; \n"
		"   EmitVertex(); \n"
		"   gs_fs_color = vs_gs_color[4]; \n"
		"   gs_fs_color.b = vs_gs_vertex_id[4] * (1. / 255.); \n"
		"   gs_fs_color.a = 1.0; \n"
		"   gl_Position = pos[4]; \n"
		"   EmitVertex(); \n"
		"   //EndPrimitive(); \n"
		"} \n";
	static const char *fs_text =
		"#version 150 \n"
		"flat in vec4 gs_fs_color; \n"
		"void main() \n"
		"{ \n"
		"   gl_FragColor = gs_fs_color; \n"
		"} \n";
	static const char *gs_xfb_varyings[] = { "gs_fs_color" };
	const char *gs_text;
	GLuint program;

	switch (input_prim) {
	case GL_LINES_ADJACENCY:
	case GL_LINE_STRIP_ADJACENCY:
		gs_text = gs_text_lines;
		break;
	case GL_TRIANGLES_ADJACENCY:
	case GL_TRIANGLE_STRIP_ADJACENCY:
		gs_text = gs_text_triangles;
		break;
	default:
		assert(!"Unexpected input_prim");
		return 0;
	}

	program = piglit_build_simple_program_unlinked_multiple_shaders(
		GL_VERTEX_SHADER, vs_text,
		GL_GEOMETRY_SHADER, gs_text,
		GL_FRAGMENT_SHADER, fs_text,
		0);

	assert(program);

	glBindAttribLocation(program, 0, "vertex");
	glBindAttribLocation(program, 1, "color");

	glTransformFeedbackVaryings(program, 1, &gs_xfb_varyings[0],
				    GL_INTERLEAVED_ATTRIBS);

	glLinkProgram(program);

	return program;
}


static GLuint
make_ref_program(void)
{
	static const char *vs_text =
		"#version 150 \n"
		"in vec4 vertex; \n"
		"uniform vec4 color; \n"
		"uniform mat4 modelViewProj; \n"
		"out vec4 vs_fs_color; \n"
		"void main() \n"
		"{ \n"
		"   gl_Position = vertex * modelViewProj; \n"
		"   vs_fs_color = color; \n"
		"} \n";

	static const char *fs_text =
		"#version 150 \n"
		"in vec4 vs_fs_color; \n"
		"void main() \n"
		"{ \n"
		"   gl_FragColor = vs_fs_color; \n"
		"} \n";

	GLuint program = piglit_build_simple_program_unlinked_multiple_shaders(
		GL_VERTEX_SHADER, vs_text,
		GL_FRAGMENT_SHADER, fs_text,
		0);

	glBindAttribLocation(program, 0, "vertex");
	glBindAttribLocation(program, 1, "color");

	glLinkProgram(program);

	return program;
}


static void
draw_elements3(GLenum mode, unsigned v0, unsigned v1, unsigned v2)
{
	GLushort elements[3];
	elements[0] = v0;
	elements[1] = v1;
	elements[2] = v2;
	glDrawElements(mode, 3, GL_UNSIGNED_SHORT, elements);
}


static void
set_color(const GLfloat c[4])
{
	glUniform4fv(colorUniform, 1, c);
}


static void
draw_lines_adj(GLuint vao, unsigned n)
{
	assert(n % 4 == 0);

	glBindVertexArray(vao);
	{
		unsigned i;
		for (i = 0; i < n; i += 4) {
			unsigned pvi =
				provoking_vertex_index(GL_LINES_ADJACENCY,
						       provoking_vertex, 1 + i/4);
			set_color(gray);

			// draw preceding "wing" line
			glDrawArrays(GL_LINES, i, 2);
			// draw trailing "wing" line
			glDrawArrays(GL_LINES, i+2, 2);

			set_color(colors[pvi]);
			// draw "real" line
			glDrawArrays(GL_LINES, i+1, 2);
		}
	}
}


static void
draw_line_strip_adj(GLuint vao, unsigned n)
{
	unsigned i;

	assert(n >= 4);

	glBindVertexArray(vao);

	set_color(gray);
	glDrawArrays(GL_LINES, 0, 2);
	glDrawArrays(GL_LINES, n-2, 2);

	for (i = 1; i < n-2; i++) {
		unsigned pvi =
			provoking_vertex_index(GL_LINE_STRIP_ADJACENCY,
					       provoking_vertex, 1 + i - 1);
		set_color(colors[pvi]);
		glDrawArrays(GL_LINES, i, 2);
	}
}


static void
draw_triangles_adj(GLuint vao, unsigned n)
{
	unsigned i;

	assert(n % 6 == 0);

	glBindVertexArray(vao);

	for (i = 0; i < n; i += 6) {
		unsigned pvi =
			provoking_vertex_index(GL_TRIANGLES_ADJACENCY,
					       provoking_vertex, 1 + i / 6);

		// draw gray outlines of "wing" triangles
		set_color(gray);
		draw_elements3(GL_LINE_LOOP, i, i+1, i+2);
		draw_elements3(GL_LINE_LOOP, i+2, i+3, i+4);
		draw_elements3(GL_LINE_LOOP, i, i+4, i+5);

		// draw "real" triangle
		set_color(colors[pvi]);
		draw_elements3(GL_TRIANGLES, i, i+2, i+4);
	}
}


static void
draw_triangle_strip_adj(GLuint vao, unsigned n)
{
	unsigned i;

	assert(n >= 6);

	glBindVertexArray(vao);

	// draw first "wing" triangle
	set_color(gray);
	glDrawArrays(GL_LINE_LOOP, 0, 3);

	for (i = 0; i < n-4; i += 2) {
		unsigned pvi =
			provoking_vertex_index(GL_TRIANGLE_STRIP_ADJACENCY,
					       provoking_vertex, 1 + i / 2);

		if (i % 4 == 2) {
			// even tri
			set_color(gray);
			draw_elements3(GL_LINE_LOOP, i, i+3, i+4);
			set_color(colors[pvi]);
			draw_elements3(GL_TRIANGLES, i, i+4, i+2);
		}
		else {
			// odd tri
			set_color(gray);
			draw_elements3(GL_LINE_LOOP, i, i+4, i+3);
			set_color(colors[pvi]);
			draw_elements3(GL_TRIANGLES, i, i+2, i+4);
		}
	}

	// draw last "wing" triangle
	set_color(gray);
	draw_elements3(GL_LINE_LOOP, i, i+2, i+3);
}


static void
use_program(GLuint program)
{
	glUseProgram(program);
	modelViewProjUniform = glGetUniformLocation(program, "modelViewProj");
	colorUniform = glGetUniformLocation(program, "color");

	piglit_ortho_matrix(ortho_matrix, -2, 2, -2, 2, -1, 1);
	glUniformMatrix4fv(modelViewProjUniform, 1, GL_FALSE, ortho_matrix);
}


static void
set_viewport(unsigned pos)
{
	int vp_w = piglit_width / 4;
	assert(pos < 4);
	glViewport(pos * vp_w, 0, vp_w, piglit_height);
}


static void
draw_gs_triangles(GLenum prim_mode, GLuint vao, unsigned num_verts,
		  unsigned vp_pos)
{
	size_t buffer_size;
	void *zeros;

	use_program(gs_triangles_program);
	set_viewport(vp_pos);
	glBindVertexArray(vao);

	buffer_size =
		12 * sizeof(float) *
		num_gs_prims(prim_mode, num_verts);
	zeros = calloc(1, buffer_size);

	glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buf);
	glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, buffer_size,
			zeros, GL_STREAM_READ);

	free(zeros);

	glBeginTransformFeedback(GL_TRIANGLES);

	if (draw_elements) {
		GLushort* elements;

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buf);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER,
			sizeof(GLushort) * num_verts, NULL,
			GL_STATIC_DRAW);
		elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
		for (unsigned i = 0; i < num_verts; i++) {
			elements[i] = i;
		}
		glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);

		glDrawElements(prim_mode, num_verts, GL_UNSIGNED_SHORT, NULL);
	} else {
		glDrawArrays(prim_mode, 0, num_verts);
	}

	glEndTransformFeedback();
}

enum piglit_result
piglit_display(void)
{
	bool pass = true;

	glClear(GL_COLOR_BUFFER_BIT);

	if (draw_with_gs) {
		use_program(gs_lines_program);
		set_viewport(0);
		glBindVertexArray(lines_adj_vao);
		glDrawArrays(GL_LINES_ADJACENCY, 0,
			     NUM_VERTS(lines_adj_verts));

		use_program(gs_line_strip_program);
		set_viewport(1);
		glBindVertexArray(line_strip_adj_vao);
		glDrawArrays(GL_LINE_STRIP_ADJACENCY, 0,
			     NUM_VERTS(line_strip_adj_verts));

		draw_gs_triangles(GL_TRIANGLES_ADJACENCY, triangles_adj_vao,
				  NUM_VERTS(triangles_adj_verts), 2);

		pass = probe_xfb(GL_TRIANGLES_ADJACENCY,
				 NUM_VERTS(triangles_adj_verts)) && pass;

		draw_gs_triangles(GL_TRIANGLE_STRIP_ADJACENCY, triangle_strip_adj_vao,
				  NUM_VERTS(triangle_strip_adj_verts), 3);

		pass = probe_xfb(GL_TRIANGLE_STRIP_ADJACENCY,
				 NUM_VERTS(triangle_strip_adj_verts)) && pass;
	}
	else {
		/* This path is basically for debugging and visualizing the
		 * "extra" lines and tris in adjacency primitives.
		 */
		use_program(ref_program);

		set_viewport(0);
		draw_lines_adj(lines_adj_vao, 8);

		set_viewport(1);
		draw_line_strip_adj(line_strip_adj_vao, 7);

		set_viewport(2);
		draw_triangles_adj(triangles_adj_vao, 6);

		set_viewport(3);
		draw_triangle_strip_adj(triangle_strip_adj_vao, 17);
	}

	/* check the rendering */
	pass = probe_prims(GL_LINES_ADJACENCY,
			   lines_adj_verts,
			   NUM_VERTS(lines_adj_verts), 0) && pass;

	pass = probe_prims(GL_LINE_STRIP_ADJACENCY,
			   line_strip_adj_verts,
			   NUM_VERTS(line_strip_adj_verts), 1) && pass;

	pass = probe_prims(GL_TRIANGLES_ADJACENCY,
			   triangles_adj_verts,
			   NUM_VERTS(triangles_adj_verts), 2) && pass;

	pass = probe_prims(GL_TRIANGLE_STRIP_ADJACENCY,
			   triangle_strip_adj_verts,
			   NUM_VERTS(triangle_strip_adj_verts), 3) && pass;

	piglit_present_results();

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}


static GLuint
create_vao(const GLfloat (*verts)[2], GLuint numVerts)
{
	GLuint vao, vbo;

	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	// positions
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, numVerts * VERTEX_SIZE,
		     verts, GL_STATIC_DRAW);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, VERTEX_SIZE, NULL);
	glEnableVertexAttribArray(0);

	// colors
	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), NULL);
	glEnableVertexAttribArray(1);

	return vao;
}


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

	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "line") == 0)
			polygon_mode = GL_LINE;
		else if (strcmp(argv[i], "cull-back") == 0)
			cull_mode = GL_BACK;
		else if (strcmp(argv[i], "cull-front") == 0)
			cull_mode = GL_FRONT;
		else if (strcmp(argv[i], "ref") == 0)
			draw_with_gs = GL_FALSE;
		else if (strcmp(argv[i], "pv-last") == 0)
			provoking_vertex = GL_LAST_VERTEX_CONVENTION;
		else if (strcmp(argv[i], "pv-first") == 0)
			provoking_vertex = GL_FIRST_VERTEX_CONVENTION;
		else if (strcmp(argv[i], "elements") == 0)
			draw_elements = true;
		else
			printf("Unexpected %s argument\n", argv[i]);
	}

	glPolygonMode(GL_FRONT_AND_BACK, polygon_mode);
	if (cull_mode != GL_NONE) {
		glCullFace(cull_mode);
		glEnable(GL_CULL_FACE);
		glFrontFace(GL_CW);
	}
	glProvokingVertex(provoking_vertex);

	glGenBuffers(1, &xfb_buf);
	glGenBuffers(1, &element_buf);

	lines_adj_vao = create_vao(lines_adj_verts,
				   NUM_VERTS(lines_adj_verts));

	line_strip_adj_vao = create_vao(line_strip_adj_verts,
					NUM_VERTS(line_strip_adj_verts));

	triangles_adj_vao = create_vao(triangles_adj_verts,
				       NUM_VERTS(triangles_adj_verts));

	triangle_strip_adj_vao = create_vao(triangle_strip_adj_verts,
					    NUM_VERTS(triangle_strip_adj_verts));

	gs_lines_program = make_gs_program(GL_LINES_ADJACENCY);
	gs_line_strip_program = make_gs_program(GL_LINE_STRIP_ADJACENCY);
	gs_triangles_program = make_gs_program(GL_TRIANGLES_ADJACENCY);
	gs_triangle_strip_program = make_gs_program(GL_TRIANGLE_STRIP_ADJACENCY);
	ref_program = make_ref_program();
}
