/*
 * 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
 *
 * Checks gl_LocalGroupSizeARB at various sizes up to the implementation
 * maximums using atomic counters.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 33;
	config.supports_gl_core_version = 33;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static GLuint atomics_bo = 0;
static GLint prog = 0;

static uint32_t global_x = 1, global_y = 1, global_z = 1;
static uint32_t local_x = 0, local_y = 0, local_z = 0;
static int32_t max_local_x = 0, max_local_y = 0, max_local_z = 0;
static int32_t max_variable_invocations = 0;

static uint32_t sizes[] = {
	1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65,
	127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024
};

static const char *compute_shader_source =
	"#version 330\n"
	"#extension GL_ARB_compute_shader: enable\n"
	"#extension GL_ARB_compute_variable_group_size: enable\n"
	"#extension GL_ARB_shader_atomic_counters: require\n"
	"\n"
	"layout(binding = 0) uniform atomic_uint a;\n"
	"layout(local_size_variable) in;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	if (gl_LocalGroupSizeARB.x == %du &&\n"
	"	    gl_LocalGroupSizeARB.y == %du &&\n"
	"	    gl_LocalGroupSizeARB.z == %du)\n"
	"		atomicCounterIncrement(a);\n"
	"}\n";

static GLuint
generate_cs_prog(uint32_t x, uint32_t y, uint32_t z, char *src)
{
	char *source = NULL;

	(void)!asprintf(&source, src, x, y, z);
	free(src);

	GLuint prog = glCreateProgram();

	GLuint shader =
		piglit_compile_shader_text_nothrow(GL_COMPUTE_SHADER, source, true);

	if (!shader) {
		glDeleteProgram(prog);
		return 0;
	}

	glAttachShader(prog, shader);

	glLinkProgram(prog);

	glDeleteShader(shader);

	if (!piglit_link_check_status(prog)) {
		glDeleteProgram(prog);
		return 0;
	}

	return prog;
}

static enum piglit_result
check_result()
{
	uint32_t expected = local_x * local_y * local_z;
	bool pass = true;
	uint32_t *p;

	glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomics_bo);
	p = glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(uint32_t),
			     GL_MAP_READ_BIT);
	if (!p) {
		printf("Couldn't map atomic counter to verify expected value.\n");
		return PIGLIT_FAIL;
	}

	if (p[0] != expected) {
		printf("Atomic counter test failed for (%d, %d, %d)\n",
		       local_x, local_y, local_z);
		printf("  Reference: %u\n", expected);
		printf("  Observed: %u\n", p[0]);
		pass = false;
	}

	glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
run_test()
{
	enum piglit_result result;
	uint32_t atomics_init = 0;

	if (local_x == 0 || local_y == 0 || local_z == 0)
		return PIGLIT_FAIL;

	glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomics_bo);
	glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(uint32_t), &atomics_init,
		     GL_STATIC_DRAW);

	glUseProgram(prog);

	glMemoryBarrier(GL_ALL_BARRIER_BITS);
	glDispatchComputeGroupSizeARB(global_x, global_y, global_z,
				      local_x, local_y, local_z);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;
	glMemoryBarrier(GL_ALL_BARRIER_BITS);

	result = check_result();
	if (result != PIGLIT_PASS)
		piglit_report_result(result);

	return result;
}

static void
clear_program()
{
	if (prog != 0) {
		local_x = 0;
		local_y = 0;
		local_z = 0;
		glDeleteProgram(prog);
		prog = 0;
	}
}

static enum piglit_result
build_program_for_size(uint32_t x, uint32_t y, uint32_t z)
{
	if (local_x == x && local_y == y &&
	    local_z == z && prog != 0) {
		return PIGLIT_PASS;
	}

	clear_program();

	prog = generate_cs_prog(x, y, z, strdup(compute_shader_source));

	if (!prog)
		return PIGLIT_FAIL;

	local_x = x;
	local_y = y;
	local_z = z;

	return PIGLIT_PASS;
}

enum piglit_result
set_local_size(uint32_t x, uint32_t y, uint32_t z)
{
	enum piglit_result result = PIGLIT_PASS;

	if (x == 0 || y == 0 || z == 0) {
		clear_program();
		return PIGLIT_FAIL;
	}

	result = build_program_for_size(x, y, z);
	if (result != PIGLIT_PASS)
		piglit_report_result(result);

	return result;
}

static enum piglit_result
test_size(uint32_t x, uint32_t y, uint32_t z)
{
	enum piglit_result result;

	result = set_local_size(x, y, z);
	if (result != PIGLIT_PASS)
		piglit_report_result(result);

	result = run_test();
	if (result != PIGLIT_PASS)
		piglit_report_result(result);

	return result;
}

static enum piglit_result
test_all_sizes()
{
	enum piglit_result result = PIGLIT_PASS;
	uint32_t xi, yi, zi;
	uint32_t x, y, z;


	for (zi = 0; zi < ARRAY_SIZE(sizes); zi++) {
		z = sizes[zi];
		if (z > max_local_z)
			break;
		for (yi = 0; yi < ARRAY_SIZE(sizes); yi++) {
			y = sizes[yi];
			if (y > max_local_y ||
			    (y * z) > max_variable_invocations)
				break;
			for (xi = 0; xi < ARRAY_SIZE(sizes); xi++) {
				x = sizes[xi];
				if (x > max_local_x ||
				    (x * y * z) > max_variable_invocations)
					break;
				result = test_size(x, y, z);
				if (result != PIGLIT_PASS)
					return result;
			}
		}
	}

	return result;
}

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");
	piglit_require_extension("GL_ARB_shader_atomic_counters");

	glGenBuffers(1, &atomics_bo);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		piglit_report_result(PIGLIT_FAIL);

	glGetIntegeri_v(GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB,
			0, &max_local_x);
	glGetIntegeri_v(GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB,
			1, &max_local_y);
	glGetIntegeri_v(GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB,
			2, &max_local_z);
	glGetIntegerv(GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB,
		      &max_variable_invocations);

	result = test_all_sizes();

	glDeleteBuffers(1, &atomics_bo);

	piglit_report_result(result);
}
