/*
 * Copyright (c) 2016 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.
 */

#include "piglit-util-gl.h"

/**
 * @file basic.c
 *
 * This test verifies the basic functionality of
 * ARB_transform_feedback_overflow_query: that it detects overflow for specific
 * streams, and on any stream too if requested. It does so by causing overflow
 * first on stream 0, and then on stream 1.
 */

#define BUFFER_OFFSET(i) ((void *)((char *)NULL + i))

const struct piglit_subtest overflow_query_subtests[];

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 32;
	config.supports_gl_core_version = 32;
	config.subtests = overflow_query_subtests;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const char vs_pass_thru_text[] =
	"#version 150\n"
	"void main() {\n"
	"  gl_Position = vec4(gl_VertexID);\n"
	"}\n";

static const char gs_overflow_single[] =
	"#version 150\n"
	"layout(points) in;\n"
	"layout(points, max_vertices = 1) out;\n"
	"out vec2 stream0_out;\n"
	"void main() {\n"
	"  gl_Position = gl_in[0].gl_Position;\n"

	"  stream0_out = vec2(gl_Position[0], gl_Position[1]);\n"
	"  EmitVertex();\n"
	"  EndPrimitive();\n"
	"}";

static const char gs_overflow_multi[] =
	"#version 150\n"
	"#extension GL_ARB_gpu_shader5 : enable\n"
	"layout(points) in;\n"
	"layout(points, max_vertices = 4) out;\n"
	"layout(stream = 0) out vec2 stream0_out;\n"
	"layout(stream = 1) out vec2 stream1_out;\n"
	"void main() {\n"
	"  gl_Position = gl_in[0].gl_Position;\n"

	"  stream0_out = vec2(gl_Position[0], gl_Position[1]);\n"
	"  EmitStreamVertex(0);\n"
	"  EndStreamPrimitive(0);\n"

	"  stream1_out = vec2(gl_Position[0], gl_Position[1]) + 20;\n"
	"  EmitStreamVertex(1);\n"
	"  EndStreamPrimitive(1);\n"
	"}";

static const char *varyings_single[] = { "stream0_out" };

static const char *varyings_multi[] = {
	"stream0_out", "gl_NextBuffer", "stream1_out",
};

#define STREAMS 2

static const char *program_in_use = NULL;

static bool
build_and_use_program(const char *gs_text, const char **gs_varyings,
		      int array_size)
{
	GLuint prog;

	if (program_in_use == gs_text)
		return true;

	prog = piglit_build_simple_program_multiple_shaders(
				GL_VERTEX_SHADER, vs_pass_thru_text,
				GL_GEOMETRY_SHADER, gs_text, 0);

	glTransformFeedbackVaryings(prog, array_size, gs_varyings,
				GL_INTERLEAVED_ATTRIBS);

	glLinkProgram(prog);
	if (!piglit_link_check_status(prog))
		return false;
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return false;

	glUseProgram(prog);

	program_in_use = gs_text;

	return true;
}

static enum piglit_result
simple_query(GLuint query, bool expected)
{
	enum piglit_result pass = PIGLIT_PASS;
	GLuint value;

	glGetQueryObjectuiv(query, GL_QUERY_RESULT, &value);
	if (value != expected) {
		printf("Wrong value for query. expected: %d, value: %d\n",
		       expected, value);
		pass = PIGLIT_FAIL;
	}

	return pass;
}

static enum piglit_result
conditional_render(GLuint query, bool inverted, bool expected)
{
	enum piglit_result pass = PIGLIT_PASS;
	bool rendered, render_expected;
	GLuint generated_q, value;
	GLuint wait = inverted ? GL_QUERY_WAIT_INVERTED : GL_QUERY_WAIT;

	glGenQueries(1, &generated_q);

	glBeginQuery(GL_PRIMITIVES_GENERATED, generated_q);
	glBeginTransformFeedback(GL_POINTS);
	glBeginConditionalRender(query, wait);
	glDrawArrays(GL_POINTS, 0, 1);
	glEndConditionalRender();
	glEndTransformFeedback();
	glEndQuery(GL_PRIMITIVES_GENERATED);

	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	glGetQueryObjectuiv(generated_q, GL_QUERY_RESULT, &value);

	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	rendered = value == 1;
	render_expected = inverted ? !expected : expected;
	if (rendered != render_expected) {
		printf("Error: expect to render? %d, rendered? %d\n",
		       render_expected, rendered);
		pass = PIGLIT_FAIL;
	}

	return pass;
}

