/*
 * Copyright © 2011 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 common.c
 *
 * Helper functions for GLSL 1.30+/EXT_gpu_shader4 texturing tests.
 */
#include "common.h"

int miplevels;
int base_size[3];
int **level_size;
struct sampler_info sampler;
bool swizzling;
int minx, miny, minz, maxx, maxy, maxz;
int sample_count;
int shader_version = 130;
bool gpu_shader4 = false;

/**
 * Load a miplevel's texel data via glTexImage.
 *
 * \param level         The miplevel to be loaded (0 <= level <= miplevels)
 * \param level_image   The data to be loaded.
 *
 * This function assumes that select_sampler() and compute_miplevel_info()
 * have already been called.
 */
void
upload_miplevel_data(GLenum target, int level, void *level_image)
{
	const GLenum format          = sampler.format;
	const GLenum internal_format = sampler.internal_format;
	const GLenum data_type       = sampler.data_type;
	GLuint bo;

	switch (target) {
	case GL_TEXTURE_1D:
		glTexImage1D(GL_TEXTURE_1D, level, internal_format,
			     level_size[level][0],
			     0, format, data_type, level_image);
		break;
	case GL_TEXTURE_2D:
	case GL_TEXTURE_RECTANGLE:
	case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
	case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
	case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
	case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
	case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
	case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
		glTexImage2D(target, level, internal_format,
			     level_size[level][0], level_size[level][1],
			     0, format, data_type, level_image);
		break;
	case GL_TEXTURE_3D:
	case GL_TEXTURE_2D_ARRAY:
	case GL_TEXTURE_CUBE_MAP_ARRAY:
		glTexImage3D(target, level, internal_format,
			     level_size[level][0],
			     level_size[level][1],
			     level_size[level][2],
			     0, format, data_type, level_image);
		break;
	case GL_TEXTURE_1D_ARRAY:
		glTexImage2D(GL_TEXTURE_1D_ARRAY, level, internal_format,
			     level_size[level][0], level_size[level][2],
			     0, format, data_type, level_image);
		break;

	case GL_TEXTURE_BUFFER:
		glGenBuffers(1, &bo);
		glBindBuffer(GL_TEXTURE_BUFFER, bo);
		glBufferData(GL_TEXTURE_BUFFER, 16 * level_size[level][0],
			     level_image, GL_STATIC_DRAW);
		glTexBuffer(GL_TEXTURE_BUFFER, internal_format, bo);
		break;

	case GL_TEXTURE_2D_MULTISAMPLE:
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4,
				internal_format,
				level_size[level][0],
				level_size[level][1],
				GL_TRUE);
		break;
	
	case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
		glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, 4,
				internal_format,
				level_size[level][0],
				level_size[level][1],
				level_size[level][2],
				GL_TRUE);
		break;

	default:
		assert(!"Not implemented yet.");
		break;
	}
}


float max2(float x, float y)
{
	return (x > y) ? x : y;
}

static float max3(float x, float y, float z)
{
	return max2(x, max2(y, z));
}

/**
 * Compute the number of miplevels, as well as the dimensions (width, height,
 * depth/number of array slices) of each level.
 *
 * This function assumes base_size is already set.
 */
