/*
 * Copyright (C) 2017 Valve 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.
 */

/** \file
 *
 * Test cases which exercice the uniform handle API with
 * glUniformHandleui*64ARB(), glGetActiveUniform(), etc.
 */

#include <inttypes.h>

#include "common.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_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const char *passthrough_vs_src =
	"#version 330\n"
	"\n"
	"in vec4 piglit_vertex;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	gl_Position = piglit_vertex;\n"
	"}\n";

static const char *fs_explicit_bound_sampler =
	"#version 330\n"
	"#extension GL_ARB_bindless_texture: require\n"
	"\n"
	"layout (bound_sampler) uniform sampler2D tex;\n"
	"out vec4 color;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	color = texture(tex, vec2(0, 0));\n"
	"}\n";

static const char *fs_explicit_bindless_sampler =
	"#version 330\n"
	"#extension GL_ARB_bindless_texture: require\n"
	"\n"
	"layout (bindless_sampler) uniform sampler2D tex;\n"
	"out vec4 color;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	color = texture(tex, vec2(0, 0));\n"
	"}\n";

static const char *fs_implicit_bound_sampler =
	"#version 330\n"
	"#extension GL_ARB_bindless_texture: require\n"
	"\n"
	"uniform sampler2D tex;\n"
	"out vec4 color;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	color = texture(tex, vec2(0, 0));\n"
	"}\n";

static const char *fs_explicit_bound_image =
	"#version 330\n"
	"#extension GL_ARB_bindless_texture: require\n"
	"#extension GL_ARB_shader_image_load_store: enable\n"
	"\n"
	"layout (bound_image) writeonly uniform image2D img;\n"
	"out vec4 color;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	imageStore(img, ivec2(0, 0), color);\n"
	"}\n";

static const char *fs_explicit_bindless_image =
	"#version 330\n"
	"#extension GL_ARB_bindless_texture: require\n"
	"#extension GL_ARB_shader_image_load_store: enable\n"
	"\n"
	"layout (bindless_image) writeonly uniform image2D img;\n"
	"out vec4 color;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	imageStore(img, ivec2(0, 0), color);\n"
	"}\n";

static const char *fs_implicit_bound_image =
	"#version 330\n"
	"#extension GL_ARB_bindless_texture: require\n"
	"#extension GL_ARB_shader_image_load_store: enable\n"
	"\n"
	"writeonly uniform image2D img;\n"
	"out vec4 color;\n"
	"\n"
	"void main()\n"
	"{\n"
	"	imageStore(img, ivec2(0, 0), color);\n"
	"}\n";

