/*
 * 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 shader-mem-barrier.c
 *
 * Test that the memoryBarrier() GLSL built-in effectively serializes
 * memory transactions initiated after and before the call.  This is
 * done by having a number of threads write to separate locations in a
 * loop serializing each pair of writes with memoryBarrier() while the
 * remaining threads monitor the evolution of the same memory
 * locations until an inconsistency is observed or the test runs to
 * completion.
 *
 * The test is repeated for the "volatile" qualifier, for all
 * execution stages and for different relative arrangements of
 * producer and monitor threads to account for implementations with
 * varying levels of parallelism and with caches of different sizes.
 *
 * Unless running in "quick" mode a series of control tests is
 * executed which disables memory barriers in order to make sure that
 * the test is meaningful.  The control test always passes as it is
 * expected to misrender.
 */

#include "common.h"

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

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

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

/** Maximum modulus. */
#define K 128

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

struct image_test_info {
        /** Test name. */
        const char *name;

        /** Image qualifier keyword. */
        const char *qualifier;

        /** Memory barrier built-in call. */
        const char *barrier;

        /** Informative "control" test likely to fail whose result is
         * ignored. */
        bool control_test;
};

const struct image_test_info image_tests[] = {
        { "control", "", "", true },
        { "'coherent' qualifier", "coherent", "memoryBarrier()", false },
        { "'volatile' qualifier", "volatile", "memoryBarrier()", false },
        { 0 }
};

char *
test_hunk(const struct image_test_info *test, unsigned k)
{
        char *s = NULL;

        (void)!asprintf(&s, "#define IMAGE_Q %s\n"
                 "#define MEMORY_BARRIER() %s\n"
                 "#define K %d\n",
                 test->qualifier, test->barrier, k);
        return s;
}

static bool
init_image(const struct image_info img)
{
        uint32_t pixels[N] = { 0 };

        return upload_image(img, 0, pixels);
}

static bool
check(const struct grid_info grid)
{
        uint32_t pixels[N];

        return download_result(grid, pixels) &&
                check_pixels(image_info_for_grid(grid), pixels, 33, 0, 0, 0);
}

static bool
run_test(const struct image_test_info *test,
         const struct image_stage_info *stage,
         unsigned k)
{
        const struct grid_info grid =
                grid_info(stage->stage, GL_R32UI, W, H);
        const struct image_info img = image_info_for_grid(grid);
        GLuint prog = generate_program(
                grid, stage->stage,
                concat(test_hunk(test, k),
                       image_hunk(img, ""),
                       hunk("IMAGE_Q IMAGE_UNIFORM_T img;\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T arg) {\n"
                            "       /*\n"
                            "        * Shader invocations are classified into producers\n"
                            "        * (check==false) and consumers (check==true), each\n"
                            "        * producer being paired with a consumer K threads to\n"
                            "        * the right.\n"
                            "        */\n"
                            "       bool check = ((idx.x / K) % 2 == 1);\n"
                            "       int x = (idx.x % K) + (idx.x / (2 * K)) * (2 * K);\n"
                            "       int i, n = 1000;\n"
                            "\n"
                            "       for (i = 0; i < n; ++i) {\n"
                            "              uint u, v;\n"
                            "\n"
                            "              if (check) {\n"
                            "                     /*\n"
                            "                      * Consumer: Monitor the evolution of a pair of\n"
                            "                      * image locations until the test runs to\n"
                            "                      * completion or an inconsistency is observed.\n"
                            "                      */\n"
                            "                     v = imageLoad(img, ivec2(x, idx.y)).x;\n"
                            "                     MEMORY_BARRIER();\n"
                            "                     u = imageLoad(img, ivec2(x + K, idx.y)).x;\n"
                            "\n"
                            "                     if (u < v)\n"
                            "                             /* Fail. */\n"
                            "                             return GRID_T(v << 16 | u, 0, 0, 1);\n"
                            "             } else {\n"
                            "                     /*\n"
                            "                      * Producer: Update the same pair of image locations\n"
                            "                      * sequentially with increasing values ordering the\n"
                            "                      * stores with a barrier.\n"
                            "                      */\n"
                            "                     imageStore(img, ivec2(x + K, idx.y), DATA_T(i));\n"
                            "                     MEMORY_BARRIER();\n"
                            "                     imageStore(img, ivec2(x, idx.y), DATA_T(i));\n"
                            "              }\n"
                            "       }\n"
                            "\n"
                            "       /* Success. */\n"
                            "       return GRID_T(33, 0, 0, 1);\n"
                            "}\n"), NULL));
        bool ret = prog &&
                init_fb(grid) &&
                init_image(img) &&
                set_uniform_int(prog, "img", 0) &&
                draw_grid(grid, prog) &&
                (check(grid) || test->control_test);

        glDeleteProgram(prog);
        return ret;
}

void
piglit_init(int argc, char **argv)
{
        const bool quick = (argc >= 2 && !strcmp(argv[1], "--quick"));
        const struct image_test_info *test;
        const struct image_stage_info *stage;
        enum piglit_result status = PIGLIT_PASS;
        unsigned k;

        piglit_require_extension("GL_ARB_shader_image_load_store");

        for (k = 1; k <= K; k *= (quick ? 4 : 2)) {
                for (test = image_tests; test->name; ++test) {
                        if (quick && test->control_test)
                                continue;

                        for (stage = image_stages(); stage->name; ++stage) {
                                subtest(&status, true,
                                        run_test(test, stage, k),
                                        "%s shader/%s memory barrier test"
                                        "/modulus=%d",
                                        stage->name, test->name, k);
                        }
                }
        }

        piglit_report_result(status);
}

enum piglit_result
piglit_display(void)
{
        return PIGLIT_FAIL;
}
