/*
 * Copyright © 2019 Advanced Micro Devices, Inc.
 *
 * 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 image_functions.c
 *
 * Test the new GLSL image functions.
 *
 * Two categories of tests in this file:
 *   - for the functions existing both in ARB_shader_image_load_store and
 *     EXT_shader_image_load_store, we simply build a program to verify that
 *     they're available.
 *   - for the 2 functions that only exist in EXT (atomicIncWrap and atomicDecWrap),
 *     we verify their behavior. Note: on nvidia this test needs to be used with
 *     "-fbo" argument.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

config.supports_gl_core_version = 32;

PIGLIT_GL_TEST_CONFIG_END

struct test_data {
	const char* function_name;
	unsigned (*compute_ref_value) (int num_exec, unsigned wrap);
	const char* type;
	GLuint (*create_texture) (unsigned tex_width);
	void (*read_texture) (void* data, size_t s);
};

static GLuint
create_texture(unsigned tex_width)
{
	int* data;
	GLuint texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_1D, texture);

	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	data = (int*) malloc(tex_width * sizeof(int));
	memset(data, 0, tex_width * sizeof(int));
	glTexImage1D(GL_TEXTURE_1D, 0, GL_R32I, tex_width, 0, GL_RED_INTEGER, GL_INT, data);
	free(data);

	return texture;
}

static void
read_texture(void* data, size_t s)
{
	glGetTexImage(GL_TEXTURE_1D, 0,
		      GL_RED_INTEGER,
		      GL_INT,
		      data);
}

static GLuint
create_buffer_texture(unsigned tex_width)
{
	int* data;
	GLuint texture, buffer;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_BUFFER, texture);

	data = (int*) malloc(tex_width * sizeof(int));
	memset(data, 0, tex_width * sizeof(int));

	glGenBuffers(1, &buffer);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferStorage(GL_ARRAY_BUFFER, tex_width * sizeof(int), data, GL_MAP_READ_BIT);

	glTexBuffer(GL_TEXTURE_BUFFER, GL_R32I, buffer);
	free(data);

	return texture;
}

static void
read_buffer_texture(void* data, size_t s)
{
	GLint buffer;
	glGetIntegerv(GL_TEXTURE_BINDING_BUFFER, &buffer);
	glGetBufferSubData(GL_ARRAY_BUFFER, 0, s, data);
	glDeleteBuffers(1, (GLuint*) &buffer);
}

static enum piglit_result
run_test(void * _data)
{
	const char* vs =
		"attribute vec4 piglit_vertex;\n"
		"void main()\n"
		"{\n"
			"gl_Position = piglit_vertex;\n"
		"}\n";

	static const char* fs_template =
		"#version 150\n"
		"#extension GL_EXT_shader_image_load_store : enable\n"
		"uniform int index;\n"
		"uniform uint wrap_value;\n"
		"layout(size1x32) uniform %s image;\n"
		"void main() {\n"
		"   uint res = %s(image, index, wrap_value);\n"
		"   gl_FragColor.rgb = vec3(float(res) / float(wrap_value));\n"
		"   gl_FragColor.a = 1.0;\n"
		"}\n";

	char fs[1024];
	bool pass = true;
	const struct test_data* test = (const struct test_data*) _data;

	sprintf(fs, fs_template, test->type, test->function_name);

	GLint program = piglit_build_simple_program(vs, fs);
	GLint image_location = glGetUniformLocation(program, "image");
	GLint wrap_location = glGetUniformLocation(program, "wrap_value");
	GLint index_location = glGetUniformLocation(program, "index");

	int texture_size[] = {32, 50, 60, 128};
	int wrap_value[] = {12, 29, 17, 41};
	GLint read_back[128];
	for (int i = 0; i < ARRAY_SIZE(texture_size); i++) {
		GLuint texture = test->create_texture(texture_size[i]);
		int index = rand() % texture_size[i];
		glBindImageTextureEXT(0, texture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32I);

		glUseProgram(program);
		glUniform1i(image_location, 0);
		glUniform1ui(wrap_location, wrap_value[i]);
		glUniform1i(index_location, index);

		piglit_draw_rect(-1, -1, 2.0, 2.0);

		glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT |
				GL_BUFFER_UPDATE_BARRIER_BIT |
				GL_PIXEL_BUFFER_BARRIER_BIT |
				GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

		test->read_texture(read_back, texture_size[i] * sizeof(int));

		GLuint ref_value = test->compute_ref_value(piglit_width * piglit_height, wrap_value[i]);

		/* The first component of the pixel at index has been written to by all invocations */
		pass = pass && read_back[index] == ref_value;

		/* All other pixels/components should be untouched */
		for (int j = 0; j < texture_size[i]; j++) {
			pass = pass && (j == index || read_back[j] == 0);
		}
		glDeleteTextures(1, &texture);

		piglit_present_results();
	}

	glDeleteProgram(program);
	return pass && piglit_check_gl_error(GL_NO_ERROR) ?
		PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
