/*
 * Copyright © 2013 The Piglit project
 *
 * 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 vertices-out.c
 *
 * Test required errors for wrong GL_GEOMETRY_VERTICES_OUT parameters with
 * variable number of output components.
 *
 * The value of GEOMETRY_VERTICES_OUT is limited by the implementation
 * dependent constants MAX_GEOMETRY_OUTPUT_VERTICES and
 * MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS.
 *
 * From the ARB_geometry_shader4 spec (section Errors):
 * "The error INVALID_VALUE is generated by ProgramParameteriARB if <pname> is
 * GEOMETRY_VERTICES_OUT_ARB and <value> is negative.
 *
 * The error INVALID_VALUE is generated by ProgramParameteriARB if <pname> is
 * GEOMETRY_VERTICES_OUT_ARB and <value> exceeds
 * MAX_GEOMETRY_OUTPUT_VERTICES_ARB.
 *
 * The error INVALID_VALUE is generated by ProgramParameteriARB if <pname> is
 * set to GEOMETRY_VERTICES_OUT_ARB and the product of <value> and the sum of
 * all components of all active varying variables exceeds
 * MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB."
 *
 * And from section Geometry Shader outputs:
 * "[...]the product of the total number of vertices and the sum of all
 * components of all active varying variables may not exceed the value of
 * MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB.  LinkProgram will fail if it
 * determines that the total component limit would be violated."
 */

#include "common.h"


static const char gs_text_var[] =
	"uniform int vertex_count;\n"
	"varying out float var[n];\n"
	"void main()\n"
	"{\n"
	"	for (int i = 0; i < vertex_count; i++) {\n"
	"		gl_Position = vec4(0.0);\n"
	"		for (int j = 0; j < n; j++)\n"
	"			var[j] = 1.0 / exp2(float(j + 1));\n"
	"		EmitVertex();\n"
	"	}\n"
	"}\n";

static const char fs_text_var[] =
	"varying float var[n];\n"
	"void main()\n"
	"{\n"
	"	gl_FragColor = vec4(0.0);\n"
	"	for (int j = 0; j < n; j++)\n"
	"		gl_FragColor += vec4(var[j]);\n"
	"}\n";

static bool transform_feedback = false;
static int components = -1;


PIGLIT_GL_TEST_CONFIG_BEGIN
	config.supports_gl_compat_version = 20;
	config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGBA;
PIGLIT_GL_TEST_CONFIG_END


static bool
test_geometry_vertices_out(const GLuint prog)
{
	bool pass = true;
	int max_geometry_output_vertices;
	int max_geometry_total_output_components;
	int max_vertices_out;

	glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES,
		      &max_geometry_output_vertices);
	glGetIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS,
		      &max_geometry_total_output_components);

	printf("Testing negative (-1) vertices out.\n");
	glProgramParameteri(prog, GL_GEOMETRY_VERTICES_OUT_ARB, -1);
	pass = piglit_check_gl_error(GL_INVALID_VALUE) && pass;

	/* Setting GEOMETRY_VERTICES_OUT to zero should generate no error but
	 * linking with a geometry shader should fail.
	 */
	printf("Testing zero (0) vertices out.\n");
	glProgramParameteri(prog, GL_GEOMETRY_VERTICES_OUT_ARB, 0);
	glLinkProgram(prog);
	pass = piglit_check_gl_error(GL_NO_ERROR) &&
		!piglit_link_check_status_quiet(prog) && pass;

	printf("Testing too many (%d) vertices out.\n",
	       max_geometry_output_vertices + 1);
	glProgramParameteri(prog, GL_GEOMETRY_VERTICES_OUT_ARB,
			    max_geometry_output_vertices + 1);
	pass = piglit_check_gl_error(GL_INVALID_VALUE) && pass;

	/* If MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS results in an even smaller
	 * limit for GEOMETRY_VERTICES_OUT than GL_MAX_GEOMETRY_OUTPUT_VERTICES
	 * test that, too.
	 *
	 * If (GEOMETRY_VERTICES_OUT * (sum of components of all out varyings))
	 * > MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, the spec requires
	 * ProgramParameter to throw an INVALID_VALUE and subsequent linking to
	 * fail.
	 * But the number of output components can only be inferred from the
	 * geometry shader source (and a geometry shader might not even be
	 * attached to the program object when ProgramParameter is called).
	 * So, ignore any errors generated from ProgramParameter and only check
	 * that linking failed.
	 */
	if ((components != 0) &&
	    (max_geometry_total_output_components / components <
	     max_geometry_output_vertices)) {
		printf("Testing too many total output components (%d vertices "
		       "=> %d total components).\n",
		       max_geometry_total_output_components / components + 1,
		       (max_geometry_total_output_components / components + 1) *
		       components);
		glProgramParameteri(prog, GL_GEOMETRY_VERTICES_OUT_ARB,
				    max_geometry_total_output_components /
				    components + 1);
		piglit_reset_gl_error();
		glLinkProgram(prog);
		pass = !piglit_link_check_status_quiet(prog) && pass;
	}

	max_vertices_out = (components != 0) ?
			   MIN2(max_geometry_output_vertices,
				max_geometry_total_output_components / components) :
			   max_geometry_output_vertices;
	printf("Testing maximal (%d) vertices out.\n", max_vertices_out);
	glProgramParameteri(prog, GL_GEOMETRY_VERTICES_OUT_ARB,
			    max_vertices_out);
	glLinkProgram(prog);
	pass = piglit_check_gl_error(GL_NO_ERROR) &&
		piglit_link_check_status(prog) && pass;

	return pass;
}


