/*
 * Copyright (C) 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 max-size.c
 *
 * Check that images work as expected up to the maximum texture size
 * reported for each target.
 */

#include "common.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

config.supports_gl_core_version = 32;

config.window_width = 1;
config.window_height = 1;
config.window_visual = PIGLIT_GL_VISUAL_DOUBLE | PIGLIT_GL_VISUAL_RGBA;
config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static bool
init_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;
        int i;

        for (i = 0; i < n; ++i)
                pixels[i] = (unit == 0 ? encode(img.format, i) : 0);

        ret = upload_image(img, unit, pixels);

        free(pixels);
        return ret;
}

static bool
check(const struct image_info img)
{
        const unsigned n = image_num_components(img.format) * product(img.size);
        uint32_t *pixels = malloc(sizeof(uint32_t) * n);
        uint32_t *expect = malloc(sizeof(uint32_t) * n);
        bool ret;
        int i;

        for (i = 0; i < n; ++i)
                expect[i] = encode(img.format, i);

        ret = download_image(img, 1, pixels) &&
                check_pixels_v(img, pixels, expect);

        free(expect);
        free(pixels);
        return ret;
}

static bool
run_test(const struct image_target_info *target,
         const struct image_extent size)
{
        const struct grid_info grid = {
                GL_FRAGMENT_SHADER_BIT,
                get_image_format(GL_RGBA32F),
                image_optimal_extent(size)
        };
        const struct image_info img = {
                target, grid.format, size,
                image_format_epsilon(grid.format)
        };
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(image_hunk(img, ""),
                       hunk("readonly IMAGE_UNIFORM_T src_img;\n"
                            "writeonly IMAGE_UNIFORM_T dst_img;\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                imageLoad(src_img, IMAGE_ADDR(idx)));\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_fb(grid) &&
                init_image(img, 0) &&
                init_image(img, 1) &&
                set_uniform_int(prog, "src_img", 0) &&
                set_uniform_int(prog, "dst_img", 1)  &&
                draw_grid(grid, prog) &&
                check(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;
}

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

        piglit_require_extension("GL_ARB_shader_image_load_store");

        for (target = image_targets(); target->name; ++target) {
                for (d = 0; d < 4; ++d) {
                        if (should_test_dimension(target, d)) {
                                const struct image_extent size =
                                        get_test_extent(target, d);

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

        piglit_report_result(status);
}

enum piglit_result
piglit_display(void)
{
        return PIGLIT_FAIL;
}
