/*
 * Copyright (C) 2016 Samuel Pitoiset
 *
 * 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
 *
 * Test cases in which the ARB_compute_variable_group_size API is expected to
 * generate an error.
 */

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

static struct piglit_gl_test_config *piglit_config;

PIGLIT_GL_TEST_CONFIG_BEGIN

	piglit_config = &config;
	config.supports_gl_compat_version = 33;
	config.supports_gl_core_version = 33;
	config.khr_no_error_support = PIGLIT_HAS_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const char *variable_work_group_size_shader =
	"#version 330\n"
	"#extension GL_ARB_compute_shader: enable\n"
	"#extension GL_ARB_compute_variable_group_size: enable\n"
	"\n"
	"layout(local_size_variable) in;\n"
	"\n"
	"void main()\n"
	"{\n"
	"}\n";

static const char *fixed_work_group_size_shader =
	"#version 330\n"
	"#extension GL_ARB_compute_shader: enable\n"
	"\n"
	"layout(local_size_x = 1) in;\n"
	"\n"
	"void main()\n"
	"{\n"
	"}\n";

static enum piglit_result
use_variable_work_group_size_normal(void *data)
{
	/* The ARB_compute_variable_group_size spec says:
	 *
	 *    An INVALID_OPERATION error is generated by DispatchCompute or
	 *    DispatchComputeIndirect if the active program for the compute
	 *    shader stage has a variable work group size.
	 */
	GLint prog = piglit_build_simple_program_multiple_shaders(
		GL_COMPUTE_SHADER, variable_work_group_size_shader, 0);
	glUseProgram(prog);
	glDispatchCompute(1, 1, 1);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;
	return PIGLIT_PASS;
}

static enum piglit_result
use_variable_work_group_size_indirect(void *data)
{
	/* The ARB_compute_variable_group_size spec says:
	 *
	 *    An INVALID_OPERATION error is generated by DispatchCompute or
	 *    DispatchComputeIndirect if the active program for the compute
	 *    shader stage has a variable work group size.
	 */
	GLuint indirect_buf[3] = { 1, 1, 1 };
	GLuint indirect_bo = 0;
	GLint prog;

	glGenBuffers(1, &indirect_bo);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, indirect_bo);
	glBufferData(GL_DISPATCH_INDIRECT_BUFFER, sizeof(indirect_buf),
		     indirect_buf, GL_STREAM_READ);

	prog = piglit_build_simple_program_multiple_shaders(
		GL_COMPUTE_SHADER, variable_work_group_size_shader, 0);
	glUseProgram(prog);

	glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, indirect_bo);
	glDispatchComputeIndirect(0);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	glDeleteBuffers(1, &indirect_bo);
	return PIGLIT_PASS;
}

static enum piglit_result
use_fixed_work_group_size(void *data)
{
	/* The ARB_compute_variable_group_size spec says:
	 *
	 *     An INVALID_OPERATION error is generated by
	 *     DispatchComputeGroupSizeARB if the active program for the
	 *     compute shader stage has a fixed work group size.
	 */
	GLint prog = piglit_build_simple_program_multiple_shaders(
		GL_COMPUTE_SHADER, fixed_work_group_size_shader, 0);
	glUseProgram(prog);
	glDispatchComputeGroupSizeARB(1, 1, 1, 1, 1, 1);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;
	return PIGLIT_PASS;
}

static enum piglit_result
use_invalid_work_group_count_values(void *data)
{
	GLuint prog;
	GLint64 v[3];

	/* The ARB_compute_variable_group_size spec says:
	 *
	 *     An INVALID_VALUE error is generated if any of num_groups_x,
	 *     num_groups_y and num_groups_z are greater than or equal to the
	 *     maximum work group count for the corresponding dimension.
	 */
	prog = piglit_build_simple_program_multiple_shaders(
		GL_COMPUTE_SHADER, variable_work_group_size_shader, 0);
	glUseProgram(prog);

	/* Use values greater than the maximum work group count. */
	glGetInteger64i_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &v[0]);
	glGetInteger64i_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &v[1]);
	glGetInteger64i_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &v[2]);

	/* Test 1 dimension at a time. */
	if (v[0] < UINT32_MAX) {
		glDispatchComputeGroupSizeARB(v[0] + 1, 1, 1, 1, 1, 1);
		if (!piglit_check_gl_error(GL_INVALID_VALUE))
			return PIGLIT_FAIL;
	}

	if (v[1] < UINT32_MAX) {
		glDispatchComputeGroupSizeARB(1, v[1] + 1, 1, 1, 1, 1);
		if (!piglit_check_gl_error(GL_INVALID_VALUE))
			return PIGLIT_FAIL;
	}

	if (v[2] < UINT32_MAX) {
		glDispatchComputeGroupSizeARB(1, 1, v[2] + 1, 1, 1, 1);
		if (!piglit_check_gl_error(GL_INVALID_VALUE))
			return PIGLIT_FAIL;
	}

	return PIGLIT_PASS;
}