void
compute_miplevel_info()
{
	int i, l;
	bool is_array = is_array_sampler();
	int max_dimension;

	/* Compute the number of miplevels */
	if (sampler.target == GL_TEXTURE_3D)
		max_dimension = max3(base_size[0], base_size[1], base_size[2]);
	else
		max_dimension = max2(base_size[0], base_size[1]);

	miplevels = (int) log2f(max_dimension) + 1;

	if (sampler.target == GL_TEXTURE_RECTANGLE ||
	    sampler.target == GL_TEXTURE_BUFFER)
		miplevels = 1;
	if (sampler.target == GL_TEXTURE_2D_MULTISAMPLE ||
		sampler.target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
		miplevels = sample_count;

	/* Compute the size of each miplevel */
	level_size = malloc(miplevels * sizeof(int *));

	level_size[0] = malloc(3 * sizeof(int));
	memcpy(level_size[0], base_size, 3 * sizeof(int));
	for (l = 1; l < miplevels; l++) {
		level_size[l] = malloc(3 * sizeof(int));

		for (i = 0; i < 3 - is_array; i++)
			if (has_samples())
				/* same size for all sample planes */
				level_size[l][i] = level_size[l-1][i];
			else
				level_size[l][i] = max2(level_size[l-1][i] / 2, 1);

		if (is_array)
			level_size[l][2] = base_size[2];
	}
}

void
free_miplevel_info() {
	free(level_size[0]);
	for (int i = 1; i < miplevels; i++) {
		free(level_size[i]);
	}
	free(level_size);
}

bool
has_height()
{
	return sampler.target == GL_TEXTURE_2D ||
	       sampler.target == GL_TEXTURE_3D ||
	       sampler.target == GL_TEXTURE_2D_ARRAY ||
	       sampler.target == GL_TEXTURE_RECTANGLE ||
	       sampler.target == GL_TEXTURE_2D_MULTISAMPLE ||
	       sampler.target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
}

bool
has_slices()
{
	return is_array_sampler() || sampler.target == GL_TEXTURE_3D;
}

bool
has_samples()
{
    return sampler.target == GL_TEXTURE_2D_MULTISAMPLE ||
           sampler.target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
}

bool
is_array_sampler()
{
	return sampler.target == GL_TEXTURE_1D_ARRAY ||
	       sampler.target == GL_TEXTURE_2D_ARRAY ||
	       sampler.target == GL_TEXTURE_CUBE_MAP_ARRAY ||
	       sampler.target == GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
}

bool
is_shadow_sampler()
{
	return sampler.format == GL_DEPTH_COMPONENT;
}

/**
 * Check if a given command line argument is a valid GLSL sampler type.
 * If so, infer dimensionality and data format based on the name.
 *
 * Returns \c true if \p name was a valid sampler.
 */
bool
select_sampler(const char *name)
{
	int i;
	bool found = false;
	struct {
		const char *name;
		GLenum type;
		GLenum target;
	} samplers[] = {
		{ "sampler1D",              GL_SAMPLER_1D,                          GL_TEXTURE_1D,            },
		{ "sampler2D",              GL_SAMPLER_2D,                          GL_TEXTURE_2D,            },
		{ "sampler3D",              GL_SAMPLER_3D,                          GL_TEXTURE_3D,            },
		{ "samplerCube",            GL_SAMPLER_CUBE,                        GL_TEXTURE_CUBE_MAP       },
		{ "sampler2DRect",          GL_SAMPLER_2D_RECT,                     GL_TEXTURE_RECTANGLE      },
		{ "sampler1DArray",         GL_SAMPLER_1D_ARRAY,                    GL_TEXTURE_1D_ARRAY       },
		{ "sampler2DArray",         GL_SAMPLER_2D_ARRAY,                    GL_TEXTURE_2D_ARRAY       },
		{ "samplerCubeArray",       GL_SAMPLER_CUBE_MAP_ARRAY,              GL_TEXTURE_CUBE_MAP_ARRAY },
		{ "samplerBuffer",          GL_SAMPLER_BUFFER,                      GL_TEXTURE_BUFFER },
		{ "sampler2DMS",            GL_SAMPLER_2D_MULTISAMPLE,              GL_TEXTURE_2D_MULTISAMPLE },
		{ "sampler2DMSArray",       GL_SAMPLER_2D_MULTISAMPLE_ARRAY,        GL_TEXTURE_2D_MULTISAMPLE_ARRAY },

		{ "sampler1DShadow",        GL_SAMPLER_1D_SHADOW,                   GL_TEXTURE_1D             },
		{ "sampler2DShadow",        GL_SAMPLER_2D_SHADOW,                   GL_TEXTURE_2D             },
		{ "samplerCubeShadow",      GL_SAMPLER_CUBE_SHADOW,                 GL_TEXTURE_CUBE_MAP       },
		{ "sampler2DRectShadow",    GL_SAMPLER_2D_RECT_SHADOW,              GL_TEXTURE_RECTANGLE      },
		{ "sampler1DArrayShadow",   GL_SAMPLER_1D_ARRAY_SHADOW,             GL_TEXTURE_1D_ARRAY       },
		{ "sampler2DArrayShadow",   GL_SAMPLER_2D_ARRAY_SHADOW,             GL_TEXTURE_2D_ARRAY       },
		{ "samplerCubeArrayShadow", GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW,       GL_TEXTURE_CUBE_MAP_ARRAY },

		{ "isampler1D",             GL_INT_SAMPLER_1D,                      GL_TEXTURE_1D             },
		{ "isampler2D",             GL_INT_SAMPLER_2D,                      GL_TEXTURE_2D             },
		{ "isampler3D",             GL_INT_SAMPLER_3D,                      GL_TEXTURE_3D             },
		{ "isamplerCube",           GL_INT_SAMPLER_CUBE,                    GL_TEXTURE_CUBE_MAP       },
		{ "isampler2DRect",         GL_INT_SAMPLER_2D_RECT,                 GL_TEXTURE_RECTANGLE      },
		{ "isampler1DArray",        GL_INT_SAMPLER_1D_ARRAY,                GL_TEXTURE_1D_ARRAY       },
		{ "isampler2DArray",        GL_INT_SAMPLER_2D_ARRAY,                GL_TEXTURE_2D_ARRAY       },
		{ "isamplerCubeArray",      GL_INT_SAMPLER_CUBE_MAP_ARRAY,          GL_TEXTURE_CUBE_MAP_ARRAY },
		{ "isamplerBuffer",         GL_INT_SAMPLER_BUFFER,                  GL_TEXTURE_BUFFER },
		{ "isampler2DMS",           GL_INT_SAMPLER_2D_MULTISAMPLE,          GL_TEXTURE_2D_MULTISAMPLE },
		{ "isampler2DMSArray",      GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY,    GL_TEXTURE_2D_MULTISAMPLE_ARRAY },

		{ "usampler1D",             GL_UNSIGNED_INT_SAMPLER_1D,             GL_TEXTURE_1D             },
		{ "usampler2D",             GL_UNSIGNED_INT_SAMPLER_2D,             GL_TEXTURE_2D             },
		{ "usampler3D",             GL_UNSIGNED_INT_SAMPLER_3D,             GL_TEXTURE_3D             },
		{ "usamplerCube",           GL_UNSIGNED_INT_SAMPLER_CUBE,           GL_TEXTURE_CUBE_MAP       },
		{ "usampler2DRect",         GL_UNSIGNED_INT_SAMPLER_2D_RECT,        GL_TEXTURE_RECTANGLE      },
		{ "usampler1DArray",        GL_UNSIGNED_INT_SAMPLER_1D_ARRAY,       GL_TEXTURE_1D_ARRAY       },
		{ "usampler2DArray",        GL_UNSIGNED_INT_SAMPLER_2D_ARRAY,       GL_TEXTURE_2D_ARRAY       },
		{ "usamplerCubeArray",      GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY, GL_TEXTURE_CUBE_MAP_ARRAY },
		{ "usamplerBuffer",         GL_UNSIGNED_INT_SAMPLER_BUFFER,         GL_TEXTURE_BUFFER },
		{ "usampler2DMS",           GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE, GL_TEXTURE_2D_MULTISAMPLE },
		{ "usampler2DMSArray",      GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY, GL_TEXTURE_2D_MULTISAMPLE_ARRAY },
	};

	for (i = 0; i < ARRAY_SIZE(samplers); i++) {
		if (strcmp(samplers[i].name, name) == 0) {
			found = true;
			break;
		}
	}

	if (!found)
		return false;

	sampler.name = name;
	sampler.type = samplers[i].type;
	sampler.target = samplers[i].target;

	/* Use 32bpc sized formats where possible; drop down to 16bpc for
	 * testing multisample targets to avoid hitting some hardware limits.
	 * on i965.
	 */

	if (name[0] == 'i') {
		sampler.data_type = GL_INT;
		sampler.format = GL_RGBA_INTEGER;
		sampler.internal_format = has_samples() ? GL_RGBA16I : GL_RGBA32I;
		sampler.return_type = "ivec4";
	} else if (name[0] == 'u') {
		sampler.data_type = GL_UNSIGNED_INT;
		sampler.format = GL_RGBA_INTEGER;
		sampler.internal_format = has_samples() ? GL_RGBA16UI : GL_RGBA32UI;
		sampler.return_type = "uvec4";
	} else if (strstr(name, "Shadow")) {
		/* Shadow Sampler */
		sampler.data_type = GL_FLOAT;
		sampler.format = GL_DEPTH_COMPONENT;
		sampler.internal_format = GL_DEPTH_COMPONENT;
		sampler.return_type = "float";
	} else {
		sampler.data_type = GL_FLOAT;
		sampler.format = GL_RGBA;
		sampler.internal_format = has_samples() ? GL_RGBA16F : GL_RGBA32F;
		sampler.return_type = "vec4";
	}

	return true;
}

/**
 * Ensures the driver supports the required extensions, GL, and GLSL versions.
 * If it doesn't, report PIGLIT_SKIP and exit the test.
 */
void
require_GL_features(enum shader_target test_stage)
{
	int tex_units;

	piglit_require_GLSL_version(shader_version);

	if (gpu_shader4)
		piglit_require_extension("GL_EXT_gpu_shader4");

	if (swizzling)
		piglit_require_extension("GL_EXT_texture_swizzle");

	switch (sampler.internal_format) {
	case GL_RGBA32I:
	case GL_RGBA16I:
		piglit_require_extension("GL_EXT_texture_integer");
		break;
	case GL_RGBA32UI:
	case GL_RGBA16UI:
		if (piglit_is_extension_supported("GL_EXT_gpu_shader4"))
			piglit_require_gl_version(21);
		else
			piglit_require_gl_version(30);
		break;
	case GL_RGBA32F:
	case GL_RGBA16F:
		piglit_require_extension("GL_ARB_texture_float");
		break;
	}

	switch (sampler.target) {
	case GL_TEXTURE_CUBE_MAP_ARRAY:
		piglit_require_extension("GL_ARB_texture_cube_map_array");
		break;
	case GL_TEXTURE_1D_ARRAY:
	case GL_TEXTURE_2D_ARRAY:
		piglit_require_extension("GL_EXT_texture_array");
		break;
	case GL_TEXTURE_CUBE_MAP:
		if (is_shadow_sampler()) {
			if (piglit_is_extension_supported("GL_EXT_gpu_shader4"))
				piglit_require_gl_version(21);
			else
				piglit_require_gl_version(30);
		}
		break;
	case GL_TEXTURE_RECTANGLE:
		piglit_require_extension("GL_ARB_texture_rectangle");
		break;
	case GL_TEXTURE_BUFFER:
		piglit_require_extension("GL_ARB_texture_buffer_object");
		break;
	case GL_TEXTURE_2D_MULTISAMPLE:
	case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
		piglit_require_extension("GL_ARB_texture_multisample");
	}

	/* If testing in the VS, check for VS texture units */
	glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &tex_units);
	if (test_stage == VS && tex_units <= 0)
		piglit_report_result(PIGLIT_SKIP);

	if (test_stage == TES)
		piglit_require_extension("GL_ARB_tessellation_shader");
}