/* Parse command line arguments.
 *
 * Recognized command line arguments are:
 *     * The optional argument "tf": Use transform feedback.
 *     * An integer indicating the number of per vertex varying components
 *       written by the geometry shader (not counting gl_Position) OR the
 *       argument "max" which indicates to use a number of components of
 *       MAX_GEOMETRY_VARYING_COMPONENTS_ARB or, if transform feedback is used,
 *       the minimum of MAX_GEOMETRY_VARYING_COMPONENTS_ARB and
 *       MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS.
 */
static void
parse_cmd_line(int argc, char **argv)
{
	int i, max_components;

	glGetIntegerv(GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB, &max_components);

	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "tf") == 0) {
			transform_feedback = true;
		} else if (strcmp(argv[i], "max") == 0) {
			components = -2;
		} else if(argv[i][0] != '-') {
			char *e;
			long int l = strtol(argv[i], &e, 10);
			if (*e == '\0')
				components = l;
		}
	}

	if (transform_feedback) {
		int max_tf_components;

		piglit_require_extension("GL_EXT_transform_feedback");

		glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,
			      &max_tf_components);
		max_components = MIN2(max_components, max_tf_components);
	}

	if (components == -2)
		components = max_components;

	if ((components < 0) || components > max_components) {
		fprintf(stderr, "Please specify number of components "
			"from 0 to %d (inclusive) on the command line\n",
			max_components);
		piglit_report_result(PIGLIT_FAIL);
	}
}

void
piglit_init(int argc, char **argv)
{
	bool pass = true;
	GLuint prog;
	const char *gs_string, *fs_string;

	piglit_require_extension("GL_ARB_geometry_shader4");
	/* NV_geometry_shader4 relaxes some restrictions on valid program
	 * parameters.
	 */
	piglit_require_not_extension("GL_NV_geometry_shader4");

	parse_cmd_line(argc, argv);

	/* Prepare shader source strings. */
	if (components == 0) {
		gs_string = gs_text;
		fs_string = fs_text;
	} else {
		(void)!asprintf((char **)&gs_string,
			 "#extension GL_ARB_geometry_shader4: enable\n"
			 "const int n = %d;\n%s", components, gs_text_var);
		if (transform_feedback)
			fs_string = NULL;
		else
			(void)!asprintf((char **)&fs_string,
				 "const int n = %d;\n%s",
				 components, fs_text_var);
	}

	printf("Running Test with %d component(s) per output vertex and "
	       "transform feedback %s.\n",
	       components, transform_feedback ? "enabled" : "disabled");

	/* Create shader and run test. */
	prog = create_shader(vs_text, gs_string, fs_string);
	pass = test_geometry_vertices_out(prog) && pass;
	glDeleteProgram(prog);

	piglit_report_result(pass ? PIGLIT_PASS : PIGLIT_FAIL);
}

enum piglit_result
piglit_display(void)
{
	/* Should never be reached */
	return PIGLIT_FAIL;
}
