/*
 * Copyright © 2012 Marek Olšák <maraeo@gmail.com>
 * Copyright © 2014 Intel 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 getcompressedtextureimage.c
 */
#include "piglit-util-gl.h"
#include "dsa-utils.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.window_width = 216;
	config.supports_gl_compat_version = 20;
	config.supports_gl_core_version = 31;

	config.window_visual = PIGLIT_GL_VISUAL_RGBA |
			       PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

#define IMAGE_WIDTH 32
#define IMAGE_HEIGHT 32
#define IMAGE_SIZE (IMAGE_WIDTH * IMAGE_HEIGHT * 4)
#define DISPLAY_GAP 4

static void
show_image(GLubyte *data, int num_layers, const char *title)
{
	GLuint name;
	int i;
	char junk[50];
	GLuint prog = 0;

	if (!piglit_automatic) {
		prog = dsa_create_program(GL_TEXTURE_2D);
		glUseProgram(prog);
		dsa_set_xform(prog, piglit_width, piglit_height);

		/* Create the texture handle. */
		glCreateTextures(GL_TEXTURE_2D, 1, &name);
		glTextureStorage2D(name, 1, GL_RGBA8, IMAGE_WIDTH,
			IMAGE_HEIGHT);
		glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glBindTextureUnit(0, name);

		/* Draw the layers, separated by some space */
		glClear(GL_COLOR_BUFFER_BIT);
		for (i = 0; i < num_layers; ++i) {
			int x = (IMAGE_WIDTH + DISPLAY_GAP) * (i % 6);
			int y = (IMAGE_HEIGHT + DISPLAY_GAP) * (i / 6);
			glTextureSubImage2D(name, 0, 0, 0,
					    IMAGE_WIDTH, IMAGE_HEIGHT,
					    GL_RGBA, GL_UNSIGNED_BYTE,
					    data + i * IMAGE_SIZE);
			piglit_draw_rect_tex(x, y, IMAGE_WIDTH, IMAGE_HEIGHT,
					     0, 0, 1, 1);
		}

		/* Make the title. */
		printf("****** %s ******\n", title);

		piglit_present_results();

		/* Pause. */
		printf("Enter any char to continue.\n>>>>>>");
		(void) scanf("%s", junk);
		printf("\n");

		glDeleteTextures(1, &name);
		glUseProgram(0);
		glDeleteProgram(prog);
	}
}

static GLubyte *
make_layer_data(int num_layers)
{
	int z;
	GLubyte *layer_data =
		malloc(num_layers * IMAGE_SIZE * sizeof(GLubyte));
	GLubyte *data = piglit_rgbw_image_ubyte(IMAGE_WIDTH,
						IMAGE_HEIGHT, true);

	for (z = 0; z < num_layers; z++) {
		memcpy(layer_data + IMAGE_SIZE * z, data, IMAGE_SIZE);
	}

	free(data);

	/* Show the first layer of the completed layer data. */
	show_image(layer_data, num_layers, "Test Data");

	return layer_data;
}

static bool
compare_layer(int layer, int num_elements, int tolerance,
			  GLubyte *data, GLubyte *expected)
{
	int i;

	for (i = 0; i < num_elements; ++i) {
		if (abs((int)data[i] - (int)expected[i]) > tolerance) {
			printf("GetCompressedTextureImage() returns incorrect"
			       " data in byte %i for layer %i\n",
			       i, layer);
			printf("    corresponding to (%i,%i), channel %i\n",
			       (i / 4) / IMAGE_WIDTH, (i / 4) % IMAGE_HEIGHT,
				i % 4);
			printf("    expected: %i\n", expected[i]);
			printf("    got: %i\n", data[i]);
			return false;
		}
	}
	return true;
}