static enum piglit_result
overflow_buffer_object(GLuint query, bool expected)
{
	enum piglit_result pass = PIGLIT_PASS;
	GLuint queryBuffer;
	const GLuint *readback;

	// Create a buffer object for the query result
	glGenBuffers(1, &queryBuffer);
	glBindBuffer(GL_QUERY_BUFFER, queryBuffer);
	glBufferData(GL_QUERY_BUFFER, sizeof(GLuint),
		     NULL, GL_DYNAMIC_COPY);

	// Get query results to buffer object
	glBindBuffer(GL_QUERY_BUFFER, queryBuffer);
	glGetQueryObjectuiv(query, GL_QUERY_RESULT, BUFFER_OFFSET(0));

	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		pass = PIGLIT_FAIL;
		goto err_del_buffer;
	}

	readback = glMapBuffer(GL_QUERY_BUFFER, GL_READ_ONLY);
	if (readback[0] != expected) {
		printf("Query buffer object error. Expected: %u, read: %u\n",
		       expected, readback[0]);
		pass = PIGLIT_FAIL;
	}

	if (!piglit_check_gl_error(GL_NO_ERROR))
		pass = PIGLIT_FAIL;

	glUnmapBuffer(GL_QUERY_BUFFER);
	glBindBuffer(GL_QUERY_BUFFER, 0);
err_del_buffer:
	glDeleteBuffers(1, &queryBuffer);

	if (!piglit_check_gl_error(GL_NO_ERROR))
		pass = PIGLIT_FAIL;

	return pass;
}

static bool
check_multistream_extensions(void)
{
	if (!piglit_is_extension_supported("GL_ARB_gpu_shader5")) {
		piglit_loge("context does not support GL_ARB_gpu_shader5; "
			    "skipping test");
		return false;
	}

	if (!piglit_is_extension_supported("GL_ARB_transform_feedback3")) {
		piglit_loge("context does not support "
			    "GL_ARB_transform_feedback3; skipping test");
		return false;
	}

	return true;
}

static enum piglit_result
run_subtest(int n_streams, int array_sizes[], int stream, GLuint query_type,
		  bool inverted, bool expected, const char *test_type)
{
	enum piglit_result pass = PIGLIT_PASS;
	GLuint query;
	GLuint *xfb;
	GLuint vao;
	int varyings_size;
	const char *gs_text;
	const char **gs_varyings;

	if (!strcmp(test_type, "buffer_object") &&
	    !piglit_is_extension_supported("GL_ARB_query_buffer_object")) {
		piglit_loge("context does not support "
			    "GL_ARB_query_buffer_object; skipping test");
	    return PIGLIT_SKIP;
	}

	if (n_streams > 1) {
		if (!check_multistream_extensions())
			return PIGLIT_SKIP;

		gs_text = gs_overflow_multi;
		gs_varyings = varyings_multi;
		varyings_size = ARRAY_SIZE(varyings_multi);
	} else {
		gs_text = gs_overflow_single;
		gs_varyings = varyings_single;
		varyings_size = ARRAY_SIZE(varyings_single);
	}

	if (!build_and_use_program(gs_text, gs_varyings, varyings_size)) {
		printf("Could not build and link program.\n");
		return PIGLIT_FAIL;
	}

	xfb = malloc(sizeof(*xfb) * n_streams);

	/* Set up the transform feedback buffers. */
	glGenBuffers(n_streams, xfb);
	for (int i = 0; i < n_streams; i++) {
		glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, i, xfb[i]);
		glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER,
			     array_sizes[i] * sizeof(float), NULL,
			     GL_STREAM_READ);
	}

	/* Test only records using transform feedback. */
	glEnable(GL_RASTERIZER_DISCARD);

	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		pass = PIGLIT_FAIL;
		goto err_del_buffers;
	}

	glGenQueries(1, &query);
	glBeginQueryIndexed(query_type, stream, query);

	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		pass = PIGLIT_FAIL;
		goto err_del_queries;
	}

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

	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		pass = PIGLIT_FAIL;
		goto err_del_vao;
	}

	/* Draw and record */
	glBeginTransformFeedback(GL_POINTS);
	glDrawArrays(GL_POINTS, 0, 3);
	glEndQueryIndexed(query_type, stream);
	glEndTransformFeedback();

	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		pass = PIGLIT_FAIL;
		goto err_del_vao;
	}

	if (!strcmp(test_type, "simple_query")) {
		pass = simple_query(query, expected);
	} else if (!strcmp(test_type, "conditional_render")) {
		pass = conditional_render(query, inverted, expected);
	} else if (!strcmp(test_type, "buffer_object")) {
		pass = overflow_buffer_object(query, expected);
	} else {
		printf("Unknown test.\n");
		pass = PIGLIT_FAIL;
	}