run_compile_test(void * data)
{
	/* Type of the uniform variable used in imageStore */
	static const char* v_value_type[] = { "vec4", "ivec4", "uvec4" };
	/* Type of the uniform variable used in the atomic operations */
	static const char* i_value_type[] = { "float", "int", "uint" };
	/* Layout qualifier for the image */
	static const char* qualifiers[] = {
		"size1x8", "size1x16", "size1x32", "size2x32", "size4x32"
	};
	/* 'coord' variable */
	static const char* coords[] = {
		"int coord = 0",
		"ivec2 coord = ivec2(0)",
		"ivec3 coord = ivec3(0)",
	};
	/* Types of the image variable using a 'int' for the coord */
	static const char* types_int[] = {
		"image1D", "iimage1D", "uimage1D", "imageBuffer", NULL,
	};
	/* Types of the image variable using a 'ivec2' for the coord */
	static const char* types_ivec2[] = {
		"image2D", "iimage2D", "uimage2D",
		"image2DRect", "iimage2DRect", "uimage2DRect",
		"image1DArray", "iimage1DArray", "uimage1DArray",
		NULL
	};
	/* Types of the image variable using a 'ivec3' for the coord */
	static const char* types_ivec3[] = {
		"image3D", "iimage3D", "uimage3D",
		"imageCube", "iimageCube", "uimageCube",
		"image2DArray", "iimage2DArray", "uimage2DArray",
		"imageCubeArray", "iimageCubeArray", "uimageCubeArray",
		NULL
	};
	/* Fragment shader template */
	static const char* fs_template =
		"#version 150\n"
		"#extension GL_ARB_ES3_1_compatibility: %s\n"
		"#extension GL_EXT_shader_image_load_store : enable\n"
		"uniform uint wrap_value;\n"
		"uniform %s v_value;\n"
		"uniform %s i_value;\n"
		"layout(%s) uniform %s image;\n"
		"void main() {\n"
		"   %s;\n"
		"   %s;\n"
		"}\n";

	char fs[2048];
	enum piglit_result res = PIGLIT_PASS;

	static const char** types[] = {
		types_int, types_ivec2, types_ivec3
	};

	const bool atomicOp = strstr(data, "Atomic") != NULL;
	const bool atomicOpWrap = atomicOp && strstr(data, "Wrap") != NULL;
	const bool atomicOpExchange = atomicOp && strstr(data, "Exchange") != NULL;
	bool enable_arb_es31_compat = false;

	for (int i = 0; i < ARRAY_SIZE(qualifiers); i++) {
		for (int j = 0; j < ARRAY_SIZE(types); j++) {
			for (int k = 0; types[j][k]; k++) {
				bool is_valid = true;
				bool floatImageType = strncmp(types[j][k], "image", 5) == 0;
				bool signedImageType = strncmp(types[j][k], "iimage", 6) == 0;

				/* The EXT_shader_image_load_store spec says:
				 *    These functions [atomic functions] support 32-bit unsigned integer
				 *    operands and 32-bit signed integer operands.
				 */
				if (atomicOp && floatImageType) {
					is_valid = false;
					/* The GL_ARB_ES3_1_compatibility says:
					 *    a new GLSL built-in function, imageAtomicExchange, which performs atomic
					 *    exchanges on r32f floating point images.
					 */
					if (atomicOpExchange && piglit_is_extension_supported("GL_ARB_ES3_1_compatibility")) {
						enable_arb_es31_compat = true;
						is_valid = true;
					}
				}

				/* The EXT_shader_image_load_store spec says:
				 *    These functions [imageAtomicIncWrap and imageAtomicDecWrap] support
				 *    only 32-bit unsigned integer operands.
				 */
				if (atomicOpWrap && signedImageType)
					is_valid = false;

				/* The EXT_shader_image_load_store spec says:
				 *     A layout of "size1x8" is illegal for image variables associated
				 *     with floating-point data types.
				 */
				if (floatImageType && i == 0)
					is_valid = false;

				/* The says:
				 *     The format of the image unit must be in the "1x32" equivalence
				 *     class [...] otherwise the atomic operation is invalid.
				 */
				if (atomicOp && i != 2)
					is_valid = false;

				/* Build the fragment template */
				sprintf(fs, fs_template,
					enable_arb_es31_compat ? "enable" : "disable",
					v_value_type[k % 3],
					i_value_type[k % 3],
					qualifiers[i],
					types[j][k],
					coords[j],
					(char*) data);

				/* And verify we can build the fragment shader */
				GLint program = piglit_compile_shader_text_nothrow(GL_FRAGMENT_SHADER, fs, is_valid);
				if (is_valid != (bool) program) {
					res = PIGLIT_FAIL;
				}
				if (program)
					glDeleteShader(program);
			}
		}
	}

	return res;
}