static enum piglit_result
check_UniformHandleui64_with_explicit_bound_sampler(void *data)
{
	GLuint vs, fs, prog;
	GLuint64 handle = 0;
	GLint loc;

	if (piglit_khr_no_error)
		return PIGLIT_SKIP;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_explicit_bound_sampler);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "tex");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by UniformHandleui64{v}ARB
	 *  if the sampler or image uniform being updated has the
	 *  "bound_sampler" or "bound_image" layout qualifier."
	 */
	glUniformHandleui64ARB(loc, handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	glProgramUniformHandleui64vARB(prog, loc, 1, &handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_Uniform_with_explicit_bindless_sampler(void *data)
{
	GLuint vs, fs, prog;
	GLuint64 handle = 0;
	GLint loc;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_explicit_bindless_sampler);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "tex");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "These modifiers control whether default-block uniforms of the
	 *  corresponding types may have their values set via both
	 *  UniformHandle* and Uniform1i (bindless_sampler and bindless_image)
	 *  or only via Uniform1i (bound_sampler and bound_image)."
	 */
	glUniform1i(loc, 5);
	glUniformHandleui64ARB(loc, handle);
	glProgramUniformHandleui64vARB(prog, loc, 1, &handle);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_Uniform_with_implicit_bound_sampler(void *data)
{
	GLuint vs, fs, prog;
	GLuint64 handle = 0;
	GLint loc;

	if (piglit_khr_no_error)
		return PIGLIT_SKIP;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_implicit_bound_sampler);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "tex");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "These modifiers control whether default-block uniforms of the
	 *  corresponding types may have their values set via both
	 *  UniformHandle* and Uniform1i (bindless_sampler and bindless_image)
	 *  or only via Uniform1i (bound_sampler and bound_image)."
	 *
	 * "In the absence of these qualifiers, sampler and image uniforms are
	 *  considered "bound"."
	 */
	glUniform1i(loc, 5);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	glUniformHandleui64ARB(loc, handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	glProgramUniformHandleui64vARB(prog, loc, 1, &handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_UniformHandleui64_with_explicit_bound_image(void *data)
{
	GLuint vs, fs, prog;
	GLuint64 handle = 0;
	GLint loc;

	if (piglit_khr_no_error)
		return PIGLIT_SKIP;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_explicit_bound_image);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "img");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by UniformHandleui64{v}ARB
	 *  if the sampler or image uniform being updated has the
	 *  "bound_sampler" or "bound_image" layout qualifier."
	 */
	glUniformHandleui64ARB(loc, handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	glProgramUniformHandleui64vARB(prog, loc, 1, &handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_Uniform_with_explicit_bindless_image(void *data)
{
	GLuint vs, fs, prog;
	GLuint64 handle = 0;
	GLint loc;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_explicit_bindless_image);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "img");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "These modifiers control whether default-block uniforms of the
	 *  corresponding types may have their values set via both
	 *  UniformHandle* and Uniform1i (bindless_sampler and bindless_image)
	 *  or only via Uniform1i (bound_sampler and bound_image)."
	 */
	glUniform1i(loc, 5);
	glUniformHandleui64ARB(loc, handle);
	glProgramUniformHandleui64vARB(prog, loc, 1, &handle);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_Uniform_with_implicit_bound_image(void *data)
{
	GLuint vs, fs, prog;
	GLuint64 handle = 0;
	GLint loc;

	if (piglit_khr_no_error)
		return PIGLIT_SKIP;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_implicit_bound_image);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "img");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "These modifiers control whether default-block uniforms of the
	 *  corresponding types may have their values set via both
	 *  UniformHandle* and Uniform1i (bindless_sampler and bindless_image)
	 *  or only via Uniform1i (bound_sampler and bound_image)."
	 *
	 * "In the absence of these qualifiers, sampler and image uniforms are
	 *  considered "bound"."
	 */
	glUniform1i(loc, 5);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	glUniformHandleui64ARB(loc, handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	glProgramUniformHandleui64vARB(prog, loc, 1, &handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static bool
check_uniform_int(GLuint prog, int loc, int expect)
{
        int v = 0xdeadcafe;

        glGetUniformiv(prog, loc, &v);
        if (v != expect) {
                fprintf(stderr, "Invalid value for uniform %d\n"
                        "   Expected: %d\n"
                        "   Observed: %d\n",
                        loc, expect, v);
                return false;
        }
        return piglit_check_gl_error(GL_NO_ERROR);
}

static bool
check_uniform_handle(GLuint prog, int loc, GLuint64 expect)
{
        GLuint64 v = 0xdeadcafedeadcafe;

        glGetUniformui64vARB(prog, loc, &v);
        if (v != expect) {
                fprintf(stderr, "Invalid value for uniform %d\n"
                        "   Expected: %"PRIx64"\n"
                        "   Observed: %"PRIx64"\n",
                        loc, expect, v);
                return false;
        }
        return piglit_check_gl_error(GL_NO_ERROR);
}

static enum piglit_result
check_Uniform_with_texture_units_and_handles(void *data)
{
	const char *fs_src =
		"#version 330\n"
		"#extension GL_ARB_bindless_texture: require\n"
		"\n"
		"layout (bindless_sampler) uniform sampler2D texs[5];\n"
		"uniform int i;\n"
		"out vec4 color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	color = texture(texs[i], vec2(0, 0));\n"
		"}\n";
	int units[5] = { 4, 7, 8, 1, 5 };
	GLuint64 handle = 0x1004002010040020;
	GLuint vs, fs, prog;
	bool pass = true;
	GLint loc;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER, fs_src);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	loc = glGetUniformLocation(prog, "texs");
	if (loc == -1)
		return PIGLIT_FAIL;

	/* Check setting an array of texture units. */
	glUniform1iv(loc, 5, units);

	for (int i = 0; i < 5; i++) {
		pass &= check_uniform_int(prog, loc + i, units[i]);
	}

	/* Check setting a texture handle. */
	glUniformHandleui64ARB(loc, handle);
	pass &= check_uniform_handle(prog, loc, handle);

	/* Make sure setting the handle didn't overwrite other values. */
	for (int i = 1; i < 5; i++) {
		pass &= check_uniform_int(prog, loc + i, units[i]);
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
use_glGetActiveUniform_with_sampler(void *data)
{
	GLuint vs, fs, prog;
	GLint num_uniforms, size;
	char name[128];
	GLsizei len;
	GLenum type;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_explicit_bound_sampler);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &num_uniforms);
	if (num_uniforms != 1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "Modify Section 2.14.4, Uniform Variables, p. 97"
	 *
	 * "(Edit Table 2.15, "OpenGL Shading Language type tokens returned
	 *  by GetActiveUniform and GetActiveUniformsiv...", pp. 104-106.  In
	 *  particular, add dots to the "Attrib" and "Xfb" columns for all
	 *  SAMPLER and IMAGE types.)"
	 */
	glGetActiveUniform(prog, 0, sizeof(name), &len, &size, &type, name);

	/* Minimal test which only checks one sampler type. */
	return type == GL_SAMPLER_2D ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
use_glGetActiveUniform_with_image(void *data)
{
	GLuint vs, fs, prog;
	GLint num_uniforms, size;
	char name[128];
	GLsizei len;
	GLenum type;

	if (!piglit_is_extension_supported("GL_ARB_shader_image_load_store"))
		return PIGLIT_SKIP;

	vs = piglit_compile_shader_text(GL_VERTEX_SHADER, passthrough_vs_src);
	fs = piglit_compile_shader_text(GL_FRAGMENT_SHADER,
					fs_explicit_bound_image);
	prog = piglit_link_simple_program(vs, fs);
	glUseProgram(prog);

	glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &num_uniforms);
	if (num_uniforms != 1)
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "Modify Section 2.14.4, Uniform Variables, p. 97"
	 *
	 * "(Edit Table 2.15, "OpenGL Shading Language type tokens returned
	 *  by GetActiveUniform and GetActiveUniformsiv...", pp. 104-106.  In
	 *  particular, add dots to the "Attrib" and "Xfb" columns for all
	 *  SAMPLER and IMAGE types.)"
	 */
	glGetActiveUniform(prog, 0, sizeof(name), &len, &size, &type, name);

	/* Minimal test which only checks one image type. */
	return type == GL_IMAGE_2D ? PIGLIT_PASS : PIGLIT_FAIL;
}

static const struct piglit_subtest subtests[] = {
	{
		"Check glUniformHandleui64*ARB() with explicit bound_sampler",
		"check_UniformHandleui64_with_explicit_bound_sampler",
		check_UniformHandleui64_with_explicit_bound_sampler,
		NULL
	},
	{
		"Check glUniform*() with explicit bindless_sampler",
		"check_Uniform_with_explicit_bindless_sampler",
		check_Uniform_with_explicit_bindless_sampler,
		NULL
	},
	{
		"Check glUniform*() with implicit bound_sampler",
		"check_Uniform_with_implicit_bound_sampler",
		check_Uniform_with_implicit_bound_sampler,
		NULL
	},
	{
		"Check glUniformHandleui64*ARB() with explicit bound_image",
		"check_UniformHandleui64_with_explicit_bound_image",
		check_UniformHandleui64_with_explicit_bound_image,
		NULL
	},
	{
		"Check glUniform*() with explicit bindless_image",
		"check_Uniform_with_explicit_bindless_image",
		check_Uniform_with_explicit_bindless_image,
		NULL
	},
	{
		"Check glUniform*() with implicit bound_image",
		"check_Uniform_with_implicit_bound_image",
		check_Uniform_with_implicit_bound_image,
		NULL
	},
	{
		"Check glUniform*() with mixed texture units/handles",
		"check_Uniform_with_texture_units_and_handles",
		check_Uniform_with_texture_units_and_handles,
		NULL
	},
	{
		"Use glGetActiveUniform with a sampler type",
		"use_glGetActiveUniform_with_sampler",
		use_glGetActiveUniform_with_sampler,
		NULL
	},
	{
		"Use glGetActiveUniform with an image type",
		"use_glGetActiveUniform_with_image",
		use_glGetActiveUniform_with_image,
		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_bindless_texture");
	result = piglit_run_selected_subtests(subtests,
					      piglit_config->selected_subtests,
					      piglit_config->num_selected_subtests,
					      PIGLIT_SKIP);
	piglit_report_result(result);
}
