//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MemoryBarrierTest:
//   Ensure that implementation of glMemoryBarrier is correct both in terms of memory barriers
//   issued and potential reordering of commands.
//
// The barrier bits accepted by glMemoryBarrier are used for synchronization as such:
//
//     VERTEX_ATTRIB_ARRAY_BARRIER_BIT: shader write -> vertex read
//     ELEMENT_ARRAY_BARRIER_BIT:       shader write -> index read
//     UNIFORM_BARRIER_BIT:             shader write -> uniform read
//     TEXTURE_FETCH_BARRIER_BIT:       shader write -> texture sample
//     SHADER_IMAGE_ACCESS_BARRIER_BIT: shader write -> image access
//                                      any access -> image write
//     COMMAND_BARRIER_BIT:             shader write -> indirect buffer read
//     PIXEL_BUFFER_BARRIER_BIT:        shader write -> pbo access
//     TEXTURE_UPDATE_BARRIER_BIT:      shader write -> texture data upload
//     BUFFER_UPDATE_BARRIER_BIT:       shader write -> buffer data upload/map
//     FRAMEBUFFER_BARRIER_BIT:         shader write -> access through framebuffer
//     TRANSFORM_FEEDBACK_BARRIER_BIT:  shader write -> transform feedback write
//     ATOMIC_COUNTER_BARRIER_BIT:      shader write -> atomic counter access
//     SHADER_STORAGE_BARRIER_BIT:      shader write -> buffer access
//                                      any access -> buffer write
//
// In summary, every bit defines a memory barrier for some access after a shader write.
// Additionally, SHADER_IMAGE_ACCESS_BARRIER_BIT and SHADER_STORAGE_BARRIER_BIT bits are used to
// define a memory barrier for shader writes after other accesses.

#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/random_utils.h"

using namespace angle;

namespace
{
enum class ShaderWritePipeline
{
    Graphics,
    Compute,
};

enum class WriteResource
{
    Image,
    ImageBuffer,
    Buffer,
};

enum class NoopOp
{
    None,
    Draw,
    Dispatch,
};

// Variations corresponding to enums above.
using MemoryBarrierVariationsTestParams =
    std::tuple<angle::PlatformParameters, ShaderWritePipeline, WriteResource, NoopOp, NoopOp>;

std::ostream &operator<<(std::ostream &out, WriteResource writeResource)
{
    switch (writeResource)
    {
        case WriteResource::Image:
            out << "image";
            break;
        case WriteResource::Buffer:
            out << "buffer";
            break;
        case WriteResource::ImageBuffer:
            out << "imagebuffer";
            break;
    }

    return out;
}

std::ostream &operator<<(std::ostream &out, ShaderWritePipeline writePipeline)
{
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        out << "graphics";
    }
    else
    {
        out << "compute";
    }

    return out;
}

std::ostream &operator<<(std::ostream &out, NoopOp noop)
{
    switch (noop)
    {
        case NoopOp::Draw:
            out << "NoopDraw";
            break;
        case NoopOp::Dispatch:
            out << "NoopDispatch";
            break;
        case NoopOp::None:
            out << "NoopNone";
            break;
    }

    return out;
}

void ParseMemoryBarrierVariationsTestParams(const MemoryBarrierVariationsTestParams &params,
                                            ShaderWritePipeline *writePipelineOut,
                                            WriteResource *writeResourceOut,
                                            NoopOp *preBarrierOpOut,
                                            NoopOp *postBarrierOpOut)
{
    *writePipelineOut = std::get<1>(params);
    *writeResourceOut = std::get<2>(params);
    *preBarrierOpOut  = std::get<3>(params);
    *postBarrierOpOut = std::get<4>(params);
}

std::string MemoryBarrierVariationsTestPrint(
    const ::testing::TestParamInfo<MemoryBarrierVariationsTestParams> &paramsInfo)
{
    const MemoryBarrierVariationsTestParams &params = paramsInfo.param;
    std::ostringstream out;

    out << std::get<0>(params);

    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;

    ParseMemoryBarrierVariationsTestParams(params, &writePipeline, &writeResource, &preBarrierOp,
                                           &postBarrierOp);

    out << "__" << writePipeline << "_" << writeResource;

    if (preBarrierOp != NoopOp::None)
    {
        out << "_prebarrier" << preBarrierOp;
    }
    if (postBarrierOp != NoopOp::None)
    {
        out << "_postbarrier" << postBarrierOp;
    }
    return out.str();
}

class MemoryBarrierTestBase
{
  protected:
    bool hasExtensions(WriteResource writeResource);

    // Helper functions
    void createFramebuffer(GLuint color, GLuint fbo, GLColor initialColor);
    void createStorageBuffer(WriteResource writeResource,
                             GLuint buffer,
                             GLuint textureBuffer,
                             size_t size,
                             const void *initialData);
    void createStorageImage(WriteResource writeResource,
                            GLuint bufferStorage,
                            GLuint texture,
                            const std::array<float, 4> &initialData);
    void createProgram(ShaderWritePipeline writePipeline,
                       WriteResource writeResource,
                       GLProgram *programOut);
    void createNoopGraphicsProgram(GLProgram *programOut);
    void createNoopComputeProgram(GLProgram *programOut);
    void createQuadVertexArray(GLuint positionBuffer);
    void setupVertexArray(ShaderWritePipeline writePipeline, GLuint program);
    void setUniformData(GLuint program, const std::array<float, 4> &data);
    void noopOp(NoopOp op);

    template <typename T>
    void verifyBufferContents(const std::array<T, 4> &expected);

    void verifyImageContents(GLuint texture, const std::array<float, 4> &expected);

    template <typename T>
    void verifyFramebufferAndBufferContents(ShaderWritePipeline writePipeline,
                                            const std::array<T, 4> &expected);

    void verifyFramebufferAndImageContents(ShaderWritePipeline writePipeline,
                                           WriteResource writeResource,
                                           GLuint texture,
                                           const std::array<float, 4> &expected);