err_del_vao:
	glDeleteVertexArrays(1, &vao);
err_del_queries:
	glDeleteQueries(1, &query);
err_del_buffers:
	glDeleteBuffers(n_streams, xfb);
	free(xfb);

	return pass;
}

/**
 * inverted = false, overflow = true.
 */
static enum piglit_result
test_overflow_single(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB;
	int array_sizes[] = { 5 };

	return run_subtest(1, array_sizes, 0, query_type, false, true, test_data);
}

/**
 * inverted = false, expected overflow: false
 */
static enum piglit_result
test_no_overflow_single(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB;
	int array_sizes[] = { 6 };

	return run_subtest(1, array_sizes, 0, query_type, false, false, test_data);
}

/**
 * Overflow on stream 0.
 * Query for overflow on stream 0.
 * inverted = false, expected overflow: true.
 */
static enum piglit_result
test_overflow_stream_0(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB;
	int array_sizes[] = { 5, 6 };

	return run_subtest(2, array_sizes, 0, query_type, false, true, test_data);
}

/**
 * Overflow on stream 1.
 * Query for overflow on stream 0.
 * inverted = true, expected overflow: false.
 */
static enum piglit_result
test_overflow_stream_1(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB;
	int array_sizes[] = { 6, 5 };

	return run_subtest(2, array_sizes, 0, query_type, true, false, test_data);
}

/**
 * Overflow on stream 1.
 * Query for overflow on stream 1.
 * inverted = true, expected overflow: true.
 */
static enum piglit_result
test_overflow_stream_2(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB;
	int array_sizes[] = { 6, 5 };

	return run_subtest(2, array_sizes, 1, query_type, true, true, test_data);
}

/**
 * Overflow on stream 1.
 * Query for overflow on any stream.
 * inverted = false, expected overflow: true.
 */
static enum piglit_result
test_overflow_stream_any(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB;
	int array_sizes[] = { 6, 5 };

	return run_subtest(2, array_sizes, 0, query_type, false, true, test_data);
}

/**
 * Overflow on stream 1.
 * Query for overflow on any stream.
 * inverted = true, expected overflow: true.
 */
static enum piglit_result
test_overflow_stream_any_inverted(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB;
	int array_sizes[] = { 6, 5 };

	return run_subtest(2, array_sizes, 0, query_type, true, true, test_data);
}

/**
 * No overflow.
 * Query for overflow on any stream.
 * inverted = false, expected overflow: false.
 */
static enum piglit_result
test_no_overflow_stream_any(void *test_data)
{
	GLuint query_type = GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB;
	int array_sizes[] = { 6, 6 };

	return run_subtest(2, array_sizes, 0, query_type, false, false,
			   test_data);
}


