/*
 * 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-images.c
 *
 * Check that images work as expected up to the reported limit of
 * image units and the per-shader and combined limit of image
 * uniforms.
 */

#include "common.h"

/** Window width. */
#define W 16

/** Window height. */
#define H 96

/** Total number of pixels in the window and images. */
#define N (W * H)

PIGLIT_GL_TEST_CONFIG_BEGIN

config.supports_gl_core_version = 32;

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

PIGLIT_GL_TEST_CONFIG_END

/**
 * Return the total number of image uniforms used by the shader stages
 * specified in the \a stages bit-field.
 */
static unsigned
num_images_for_stages(const struct grid_info grid, unsigned stages)
{
        const struct image_stage_info *stage;
        unsigned n = 0;

        for (stage = image_stages(); stage->name; ++stage) {
                if (grid.stages & stages & stage->bit)
                        n += image_stage_max_images(stage);
        }


        return MIN2(max_combined_images(), n);
}

/**
 * Return the total number of image uniforms used by the specified
 * shader stage.
 */
static unsigned
num_images_for_stage(const struct grid_info grid,
                     const struct image_stage_info *stage)
{
        return num_images_for_stages(grid, (stage->bit << 1) - 1) -
                num_images_for_stages(grid, stage->bit - 1);
}

static bool
init_images(const struct image_info img)
{
        uint32_t pixels[N];
        int unit, i;

        for (unit = 0; unit < max_image_units(); ++unit) {
                for (i = 0; i < N; ++i)
                        pixels[i] = (i == unit ? 1 : 0);

                if (!upload_image(img, unit, pixels))
                        return false;
        }

        return true;
}

/**
 * Bind all image uniforms present in the program to the available
 * image units, re-using the same unit several times if necessary in
 * cyclical order.
 */
static bool
bind_images(const struct grid_info grid, GLuint prog)
{
        const unsigned m = max_image_units();
        const struct image_stage_info *stage;

        for (stage = image_stages(); stage->name; ++stage) {
                if (grid.stages & stage->bit) {
                        const unsigned first =
                                num_images_for_stages(grid, stage->bit - 1);
                        const unsigned n = num_images_for_stage(grid, stage);
                        const unsigned stage_idx = stage - image_stages();
                        int i;

                        for (i = 0; i < n; ++i) {
                                char *name = NULL;

                                (void)!asprintf(&name, "imgs_%d[%d]", stage_idx, i);

                                if (!set_uniform_int(prog, name,
                                                     (first + i) % m))
                                        return false;

                                free(name);
                        }
                }
        }

        return true;
}

static char *
stage_hunk(const struct grid_info grid,
           const struct image_stage_info *stage)
{
        char *s = NULL;

        (void)!asprintf(&s, "#define IMGS imgs_%d\n"
                 "#define NUM_IMGS %d\n",
                 (int)(stage - image_stages()),
                 num_images_for_stage(grid, stage));
        return s;
}

static char *
generate_source(const struct grid_info grid,
                const struct image_info img, GLuint s)
{
        const struct image_stage_info *stage = get_image_stage(s);

        if (stage && num_images_for_stage(grid, stage)) {
                /*
                 * Sum up the values read from corresponding locations
                 * of all bound image uniforms.
                 */
                return concat(stage_hunk(grid, stage),
                              image_hunk(img, ""),
                              hunk("IMAGE_UNIFORM_T IMGS[NUM_IMGS];\n"
                                   "\n"
                                   "GRID_T op(ivec2 idx, GRID_T x) {\n"
                                   "        int i;\n"
                                   "\n"
                                   "        for (i = 0; i < NUM_IMGS; ++i)\n"
                                   "                x += imageLoad(IMGS[i], IMAGE_ADDR(idx));\n"
                                   "\n"
                                   "        return x;\n"
                                   "}\n"), NULL);

        } else {
                return NULL;
        }
}

static bool
check(const struct grid_info grid, const struct image_info img)
{
        const int n = num_images_for_stages(grid, ~0);
        const int m = max_image_units();
        uint32_t pixels[N], expect[N];
        int i;

        for (i = 0; i < N; ++i) {
                if (i < m) {
                        /*
                         * The sum at this location is just the number
                         * of times that the image with index i was
                         * bound to the pipeline.
                         */
                        expect[i] = (n - i + m - 1) / m;
                } else {
                        /*
                         * No image has a non-zero value at this
                         * location, so the sum is zero.
                         */
                        expect[i] = 0;
                }
        }

        return download_result(grid, pixels) &&
                check_pixels_v(img, pixels, expect);
}

static bool
run_test(GLbitfield shaders)
{
        const struct grid_info grid = {
                shaders,
                get_image_format(GL_R32UI),
                { W, H, 1, 1 }
        };
        const struct image_info img = image_info_for_grid(grid);
        GLuint prog = generate_program(
                grid,
                GL_VERTEX_SHADER,
                generate_source(grid, img, GL_VERTEX_SHADER),
                GL_TESS_CONTROL_SHADER,
                generate_source(grid, img, GL_TESS_CONTROL_SHADER),
                GL_TESS_EVALUATION_SHADER,
                generate_source(grid, img, GL_TESS_EVALUATION_SHADER),
                GL_GEOMETRY_SHADER,
                generate_source(grid, img, GL_GEOMETRY_SHADER),
                GL_FRAGMENT_SHADER,
                generate_source(grid, img, GL_FRAGMENT_SHADER),
                GL_COMPUTE_SHADER,
                generate_source(grid, img, GL_COMPUTE_SHADER));
        bool ret = prog && init_fb(grid) &&
                init_images(img) &&
                bind_images(grid, prog) &&
                draw_grid(grid, prog) &&
                check(grid, img);

        glDeleteProgram(prog);
        return ret;
}

void
piglit_init(int argc, char **argv)
{
        enum piglit_result status = PIGLIT_PASS;
        const struct image_stage_info *stage;

        piglit_require_extension("GL_ARB_shader_image_load_store");

        for (stage = image_stages(); stage->name; ++stage) {
                subtest(&status, true,
                        run_test(stage->bit),
                        "%s shader max image uniforms test",
                        stage->name);
        }

        subtest(&status, true,
                run_test((get_image_stage(GL_VERTEX_SHADER) ?
                          GL_VERTEX_SHADER_BIT : 0) |
                         (get_image_stage(GL_TESS_CONTROL_SHADER) ?
                          GL_TESS_CONTROL_SHADER_BIT : 0) |
                         (get_image_stage(GL_TESS_EVALUATION_SHADER) ?
                          GL_TESS_EVALUATION_SHADER_BIT : 0) |
                         (get_image_stage(GL_GEOMETRY_SHADER) ?
                          GL_GEOMETRY_SHADER_BIT : 0) |
                         GL_FRAGMENT_SHADER_BIT),
                "Combined max image uniforms test");

        piglit_report_result(status);
}

enum piglit_result
piglit_display(void)
{
        return PIGLIT_FAIL;
}