static enum piglit_result
use_invalid_variable_work_group_size_values(void *data)
{
	/* The ARB_compute_variable_group_size spec says:
	 *
	 *     An INVALID_VALUE error is generated by
	 *     DispatchComputeGroupSizeARB if any of <group_size_x>,
	 *     <group_size_y>, or <group_size_z> is less than or equal to zero
	 *     or greater than the maximum local work group size for compute
	 *     shaders with variable group size
	 *     (MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB) in the corresponding
	 *     dimension.
	 */
	GLint prog, v[3];

	prog = piglit_build_simple_program_multiple_shaders(
		GL_COMPUTE_SHADER, variable_work_group_size_shader, 0);
	glUseProgram(prog);

	/* Use values equal to zero (because "less than" is a spec bug). */
	glDispatchComputeGroupSizeARB(1, 1, 1, 0, 0, 0);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	/* Use values greater than the maximum local work group size. */
	glGetIntegeri_v(GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB, 0, &v[0]);
	glGetIntegeri_v(GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB, 1, &v[1]);
	glGetIntegeri_v(GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB, 2, &v[2]);
	glDispatchComputeGroupSizeARB(1, 1, 1, v[0] + 1, v[1] + 1, v[0] + 1);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;
	return PIGLIT_PASS;
}

static enum piglit_result
use_invalid_variable_group_invocations_values(void *data)
{
	/* The ARB_compute_variable_group_size spec says:
	 *
	 *    An INVALID_VALUE error is generated by
	 *    DispatchComputeGroupSizeARB if the product of <group_size_x>,
	 *    <group_size_y>, and <group_size_z> exceeds the
	 *    implementation-dependent maximum local work group invocation
	 *    count for compute shaders with variable group size
	 *    (MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB).
	 */
	GLint prog, v;

	prog = piglit_build_simple_program_multiple_shaders(
		GL_COMPUTE_SHADER, variable_work_group_size_shader, 0);
	glUseProgram(prog);

	glGetIntegerv(GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB, &v);
	glDispatchComputeGroupSizeARB(1, 1, 1, v, v, v);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;
	return PIGLIT_PASS;
}

static const struct piglit_subtest subtests[] = {
	{
		"Use a variable work group size with DispatchCompute",
		"use_variable_work_group_size_normal",
		use_variable_work_group_size_normal,
		NULL
	},
	{
		"Use a variable work group size with DispatchComputeIndirect",
		"use_variable_work_group_size_indirect",
		use_variable_work_group_size_indirect,
		NULL
	},

	{
		"Use a fixed work group size with DispatchComputeGroupSizeARB",
		"use_fixed_work_group_size",
		use_fixed_work_group_size,
		NULL
	},
	{
		"Use invalid work group count values",
		"use_invalid_work_group_count_values",
		use_invalid_work_group_count_values,
		NULL
	},
	{
		"Use invalid variable work group size values",
		"use_invalid_variable_work_group_size_values",
		use_invalid_variable_work_group_size_values,
		NULL
	},
	{
		"Use invalid variable group invocations values",
		"use_invalid_variable_group_invocations_values",
		use_invalid_variable_group_invocations_values,
		NULL
	},
	{
		NULL,
		NULL,
		NULL,
		NULL
	}
};

enum piglit_result
piglit_display(void)
{
	return PIGLIT_FAIL;
}

void
piglit_init(int argc, char **argv)
{
	enum piglit_result result;

	piglit_require_extension("GL_ARB_compute_variable_group_size");
	result = piglit_run_selected_subtests(subtests,
					      piglit_config->selected_subtests,
					      piglit_config->num_selected_subtests,
					      PIGLIT_SKIP);
	piglit_report_result(result);
}