const struct piglit_subtest overflow_query_subtests[] = {
	{
		"arb_transform_feedback_overflow_query-simple_query_single",
		"arb_transform_feedback_overflow_query-simple_query_single",
		test_overflow_single,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-simple_query_no_overflow_single",
		"arb_transform_feedback_overflow_query-simple_query_no_overflow_single",
		test_no_overflow_single,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_single",
		"arb_transform_feedback_overflow_query-conditional_render_single",
		test_overflow_single,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_no_overflow_single",
		"arb_transform_feedback_overflow_query-conditional_render_no_overflow_single",
		test_no_overflow_single,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_single",
		"arb_transform_feedback_overflow_query-buffer_object_single",
		test_overflow_single,
		"buffer_object"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_no_overflow_single",
		"arb_transform_feedback_overflow_query-buffer_object_no_overflow_single",
		test_no_overflow_single,
		"buffer_object"
	},
	{
		"arb_transform_feedback_overflow_query-simple_query_0",
		"arb_transform_feedback_overflow_query-simple_query_0",
		test_overflow_stream_0,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-simple_query_1",
		"arb_transform_feedback_overflow_query-simple_query_1",
		test_overflow_stream_1,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-simple_query_2",
		"arb_transform_feedback_overflow_query-simple_query_2",
		test_overflow_stream_2,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-simple_query_any",
		"arb_transform_feedback_overflow_query-simple_query_any",
		test_overflow_stream_any,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-simple_query_no_overflow",
		"arb_transform_feedback_overflow_query-simple_query_no_overflow",
		test_no_overflow_stream_any,
		"simple_query"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_0",
		"arb_transform_feedback_overflow_query-conditional_render_0",
		test_overflow_stream_0,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_1",
		"arb_transform_feedback_overflow_query-conditional_render_1",
		test_overflow_stream_1,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_2",
		"arb_transform_feedback_overflow_query-conditional_render_2",
		test_overflow_stream_2,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_any",
		"arb_transform_feedback_overflow_query-conditional_render_any",
		test_overflow_stream_any,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_any_inverted",
		"arb_transform_feedback_overflow_query-conditional_render_any_inverted",
		test_overflow_stream_any_inverted,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-conditional_render_no_overflow",
		"arb_transform_feedback_overflow_query-conditional_render_no_overflow",
		test_no_overflow_stream_any,
		"conditional_render"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_0",
		"arb_transform_feedback_overflow_query-buffer_object_0",
		test_overflow_stream_0,
		"buffer_object"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_1",
		"arb_transform_feedback_overflow_query-buffer_object_1",
		test_overflow_stream_1,
		"buffer_object"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_2",
		"arb_transform_feedback_overflow_query-buffer_object_2",
		test_overflow_stream_2,
		"buffer_object"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_any",
		"arb_transform_feedback_overflow_query-buffer_object_any",
		test_overflow_stream_any,
		"buffer_object"
	},
	{
		"arb_transform_feedback_overflow_query-buffer_object_no_overflow",
		"arb_transform_feedback_overflow_query-buffer_object_no_overflow",
		test_no_overflow_stream_any,
		"buffer_object"
	},
	{0},
};

void
piglit_init(int argc, char **argv)
{
	enum piglit_result result = PIGLIT_SKIP;
	const char **selected_subtests = NULL;
	size_t num_selected_subtests = 0;
	const struct piglit_subtest *subtests = overflow_query_subtests;

	piglit_require_extension("GL_ARB_transform_feedback_overflow_query");

	/* Strip common piglit args. */
	piglit_strip_arg(&argc, argv, "-fbo");
	piglit_strip_arg(&argc, argv, "-auto");
	num_selected_subtests = piglit_get_selected_tests(&selected_subtests);

	if (argc > 1) {
		fprintf(stderr, "usage error\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	result = piglit_run_selected_subtests(subtests, selected_subtests,
					      num_selected_subtests, result);
	piglit_report_result(result);
}

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