/*
 * 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 host-mem-barrier.c
 *
 * Unlike other GL API objects, images are not implicitly synchronized
 * with subsequent GL operations.  The glMemoryBarrier() API is provided
 * to serialize shader memory transactions issued by previous drawing
 * commands with respect to a given set of subsequent GL commands
 * specified as a bit set.
 *
 * This test should cause several kinds of data hazard situations
 * deliberately (RaW, WaR, WaW) between image loads and stores and other
 * parts of the pipeline including vertex, element and indirect command
 * fetch, shader uniform buffer, image and atomic counter access, texture
 * sampling, pixel transfer operations, texture and buffer update
 * commands, framebuffer writes and reads and transform feedback output.
 *
 * The test is repeated for different execution sizes 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 inhibits all glMemoryBarrier()
 * calls in order to make sure that the test is leading to data
 * hazards, since otherwise the main test is not meaningful.  The
 * control test always passes as it is expected to misrender.
 */

#include "common.h"

/** Maximum image width. */
#define L 64

/** Maximum number of pixels. */
#define N (L * L)

PIGLIT_GL_TEST_CONFIG_BEGIN

config.supports_gl_core_version = 32;

config.window_width = L;
config.window_height = L;
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 char *
common_hunk(const struct image_info img)
{
        return concat(hunk("#extension GL_ARB_shader_atomic_counters : enable\n"),
                      image_hunk(img, ""),
                      hunk("#define RED DATA_T(1, 0, 0, 1)\n"
                           "#define GREEN DATA_T(0, 1, 0, 1)\n"
                           "\n"
                           "IMAGE_UNIFORM_T src_img;\n"
                           "IMAGE_UNIFORM_T dst_img;\n"
                           "uniform int pass;\n"), NULL);
}

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

        /** Invoke a memory barrier affecting the specified units. */
        bool (*run_barrier)(GLbitfield barriers);

        /** Informative "control" test with no barriers whose result
         * is ignored. */
        bool control_test;
};

static bool
run_barrier_none(GLbitfield barriers)
{
        return true;
}

static bool
run_barrier_one(GLbitfield barriers)
{
        glMemoryBarrier(barriers);
        return piglit_check_gl_error(GL_NO_ERROR);
}

static bool
run_barrier_full(GLbitfield barriers)
{
        glMemoryBarrier(GL_ALL_BARRIER_BITS);
        return piglit_check_gl_error(GL_NO_ERROR);
}

const struct image_barrier_info image_barriers[] = {
        { "control", run_barrier_none, true },
        { "one bit", run_barrier_one, false },
        { "full", run_barrier_full, false },
        { 0 }
};

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

        return download_result(grid, pixels[0]) &&
                check_pixels(image_info_for_grid(grid),
                             pixels[0], 0, 1, 0, 1);
}

static bool
check_img_green(const struct image_info img)
{
        uint32_t pixels[N][4];

        return download_image(img, 1, pixels[0]) &&
                check_pixels(img, pixels[0], 0, 1, 0, 1);
}

static bool
init_common(const struct grid_info grid, const struct image_info img,
            GLuint prog)
{
        uint32_t pixels[N][4];

        if (!init_pixels(img, pixels[0], 0, 1, 0, 1) ||
            !upload_image(img, 0, pixels[0]) ||
            !init_pixels(img, pixels[0], 66, 66, 66, 66) ||
            !upload_image(img, 1, pixels[0]))
                return false;

        set_uniform_int(prog, "src_img", 0);
        set_uniform_int(prog, "dst_img", 1);
        set_uniform_int(prog, "pass", 0);

        return init_fb(grid);
}