static unsigned compute_imageAtomicIncWrap(int num_exec, unsigned wrap)
{
       unsigned value = 0;
       /* The EXT_shader_image_load_store spec says:
        *
        *    imageAtomicIncWrap() computes a new value by adding one to the contents of
        *    the selected texel, and then forcing the result to zero if and only if the
        *    incremented value is greater than or equal to <wrap>.  These functions
        *    support only 32-bit unsigned integer operands.
        */
       for (int i = 0; i < num_exec; i++) {
               value += 1;
               /* nvidia and amdgpu-pro drivers interprets the spec as > instead of >= */
               if (value > wrap) {
                       value = 0;
               }
       }
       return value;
}

static unsigned compute_imageAtomicDecWrap(int num_exec, unsigned wrap)
{
	unsigned value = 0;
	/* The EXT_shader_image_load_store spec says:
	 *
	 *    imageAtomicDecWrap() computes a new value by subtracting one from the
	 *    contents of the selected texel, and then forcing the result to <wrap>-1 if
	 *    the original value read from the selected texel was either zero or greater
	 *    than <wrap>.  These functions support only 32-bit unsigned integer
	 *    operands.
	 */
	for (int i = 0; i < num_exec; i++) {
		if (value == 0 || value > wrap) {
			/* nvidia and amdgpu-pro drivers wraps to "wrap" and not "wrap - 1" */
			value = wrap;
		} else {
			value -= 1;
		}
	}
	return value;
}

void
piglit_init(int argc, char **argv)
{
	struct test_data test_data[] = {
		{
			"imageAtomicIncWrap",
			compute_imageAtomicIncWrap,
			"uimage1D",
			create_texture,
			read_texture
		},
		{
			"imageAtomicIncWrap",
			compute_imageAtomicIncWrap,
			"uimageBuffer",
			create_buffer_texture,
			read_buffer_texture
		},
		{
			"imageAtomicDecWrap",
			compute_imageAtomicDecWrap,
			"uimage1D",
			create_texture,
			read_texture
		},
		{
			"imageAtomicDecWrap",
			compute_imageAtomicDecWrap,
			"uimageBuffer",
			create_buffer_texture,
			read_buffer_texture
		},
	};
	const struct piglit_subtest tests[] =
	{
		{
			"imageAtomicIncWrap uimage1D",
			NULL,
			run_test,
			&test_data[0],
		},
		{
			"imageAtomicIncWrap uimageBuffer",
			NULL,
			run_test,
			&test_data[1],
		},
		{
			"imageAtomicDecWrap uimage1D",
			NULL,
			run_test,
			&test_data[2],
		},
		{
			"imageAtomicDecWrap uimageBuffer",
			NULL,
			run_test,
			&test_data[3],
		},
		/* Compile only tests */
		{
			"imageLoad",
			NULL,
			run_compile_test,
			"imageLoad(image, coord)",
		},
		{
			"imageStore",
			NULL,
			run_compile_test,
			"imageStore(image, coord, v_value)",
		},
		{
			"imageAtomicAdd",
			NULL,
			run_compile_test,
			"imageAtomicAdd(image, coord, i_value)",
		},
		{
			"imageAtomicMin",
			NULL,
			run_compile_test,
			"imageAtomicMin(image, coord, i_value)",
		},
		{
			"imageAtomicMax",
			NULL,
			run_compile_test,
			"imageAtomicMax(image, coord, i_value)",
		},
		{
			"imageAtomicAnd",
			NULL,
			run_compile_test,
			"imageAtomicAnd(image, coord, i_value)",
		},
		{
			"imageAtomicOr",
			NULL,
			run_compile_test,
			"imageAtomicOr(image, coord, i_value)",
		},
		{
			"imageAtomicXor",
			NULL,
			run_compile_test,
			"imageAtomicXor(image, coord, i_value)",
		},
		{
			"imageAtomicExchange",
			NULL,
			run_compile_test,
			"imageAtomicExchange(image, coord, i_value)",
		},
		{
			"imageAtomicCompSwap",
			NULL,
			run_compile_test,
			"imageAtomicCompSwap(image, coord, i_value, i_value)",
		},
		{
			"imageAtomicIncWrap",
			NULL,
			run_compile_test,
			"imageAtomicIncWrap(image, coord, wrap_value)",
		},
		{
			"imageAtomicDecWrap",
			NULL,
			run_compile_test,
			"imageAtomicDecWrap(image, coord, wrap_value)",
		},
		{0},
	};

	piglit_require_extension("GL_EXT_shader_image_load_store");

	enum piglit_result result = PIGLIT_PASS;

	result = piglit_run_selected_subtests(
		tests,
		NULL,
		0,
		PIGLIT_PASS);

	piglit_report_result(result);
}

enum piglit_result
piglit_display(void)
{
		return PIGLIT_FAIL;
}