    // Barrier bits affecting only buffers and imageBuffers
    void createVertexVerifyProgram(GLuint vertexBuffer, GLProgram *programOut);
    void vertexAttribArrayBitBufferWriteThenVertexRead(ShaderWritePipeline writePipeline,
                                                       WriteResource writeResource,
                                                       NoopOp preBarrierOp,
                                                       NoopOp postBarrierOp);
    void vertexAttribArrayBitVertexReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                                       WriteResource writeResource,
                                                       NoopOp preBarrierOp,
                                                       NoopOp postBarrierOp,
                                                       GLbitfield barrierBit);

    void createIndexVerifyProgram(GLuint indexBuffer, GLProgram *programOut);
    void elementArrayBitBufferWriteThenIndexRead(ShaderWritePipeline writePipeline,
                                                 WriteResource writeResource,
                                                 NoopOp preBarrierOp,
                                                 NoopOp postBarrierOp);
    void elementArrayBitIndexReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                                 WriteResource writeResource,
                                                 NoopOp preBarrierOp,
                                                 NoopOp postBarrierOp,
                                                 GLbitfield barrierBit);

    void createUBOVerifyProgram(GLuint buffer, GLProgram *programOut);
    void uniformBitBufferWriteThenUBORead(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp);
    void uniformBitUBOReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp,
                                          GLbitfield barrierBit);

    void createIndirectVerifyProgram(GLuint buffer, GLProgram *programOut);
    void commandBitBufferWriteThenIndirectRead(ShaderWritePipeline writePipeline,
                                               WriteResource writeResource,
                                               NoopOp preBarrierOp,
                                               NoopOp postBarrierOp);
    void commandBitIndirectReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                               WriteResource writeResource,
                                               NoopOp preBarrierOp,
                                               NoopOp postBarrierOp,
                                               GLbitfield barrierBit);

    void pixelBufferBitBufferWriteThenPack(ShaderWritePipeline writePipeline,
                                           WriteResource writeResource,
                                           NoopOp preBarrierOp,
                                           NoopOp postBarrierOp);
    void pixelBufferBitBufferWriteThenUnpack(ShaderWritePipeline writePipeline,
                                             WriteResource writeResource,
                                             NoopOp preBarrierOp,
                                             NoopOp postBarrierOp);
    void pixelBufferBitPackThenBufferWrite(ShaderWritePipeline writePipeline,
                                           WriteResource writeResource,
                                           NoopOp preBarrierOp,
                                           NoopOp postBarrierOp,
                                           GLbitfield barrierBit);
    void pixelBufferBitUnpackThenBufferWrite(ShaderWritePipeline writePipeline,
                                             WriteResource writeResource,
                                             NoopOp preBarrierOp,
                                             NoopOp postBarrierOp,
                                             GLbitfield barrierBit);

    void bufferUpdateBitBufferWriteThenCopy(ShaderWritePipeline writePipeline,
                                            WriteResource writeResource,
                                            NoopOp preBarrierOp,
                                            NoopOp postBarrierOp);
    void bufferUpdateBitCopyThenBufferWrite(ShaderWritePipeline writePipeline,
                                            WriteResource writeResource,
                                            NoopOp preBarrierOp,
                                            NoopOp postBarrierOp,
                                            GLbitfield barrierBit);

    void createXfbVerifyProgram(GLuint buffer, GLProgram *programOut);
    void transformFeedbackBitBufferWriteThenCapture(ShaderWritePipeline writePipeline,
                                                    WriteResource writeResource,
                                                    NoopOp preBarrierOp,
                                                    NoopOp postBarrierOp);
    void transformFeedbackBitCaptureThenBufferWrite(ShaderWritePipeline writePipeline,
                                                    WriteResource writeResource,
                                                    NoopOp preBarrierOp,
                                                    NoopOp postBarrierOp,
                                                    GLbitfield barrierBit);

    void createAtomicCounterVerifyProgram(GLuint buffer, GLProgram *programOut);
    void atomicCounterBitBufferWriteThenAtomic(ShaderWritePipeline writePipeline,
                                               WriteResource writeResource,
                                               NoopOp preBarrierOp,
                                               NoopOp postBarrierOp);
    void atomicCounterBitAtomicThenBufferWrite(ShaderWritePipeline writePipeline,
                                               WriteResource writeResource,
                                               NoopOp preBarrierOp,
                                               NoopOp postBarrierOp,
                                               GLbitfield barrierBit);

    void createSsboVerifyProgram(WriteResource writeResourcee, GLProgram *programOut);
    void shaderStorageBitBufferWriteThenBufferRead(ShaderWritePipeline writePipeline,
                                                   WriteResource writeResource,
                                                   NoopOp preBarrierOp,
                                                   NoopOp postBarrierOp);
    void shaderStorageBitBufferReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                                   WriteResource writeResource,
                                                   NoopOp preBarrierOp,
                                                   NoopOp postBarrierOp,
                                                   GLbitfield barrierBit);

    // Barrier bits affecting only images and imageBuffers
    void createTextureVerifyProgram(WriteResource writeResource,
                                    GLuint texture,
                                    GLProgram *programOut);
    void textureFetchBitImageWriteThenSamplerRead(ShaderWritePipeline writePipeline,
                                                  WriteResource writeResource,
                                                  NoopOp preBarrierOp,
                                                  NoopOp postBarrierOp);
    void textureFetchBitSamplerReadThenImageWrite(ShaderWritePipeline writePipeline,
                                                  WriteResource writeResource,
                                                  NoopOp preBarrierOp,
                                                  NoopOp postBarrierOp,
                                                  GLbitfield barrierBit);

    void createImageVerifyProgram(WriteResource writeResource,
                                  GLuint texture,
                                  GLProgram *programOut);
    void shaderImageAccessBitImageWriteThenImageRead(ShaderWritePipeline writePipeline,
                                                     WriteResource writeResource,
                                                     NoopOp preBarrierOp,
                                                     NoopOp postBarrierOp);
    void shaderImageAccessBitImageReadThenImageWrite(ShaderWritePipeline writePipeline,
                                                     WriteResource writeResource,
                                                     NoopOp preBarrierOp,
                                                     NoopOp postBarrierOp);

    // Barrier bits affecting only images
    void textureUpdateBitImageWriteThenCopy(ShaderWritePipeline writePipeline,
                                            WriteResource writeResource,
                                            NoopOp preBarrierOp,
                                            NoopOp postBarrierOp);
    void textureUpdateBitCopyThenImageWrite(ShaderWritePipeline writePipeline,
                                            WriteResource writeResource,
                                            NoopOp preBarrierOp,
                                            NoopOp postBarrierOp,
                                            GLbitfield barrierBit);

    void framebufferBitImageWriteThenDraw(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp);
    void framebufferBitImageWriteThenReadPixels(ShaderWritePipeline writePipeline,
                                                WriteResource writeResource,
                                                NoopOp preBarrierOp,
                                                NoopOp postBarrierOp);
    void framebufferBitImageWriteThenCopy(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp);
    void framebufferBitImageWriteThenBlit(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp);
    void framebufferBitDrawThenImageWrite(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp,
                                          GLbitfield barrierBit);
    void framebufferBitReadPixelsThenImageWrite(ShaderWritePipeline writePipeline,
                                                WriteResource writeResource,
                                                NoopOp preBarrierOp,
                                                NoopOp postBarrierOp,
                                                GLbitfield barrierBit);
    void framebufferBitCopyThenImageWrite(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp,
                                          GLbitfield barrierBit);
    void framebufferBitBlitThenImageWrite(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          NoopOp preBarrierOp,
                                          NoopOp postBarrierOp,
                                          GLbitfield barrierBit);

    static constexpr int kTextureSize    = 1;
    static constexpr char kUniformName[] = "uniformData";
};

bool MemoryBarrierTestBase::hasExtensions(WriteResource writeResource)
{
    return writeResource != WriteResource::ImageBuffer ||
           IsGLExtensionEnabled("GL_OES_texture_buffer");
}

void MemoryBarrierTestBase::createFramebuffer(GLuint color, GLuint fbo, GLColor initialColor)
{
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureSize, kTextureSize);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE,
                    &initialColor);
    EXPECT_GL_NO_ERROR();

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_GL_NO_ERROR();

    // Ensure all staged data is flushed.
    EXPECT_PIXEL_COLOR_EQ(0, 0, initialColor);
}

void MemoryBarrierTestBase::createStorageBuffer(WriteResource writeResource,
                                                GLuint buffer,
                                                GLuint textureBuffer,
                                                size_t size,
                                                const void *initialData)
{
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, size, initialData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer);
    EXPECT_GL_NO_ERROR();

    if (writeResource == WriteResource::ImageBuffer)
    {
        glBindTexture(GL_TEXTURE_BUFFER, textureBuffer);
        glTexBufferEXT(GL_TEXTURE_BUFFER, GL_RGBA32F, buffer);
        glBindImageTexture(0, textureBuffer, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
        EXPECT_GL_NO_ERROR();
    }
}

void MemoryBarrierTestBase::createStorageImage(WriteResource writeResource,
                                               GLuint bufferStorage,
                                               GLuint texture,
                                               const std::array<float, 4> &initialData)
{
    const std::array<uint8_t, 4> initialDataAsUnorm = {
        static_cast<uint8_t>(initialData[0] * 255),
        static_cast<uint8_t>(initialData[1] * 255),
        static_cast<uint8_t>(initialData[2] * 255),
        static_cast<uint8_t>(initialData[3] * 255),
    };

    if (writeResource == WriteResource::ImageBuffer)
    {
        glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufferStorage);
        glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(initialData), initialData.data(),
                     GL_STATIC_DRAW);
        EXPECT_GL_NO_ERROR();

        glBindTexture(GL_TEXTURE_BUFFER, texture);
        glTexBufferEXT(GL_TEXTURE_BUFFER, GL_RGBA32F, bufferStorage);
        glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
        EXPECT_GL_NO_ERROR();
    }
    else
    {
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureSize, kTextureSize);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA,
                        GL_UNSIGNED_BYTE, initialDataAsUnorm.data());
        glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);
    }
}

void MemoryBarrierTestBase::createProgram(ShaderWritePipeline writePipeline,
                                          WriteResource writeResource,
                                          GLProgram *programOut)
{
    constexpr char kGraphicsImageFS[] = R"(#version 310 es
precision mediump float;
layout(rgba8, binding = 0) uniform highp writeonly image2D dst;
uniform vec4 uniformData;
out vec4 colorOut;
void main()
{
    colorOut = vec4(0, 0, 1.0, 1.0);
    imageStore(dst, ivec2(gl_FragCoord.xy), uniformData);
})";

    constexpr char kGraphicsBufferFS[] = R"(#version 310 es
precision mediump float;
uniform vec4 uniformData;
layout(std430, binding = 0) buffer block {
    vec4 data;
} outBlock;
out vec4 colorOut;
void main()
{
    colorOut = vec4(0, 0, 1.0, 1.0);
    outBlock.data = uniformData;
}
)";

    constexpr char kGraphicsImageBufferFS[] = R"(#version 310 es