static bool
run_test_vertex_array_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_VERTEX_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        static GLuint vao, vbo;
        GLuint prog = generate_program(
                grid, GL_VERTEX_SHADER,
                concat(common_hunk(img),
                       hunk("in vec4 piglit_texcoord;\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return piglit_texcoord;\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog) &&
                generate_grid_arrays(&vao, &vbo,
                                     1.0 / l - 1.0, 1.0 / l - 1.0,
                                     2.0 / l, 2.0 / l, l, l);

        /* Bind the image as texcoord vbo simultaneously. */
        glBindBuffer(GL_ARRAY_BUFFER, get_buffer(1));
        glVertexAttribPointer(PIGLIT_ATTRIB_TEX, 4, GL_FLOAT, GL_FALSE,
                               0, 0);
        glEnableVertexAttribArray(PIGLIT_ATTRIB_TEX);

        /* First pass: render green to the vbo. */
        glDrawArrays(GL_POINTS, 0, l * l);

        /* Barrier. */
        bar->run_barrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);

        /* Second pass: check that the texcoords are all green
         * (read-after-write). */
        set_uniform_int(prog, "pass", 1);
        glDrawArrays(GL_POINTS, 0, l * l);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_element_array_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_R32UI, l, l);
        static GLuint vao, vbo;
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return GRID_T(GREEN);\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                           DATA_T(IMAGE_ADDR(idx)));\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog) &&
                generate_grid_arrays(&vao, &vbo,
                                     1.0 / l - 1.0, 1.0 / l - 1.0,
                                     2.0 / l, 2.0 / l, l, l);

        /* Bind the image as element buffer simultaneously. */
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, get_buffer(1));

        /* First pass: write sequential indices to the element buffer. */
        glDrawArrays(GL_POINTS, 0, l * l);

        /* Barrier. */
        bar->run_barrier(GL_ELEMENT_ARRAY_BARRIER_BIT);

        /* Second pass: render the generated element buffer
         * (read-after-write). */
        set_uniform_int(prog, "pass", 1);
        glDrawElements(GL_POINTS, l * l, GL_UNSIGNED_INT, 0);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
can_test_ubo_raw(void)
{
   int size = 0;
   glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &size);
   return size >= 16 * N;
}

