/*
 * Copyright 2015 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 builtin.c
 *
 * Tests GLSL's imageSize builtin. The code is highly-based on
 * image_load_store's max-size test from Francisco Jerez. It also uses the
 * grid framework he introduced.
 *
 * From GL_ARB_shader_image_size's spec:
 *
 * "Including the following line in a shader can be used to control the
 * language features described in this extension:
 *
 *      #extension GL_ARB_shader_image_size
 *
 * A new preprocessor #define is added to the OpenGL Shading Language:
 *
 *      #define GL_ARB_shader_image_size 1
 *
 * Add to section 8.11 "Image Functions"
 *
 * Syntax:
 *      int imageSize(gimage1D image)
 *      ivec2 imageSize(gimage2D image)
 *      ivec3 imageSize(gimage3D image)
 *      ivec2 imageSize(gimageCube image)
 *      ivec3 imageSize(gimageCubeArray image)
 *      ivec2 imageSize(gimageRect image)
 *      ivec2 imageSize(gimage1DArray image)
 *      ivec3 imageSize(gimage2DArray image)
 *      int imageSize(gimageBuffer image)
 *      ivec2 imageSize(gimage2DMS image)
 *      ivec3 imageSize(gimage2DMSArray image)
 *
 * Description:
 *
 *      Returns the dimensions of the image or images bound to <image>.  For
 *      arrayed images, the last component of the return value will hold the
 *      size of the array.  Cube images return the dimensions of one face, and
 *      number of cubes in the cube map array, if arrayed."
 */

#include "../arb_shader_image_load_store/common.h"
#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_core_version = 32;

	config.window_visual = PIGLIT_GL_VISUAL_RGBA |
		PIGLIT_GL_VISUAL_DOUBLE;

PIGLIT_GL_TEST_CONFIG_END

static bool
randomize_image(const struct image_info img, unsigned unit)
{
	const unsigned n = image_num_components(img.format) * product(img.size);
	uint32_t *pixels = malloc(sizeof(uint32_t) * n);
	bool ret;

	ret = upload_image(img, unit, pixels);

	free(pixels);
	return ret;
}

static bool
check(const struct grid_info grid, struct image_info img_src)
{
	struct image_info img = image_info_for_grid(grid);
	const unsigned n = image_num_components(img.format) * product(img.size);
	uint32_t *pixels = malloc(sizeof(uint32_t) * n);
	bool ret;

	ret = download_result(grid, pixels);

	if (img_src.target->target == GL_TEXTURE_CUBE_MAP_ARRAY) {
		/* Unlike image-load-store's size (exported by the framework
		 * used for this test), image-size reports the third
		 * coordinate of cubemap arrays as the number of cubes and not
		 * the number of faces. Correct for this by dividing by 6.
		 */
		ret &= check_pixels(img, pixels, img_src.size.x, img_src.size.y,
			     img_src.size.z / 6.0, img_src.size.w);
	} else if (img_src.target->target == GL_TEXTURE_CUBE_MAP) {
		/* Unlike image-load-store's size (exported by the framework
		 * used for this test), image-size reports only the size of one
		 * face, not including the number of faces. From the
		 * ARB_shader_image_size extension:
		 * "Cube images return the dimensions of one face."
		 */
		ret &= check_pixels(img, pixels, img_src.size.x, img_src.size.y,
			     1.0, 1.0);
	} else if (image_target_samples(img_src.target) > 1) {
		/* Unlike image-load-store's size (exported by the framework
		 * used for this test), image-size does not report the sample
		 * count as the first component. Let's fix this by shiftling
		 * left the components.
		 */
		ret &= check_pixels(img, pixels, img_src.size.y, img_src.size.z,
					     img_src.size.w, 1.0);
	} else  {
		ret &= check_pixels(img, pixels, img_src.size.x, img_src.size.y,
		                    img_src.size.z, img_src.size.w);
	}

	free(pixels);
	return ret;
}

static bool
run_test(const struct image_format_info *format,
	 const struct image_target_info *target,
	 const struct image_stage_info *stage,
	 const struct image_extent size)
{
	const struct grid_info grid = grid_info(stage->stage, GL_RGBA32I,
						16, 16);
	const struct image_info img = {
		target, format, size,
		image_format_epsilon(grid.format)
	};
	GLuint prog = generate_program(
		grid, stage->stage,
		concat(hunk("#extension GL_ARB_shader_image_size : enable\n"),
		       image_hunk(img, ""),
		       hunk("readonly IMAGE_UNIFORM_T src_img;\n"
			    "\n"
			    "GRID_T op(ivec2 idx, GRID_T x) {\n"
			    "        return ivec4(imageSize(src_img), ivec3(1));\n"
			    "}\n"), NULL));
	bool ret = prog && init_fb(grid) &&
		randomize_image(img, 0) &&
		set_uniform_int(prog, "src_img", 0) &&
		draw_grid(grid, prog) &&
		check(grid, img);

	glDeleteProgram(prog);
	return ret;
}

