/*
 * Copyright © 2014 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 transform-feedback-builtins.c
 *
 * Intel's i965 driver has a special internal handling for 'gl_Layer' and
 * 'gl_ViewportIndex' builtin variables. This test verifies that transform
 * feedback works fine for these builtin variables.
 *
 * Test creates a geometry shader which outputs the following builtin variables:
 * - gl_Layer
 * - gl_ViewportIndex
 *
 * Then it verifies that data captured by transform feedback is consistent with
 * the assignments in geometry shader.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN
	config.supports_gl_compat_version = 32;
	config.supports_gl_core_version = 32;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;
PIGLIT_GL_TEST_CONFIG_END

#define NUM_OUTPUT_INTS 3
static const char *vstext =
	"#version 150\n"
	"void main()\n"
	"{\n"
        "  gl_Position = vec4(0.0);\n"
	"}\n";

static const char *gs_template =
	"#version 150\n"
	"%s"
	"layout(triangles) in;\n"
	"layout(triangle_strip, max_vertices = 3) out;\n"
	"void main()\n"
	"{\n"
	"  for (int i = 0; i < 3; i++) {\n"
	"    gl_Layer = 2 * i + 1;\n"
	"    gl_PrimitiveID = gl_PrimitiveIDIn + i;\n"
	"%s"
	"    EmitVertex();\n"
	"  }\n"
	"  EndPrimitive();\n"
	"}\n";

/**
 * When below set of varyings are captured from the geometry shader
 * above, the output should be a sequence of integers defined in this
 * array.
 */
static const int expected[3][3] = {{1, 3, 5}, /* gl_Layer */
				   {0, 1, 2}, /* gl_PrimitiveID */
				   {1, 2, 3}}; /* gl_ViewportIndex */

static const char *varyings[] = {"gl_Layer", "gl_PrimitiveID", "gl_ViewportIndex"};

void
piglit_init(int argc, char **argv)
{
	int i, j, num_xfb_buffers;
	GLuint prog, vao, xfb_buf[3];
	const GLint *readback;
	char *gstext;
	bool pass = true;
	bool test_gl_ViewportIndex =
		piglit_is_extension_supported("GL_ARB_viewport_array");

	num_xfb_buffers = ARRAY_SIZE(xfb_buf);

	if (test_gl_ViewportIndex) {
		(void)!asprintf(&gstext, gs_template,
			 "#extension GL_ARB_viewport_array : require\n",
			 "    gl_ViewportIndex = i + 1;\n");
	} else {
		printf("Skip testing 'gl_ViewportIndex'\n");
		(void)!asprintf(&gstext, gs_template, "", "");
		num_xfb_buffers -= 1;
	}

	prog = piglit_build_simple_program_unlinked_multiple_shaders(
		GL_VERTEX_SHADER, vstext,
		GL_GEOMETRY_SHADER, gstext,
		0, NULL);

	glTransformFeedbackVaryings(prog, num_xfb_buffers, varyings,
				    GL_SEPARATE_ATTRIBS);

	glLinkProgram(prog);
	if (!piglit_link_check_status(prog)) {
		glDeleteProgram(prog);
		piglit_report_result(PIGLIT_FAIL);
	}
	glUseProgram(prog);

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

	/* Setup transform feedback buffers */
	glGenBuffers(num_xfb_buffers, xfb_buf);
	for (i = 0; i < num_xfb_buffers; i++) {
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, i, xfb_buf[i]);
		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER,
			     NUM_OUTPUT_INTS * sizeof(GLint), NULL,
			     GL_STATIC_READ);
	}

	glEnable(GL_RASTERIZER_DISCARD);

	/* Do drawing */
	glBeginTransformFeedback(GL_TRIANGLES);
	glDrawArrays(GL_TRIANGLES, 0, 3);
	glEndTransformFeedback();

	/* Check if the correct data was written into the transform feedback
	 * buffers.
	 */
	for (i = 0; i < num_xfb_buffers; i++) {
		glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfb_buf[i], 0,
				  NUM_OUTPUT_INTS * sizeof(GLint));
		readback = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
					    NUM_OUTPUT_INTS * sizeof(GLint),
					    GL_MAP_READ_BIT);
		for (j = 0; j < NUM_OUTPUT_INTS; j++) {
			if (readback[j] != expected[i][j]) {
				printf("Incorrect data for '%s' output %d."
				       "  Expected %d, got %d.\n", varyings[i],
				       j, expected[i][j], readback[j]);
				pass = false;
			}
		}
		glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
	}

	glDeleteBuffers(num_xfb_buffers, xfb_buf);

	/* Check for the errors */
	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	piglit_report_result(pass ? PIGLIT_PASS : PIGLIT_FAIL);
}

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