static bool
run_test_ubo_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("layout(std140) uniform u {\n"
                            "        vec4 xs[N];\n"
                            "};\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return xs[IMAGE_ADDR(idx)];\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);

        /* Bind the image as uniform buffer simultaneously. */
        glBindBufferBase(GL_UNIFORM_BUFFER, 0, get_buffer(1));

        /* First pass: render green to the uniform buffer. */
        ret &= draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_UNIFORM_BARRIER_BIT) &&

                /* Second pass: check that the uniforms are all green
                 * (read-after-write). */
                set_uniform_int(prog, "pass", 1) &&
                draw_grid(grid, prog) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_tex_fetch_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("uniform sampler2D tex;\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return texelFetch(tex, idx, 0);\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);

        /* Bind the image as texture simultaneously. */
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, get_texture(1));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        /* First pass: render green to the texture. */
        ret &= draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_TEXTURE_FETCH_BARRIER_BIT) &&
                set_uniform_int(prog, "pass", 1) &&

                /* Second pass: check that the texture is green
                 * (read-after-write). */
                draw_grid(grid, prog) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_image_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return imageLoad(dst_img, IMAGE_ADDR(idx));\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog) &&

                /* First pass: render green to the image. */
                draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) &&

                /* Second pass: check that the image is green
                 * (read-after-write). */
                set_uniform_int(prog, "pass", 1) &&
                draw_grid(grid, prog) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_image_war(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                imageStore(src_img, IMAGE_ADDR(idx), RED);\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                           imageLoad(src_img, IMAGE_ADDR(idx)));\n"
                            "        }\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog) &&

                /* First pass: read back the source image. */
                draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) &&

                /* Second pass: render red to the source image
                 * (write-after-read). */
                set_uniform_int(prog, "pass", 1) &&
                draw_grid(grid, prog) &&

                /* Check that the read-back results from the first
                 * pass are green. */
                check_img_green(img);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_indirect_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_VERTEX_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32UI, l, l);
        static GLuint vao, vbo;
        GLuint prog = generate_program(
                grid, GL_VERTEX_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return GRID_T(gl_InstanceID == 2 ? GREEN : RED);\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                           DATA_T(1, 3, IMAGE_ADDR(idx), 0));\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog) &&
                generate_grid_arrays(&vao, &vbo,
                                     1.0 / l - 1.0, 1.0 / l - 1.0,
                                     2.0 / l, 2.0 / l, l, l);

        /* Bind the image as indirect command buffer
         * simultaneously. */
        glBindBuffer(GL_DRAW_INDIRECT_BUFFER, get_buffer(1));

        /* First pass: Render instanced draw commands to the indirect
         * buffer. */
        glDrawArrays(GL_POINTS, 0, l * l);

        /* Barrier. */
        bar->run_barrier(GL_COMMAND_BARRIER_BIT);

        /* Second pass: render the generated indirect buffer
         * (read-after-write). */
        set_uniform_int(prog, "pass", 1);
        glMultiDrawArraysIndirect(GL_POINTS, 0, l * l, 0);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_pixel_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];
        GLuint tex;

        /* Bind the image as pixel unpack buffer simultaneously. */
        glGenTextures(1, &tex);
        glBindTexture(GL_TEXTURE_2D, tex);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, get_buffer(1));

        /* First pass: render green to the image. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_PIXEL_BUFFER_BARRIER_BIT);

        /* Second pass: use the result as pixel source
         * (read-after-write). */
        glTexImage2D(GL_TEXTURE_2D, 0, img.format->format,
                     l, l, 0, img.format->pixel_format,
                     img.format->pixel_type, 0);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

        /* Check that the resulting texture is green. */
        glGetTexImage(GL_TEXTURE_2D, 0, img.format->pixel_format,
                      image_base_type(img.format), pixels);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_pixels(img, pixels[0], 0, 1, 0, 1);

        glDeleteTextures(1, &tex);
        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_pixel_waw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx), RED);\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];
        GLuint tex;

        init_pixels(img, pixels[0], 0, 1, 0, 1);

        glGenTextures(1, &tex);
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, img.format->format,
                     l, l, 0, img.format->pixel_format,
                     image_base_type(img.format), pixels);

        /* Bind the image as pixel pack buffer simultaneously. */
        glBindBuffer(GL_PIXEL_PACK_BUFFER, get_buffer(1));

        /* First pass: render red to the image. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_PIXEL_BUFFER_BARRIER_BIT);

        /* Second pass: use the image as pixel destination filling it
         * with green (write-after-write). */
        glGetTexImage(GL_TEXTURE_2D, 0, img.format->pixel_format,
                      img.format->pixel_type, 0);
        glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

        /* Check that the resulting image is green. */
        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_img_green(img);

        glDeleteTextures(1, &tex);
        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_tex_update_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];

        glBindTexture(GL_TEXTURE_2D, get_texture(1));

        /* First pass: render green to the image. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT);

        /* Read back the result (read-after-write). */
        glGetTexImage(GL_TEXTURE_2D, 0, img.format->pixel_format,
                      image_base_type(img.format), pixels);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_pixels(img, pixels[0], 0, 1, 0, 1);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_tex_update_waw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                   (idx.y >= H / 2 ? GREEN : RED));\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];

        init_pixels(set_image_size(img, l, l / 2, 1, 1),
                    pixels[0], 0, 1, 0, 1);

        glBindTexture(GL_TEXTURE_2D, get_texture(1));

        /* First pass: render red to the first half of the image,
         * green to the second half. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT);

        /* Fill the first half with green (write-after-write). */
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, l, l / 2,
                        img.format->pixel_format, image_base_type(img.format),
                        pixels);

        /* Check that the resulting image is green. */
        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_img_green(img);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_buf_update_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];

        glBindBuffer(GL_TEXTURE_BUFFER, get_buffer(1));

        /* First pass: render green to the image. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_BUFFER_UPDATE_BARRIER_BIT);

        /* Read back the result (read-after-write). */
        glGetBufferSubData(GL_TEXTURE_BUFFER, 0,
                           4 * l * l * sizeof(uint32_t), pixels[0]);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_pixels(img, pixels[0], 0, 1, 0, 1);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_buf_update_waw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                   (idx.y >= H / 2 ? GREEN : RED));\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];

        init_pixels(set_image_size(img, l * l / 2, 1, 1, 1),
                    pixels[0], 0, 1, 0, 1);
        glBindBuffer(GL_TEXTURE_BUFFER, get_buffer(1));

        /* First pass: render red to the first half of the image,
         * green to the second half. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_BUFFER_UPDATE_BARRIER_BIT);

        /* Fill the first half with green (write-after-write). */
        glBufferSubData(GL_TEXTURE_BUFFER, 0, 4 * l * l / 2 * sizeof(uint32_t),
                        pixels);

        /* Check that the resulting image is green. */
        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_img_green(img);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_fb_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        uint32_t pixels[N][4];
        GLuint fb;

        glGenFramebuffers(1, &fb);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fb);

        /* Bind the image as color attachment of the read framebuffer
         * simultaneously. */
        glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                             get_texture(1), 0);

        /* First pass: render green to the image. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_FRAMEBUFFER_BARRIER_BIT);

        /* Read back and check the result from the read
         * framebuffer (read-after-write). */
        glReadPixels(0, 0, l, l, img.format->pixel_format,
                     image_base_type(img.format), pixels);

        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_pixels(img, pixels[0], 0, 1, 0, 1);

        glDeleteFramebuffers(1, &fb);
        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_fb_waw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_2D, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return GREEN;"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), RED);\n"
                            "                return RED;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);
        GLuint fb;

        glGenFramebuffers(1, &fb);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb);

        /* Bind the image as color attachment of the framebuffer
         * simultaneously. */
        glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                             get_texture(1), 0);

        /* First pass: render red to the image. */
        ret &= draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_FRAMEBUFFER_BARRIER_BIT) &&

                /* Second pass: render green to the framebuffer
                 * (write-after-write). */
                set_uniform_int(prog, "pass", 1) &&
                draw_grid(grid, prog) &&

                /* Check that the resulting image is green. */
                check_img_green(img);

        glDeleteFramebuffers(1, &fb);
        glDeleteProgram(prog);
        return ret;
}