#extension GL_OES_texture_buffer : require
precision mediump float;
layout(rgba32f, binding = 0) uniform highp writeonly imageBuffer dst;
uniform vec4 uniformData;
out vec4 colorOut;
void main()
{
    colorOut = vec4(0, 0, 1.0, 1.0);
    imageStore(dst, int(gl_FragCoord.x), uniformData);
})";

    constexpr char kComputeImage[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba8, binding = 0) uniform highp writeonly image2D dst;
uniform vec4 uniformData;
void main()
{
    imageStore(dst, ivec2(gl_GlobalInvocationID.xy), uniformData);
})";

    constexpr char kComputeBuffer[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
uniform vec4 uniformData;
layout(std430, binding = 0) buffer block {
    vec4 data;
} outBlock;
void main()
{
    outBlock.data = uniformData;
}
)";

    constexpr char kComputeImageBuffer[] = R"(#version 310 es
#extension GL_OES_texture_buffer : require
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32f, binding = 0) uniform highp writeonly imageBuffer dst;
uniform vec4 uniformData;
void main()
{
    imageStore(dst, int(gl_GlobalInvocationID.x), uniformData);
})";

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        const char *fs = "";
        switch (writeResource)
        {
            case WriteResource::Image:
                fs = kGraphicsImageFS;
                break;
            case WriteResource::Buffer:
                fs = kGraphicsBufferFS;
                break;
            case WriteResource::ImageBuffer:
                fs = kGraphicsImageBufferFS;
                break;
        }

        programOut->makeRaster(essl31_shaders::vs::Simple(), fs);
    }
    else
    {
        const char *cs = "";
        switch (writeResource)
        {
            case WriteResource::Image:
                cs = kComputeImage;
                break;
            case WriteResource::Buffer:
                cs = kComputeBuffer;
                break;
            case WriteResource::ImageBuffer:
                cs = kComputeImageBuffer;
                break;
        }

        programOut->makeCompute(cs);
    }

    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);
}

void MemoryBarrierTestBase::createNoopGraphicsProgram(GLProgram *programOut)
{
    programOut->makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    ASSERT_TRUE(programOut->valid());
}

void MemoryBarrierTestBase::createNoopComputeProgram(GLProgram *programOut)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
void main()
{
})";

    programOut->makeCompute(kCS);
    ASSERT_TRUE(programOut->valid());
}

void MemoryBarrierTestBase::createQuadVertexArray(GLuint positionBuffer)
{
    const std::array<Vector3, 6> kQuadVertices = {{
        Vector3(-1.0f, 1.0f, 0.5f),
        Vector3(-1.0f, -1.0f, 0.5f),
        Vector3(1.0f, -1.0f, 0.5f),
        Vector3(-1.0f, 1.0f, 0.5f),
        Vector3(1.0f, -1.0f, 0.5f),
        Vector3(1.0f, 1.0f, 0.5f),
    }};

    const size_t bufferSize = kQuadVertices.size() * sizeof(Vector3);

    glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
    glBufferData(GL_ARRAY_BUFFER, bufferSize, kQuadVertices.data(), GL_STATIC_DRAW);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::setupVertexArray(ShaderWritePipeline writePipeline, GLuint program)
{
    if (writePipeline == ShaderWritePipeline::Compute)
    {
        return;
    }

    GLint positionLoc = glGetAttribLocation(program, essl31_shaders::PositionAttrib());
    ASSERT_NE(-1, positionLoc);

    glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(positionLoc);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::setUniformData(GLuint program, const std::array<float, 4> &data)
{
    GLint uniformLocation = glGetUniformLocation(program, kUniformName);
    ASSERT_NE(uniformLocation, -1);

    glUniform4f(uniformLocation, data[0], data[1], data[2], data[3]);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::noopOp(NoopOp op)
{
    if (op == NoopOp::None)
    {
        return;
    }

    GLProgram noopProgram;
    if (op == NoopOp::Draw)
    {
        createNoopGraphicsProgram(&noopProgram);

        glEnable(GL_BLEND);
        glBlendFunc(GL_ZERO, GL_ONE);

        glUseProgram(noopProgram);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glDisable(GL_BLEND);
    }
    else
    {
        createNoopComputeProgram(&noopProgram);
        glUseProgram(noopProgram);
        glDispatchCompute(1, 1, 1);
    }

    EXPECT_GL_NO_ERROR();
}

template <typename T>
void MemoryBarrierTestBase::verifyBufferContents(const std::array<T, 4> &expected)
{
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
    T *bufferContents = static_cast<T *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(expected), GL_MAP_READ_BIT));
    EXPECT_GL_NO_ERROR();

    EXPECT_EQ(bufferContents[0], expected[0]);
    EXPECT_EQ(bufferContents[1], expected[1]);
    EXPECT_EQ(bufferContents[2], expected[2]);
    EXPECT_EQ(bufferContents[3], expected[3]);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

void MemoryBarrierTestBase::verifyImageContents(GLuint texture,
                                                const std::array<float, 4> &expected)
{
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
    glBindTexture(GL_TEXTURE_2D, texture);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);
    EXPECT_GL_NO_ERROR();

    const GLColor kExpected(expected[0] * 255, expected[1] * 255, expected[2] * 255,
                            expected[3] * 255);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, kExpected, 1);
}

template <typename T>
void MemoryBarrierTestBase::verifyFramebufferAndBufferContents(ShaderWritePipeline writePipeline,
                                                               const std::array<T, 4> &expected)
{
    // Verify the result of the verify shader
    const GLColor kExpected =
        writePipeline == ShaderWritePipeline::Graphics ? GLColor::cyan : GLColor::green;
    EXPECT_PIXEL_COLOR_EQ(0, 0, kExpected);

    // Verify the contents of the buffer
    verifyBufferContents(expected);
}

void MemoryBarrierTestBase::verifyFramebufferAndImageContents(ShaderWritePipeline writePipeline,
                                                              WriteResource writeResource,
                                                              GLuint texture,
                                                              const std::array<float, 4> &expected)
{
    // Verify the result of the verify shader
    const GLColor kExpected =
        writePipeline == ShaderWritePipeline::Graphics ? GLColor::cyan : GLColor::green;
    EXPECT_PIXEL_COLOR_EQ(0, 0, kExpected);

    if (writeResource == WriteResource::ImageBuffer)
    {
        // Verify the contents of the buffer
        verifyBufferContents(expected);
    }
    else
    {
        // Verify the contents of the image
        verifyImageContents(texture, expected);
    }
}