static enum piglit_result
getTexImage(bool doPBO, GLenum target, GLubyte *data,
	    GLenum internalformat, int tolerance)
{
	int i;
	int num_layers=1, num_faces=1, layer_size;
	GLubyte *data2 = NULL;
	GLubyte *dataGet;
	GLuint packPBO;
	bool pass = true;
	GLuint name;
	GLint compressed;
	GLint comp_size;

	/* Upload the data.  These are all targets that can be compressed
	 * according to _mesa_target_can_be_compressed.
	 */
	switch (target) {
	case GL_TEXTURE_2D:
		glCreateTextures(target, 1, &name);
		glTextureStorage2D(name, 1, internalformat, IMAGE_WIDTH,
				   IMAGE_HEIGHT);
		glTextureSubImage2D(name, 0, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT,
				    GL_RGBA, GL_UNSIGNED_BYTE, data);
		layer_size = IMAGE_SIZE;
		break;

	case GL_TEXTURE_CUBE_MAP:
		num_faces = 6;
		glCreateTextures(target, 1, &name);
		if (!piglit_khr_no_error) {
			/* This is invalid. You must use 2D storage call for
			 * cube.
			 */
			glTextureStorage3D(name, 1, internalformat,
					   IMAGE_WIDTH, IMAGE_HEIGHT,
					   num_faces);
			pass &= piglit_check_gl_error(GL_INVALID_OPERATION);
		}
		glTextureStorage2D(name, 1, internalformat,
				   IMAGE_WIDTH, IMAGE_HEIGHT);
		/* This is legal. */
		glTextureSubImage3D(name, 0, 0, 0, 0, IMAGE_WIDTH,
				    IMAGE_HEIGHT, num_faces, GL_RGBA,
				    GL_UNSIGNED_BYTE, data);
		layer_size = IMAGE_SIZE;
		break;

	case GL_TEXTURE_2D_ARRAY:
		num_layers = 7; /* Fall through. */
	case GL_TEXTURE_CUBE_MAP_ARRAY:
		num_layers = 6 * 3;
		glCreateTextures(target, 1, &name);
		glTextureStorage3D(name, 1, internalformat, IMAGE_WIDTH,
				   IMAGE_HEIGHT, num_layers);
		glTextureSubImage3D(name, 0, 0, 0, 0,
				    IMAGE_WIDTH, IMAGE_HEIGHT, num_layers,
				    GL_RGBA, GL_UNSIGNED_BYTE, data);
		layer_size = IMAGE_SIZE;
		break;

	default:
		puts("Invalid texture target.");
		return PIGLIT_FAIL;
	}

	/* Make sure the driver has compressed the image. */
	glGetTextureLevelParameteriv(name, 0, GL_TEXTURE_COMPRESSED,
		&compressed);
	printf("\tIs the texture compressed? %s.\n",
		compressed ? "yes" : "no");

	glGetTextureLevelParameteriv(name, 0,
				     GL_TEXTURE_COMPRESSED_IMAGE_SIZE,
				     &comp_size);
	/* Section 8.11 (Texture Queries) of the OpenGL 4.5 Core Profile spec
	 * says:
	 *
	 *     "For GetTextureLevelParameter* only, texture may also be a cube
	 *     map texture object.  In this case the query is always performed
	 *     for face zero (the TEXTURE_CUBE_MAP_POSITIVE_X face), since
	 *     there is no way to specify another face."
	 */
	if (target == GL_TEXTURE_CUBE_MAP)
		comp_size *= num_faces;
	printf("\the size of the texture in bytes is %d.\n", comp_size);

	/* Show the uncompressed data. */
	show_image(data, num_layers * num_faces, "Data Before Compression");


	/* Setup the PBO or data array to read into from
	 * glGetCompressedTextureImage
	 */
	if (doPBO) {
		glGenBuffers(1, &packPBO);
		glBindBuffer(GL_PIXEL_PACK_BUFFER, packPBO);
		/* Make the buffer big enough to hold uncompressed data. */
		glBufferData(GL_PIXEL_PACK_BUFFER, layer_size * num_faces *
			     num_layers * sizeof(GLubyte),
			     NULL, GL_STREAM_READ);
	} else {
		glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
		data2 = malloc(layer_size * num_faces * num_layers *
			       sizeof(GLubyte));
		memset(data2, 123, layer_size * num_faces * num_layers *
			       sizeof(GLubyte));
	}
	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	assert(num_layers * num_faces * layer_size <= 18 * IMAGE_SIZE);


	/* Download the compressed texture image. */
	if (doPBO)
		glGetCompressedTextureImage(name, 0, comp_size, NULL);
	else
		glGetCompressedTextureImage(name, 0, comp_size, data2);
	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	if (doPBO)
		dataGet = (GLubyte *) glMapBufferRange(
					       GL_PIXEL_PACK_BUFFER, 0,
					       comp_size,
					       GL_MAP_READ_BIT);
	else
		dataGet = data2;

	/* Re-upload the texture in compressed form. */
	switch (target) {
	case GL_TEXTURE_2D:
		glCompressedTextureSubImage2D(name, 0, 0, 0,
					      IMAGE_WIDTH, IMAGE_HEIGHT,
					      internalformat, comp_size,
					      dataGet);
		break;

	case GL_TEXTURE_CUBE_MAP:
		glCompressedTextureSubImage3D(name, 0, 0, 0, 0,
					      IMAGE_WIDTH, IMAGE_HEIGHT,
					      num_faces,
					      internalformat, comp_size,
					      dataGet);
		break;

	case GL_TEXTURE_2D_ARRAY:
	case GL_TEXTURE_CUBE_MAP_ARRAY:
		glCompressedTextureSubImage3D(name, 0, 0, 0, 0,
					      IMAGE_WIDTH, IMAGE_HEIGHT,
					      num_layers,
					      internalformat, comp_size,
					      dataGet);
		break;
	}


	/* Get the uncompressed version for comparison. */
	if (doPBO) {
		glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
		glGetTextureImage(name, 0, GL_RGBA, GL_UNSIGNED_BYTE,
			layer_size * num_layers * num_faces * sizeof(GLubyte),
			NULL);
	}
	else {
		glGetTextureImage(name, 0, GL_RGBA, GL_UNSIGNED_BYTE,
			layer_size * num_layers * num_faces * sizeof(GLubyte),
			data2);
	}
	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;
	if (doPBO)
		dataGet = (GLubyte *) glMapBufferRange(
					       GL_PIXEL_PACK_BUFFER, 0,
					       layer_size * num_layers *
					       num_faces * sizeof(GLubyte),
					       GL_MAP_READ_BIT);
	else
		dataGet = data2;

	/* Examine the image after pulling it off the graphics card. */
	show_image(dataGet, num_layers * num_faces, "Data After Compression");

	/* Do the comparison */
	for (i = 0; i < num_faces * num_layers; i++) {
		pass = compare_layer(i, layer_size, tolerance, dataGet,
				     data + (i * layer_size)) && pass;
		dataGet += layer_size;
	}

	if (doPBO) {
		glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
		glDeleteBuffers(1, &packPBO);
	}

	glDeleteTextures(1, &name);
	free(data2);

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

struct target_and_mask {
	GLenum target;
	bool mask;
};

static struct target_and_mask targets[] = {
	{GL_TEXTURE_2D, 1},
	{GL_TEXTURE_CUBE_MAP, 1},
	{GL_TEXTURE_2D_ARRAY, 1},
	{GL_TEXTURE_CUBE_MAP_ARRAY, 1},
};

static void
clear_target_mask(GLenum target)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(targets); ++i) {
		if (targets[i].target == target) {
			targets[i].mask = 0;
		}
	}
}