static struct image_extent
get_test_extent(const struct image_target_info *target, unsigned d)
{
	const struct image_extent ls = image_target_limits(target);
	const unsigned high = ~0, low = 8;
	struct image_extent ext;
	int i;

	for (i = 0; i < 4; ++i)
		set_idx(ext, i, MIN2(get_idx(ls, i), (i == d ? high : low)));

	if (target->target == GL_TEXTURE_CUBE_MAP ||
	    target->target == GL_TEXTURE_CUBE_MAP_ARRAY) {
		/* Cube maps have to be square and the number of faces
		 * should be a multiple of six. */
		ext.y = ext.x;
		ext.z = 6 * MAX2(ext.z / 6, 1);

	} else if (image_target_samples(target) > 1) {
		/* Use the maximum number of samples to keep things
		 * interesting. */
		ext.x = image_target_samples(target);
	}

	return ext;
}

static bool
should_test_dimension(const struct image_target_info *target, int d)
{
	const struct image_extent ls = image_target_limits(target);

	return get_idx(ls, d) > 1 &&
		/* Skip second cube map dimension as faces have to be
		 * square. */
		!(target->target == GL_TEXTURE_CUBE_MAP && d >= 1) &&
		!(target->target == GL_TEXTURE_CUBE_MAP_ARRAY && d == 1) &&
		/* Skip sample dimension. */
		!(image_target_samples(target) > 1 && d == 0);
}

static bool
is_test_reasonable(bool quick, const struct image_extent size)
{
	/* Set an arbitrary limit on the number of texels so the test
	 * doesn't take forever. */
	return product(size) < (quick ? 4 : 64) * 1024 * 1024;
}

static bool
is_format_interesting(const struct image_format_info *format, bool override)
{
	switch(format->format)
	{
	case GL_RGBA32F:
	case GL_RGBA16F:
	case GL_RGBA32I:
	case GL_RGBA16I:
	case GL_RGBA8I:
	case GL_RGBA32UI:
	case GL_RGBA16UI:
	case GL_RGBA8UI:
		return true;
	default:
		return override;
	}
}

static bool
is_stage_interesting(const struct image_stage_info *stage, bool override)
{
	switch(stage->stage)
	{
	case GL_FRAGMENT_SHADER:
	case GL_COMPUTE_SHADER:
		return true;
	default:
		return override;
	}
}

static void
test_max_dimensions(const struct image_format_info *format,
		    const struct image_target_info *target,
		    const struct image_stage_info *stage,
		    enum piglit_result *status,
		    bool quick, bool slow)
{
	int d;
	for (d = 0; d < 4; ++d) {
		if (should_test_dimension(target, d)) {
			const struct image_extent size =
					get_test_extent(target, d);

			if (quick ||
			    !is_test_reasonable(!slow, size) ||
			    !is_format_interesting(format, slow) ||
			    !is_stage_interesting(stage, slow))
				continue;

			subtest(status, true,
				run_test(format, target, stage, size),
				"%s/%s/image%s max size test/%dx%dx%dx%d",
				format->name, stage->name, target->name,
				size.x, size.y, size.z,	size.w);
		}
	}
}

static void
test_small_dimensions(const struct image_format_info *format,
		      const struct image_target_info *target,
		      const struct image_stage_info *stage,
		      enum piglit_result *status,
		      bool slow)
{
	const struct image_extent size =
			image_extent_for_target(target,
						16, 96);

	if (!is_format_interesting(format, slow) ||
	    !is_stage_interesting(stage, slow))
		return;

	subtest(status, true,
		run_test(format, target, stage, size),
		"%s/%s/image%s size test/%dx%dx%dx%d",
		format->name, stage->name, target->name,
		size.x, size.y, size.z,	size.w);
}

void
piglit_init(int argc, char **argv)
{
	const bool quick = (argc >= 2 && !strcmp(argv[1], "--quick"));
	const bool slow = (argc >= 2 && !strcmp(argv[1], "--slow"));
	enum piglit_result status = PIGLIT_PASS;
	const struct image_format_info *format;
	const struct image_target_info *target;
	const struct image_stage_info *stage;

	/* The spec of the extension says we should require GL 4.2 but let's
	 * just request GL_ARB_shader_image_size which will in turn require
	 * GL_ARB_shader_image_load_store which should be sufficient.
	 */
	piglit_require_extension("GL_ARB_shader_image_size");

	for (format = image_formats_load_store; format->format; ++format) {
		for (stage = image_stages(); stage->stage; ++stage) {
			for (target = image_targets(); target->name; ++target) {
				test_small_dimensions(format, target,
						     stage, &status,
						     slow);

				test_max_dimensions(format, target,
						    stage, &status,
						    quick, slow);
			}
		}
	}

	piglit_report_result(status);
}

enum piglit_result
piglit_display(void)
{
	return PIGLIT_FAIL;
}
