/*
 * 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 new bindless API.
 */

#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_HAS_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static enum piglit_result
check_GetTextureHandle_invalid_values(void *data)
{
	GLuint sampler, texture;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_VALUE is generated by GetTextureHandleARB or
	 *  GetTextureSamplerHandleARB if <texture> is zero or not the name of
	 *  an existing texture object."
	 */

	/* Invalid texture */
	glGetTextureHandleARB(42);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	/* Invalid texture */
	glGenSamplers(1, &sampler);
	glGetTextureSamplerHandleARB(42, sampler);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_VALUE is generated by GetTextureSamplerHandleARB
	 *  if <sampler> is zero or is not the name of an existing sampler
	 *  object."
	 */

	/* Invalid sampler */
	glGenTextures(1, &texture);
	glGetTextureSamplerHandleARB(texture, 42);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_GetTextureHandle_invalid_operations(void *data)
{
	GLuint sampler, texture;

	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by GetTextureHandleARB or
	 *  GetTextureSamplerHandleARB if the texture object specified by
	 *  <texture> is not complete."
	 */

	/* Incomplete texture */
	glGetTextureHandleARB(texture);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	/* Incomplete texture */
	glGenSamplers(1, &sampler);
	glBindSampler(0, sampler);
	glGetTextureSamplerHandleARB(texture, sampler);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_MakeTextureHandleResident_invalid_operations(void *data)
{
	GLuint64 handle;
	GLuint texture;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by
	 *  MakeTextureHandleResidentARB if <handle> is not a valid texture
	 *  handle, or if <handle> is already resident in the current GL
	 *  context."
	 */

	/* Invalid handle */
	glMakeTextureHandleResidentARB(42);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	texture = piglit_rgbw_texture(GL_RGBA, 16, 16, GL_FALSE, GL_FALSE,
				      GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	handle = glGetTextureHandleARB(texture);
	glMakeTextureHandleResidentARB(handle);

	/* Already resident */
	glMakeTextureHandleResidentARB(handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_MakeTextureHandleNonResident_invalid_operations(void *data)
{
	GLuint64 handle;
	GLuint texture;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by
	 *  MakeTextureHandleNonResidentARB if <handle> is not a valid texture
	 *  handle, or if <handle> is not resident in the current GL context."
	 */

	/* Invalid handle */
	glMakeTextureHandleNonResidentARB(42);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	texture = piglit_rgbw_texture(GL_RGBA, 16, 16, GL_FALSE, GL_FALSE,
				      GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	handle = glGetTextureHandleARB(texture);
	glMakeTextureHandleResidentARB(handle);
	glMakeTextureHandleNonResidentARB(handle);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	/* Not resident */
	glMakeTextureHandleNonResidentARB(handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_IsTextureHandleResident_invalid_operations(void *data)
{
	GLboolean ret;

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION will be generated by
	 *  IsTextureHandleResidentARB and IsImageHandleResidentARB if <handle>
	 *  is not a valid texture or image handle, respectively."
	 */
	ret = glIsTextureHandleResidentARB(42);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return ret == false ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
check_GetImageHandle_invalid_values(void *data)
{
	GLuint tex;

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

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_VALUE is generated by GetImageHandleARB if
	 *  <texture> is zero or not the name of an existing texture object, if
	 *  the image for <level> does not existing in <texture>, or if
	 *  <layered> is FALSE and <layer> is greater than or equal to the
	 *  number of layers in the image at <level>."
	 *
	 * NOTE: The spec says nothing about invalid formats, but I think it
	 * makes sense to return INVALID_VALUE like BindImageTexture().
	 */

	/* Invalid texture */
	glGetImageHandleARB(42, 0, GL_FALSE, 0, GL_RGBA32F);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	tex = piglit_rgbw_texture(GL_RGBA32F, 16, 16, GL_TRUE, GL_FALSE,
				  GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Invalid level */
	glGetImageHandleARB(tex, 42, GL_FALSE, 0, GL_RGBA32F);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	/* Invalid layer */
	glGetImageHandleARB(tex, 0, GL_FALSE, 42, GL_RGBA32F);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	/* Invalid format */
	glGetImageHandleARB(tex, 0, GL_FALSE, 0, GL_RGBA);
	if (!piglit_check_gl_error(GL_INVALID_VALUE))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_GetImageHandle_invalid_operations(void *data)
{
	GLuint tex;

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

	glGenTextures(1, &tex);
	glBindTexture(GL_TEXTURE_2D, tex);

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by GetImageHandleARB if
	 *  the texture object <texture> is not complete or if <layered> is
	 *  TRUE and <texture> is not a three-dimensional, one-dimensional
	 *  array, two dimensional array, cube map, or cube map array texture."
	 */

	/* Incomplete texture */
	glGetImageHandleARB(tex, 0, GL_FALSE, 0, GL_RGBA32F);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	tex = piglit_rgbw_texture(GL_RGBA32F, 16, 16, GL_FALSE, GL_FALSE,
				  GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Not layered texture */
	glGetImageHandleARB(tex, 0, GL_TRUE, 0, GL_RGBA32F);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_MakeImageHandleResident_invalid_enum(void *data)
{
	GLuint64 handle;
	GLuint tex;

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

	/* The ARB_bindless_texture spec says:
	 *
	 * "To make an image handle accessible to shaders for image loads,
	 *  stores, and atomic operations, the handle must be made resident by
	 *  calling:
	 *
	 *  void MakeImageHandleResidentARB(uint64 handle, enum access);
	 *
	 *  <access> specifies whether the texture bound to the image handle
	 *  will be treated as READ_ONLY, WRITE_ONLY, or READ_WRITE."
	 *
	 * NOTE: The spec doesn't explicitely say that INVALID_ENUM should
	 * be returned, but I think it makes sense (like NVIDIA).
	 */
	tex = piglit_rgbw_texture(GL_RGBA32F, 16, 16, GL_FALSE, GL_FALSE,
				  GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* Invalid access */
	handle = glGetImageHandleARB(tex, 0, GL_FALSE, 0, GL_RGBA32F);
	glMakeImageHandleResidentARB(handle, GL_DEPTH);
	if (!piglit_check_gl_error(GL_INVALID_ENUM))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_MakeImageHandleResident_invalid_operations(void *data)
{
	GLuint64 handle;
	GLuint tex;

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

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by
	 *  MakeImageHandleResidentARB if <handle> is not a valid image handle,
	 *  or if <handle> is already resident in the current GL context."
	 */

	/* Invalid handle */
	glMakeImageHandleResidentARB(42, GL_READ_WRITE);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	tex = piglit_rgbw_texture(GL_RGBA32F, 16, 16, GL_FALSE, GL_FALSE,
				  GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	handle = glGetImageHandleARB(tex, 0, GL_FALSE, 0, GL_RGBA32F);
	glMakeImageHandleResidentARB(handle, GL_READ_WRITE);

	/* Handle already resident. */
	glMakeImageHandleResidentARB(handle, GL_READ_WRITE);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_MakeImageHandleNonResident_invalid_operations(void *data)
{
	GLuint64 handle;
	GLuint tex;

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

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION is generated by
	 *  MakeImageHandleNonResidentARB if <handle> is not a valid image
	 *  handle, or if <handle> is not resident in the current GL context."
	 */

	/* Invalid handle */
	glMakeImageHandleNonResidentARB(42);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	tex = piglit_rgbw_texture(GL_RGBA32F, 16, 16, GL_FALSE, GL_FALSE,
				  GL_UNSIGNED_NORMALIZED);
	glBindTexture(GL_TEXTURE_2D, 0);

	handle = glGetImageHandleARB(tex, 0, GL_FALSE, 0, GL_RGBA32F);
	glMakeImageHandleResidentARB(handle, GL_READ_WRITE);
	glMakeImageHandleNonResidentARB(handle);
	if (!piglit_check_gl_error(GL_NO_ERROR))
		return PIGLIT_FAIL;

	/* Handle not resident. */
	glMakeImageHandleNonResidentARB(handle);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return PIGLIT_PASS;
}

static enum piglit_result
check_IsImageHandleResident_invalid_operations(void *data)
{
	GLboolean ret;

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

	/* The ARB_bindless_texture spec says:
	 *
	 * "The error INVALID_OPERATION will be generated by
	 *  IsTextureHandleResidentARB and IsImageHandleResidentARB if <handle>
	 *  is not a valid texture or image handle, respectively."
	 */
	ret = glIsImageHandleResidentARB(42);
	if (!piglit_check_gl_error(GL_INVALID_OPERATION))
		return PIGLIT_FAIL;

	return ret == false ? PIGLIT_PASS : PIGLIT_FAIL;
}

static const struct piglit_subtest subtests[] = {
	{
		"Check glGetTexture*HandleARB() invalid values",
		"check_GetTextureHandle_invalid_values",
		check_GetTextureHandle_invalid_values,
		NULL
	},
	{
		"Check glGetTexture*HandleARB() invalid operations",
		"check_GetTextureHandle_invalid_operations",
		check_GetTextureHandle_invalid_operations,
		NULL
	},
	{
		"Check glMakeTextureHandleResidentARB() invalid operations",
		"check_MakeTextureHandleResident_invalid_operations",
		check_MakeTextureHandleResident_invalid_operations,
		NULL
	},
	{
		"Check glMakeTextureHandleNonResidentARB() invalid operations",
		"check_MakeTextureHandleNonResident_invalid_operations",
		check_MakeTextureHandleNonResident_invalid_operations,
		NULL
	},
	{
		"Check glIsTextureHandleResidentARB() invalid operations",
		"check_IsTextureHandleResident_invalid_operations",
		check_IsTextureHandleResident_invalid_operations,
		NULL
	},
	{
		"Check glGetImageHandleARB() invalid values",
		"check_GetImageHandle_invalid_values",
		check_GetImageHandle_invalid_values,
		NULL
	},
	{
		"Check glGetImageHandleARB() invalid operations",
		"check_GetImageHandle_invalid_operations",
		check_GetImageHandle_invalid_operations,
		NULL
	},
	{
		"Check glMakeImageHandleResidentARB() invalid enum",
		"check_MakeImageHandleResident_invalid_enum",
		check_MakeImageHandleResident_invalid_enum,
		NULL
	},
	{
		"Check glMakeImageHandleResidentARB() invalid operations",
		"check_MakeImageHandleResident_invalid_operations",
		check_MakeImageHandleResident_invalid_operations,
		NULL
	},
	{
		"Check glMakeImageHandleNonResidentARB() invalid operations",
		"check_MakeImageHandleNonResident_invalid_operations",
		check_MakeImageHandleNonResident_invalid_operations,
		NULL
	},
	{
		"Check glIsImageHandleResidentARB() invalid operations",
		"check_IsImageHandleResident_invalid_operations",
		check_IsImageHandleResident_invalid_operations,
		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);
}