void
piglit_init(int argc, char **argv)
{
	piglit_require_extension("GL_ARB_direct_state_access");
	piglit_require_extension("GL_ARB_texture_storage");

	if (!piglit_is_extension_supported("GL_EXT_texture_array"))
		clear_target_mask(GL_TEXTURE_2D_ARRAY);
	if (!piglit_is_extension_supported("GL_ARB_texture_cube_map_array"))
		clear_target_mask(GL_TEXTURE_CUBE_MAP_ARRAY);

	glClearColor(0.5, 0.5, 0.5, 1);
}

enum piglit_result
piglit_display(void)
{
	int i;
	GLenum internalformat = GL_COMPRESSED_RGBA_FXT1_3DFX;
	int tolerance = 8;
	GLubyte *data;
	enum piglit_result subtest;
	enum piglit_result result = PIGLIT_PASS;

	piglit_require_extension("GL_3DFX_texture_compression_FXT1");

	data = make_layer_data(18);

	for (i = 0; i < ARRAY_SIZE(targets); ++i) {
		if (!targets[i].mask)
			continue;

		printf("Testing %s into PBO\n",
			piglit_get_gl_enum_name(targets[i].target));
		subtest = getTexImage(true, targets[i].target, data,
				      internalformat, tolerance);
		piglit_report_subtest_result(subtest, "getTexImage %s PBO",
					     piglit_get_gl_enum_name(
						targets[i].target));
		if (subtest == PIGLIT_FAIL)
			result = PIGLIT_FAIL;

		printf("\n"); /* Separate tests with some white space. */

		printf("Testing %s into client array\n",
			piglit_get_gl_enum_name(targets[i].target));
		subtest = getTexImage(false, targets[i].target, data,
				      internalformat, tolerance);
		piglit_report_subtest_result(subtest, "getTexImage %s",
					     piglit_get_gl_enum_name(
						targets[i].target));
		if (subtest == PIGLIT_FAIL)
			result = PIGLIT_FAIL;

		printf("\n\n"); /* Separate targets with some white space. */

		if (!piglit_check_gl_error(GL_NO_ERROR))
			result = PIGLIT_FAIL;
	}

	/* 1D targets can't be compressed in Mesa right now, but here is a
	 * trivial test for the entry point.
	 */
	glCompressedTextureSubImage1D(250, 0, 0, 60,
				      internalformat, 60*4*8,
				      NULL);

	if (!piglit_khr_no_error) {
		 /* Bad texture */
		if (!piglit_check_gl_error(GL_INVALID_OPERATION))
			subtest = PIGLIT_FAIL;
		else
			subtest = PIGLIT_PASS;
		piglit_report_subtest_result(subtest, "Compressed Texture"
					     " Sub Image 1D");
		if (subtest == PIGLIT_FAIL)
			result = PIGLIT_FAIL;
	}

	free(data);

	return result;
}