void MemoryBarrierTestBase::createVertexVerifyProgram(GLuint vertexBuffer, GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
in float attribIn;
out float v;

void main()
{
    v = attribIn;
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
in float v;
out vec4 colorOut;
void main()
{
    if (v == 2.0)
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    programOut->makeRaster(kVS, kFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    GLint attribLoc = glGetAttribLocation(*programOut, "attribIn");
    ASSERT_NE(-1, attribLoc);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glVertexAttribPointer(attribLoc, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(attribLoc);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::vertexAttribArrayBitBufferWriteThenVertexRead(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer vertexBuffer;
    GLTexture vertexTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, vertexBuffer, vertexTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<float, 4> kWriteData = {2.0, 2.0, 2.0, 2.0};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram readProgram;
    createVertexVerifyProgram(vertexBuffer, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    // Verify the vertex data was read correctly
    const GLColor kExpected =
        writePipeline == ShaderWritePipeline::Graphics ? GLColor::cyan : GLColor::green;
    EXPECT_PIXEL_COLOR_EQ(0, 0, kExpected);

    // Verify the contents of the buffer
    verifyBufferContents(kWriteData);
}

void MemoryBarrierTestBase::vertexAttribArrayBitVertexReadThenBufferWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp,
    GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer vertexBuffer;
    GLTexture vertexTextureBuffer;
    constexpr std::array<float, 4> kInitData = {2.0, 2.0, 2.0, 2.0};
    createStorageBuffer(writeResource, vertexBuffer, vertexTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    GLProgram readProgram;
    createVertexVerifyProgram(vertexBuffer, &readProgram);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    GLint attribLoc = glGetAttribLocation(readProgram, "attribIn");
    glDisableVertexAttribArray(attribLoc);

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::createIndexVerifyProgram(GLuint indexBuffer, GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 colorOut;
void main()
{
    colorOut = vec4(0, 1.0, 0, 1.0);
})";

    programOut->makeRaster(kVS, kFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::elementArrayBitBufferWriteThenIndexRead(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer indexBuffer;
    GLTexture indexTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, indexBuffer, indexTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<uint32_t, 4> kWriteData = {0, 1, 2, 3};
    const std::array<float, 4> kWriteDataAsFloat = {
        *reinterpret_cast<const float *>(&kWriteData[0]),
        *reinterpret_cast<const float *>(&kWriteData[1]),
        *reinterpret_cast<const float *>(&kWriteData[2]),
        *reinterpret_cast<const float *>(&kWriteData[3]),
    };
    setUniformData(writeProgram, kWriteDataAsFloat);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_ELEMENT_ARRAY_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram readProgram;
    createIndexVerifyProgram(indexBuffer, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, 0);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::elementArrayBitIndexReadThenBufferWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp,
    GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer indexBuffer;
    GLTexture indexTextureBuffer;
    constexpr std::array<uint32_t, 4> kInitData = {0, 1, 2, 3};
    createStorageBuffer(writeResource, indexBuffer, indexTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    GLProgram readProgram;
    createIndexVerifyProgram(indexBuffer, &readProgram);

    glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, 0);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::createUBOVerifyProgram(GLuint buffer, GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
layout(binding = 0) uniform block {
    vec4 data;
} ubo;
out vec4 colorOut;
void main()
{
    if (ubo.data == vec4(1.5, 3.75, 5.0, 12.125))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    programOut->makeRaster(kVS, kFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    glBindBuffer(GL_UNIFORM_BUFFER, buffer);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::uniformBitBufferWriteThenUBORead(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer uniformBuffer;
    GLTexture uniformTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, uniformBuffer, uniformTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<float, 4> kWriteData = {1.5, 3.75, 5.0, 12.125};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_UNIFORM_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram readProgram;
    createUBOVerifyProgram(uniformBuffer, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::uniformBitUBOReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp,
                                                             GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer uniformBuffer;
    GLTexture uniformTextureBuffer;
    constexpr std::array<float, 4> kInitData = {1.5, 3.75, 5.0, 12.125};
    createStorageBuffer(writeResource, uniformBuffer, uniformTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    GLProgram readProgram;
    createUBOVerifyProgram(uniformBuffer, &readProgram);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::createIndirectVerifyProgram(GLuint buffer, GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 colorOut;
void main()
{
    colorOut = vec4(0, 1.0, 0, 1.0);
})";

    programOut->makeRaster(kVS, kFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffer);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::commandBitBufferWriteThenIndirectRead(ShaderWritePipeline writePipeline,
                                                                  WriteResource writeResource,
                                                                  NoopOp preBarrierOp,
                                                                  NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer indirectBuffer;
    GLTexture indirectTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, indirectBuffer, indirectTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<uint32_t, 4> kWriteData = {4, 1, 0, 0};
    const std::array<float, 4> kWriteDataAsFloat = {
        *reinterpret_cast<const float *>(&kWriteData[0]),
        *reinterpret_cast<const float *>(&kWriteData[1]),
        *reinterpret_cast<const float *>(&kWriteData[2]),
        *reinterpret_cast<const float *>(&kWriteData[3]),
    };
    setUniformData(writeProgram, kWriteDataAsFloat);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_COMMAND_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram readProgram;
    createIndirectVerifyProgram(indirectBuffer, &readProgram);

    GLVertexArray vao;
    glBindVertexArray(vao);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArraysIndirect(GL_TRIANGLE_STRIP, nullptr);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::commandBitIndirectReadThenBufferWrite(ShaderWritePipeline writePipeline,
                                                                  WriteResource writeResource,
                                                                  NoopOp preBarrierOp,
                                                                  NoopOp postBarrierOp,
                                                                  GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer indirectBuffer;
    GLTexture indirectTextureBuffer;
    constexpr std::array<uint32_t, 4> kInitData = {4, 1, 0, 0};
    createStorageBuffer(writeResource, indirectBuffer, indirectTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    GLProgram readProgram;
    createIndirectVerifyProgram(indirectBuffer, &readProgram);

    GLVertexArray vao;
    glBindVertexArray(vao);

    glDrawArraysIndirect(GL_TRIANGLE_STRIP, nullptr);
    EXPECT_GL_NO_ERROR();

    glBindVertexArray(0);

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::pixelBufferBitBufferWriteThenPack(ShaderWritePipeline writePipeline,
                                                              WriteResource writeResource,
                                                              NoopOp preBarrierOp,
                                                              NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer packBuffer;
    GLTexture packTextureBuffer;
    constexpr std::array<float, 4> kInitData = {1.5, 3.75, 5.0, 12.125};
    createStorageBuffer(writeResource, packBuffer, packTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    glBindBuffer(GL_PIXEL_PACK_BUFFER, packBuffer);
    glReadPixels(0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    const std::array<uint32_t, 4> kExpectedData = {
        writePipeline == ShaderWritePipeline::Graphics ? 0xFFFFFF00u : 0xFF00FF00u,
        *reinterpret_cast<const uint32_t *>(&kWriteData[1]),
        *reinterpret_cast<const uint32_t *>(&kWriteData[2]),
        *reinterpret_cast<const uint32_t *>(&kWriteData[3]),
    };
    verifyFramebufferAndBufferContents(writePipeline, kExpectedData);
}

void MemoryBarrierTestBase::pixelBufferBitBufferWriteThenUnpack(ShaderWritePipeline writePipeline,
                                                                WriteResource writeResource,
                                                                NoopOp preBarrierOp,
                                                                NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer unpackBuffer;
    GLTexture unpackTextureBuffer;
    constexpr std::array<float, 4> kInitData = {1.5, 3.75, 5.0, 12.125};
    createStorageBuffer(writeResource, unpackBuffer, unpackTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    const std::array<float, 4> kWriteData = {*reinterpret_cast<const float *>(&GLColor::green), 5.6,
                                             78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE,
                    0);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    // Verify the result of the unpack operation
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Verify the contents of the buffer
    verifyBufferContents(kWriteData);
}

void MemoryBarrierTestBase::pixelBufferBitPackThenBufferWrite(ShaderWritePipeline writePipeline,
                                                              WriteResource writeResource,
                                                              NoopOp preBarrierOp,
                                                              NoopOp postBarrierOp,
                                                              GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer packBuffer;
    GLTexture packTextureBuffer;
    constexpr std::array<float, 4> kInitData = {1.5, 3.75, 5.0, 12.125};
    createStorageBuffer(writeResource, packBuffer, packTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    glBindBuffer(GL_PIXEL_PACK_BUFFER, packBuffer);
    glReadPixels(0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::pixelBufferBitUnpackThenBufferWrite(ShaderWritePipeline writePipeline,
                                                                WriteResource writeResource,
                                                                NoopOp preBarrierOp,
                                                                NoopOp postBarrierOp,
                                                                GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer unpackBuffer;
    GLTexture unpackTextureBuffer;
    const std::array<float, 4> kInitData = {*reinterpret_cast<const float *>(&GLColor::green), 3.75,
                                            5.0, 12.125};
    createStorageBuffer(writeResource, unpackBuffer, unpackTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE,
                    0);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::bufferUpdateBitBufferWriteThenCopy(ShaderWritePipeline writePipeline,
                                                               WriteResource writeResource,
                                                               NoopOp preBarrierOp,
                                                               NoopOp postBarrierOp)
{
    GLBuffer srcBuffer;
    GLTexture srcTextureBuffer;
    constexpr std::array<float, 4> kSrcInitData = {9.3, 3.7, 11.34, 0.65};
    createStorageBuffer(WriteResource::Buffer, srcBuffer, srcTextureBuffer, sizeof(kSrcInitData),
                        kSrcInitData.data());

    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer writeBuffer;
    GLTexture writeTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, writeBuffer, writeTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<float, 4> kWriteData = {1.5, 3.75, 5.0, 12.125};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Copy from src buffer over the buffer
    glBindBuffer(GL_UNIFORM_BUFFER, srcBuffer);
    glCopyBufferSubData(GL_UNIFORM_BUFFER, GL_SHADER_STORAGE_BUFFER, 0, 0, sizeof(kInitData));

    verifyFramebufferAndBufferContents(writePipeline, kSrcInitData);

    // Verify the src buffer is unaffected
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, srcBuffer);
    verifyBufferContents(kSrcInitData);
}

void MemoryBarrierTestBase::bufferUpdateBitCopyThenBufferWrite(ShaderWritePipeline writePipeline,
                                                               WriteResource writeResource,
                                                               NoopOp preBarrierOp,
                                                               NoopOp postBarrierOp,
                                                               GLbitfield barrierBit)
{
    GLBuffer srcBuffer;
    GLTexture srcTextureBuffer;
    constexpr std::array<float, 4> kSrcInitData = {9.3, 3.7, 11.34, 0.65};
    createStorageBuffer(WriteResource::Buffer, srcBuffer, srcTextureBuffer, sizeof(kSrcInitData),
                        kSrcInitData.data());

    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer writeBuffer;
    GLTexture writeTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, writeBuffer, writeTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Copy from src buffer over the buffer
    glBindBuffer(GL_UNIFORM_BUFFER, srcBuffer);
    glCopyBufferSubData(GL_UNIFORM_BUFFER, GL_SHADER_STORAGE_BUFFER, 0, 0, sizeof(kInitData));

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {1.5, 3.75, 5.0, 12.125};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);

    // Verify the src buffer is unaffected
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, srcBuffer);
    verifyBufferContents(kSrcInitData);
}

void MemoryBarrierTestBase::createXfbVerifyProgram(GLuint buffer, GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //   0 (000)     -1   -1
    //   1 (001)      1   -1
    //   2 (010)     -1    1
    //   3 (011)      1    1
    //   4 (100)     -1    1
    //   5 (101)      1   -1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID < 4 ? gl_VertexID >> 1 & 1 : ~bit0 & 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 colorOut;
void main()
{
    colorOut = vec4(0, 1.0, 0, 1.0);
})";

    const std::vector<std::string> &tfVaryings = {"gl_Position"};

    programOut->makeRasterWithTransformFeedback(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::transformFeedbackBitBufferWriteThenCapture(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer xfbBuffer;
    GLTexture xfbTextureBuffer;
    constexpr size_t kOneInstanceDataSize                                       = 4;
    constexpr size_t kInstanceCount                                             = 6;
    constexpr std::array<float, kOneInstanceDataSize *kInstanceCount> kInitData = {12.34, 5.6,
                                                                                   78.91, 123.456};
    createStorageBuffer(writeResource, xfbBuffer, xfbTextureBuffer,
                        sizeof(kInitData[0]) * kInitData.size(), kInitData.data());

    constexpr std::array<float, 4> kWriteData = {1.5, 3.75, 5.0, 12.125};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_TRANSFORM_FEEDBACK_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram xfbProgram;
    createXfbVerifyProgram(xfbBuffer, &xfbProgram);

    glBeginTransformFeedback(GL_TRIANGLES);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glEndTransformFeedback();
    EXPECT_GL_NO_ERROR();

    const std::array<float, 4> kExpectedData = {-1.0, -1.0, 0.0, 1.0};
    verifyFramebufferAndBufferContents(writePipeline, kExpectedData);
}

void MemoryBarrierTestBase::transformFeedbackBitCaptureThenBufferWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp,
    GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer xfbBuffer;
    GLTexture xfbTextureBuffer;
    constexpr size_t kOneInstanceDataSize                                       = 4;
    constexpr size_t kInstanceCount                                             = 6;
    constexpr std::array<float, kOneInstanceDataSize *kInstanceCount> kInitData = {12.34, 5.6,
                                                                                   78.91, 123.456};
    createStorageBuffer(writeResource, xfbBuffer, xfbTextureBuffer,
                        sizeof(kInitData[0]) * kInitData.size(), kInitData.data());

    // Use the buffer
    GLProgram xfbProgram;
    createXfbVerifyProgram(xfbBuffer, &xfbProgram);

    glBeginTransformFeedback(GL_TRIANGLES);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glEndTransformFeedback();
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {1.5, 3.75, 5.0, 12.125};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::createAtomicCounterVerifyProgram(GLuint buffer, GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
layout(binding = 0, offset = 0) uniform atomic_uint ac[4];
out vec4 colorOut;
void main()
{
    uvec4 acValue = uvec4(atomicCounterIncrement(ac[0]),
                          atomicCounterIncrement(ac[1]),
                          atomicCounterIncrement(ac[2]),
                          atomicCounterIncrement(ac[3]));

    if (all(equal(acValue, uvec4(10, 20, 30, 40))))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    programOut->makeRaster(kVS, kFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, buffer);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::atomicCounterBitBufferWriteThenAtomic(ShaderWritePipeline writePipeline,
                                                                  WriteResource writeResource,
                                                                  NoopOp preBarrierOp,
                                                                  NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer atomicCounterBuffer;
    GLTexture atomicCounterTextureBuffer;
    constexpr std::array<uint32_t, 4> kInitData = {0x12345678u, 0x9ABCDEF0u, 0x13579BDFu,
                                                   0x2468ACE0u};
    createStorageBuffer(writeResource, atomicCounterBuffer, atomicCounterTextureBuffer,
                        sizeof(kInitData), kInitData.data());

    constexpr std::array<uint32_t, 4> kWriteData = {10, 20, 30, 40};
    const std::array<float, 4> kWriteDataAsFloat = {
        *reinterpret_cast<const float *>(&kWriteData[0]),
        *reinterpret_cast<const float *>(&kWriteData[1]),
        *reinterpret_cast<const float *>(&kWriteData[2]),
        *reinterpret_cast<const float *>(&kWriteData[3]),
    };
    setUniformData(writeProgram, kWriteDataAsFloat);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram readProgram;
    createAtomicCounterVerifyProgram(atomicCounterBuffer, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    constexpr std::array<uint32_t, 4> kExpectedData = {11, 21, 31, 41};
    verifyFramebufferAndBufferContents(writePipeline, kExpectedData);
}

void MemoryBarrierTestBase::atomicCounterBitAtomicThenBufferWrite(ShaderWritePipeline writePipeline,
                                                                  WriteResource writeResource,
                                                                  NoopOp preBarrierOp,
                                                                  NoopOp postBarrierOp,
                                                                  GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer atomicCounterBuffer;
    GLTexture atomicCounterTextureBuffer;
    constexpr std::array<uint32_t, 4> kInitData = {10, 20, 30, 40};
    createStorageBuffer(writeResource, atomicCounterBuffer, atomicCounterTextureBuffer,
                        sizeof(kInitData), kInitData.data());

    // Use the buffer
    GLProgram readProgram;
    createAtomicCounterVerifyProgram(atomicCounterBuffer, &readProgram);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the buffer
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::createSsboVerifyProgram(WriteResource writeResource,
                                                    GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
layout(std430, binding = 0) buffer block {
    vec4 data;
} inBlock;
out vec4 colorOut;
void main()
{
    if (all(lessThan(abs(inBlock.data - vec4(1.5, 3.75, 5.0, 12.125)), vec4(0.01))))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    programOut->makeRaster(kVS, kFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);
}

void MemoryBarrierTestBase::shaderStorageBitBufferWriteThenBufferRead(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer writeBuffer;
    GLTexture writeTextureBuffer;
    constexpr std::array<float, 4> kInitData = {12.34, 5.6, 78.91, 123.456};
    createStorageBuffer(writeResource, writeBuffer, writeTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    constexpr std::array<float, 4> kWriteData = {1.5, 3.75, 5.0, 12.125};
    setUniformData(writeProgram, kWriteData);

    // Fill the buffer
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the buffer
    GLProgram readProgram;
    createSsboVerifyProgram(writeResource, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::shaderStorageBitBufferReadThenBufferWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp,
    GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer writeBuffer;
    GLTexture writeTextureBuffer;
    constexpr std::array<float, 4> kInitData = {1.5, 3.75, 5.0, 12.125};
    createStorageBuffer(writeResource, writeBuffer, writeTextureBuffer, sizeof(kInitData),
                        kInitData.data());

    // Use the buffer
    GLProgram readProgram;
    createSsboVerifyProgram(writeResource, &readProgram);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {12.34, 5.6, 78.91, 123.456};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndBufferContents(writePipeline, kWriteData);
}

void MemoryBarrierTestBase::createTextureVerifyProgram(WriteResource writeResource,
                                                       GLuint texture,
                                                       GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kImageFS[] = R"(#version 310 es
precision mediump float;
uniform sampler2D s;
out vec4 colorOut;
void main()
{
    if (all(lessThan(abs(texelFetch(s, ivec2(0, 0), 0)- vec4(0.125, 0.25, 0.5, 0.75)), vec4(0.01))))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    constexpr char kImageBufferFS[] = R"(#version 310 es
#extension GL_OES_texture_buffer : require
precision mediump float;
uniform highp samplerBuffer s;
out vec4 colorOut;
void main()
{
    if (texelFetch(s, 0) == vec4(0.125, 0.25, 0.5, 0.75))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    programOut->makeRaster(kVS,
                           writeResource == WriteResource::ImageBuffer ? kImageBufferFS : kImageFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    glBindTexture(writeResource == WriteResource::ImageBuffer ? GL_TEXTURE_BUFFER : GL_TEXTURE_2D,
                  texture);
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::textureFetchBitImageWriteThenSamplerRead(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the image
    GLProgram readProgram;
    createTextureVerifyProgram(writeResource, texture, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);
}

void MemoryBarrierTestBase::textureFetchBitSamplerReadThenImageWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp,
    GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.125, 0.25, 0.5, 0.75};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Use the image
    GLProgram readProgram;
    createTextureVerifyProgram(writeResource, texture, &readProgram);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.65, 0.20, 0.40, 0.95};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);
}

void MemoryBarrierTestBase::createImageVerifyProgram(WriteResource writeResource,
                                                     GLuint texture,
                                                     GLProgram *programOut)
{
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kImageFS[] = R"(#version 310 es
precision mediump float;
layout(rgba8, binding = 0) uniform highp readonly image2D img;
out vec4 colorOut;
void main()
{
    if (all(lessThan(abs(imageLoad(img, ivec2(0, 0))- vec4(0.125, 0.25, 0.5, 0.75)), vec4(0.01))))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    constexpr char kImageBufferFS[] = R"(#version 310 es
#extension GL_OES_texture_buffer : require
precision mediump float;
layout(rgba32f, binding = 0) uniform highp readonly imageBuffer img;
out vec4 colorOut;
void main()
{
    if (imageLoad(img, 0) == vec4(0.125, 0.25, 0.5, 0.75))
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    programOut->makeRaster(kVS,
                           writeResource == WriteResource::ImageBuffer ? kImageBufferFS : kImageFS);
    ASSERT_TRUE(programOut->valid());
    glUseProgram(*programOut);

    if (writeResource == WriteResource::ImageBuffer)
    {
        glBindTexture(GL_TEXTURE_BUFFER, texture);
        glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
    }
    else
    {
        glBindTexture(GL_TEXTURE_2D, texture);
        glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
    }
    EXPECT_GL_NO_ERROR();
}

void MemoryBarrierTestBase::shaderImageAccessBitImageWriteThenImageRead(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Use the image
    GLProgram readProgram;
    createImageVerifyProgram(writeResource, texture, &readProgram);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);
}

void MemoryBarrierTestBase::shaderImageAccessBitImageReadThenImageWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::black);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.125, 0.25, 0.5, 0.75};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Use the image
    GLProgram readProgram;
    createImageVerifyProgram(writeResource, texture, &readProgram);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.65, 0.20, 0.40, 0.95};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);
}

void MemoryBarrierTestBase::textureUpdateBitImageWriteThenCopy(ShaderWritePipeline writePipeline,
                                                               WriteResource writeResource,
                                                               NoopOp preBarrierOp,
                                                               NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Copy from framebuffer over the texture
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kTextureSize, kTextureSize);

    const std::array<float, 4> kExpectedData = {
        0, 1.0f, writePipeline == ShaderWritePipeline::Graphics ? 1.0f : 0, 1.0f};
    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kExpectedData);
}

void MemoryBarrierTestBase::textureUpdateBitCopyThenImageWrite(ShaderWritePipeline writePipeline,
                                                               WriteResource writeResource,
                                                               NoopOp preBarrierOp,
                                                               NoopOp postBarrierOp,
                                                               GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Copy from framebuffer over the texture
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kTextureSize, kTextureSize);

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);
}

void MemoryBarrierTestBase::framebufferBitImageWriteThenDraw(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Draw to image via framebuffer
    ANGLE_GL_PROGRAM(verifyProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(verifyProgram);
    GLint colorUniformLocation =
        glGetUniformLocation(verifyProgram, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);
    setupVertexArray(ShaderWritePipeline::Graphics, verifyProgram);

    GLFramebuffer drawFbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFbo);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);

    constexpr std::array<float, 4> kBlendData = {0.125, 0.25, 0.5, 0.25};
    glUniform4fv(colorUniformLocation, 1, kBlendData.data());

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glDisable(GL_BLEND);
    EXPECT_GL_NO_ERROR();

    const std::array<float, 4> kExpectedData = {
        kWriteData[0] + kBlendData[0],
        kWriteData[1] + kBlendData[1],
        kWriteData[2] + kBlendData[2],
        kWriteData[3] + kBlendData[3],
    };
    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kExpectedData);
}

void MemoryBarrierTestBase::framebufferBitImageWriteThenReadPixels(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {1.0, 1.0, 0.0, 1.0};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Read from image via framebuffer
    GLBuffer packBuffer;
    glBindBuffer(GL_PIXEL_PACK_BUFFER, packBuffer);
    constexpr std::array<uint32_t, 4> kPBOInitData = {0x12345678u, 0x9ABCDEF0u, 0x13579BDFu,
                                                      0x2468ACE0u};
    glBufferData(GL_PIXEL_PACK_BUFFER, sizeof(kInitData), kPBOInitData.data(), GL_STATIC_DRAW);
    EXPECT_GL_NO_ERROR();

    GLFramebuffer readFbo;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);

    glReadPixels(0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);

    // Verify the PBO for completeness
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, packBuffer);
    constexpr std::array<uint32_t, 4> kExpectedData = {
        0xFF00FFFFu,
        kPBOInitData[1],
        kPBOInitData[2],
        kPBOInitData[3],
    };
    verifyBufferContents(kExpectedData);
}

void MemoryBarrierTestBase::framebufferBitImageWriteThenCopy(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {1.0, 1.0, 0.0, 1.0};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Copy from framebuffer to another texture
    GLFramebuffer readFbo;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);

    GLTexture copyTexture;
    glBindTexture(GL_TEXTURE_2D, copyTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureSize, kTextureSize);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kTextureSize, kTextureSize);

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);

    // Verify the copy texture for completeness
    verifyImageContents(copyTexture, kWriteData);
}

void MemoryBarrierTestBase::framebufferBitImageWriteThenBlit(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp)
{
    GLTexture blitColor;
    GLFramebuffer blitFbo;
    createFramebuffer(blitColor, blitFbo, GLColor::black);

    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    constexpr std::array<float, 4> kWriteData = {1.0, 1.0, 0.0, 1.0};
    setUniformData(writeProgram, kWriteData);

    // Fill the image
    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisable(GL_BLEND);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);

    noopOp(postBarrierOp);

    // Blit from framebuffer to another framebuffer
    GLFramebuffer readFbo;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, blitFbo);
    glBlitFramebuffer(0, 0, kTextureSize, kTextureSize, 0, 0, kTextureSize, kTextureSize,
                      GL_COLOR_BUFFER_BIT, GL_NEAREST);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    EXPECT_GL_NO_ERROR();

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);

    // Verify the blit fbo for completeness
    glBindFramebuffer(GL_READ_FRAMEBUFFER, blitFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

void MemoryBarrierTestBase::framebufferBitDrawThenImageWrite(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp,
                                                             GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {0.65, 0.92, 0.11, 0.54};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Draw to image via framebuffer
    ANGLE_GL_PROGRAM(verifyProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    glUseProgram(verifyProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(ShaderWritePipeline::Graphics, verifyProgram);

    GLFramebuffer drawFbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFbo);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);
}

void MemoryBarrierTestBase::framebufferBitReadPixelsThenImageWrite(
    ShaderWritePipeline writePipeline,
    WriteResource writeResource,
    NoopOp preBarrierOp,
    NoopOp postBarrierOp,
    GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {1.0, 1.0, 0.0, 1.0};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Read from image via framebuffer
    GLBuffer packBuffer;
    glBindBuffer(GL_PIXEL_PACK_BUFFER, packBuffer);
    constexpr std::array<uint32_t, 4> kPBOInitData = {0x12345678u, 0x9ABCDEF0u, 0x13579BDFu,
                                                      0x2468ACE0u};
    glBufferData(GL_PIXEL_PACK_BUFFER, sizeof(kInitData), kPBOInitData.data(), GL_STATIC_DRAW);
    EXPECT_GL_NO_ERROR();

    GLFramebuffer readFbo;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);

    glReadPixels(0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);

    // Verify the PBO for completeness
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, packBuffer);
    constexpr std::array<uint32_t, 4> kExpectedData = {
        0xFF00FFFFu,
        kPBOInitData[1],
        kPBOInitData[2],
        kPBOInitData[3],
    };
    verifyBufferContents(kExpectedData);
}

void MemoryBarrierTestBase::framebufferBitCopyThenImageWrite(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp,
                                                             GLbitfield barrierBit)
{
    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {1.0, 1.0, 0.0, 1.0};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Copy from framebuffer to another texture
    GLFramebuffer readFbo;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);

    GLTexture copyTexture;
    glBindTexture(GL_TEXTURE_2D, copyTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTextureSize, kTextureSize);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kTextureSize, kTextureSize);

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);

    // Verify the copy texture for completeness
    verifyImageContents(copyTexture, kInitData);
}

void MemoryBarrierTestBase::framebufferBitBlitThenImageWrite(ShaderWritePipeline writePipeline,
                                                             WriteResource writeResource,
                                                             NoopOp preBarrierOp,
                                                             NoopOp postBarrierOp,
                                                             GLbitfield barrierBit)
{
    GLTexture blitColor;
    GLFramebuffer blitFbo;
    createFramebuffer(blitColor, blitFbo, GLColor::black);

    GLTexture color;
    GLFramebuffer fbo;
    GLProgram writeProgram;

    createFramebuffer(color, fbo, GLColor::green);

    GLBuffer textureBufferStorage;
    GLTexture texture;
    constexpr std::array<float, 4> kInitData = {1.0, 1.0, 0.0, 1.0};
    createStorageImage(writeResource, textureBufferStorage, texture, kInitData);

    // Blit from framebuffer to another framebuffer
    GLFramebuffer readFbo;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_READ_FRAMEBUFFER);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, blitFbo);
    glBlitFramebuffer(0, 0, kTextureSize, kTextureSize, 0, 0, kTextureSize, kTextureSize,
                      GL_COLOR_BUFFER_BIT, GL_NEAREST);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    EXPECT_GL_NO_ERROR();

    noopOp(preBarrierOp);

    // Issue the appropriate memory barrier
    glMemoryBarrier(barrierBit);

    noopOp(postBarrierOp);

    // Fill the image
    createProgram(writePipeline, writeResource, &writeProgram);

    GLBuffer positionBuffer;
    createQuadVertexArray(positionBuffer);
    setupVertexArray(writePipeline, writeProgram);

    constexpr std::array<float, 4> kWriteData = {0.125, 0.25, 0.5, 0.75};
    setUniformData(writeProgram, kWriteData);

    if (writePipeline == ShaderWritePipeline::Graphics)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }
    else
    {
        glDispatchCompute(1, 1, 1);
    }

    verifyFramebufferAndImageContents(writePipeline, writeResource, texture, kWriteData);

    // Verify the blit fbo for completeness
    glBindFramebuffer(GL_READ_FRAMEBUFFER, blitFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

class MemoryBarrierBufferTest : public MemoryBarrierTestBase,
                                public ANGLETest<MemoryBarrierVariationsTestParams>
{
  protected:
    MemoryBarrierBufferTest()
    {
        setWindowWidth(16);
        setWindowHeight(32);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

class MemoryBarrierImageTest : public MemoryBarrierTestBase,
                               public ANGLETest<MemoryBarrierVariationsTestParams>
{
  protected:
    MemoryBarrierImageTest()
    {
        setWindowWidth(16);
        setWindowHeight(32);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

class MemoryBarrierImageBufferOnlyTest : public MemoryBarrierTestBase,
                                         public ANGLETest<MemoryBarrierVariationsTestParams>
{
  protected:
    MemoryBarrierImageBufferOnlyTest()
    {
        setWindowWidth(16);
        setWindowHeight(32);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

class MemoryBarrierImageOnlyTest : public MemoryBarrierTestBase,
                                   public ANGLETest<MemoryBarrierVariationsTestParams>
{
  protected:
    MemoryBarrierImageOnlyTest()
    {
        setWindowWidth(16);
        setWindowHeight(32);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

class MemoryBarrierBufferOnlyTest : public MemoryBarrierTestBase,
                                    public ANGLETest<MemoryBarrierVariationsTestParams>
{
  protected:
    MemoryBarrierBufferOnlyTest()
    {
        setWindowWidth(16);
        setWindowHeight(32);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

// Test GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT; shader write -> vertex read
TEST_P(MemoryBarrierBufferTest, VertexAtrribArrayBitWriteThenVertexRead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    vertexAttribArrayBitBufferWriteThenVertexRead(writePipeline, writeResource, preBarrierOp,
                                                  postBarrierOp);
}

// Test GL_ELEMENT_ARRAY_BARRIER_BIT; shader write -> index read
TEST_P(MemoryBarrierBufferTest, ElementArrayBitWriteThenIndexRead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    elementArrayBitBufferWriteThenIndexRead(writePipeline, writeResource, preBarrierOp,
                                            postBarrierOp);
}

// Test GL_UNIFORM_BARRIER_BIT; shader write -> ubo read
TEST_P(MemoryBarrierBufferTest, UniformBitWriteThenUBORead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    uniformBitBufferWriteThenUBORead(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_TEXTURE_FETCH_BARRIER_BIT; shader write -> sampler read
TEST_P(MemoryBarrierImageTest, TextureFetchBitWriteThenSamplerRead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    textureFetchBitImageWriteThenSamplerRead(writePipeline, writeResource, preBarrierOp,
                                             postBarrierOp);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; shader write -> image read
TEST_P(MemoryBarrierImageTest, ShaderImageAccessBitWriteThenImageRead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    shaderImageAccessBitImageWriteThenImageRead(writePipeline, writeResource, preBarrierOp,
                                                postBarrierOp);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; image read -> shader write
TEST_P(MemoryBarrierImageTest, ShaderImageAccessBitImageReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    // Looks like the implementation is missing this line from the spec:
    //
    // > Additionally, image stores issued after the barrier will not execute until all memory
    // > accesses (e.g., loads, stores, texture fetches, vertex fetches) initiated prior to the
    // > barrier complete.
    //
    // http://anglebug.com/5650
    ANGLE_SKIP_TEST_IF(IsOpenGL() && IsNVIDIA() && writeResource == WriteResource::Image);

    shaderImageAccessBitImageReadThenImageWrite(writePipeline, writeResource, preBarrierOp,
                                                postBarrierOp);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; vertex read -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitVertexReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    vertexAttribArrayBitVertexReadThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                                  postBarrierOp,
                                                  GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; index read -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitIndexReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    elementArrayBitIndexReadThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                            postBarrierOp, GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; ubo read -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitUBOReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    uniformBitUBOReadThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                     GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; sampler read -> shader write
TEST_P(MemoryBarrierImageTest, ShaderImageAccessBitSamplerReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    textureFetchBitSamplerReadThenImageWrite(writePipeline, writeResource, preBarrierOp,
                                             postBarrierOp, GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; indirect read -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitIndirectReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    commandBitIndirectReadThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                          GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; pixel pack -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitPackThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    pixelBufferBitPackThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                      GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; pixel unpack -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitUnpackThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    pixelBufferBitUnpackThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                        GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; texture copy -> shader write
TEST_P(MemoryBarrierImageOnlyTest, ShaderImageAccessBitCopyThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    textureUpdateBitCopyThenImageWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                       GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; buffer copy -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitCopyThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    bufferUpdateBitCopyThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                       GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; draw -> shader write
TEST_P(MemoryBarrierImageOnlyTest, ShaderImageAccessBitDrawThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitDrawThenImageWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                     GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; read pixels -> shader write
TEST_P(MemoryBarrierImageOnlyTest, ShaderImageAccessBitReadPixelsThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitReadPixelsThenImageWrite(writePipeline, writeResource, preBarrierOp,
                                           postBarrierOp, GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; fbo copy -> shader write
TEST_P(MemoryBarrierImageOnlyTest, ShaderImageAccessBitFBOCopyThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitCopyThenImageWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                     GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; blit -> shader write
TEST_P(MemoryBarrierImageOnlyTest, ShaderImageAccessBitBlitThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitBlitThenImageWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                     GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; xfb capture -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitCaptureThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    transformFeedbackBitCaptureThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                               postBarrierOp, GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; atomic write -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitAtomicThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    atomicCounterBitAtomicThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                          GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; buffer read -> shader write
TEST_P(MemoryBarrierImageBufferOnlyTest, ShaderImageAccessBitBufferReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    shaderStorageBitBufferReadThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                              postBarrierOp, GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}

// Test GL_COMMAND_BARRIER_BIT; shader write -> indirect read
TEST_P(MemoryBarrierBufferTest, CommandBitWriteThenIndirectRead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    commandBitBufferWriteThenIndirectRead(writePipeline, writeResource, preBarrierOp,
                                          postBarrierOp);
}

// Test GL_PIXEL_BUFFER_BARRIER_BIT; shader write -> pixel pack
TEST_P(MemoryBarrierBufferTest, PixelBufferBitWriteThenPack)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    pixelBufferBitBufferWriteThenPack(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_PIXEL_BUFFER_BARRIER_BIT; shader write -> pixel unpack
TEST_P(MemoryBarrierBufferTest, PixelBufferBitWriteThenUnpack)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    pixelBufferBitBufferWriteThenUnpack(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_TEXTURE_UPDATE_BARRIER_BIT; shader write -> texture copy
TEST_P(MemoryBarrierImageOnlyTest, TextureUpdateBitWriteThenCopy)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    textureUpdateBitImageWriteThenCopy(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_BUFFER_UPDATE_BARRIER_BIT; shader write -> buffer copy
TEST_P(MemoryBarrierBufferTest, BufferUpdateBitWriteThenCopy)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    bufferUpdateBitBufferWriteThenCopy(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_FRAMEBUFFER_BARRIER_BIT; shader write -> draw
TEST_P(MemoryBarrierImageOnlyTest, FramebufferBitWriteThenDraw)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitImageWriteThenDraw(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_FRAMEBUFFER_BARRIER_BIT; shader write -> read pixels
TEST_P(MemoryBarrierImageOnlyTest, FramebufferBitWriteThenReadPixels)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitImageWriteThenReadPixels(writePipeline, writeResource, preBarrierOp,
                                           postBarrierOp);
}

// Test GL_FRAMEBUFFER_BARRIER_BIT; shader write -> copy
TEST_P(MemoryBarrierImageOnlyTest, FramebufferBitWriteThenCopy)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitImageWriteThenCopy(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_FRAMEBUFFER_BARRIER_BIT; shader write -> blit
TEST_P(MemoryBarrierImageOnlyTest, FramebufferBitWriteThenBlit)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    framebufferBitImageWriteThenBlit(writePipeline, writeResource, preBarrierOp, postBarrierOp);
}

// Test GL_TRANSFORM_FEEDBACK_BARRIER_BIT; shader write -> xfb capture
TEST_P(MemoryBarrierBufferTest, TransformFeedbackBitWriteThenCapture)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    transformFeedbackBitBufferWriteThenCapture(writePipeline, writeResource, preBarrierOp,
                                               postBarrierOp);
}

// Test GL_ATOMIC_COUNTER_BARRIER_BIT; shader write -> atomic write
TEST_P(MemoryBarrierBufferTest, AtomicCounterBitWriteThenAtomic)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    atomicCounterBitBufferWriteThenAtomic(writePipeline, writeResource, preBarrierOp,
                                          postBarrierOp);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; shader write -> shader read
TEST_P(MemoryBarrierBufferTest, ShaderStorageBitWriteThenRead)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    shaderStorageBitBufferWriteThenBufferRead(writePipeline, writeResource, preBarrierOp,
                                              postBarrierOp);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; shader read -> buffer write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    shaderStorageBitBufferReadThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                              postBarrierOp, GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; vertex read -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitVertexReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    vertexAttribArrayBitVertexReadThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                                  postBarrierOp, GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; index read -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitIndexReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    elementArrayBitIndexReadThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                            postBarrierOp, GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; ubo read -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitUBOReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    uniformBitUBOReadThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                     GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; indirect read -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitIndirectReadThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    commandBitIndirectReadThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                          GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; pixel pack -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitPackThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    pixelBufferBitPackThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                      GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; pixel unpack -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitUnpackThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    pixelBufferBitUnpackThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                        GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; buffer copy -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitCopyThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    bufferUpdateBitCopyThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                       GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; xfb capture -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitCaptureThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    transformFeedbackBitCaptureThenBufferWrite(writePipeline, writeResource, preBarrierOp,
                                               postBarrierOp, GL_SHADER_STORAGE_BARRIER_BIT);
}

// Test GL_SHADER_STORAGE_BARRIER_BIT; atomic write -> shader write
TEST_P(MemoryBarrierBufferOnlyTest, ShaderStorageBitAtomicThenWrite)
{
    ShaderWritePipeline writePipeline;
    WriteResource writeResource;
    NoopOp preBarrierOp;
    NoopOp postBarrierOp;
    ParseMemoryBarrierVariationsTestParams(GetParam(), &writePipeline, &writeResource,
                                           &preBarrierOp, &postBarrierOp);

    ANGLE_SKIP_TEST_IF(!hasExtensions(writeResource));

    atomicCounterBitAtomicThenBufferWrite(writePipeline, writeResource, preBarrierOp, postBarrierOp,
                                          GL_SHADER_STORAGE_BARRIER_BIT);
}

constexpr ShaderWritePipeline kWritePipelines[] = {
    ShaderWritePipeline::Graphics,
    ShaderWritePipeline::Compute,
};
constexpr WriteResource kBufferWriteResources[] = {
    WriteResource::Buffer,
    WriteResource::ImageBuffer,
};
constexpr WriteResource kImageWriteResources[] = {
    WriteResource::Image,
    WriteResource::ImageBuffer,
};
constexpr WriteResource kImageBufferOnlyWriteResources[] = {
    WriteResource::ImageBuffer,
};
constexpr WriteResource kImageOnlyWriteResources[] = {
    WriteResource::Image,
};
constexpr WriteResource kBufferOnlyWriteResources[] = {
    WriteResource::Buffer,
};
constexpr NoopOp kNoopOps[] = {
    NoopOp::None,
    NoopOp::Draw,
    NoopOp::Dispatch,
};

// Note: due to large number of tests, these are only run on Vulkan and a single configuration
// (swiftshader).

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryBarrierBufferTest);
ANGLE_INSTANTIATE_TEST_COMBINE_4(MemoryBarrierBufferTest,
                                 MemoryBarrierVariationsTestPrint,
                                 testing::ValuesIn(kWritePipelines),
                                 testing::ValuesIn(kBufferWriteResources),
                                 testing::ValuesIn(kNoopOps),
                                 testing::ValuesIn(kNoopOps),
                                 ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryBarrierImageTest);
ANGLE_INSTANTIATE_TEST_COMBINE_4(MemoryBarrierImageTest,
                                 MemoryBarrierVariationsTestPrint,
                                 testing::ValuesIn(kWritePipelines),
                                 testing::ValuesIn(kImageWriteResources),
                                 testing::ValuesIn(kNoopOps),
                                 testing::ValuesIn(kNoopOps),
                                 ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryBarrierImageBufferOnlyTest);
ANGLE_INSTANTIATE_TEST_COMBINE_4(MemoryBarrierImageBufferOnlyTest,
                                 MemoryBarrierVariationsTestPrint,
                                 testing::ValuesIn(kWritePipelines),
                                 testing::ValuesIn(kImageBufferOnlyWriteResources),
                                 testing::ValuesIn(kNoopOps),
                                 testing::ValuesIn(kNoopOps),
                                 ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryBarrierImageOnlyTest);
ANGLE_INSTANTIATE_TEST_COMBINE_4(MemoryBarrierImageOnlyTest,
                                 MemoryBarrierVariationsTestPrint,
                                 testing::ValuesIn(kWritePipelines),
                                 testing::ValuesIn(kImageOnlyWriteResources),
                                 testing::ValuesIn(kNoopOps),
                                 testing::ValuesIn(kNoopOps),
                                 ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MemoryBarrierBufferOnlyTest);
ANGLE_INSTANTIATE_TEST_COMBINE_4(MemoryBarrierBufferOnlyTest,
                                 MemoryBarrierVariationsTestPrint,
                                 testing::ValuesIn(kWritePipelines),
                                 testing::ValuesIn(kBufferOnlyWriteResources),
                                 testing::ValuesIn(kNoopOps),
                                 testing::ValuesIn(kNoopOps),
                                 ES31_VULKAN_SWIFTSHADER());

}  // anonymous namespace