static bool
setup_xfb_varying(GLuint prog, const char *varying)
{
        glTransformFeedbackVaryings(prog, 1, &varying, GL_INTERLEAVED_ATTRIBS);
        glLinkProgram(prog);

        return piglit_check_gl_error(GL_NO_ERROR);
}

static bool
run_test_xfb_waw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_VERTEX_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32F, l, l);
        GLuint prog = generate_program(
                grid, GL_VERTEX_SHADER,
                concat(common_hunk(img),
                       hunk("GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return GREEN;\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), RED);\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && setup_xfb_varying(prog, "vcolor") &&
                init_common(grid, img, prog);
        GLuint xfb;

        glGenTransformFeedbacks(1, &xfb);
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb);

        /* Bind the image as transform feedback buffer
         * simultaneously. */
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, get_buffer(1));
        glBeginTransformFeedback(GL_POINTS);
        glPauseTransformFeedback();

        /* First pass: render red to the image. */
        draw_grid(grid, prog);

        /* Barrier. */
        bar->run_barrier(GL_TRANSFORM_FEEDBACK_BARRIER_BIT);

        /* Second pass: Write out the vcolor output to the transform
         * feedback buffer (write-after-write). */
        set_uniform_int(prog, "pass", 1);
        glResumeTransformFeedback();
        draw_grid(grid, prog);
        glEndTransformFeedback();

        /* Check that the resulting image is green. */
        ret &= piglit_check_gl_error(GL_NO_ERROR) &&
                check_img_green(img);

        glDeleteTransformFeedbacks(1, &xfb);
        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_atom_raw(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32UI, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("layout(binding=0, offset=0) uniform atomic_uint c[4];\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                return GRID_T(atomicCounter(c[0]),"
                            "                              atomicCounter(c[1]),"
                            "                              atomicCounter(c[2]),"
                            "                              atomicCounter(c[3]));\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx), GREEN);\n"
                            "                return x;\n"
                            "        }\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);

        /* Bind the image as atomic counter buffer simultaneously. */
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, get_buffer(1));

        /* First pass: render green to the image. */
        ret &= draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_ATOMIC_COUNTER_BARRIER_BIT) &&

                /* Second pass: Check that the atomic counters read
                 * back green (read-after-write). */
                set_uniform_int(prog, "pass", 1) &&
                draw_grid(grid, prog) &&
                check_fb_green(grid);

        glDeleteProgram(prog);
        return ret;
}