/**
 * Performs an in-place swizzle of a vec4 based on the EXT_texture_swizzle mode.
 */
void
swizzle(float vec[])
{
	int i;
	float temp[4];

	if (!swizzling)
		return;

	memcpy(temp, vec, 4*sizeof(float));

	for (i = 0; i < 4; i++) {
		switch (sampler.swizzle[i]) {
		case GL_RED:
			vec[i] = temp[0];
			break;
		case GL_GREEN:
			vec[i] = temp[1];
			break;
		case GL_BLUE:
			vec[i] = temp[2];
			break;
		case GL_ALPHA:
			vec[i] = temp[3];
			break;
		case GL_ZERO:
			vec[i] = 0.0;
			break;
		case GL_ONE:
			vec[i] = 1.0;
			break;
		default:
			assert(!"Should not get here.");
		}
	}
}

/**
 * Parse the command line argument for the EXT_texture_swizzle mode.
 * It should be a string of length 4 consisting of r, g, b, a, 0, or 1.
 * For example, "bgr1".
 */
bool
parse_swizzle(const char *swiz)
{
	int i;
	if (strlen(swiz) != 4 || strspn(swiz, "rgba01") != 4)
		return false;

	for (i = 0; i < 4; i++) {
		switch (swiz[i]) {
		case 'r':
			sampler.swizzle[i] = GL_RED;
			break;
		case 'g':
			sampler.swizzle[i] = GL_GREEN;
			break;
		case 'b':
			sampler.swizzle[i] = GL_BLUE;
			break;
		case 'a':
			sampler.swizzle[i] = GL_ALPHA;
			break;
		case '0':
			sampler.swizzle[i] = GL_ZERO;
			break;
		case '1':
			sampler.swizzle[i] = GL_ONE;
			break;
		default:
			assert(!"Should not get here.");
		}
	}

	return true;
}