static bool
run_test_atom_war(const struct image_barrier_info *bar, unsigned l)
{
        const struct grid_info grid =
                grid_info(GL_FRAGMENT_SHADER, GL_RGBA32F, l, l);
        const struct image_info img =
                image_info(GL_TEXTURE_BUFFER, GL_RGBA32UI, l, l);
        GLuint prog = generate_program(
                grid, GL_FRAGMENT_SHADER,
                concat(common_hunk(img),
                       hunk("layout(binding=0, offset=0) uniform atomic_uint c[4];\n"
                            "\n"
                            "GRID_T op(ivec2 idx, GRID_T x) {\n"
                            "        if (pass == 1) {\n"
                            "                atomicCounterIncrement(c[0]);"
                            "                atomicCounterIncrement(c[1]);"
                            "                atomicCounterIncrement(c[2]);"
                            "                atomicCounterIncrement(c[3]);\n"
                            "        } else {\n"
                            "                imageStore(dst_img, IMAGE_ADDR(idx),"
                            "                           imageLoad(src_img, IMAGE_ADDR(idx)));\n"
                            "        }\n"
                            "        return x;\n"
                            "}\n"), NULL));
        bool ret = prog && init_common(grid, img, prog);

        /* Bind the image as atomic counter buffer simultaneously. */
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, get_buffer(0));

        /* First pass: read back the source image. */
        ret &= draw_grid(grid, prog) &&

                /* Barrier. */
                bar->run_barrier(GL_ATOMIC_COUNTER_BARRIER_BIT) &&

                /* Second pass: Modify the source image using atomic
                 * counter increments (write-after-read). */
                set_uniform_int(prog, "pass", 1) &&
                draw_grid(grid, prog) &&

                /* Check that the read-back results from the first
                 * pass are green. */
                check_img_green(img);

        glDeleteProgram(prog);
        return ret;
}

static void
barrier_subtest(enum piglit_result *status, bool guard,
                bool (*run)(const struct image_barrier_info *, unsigned),
                const char *name, const struct image_barrier_info *bar,
                unsigned l)
{
        subtest(status, guard, run(bar, l) || bar->control_test,
                "%s/%s barrier test/%dx%d", name, bar->name, l, l);
}

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

        piglit_require_extension("GL_ARB_shader_image_load_store");
        piglit_require_extension("GL_ARB_shader_atomic_counters");

        for (l = 4; l <= L; l *= 4) {
                for (bar = image_barriers; bar->name; ++bar) {
                        if (quick && bar->control_test)
                                continue;

                        barrier_subtest(&status,
                                        get_image_stage(GL_VERTEX_SHADER),
                                        run_test_vertex_array_raw,
                                        "Vertex array/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_element_array_raw,
                                        "Element array/RaW", bar, l);

                        barrier_subtest(&status, can_test_ubo_raw(),
                                        run_test_ubo_raw,
                                        "Uniform buffer/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_tex_fetch_raw,
                                        "Texture fetch/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_image_raw,
                                        "Image/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_image_war,
                                        "Image/WaR", bar, l);

                        barrier_subtest(&status,
                                        (get_image_stage(GL_VERTEX_SHADER) &&
                                         piglit_is_extension_supported(
                                                 "GL_ARB_draw_indirect")),
                                        run_test_indirect_raw,
                                        "Indirect/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_pixel_raw,
                                        "Pixel/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_pixel_waw,
                                        "Pixel/WaW", bar, l);

                        barrier_subtest(&status, true, run_test_tex_update_raw,
                                        "Texture update/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_tex_update_waw,
                                        "Texture update/WaW", bar, l);

                        barrier_subtest(&status, true, run_test_buf_update_raw,
                                        "Buffer update/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_buf_update_waw,
                                        "Buffer update/WaW", bar, l);

                        barrier_subtest(&status, true, run_test_fb_raw,
                                        "Framebuffer/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_fb_waw,
                                        "Framebuffer/WaW", bar, l);

                        barrier_subtest(&status,
                                        (get_image_stage(GL_VERTEX_SHADER) &&
                                         piglit_is_extension_supported(
                                                 "GL_ARB_transform_feedback2")),
                                        run_test_xfb_waw,
                                        "Transform feedback/WaW", bar, l);

                        barrier_subtest(&status, true, run_test_atom_raw,
                                        "Atomic counter/RaW", bar, l);

                        barrier_subtest(&status, true, run_test_atom_war,
                                        "Atomic counter/WaR", bar, l);
                }
        }

        piglit_report_result(status);
}

enum piglit_result
piglit_display(void)
{
        return PIGLIT_FAIL;
}
