//
// Copyright 2022 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.
//

#include <sstream>
#include <string>
#include "common/unsafe_buffers.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"

using namespace angle;

#define ASSERT_GL_INTEGER(pname, expected) \
    {                                      \
        GLint value;                       \
        glGetIntegerv(pname, &value);      \
        ASSERT_EQ(value, GLint(expected)); \
    }

#define EXPECT_GL_INTEGER(pname, expected) \
    {                                      \
        GLint value;                       \
        glGetIntegerv(pname, &value);      \
        EXPECT_EQ(value, GLint(expected)); \
    }

#define EXPECT_PLS_INTEGER(plane, pname, expected)                                                 \
    {                                                                                              \
        GLint value = 0xbaadc0de;                                                                  \
        glGetFramebufferPixelLocalStorageParameterivRobustANGLE(plane, pname, 1, nullptr, &value); \
        EXPECT_EQ(value, GLint(expected));                                                         \
        value = 0xbaadc0de;                                                                        \
        glGetFramebufferPixelLocalStorageParameterivANGLE(plane, pname, &value);                   \
        EXPECT_EQ(value, GLint(expected));                                                         \
    }

#define EXPECT_GL_SINGLE_ERROR(err)                \
    EXPECT_GL_ERROR(err);                          \
    while (GLenum nextError = glGetError())        \
    {                                              \
        EXPECT_EQ(nextError, GLenum(GL_NO_ERROR)); \
    }

#define EXPECT_GL_SINGLE_ERROR_MSG(msg)                                                 \
    if (mHasDebugKHR)                                                                   \
    {                                                                                   \
        EXPECT_EQ(mErrorMessages.size(), size_t(1));                                    \
        if (mErrorMessages.size() == 1)                                                 \
        {                                                                               \
            EXPECT_NE(mErrorMessages.back().find(std::string(msg)), std::string::npos); \
        }                                                                               \
        mErrorMessages.clear();                                                         \
    }

#define EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(plane, rgba)                             \
    {                                                                                 \
        std::array<GLfloat, 4> expected rgba;                                         \
        std::array<GLfloat, 4> value;                                                 \
        value.fill(std::numeric_limits<GLfloat>::quiet_NaN());                        \
        glGetFramebufferPixelLocalStorageParameterfvANGLE(                            \
            plane, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, value.data());             \
        EXPECT_EQ(value, expected);                                                   \
        value.fill(std::numeric_limits<GLfloat>::quiet_NaN());                        \
        glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(                      \
            plane, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, nullptr, value.data()); \
        EXPECT_EQ(value, expected);                                                   \
    }

#define EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(plane, rgba)                             \
    {                                                                               \
        std::array<GLint, 4> expected rgba;                                         \
        std::array<GLint, 4> value;                                                 \
        value.fill(0xbaadc0de);                                                     \
        glGetFramebufferPixelLocalStorageParameterivANGLE(                          \
            plane, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE, value.data());             \
        EXPECT_EQ(value, expected);                                                 \
        value.fill(0xbaadc0de);                                                     \
        glGetFramebufferPixelLocalStorageParameterivRobustANGLE(                    \
            plane, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE, 4, nullptr, value.data()); \
        EXPECT_EQ(value, expected);                                                 \
    }

#define EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(plane, rgba)                             \
    {                                                                                        \
        std::array<GLuint, 4> expected rgba;                                                 \
        std::array<GLuint, 4> value;                                                         \
        value.fill(0xbaadc0de);                                                              \
        glGetFramebufferPixelLocalStorageParameteruivANGLE(                                  \
            plane, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, value.data());             \
        EXPECT_EQ(value, expected);                                                          \
        value.fill(0xbaadc0de);                                                              \
        glGetFramebufferPixelLocalStorageParameteruivRobustANGLE(                            \
            plane, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 4, nullptr, value.data()); \
        EXPECT_EQ(value, expected);                                                          \
    }

#define EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(target, pname, expectedValue) \
    {                                                                       \
        GLint value = 0xbaadc0de;                                           \
        glGetFramebufferParameterivMESA(target, pname, &value);             \
        EXPECT_EQ(value, static_cast<GLint>(expectedValue));                \
    }

#define EXPECT_FRAMEBUFFER_PARAMETER_INT(target, pname, expectedValue) \
    {                                                                  \
        GLint value = 0xbaadc0de;                                      \
        glGetFramebufferParameteriv(target, pname, &value);            \
        EXPECT_EQ(value, static_cast<GLint>(expectedValue));           \
    }

#define EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(framebuffer, attachment, value)                    \
    {                                                                                         \
        GLint attachmentType = 1234567890;                                                    \
        glGetFramebufferAttachmentParameteriv(                                                \
            framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &attachmentType); \
        EXPECT_EQ(attachmentType, static_cast<GLint>(value));                                 \
    }

#define EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(framebuffer, attachment, value)                    \
    {                                                                                         \
        GLint attachmentName = 1234567890;                                                    \
        glGetFramebufferAttachmentParameteriv(                                                \
            framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &attachmentName); \
        EXPECT_EQ(attachmentName, static_cast<GLint>(value));                                 \
    }

#define EXPECT_FRAMEBUFFER_ATTACHMENT_LEVEL(framebuffer, attachment, value)                      \
    {                                                                                            \
        GLint attachmentLevel = 1234567890;                                                      \
        glGetFramebufferAttachmentParameteriv(                                                   \
            framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &attachmentLevel); \
        EXPECT_EQ(attachmentLevel, static_cast<GLint>(value));                                   \
    }

#define EXPECT_FRAMEBUFFER_ATTACHMENT_LAYER(framebuffer, attachment, value)                      \
    {                                                                                            \
        GLint attachmentLayer = 1234567890;                                                      \
        glGetFramebufferAttachmentParameteriv(                                                   \
            framebuffer, attachment, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &attachmentLayer); \
        EXPECT_EQ(attachmentLayer, static_cast<GLint>(value));                                   \
    }

#define EXPECT_GL_COLOR_MASK(r, g, b, a)                         \
    {                                                            \
        std::array<GLboolean, 4> mask;                           \
        glGetBooleanv(GL_COLOR_WRITEMASK, mask.data());          \
        EXPECT_EQ(mask, (std::array<GLboolean, 4>{r, g, b, a})); \
    }

// glGetBooleani_v is not in ES3.
#define EXPECT_GL_COLOR_MASK_INDEXED(index, r, g, b, a)          \
    {                                                            \
        std::array<GLint, 4> mask;                               \
        glGetIntegeri_v(GL_COLOR_WRITEMASK, index, mask.data()); \
        EXPECT_EQ(mask, (std::array<GLint, 4>{r, g, b, a}));     \
    }

constexpr static int W = 128, H = 128;
constexpr static std::array<float, 4> FULLSCREEN = {0, 0, W, H};

// For building the <loadops> parameter of glBeginPixelLocalStorageANGLE.
template <typename T>
struct Array
{
    Array(const std::initializer_list<T> &list) : mVec(list) {}
    operator const T *() const { return mVec.data(); }
    std::vector<T> mVec;
};

template <typename T>
static Array<T> MakeArray(const std::initializer_list<T> &list)
{
    return Array<T>(list);
}

static Array<GLenum> GLenumArray(const std::initializer_list<GLenum> &list)
{
    return Array<GLenum>(list);
}

static Array<GLfloat> ClearF(GLfloat r, GLfloat g, GLfloat b, GLfloat a)
{
    return MakeArray({r, g, b, a});
}

static Array<GLint> ClearI(GLint r, GLint g, GLint b, GLint a)
{
    return MakeArray({r, g, b, a});
}

static Array<GLuint> ClearUI(GLuint r, GLuint g, GLuint b, GLuint a)
{
    return MakeArray({r, g, b, a});
}

class PLSTestTexture
{
  public:
    PLSTestTexture(GLenum internalformat) { reset(internalformat); }
    PLSTestTexture(GLenum internalformat, int w, int h) { reset(internalformat, w, h, 1); }
    PLSTestTexture(GLenum internalformat, int w, int h, int mipLevels)
    {
        reset(internalformat, w, h, mipLevels);
    }
    PLSTestTexture(PLSTestTexture &&that) : mID(std::exchange(that.mID, 0)) {}
    void reset()
    {
        glDeleteTextures(1, &mID);
        mID = 0;
    }
    void reset(GLenum internalformat) { reset(internalformat, W, H, 1); }
    void reset(GLenum internalformat, int w, int h, int mipLevels)
    {
        GLuint id;
        glGenTextures(1, &id);
        glBindTexture(GL_TEXTURE_2D, id);
        glTexStorage2D(GL_TEXTURE_2D, mipLevels, internalformat, w, h);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        if (mID)
        {
            glDeleteTextures(1, &mID);
        }
        mID = id;
    }
    ~PLSTestTexture()
    {
        if (mID)
        {
            glDeleteTextures(1, &mID);
        }
    }
    operator GLuint() const { return mID; }
    GLuint id() const { return mID; }

  private:
    PLSTestTexture &operator=(const PLSTestTexture &) = delete;
    PLSTestTexture(const PLSTestTexture &)            = delete;
    GLuint mID                                        = 0;
};

struct Box
{
    using float4 = std::array<float, 4>;
    constexpr Box(float4 rect) : rect(rect), color{}, aux1{}, aux2{} {}
    constexpr Box(float4 rect, float4 incolor) : rect(rect), color(incolor), aux1{}, aux2{} {}
    constexpr Box(float4 rect, float4 incolor, float4 inaux1)
        : rect(rect), color(incolor), aux1(inaux1), aux2{}
    {}
    constexpr Box(float4 rect, float4 incolor, float4 inaux1, float4 inaux2)
        : rect(rect), color(incolor), aux1(inaux1), aux2(inaux2)
    {}
    float4 rect;
    float4 color;
    float4 aux1;
    float4 aux2;
};

enum class UseBarriers
{
    Never,
    IfNotCoherent,
    Always,
};

class PLSProgram
{
  public:
    enum class VertexArray
    {
        Default,
        VAO
    };

    PLSProgram(VertexArray vertexArray = VertexArray::VAO)
    {
        if (vertexArray == VertexArray::VAO)
        {
            glGenVertexArrays(1, &mVertexArray);
            glGenBuffers(1, &mVertexBuffer);
        }
    }

    ~PLSProgram() { reset(); }

    void reset()
    {
        if (mVertexArray != 0)
        {
            glDeleteVertexArrays(1, &mVertexArray);
            mVertexArray = 0;
        }
        if (mVertexBuffer != 0)
        {
            glDeleteBuffers(1, &mVertexBuffer);
            mVertexBuffer = 0;
        }
    }

    int widthUniform() const { return mWidthUniform; }
    int heightUniform() const { return mHeightUniform; }

    void compile(std::string fsMain) { compile("", fsMain); }

    void compile(std::string extensions, std::string fsMain)
    {
        glBindVertexArray(mVertexArray);
        glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);

        if (mVertexArray == 0)
        {
            if (mLTRBLocation >= 0)
            {
                glDisableVertexAttribArray(mLTRBLocation);
            }
            if (mRGBALocation >= 0)
            {
                glDisableVertexAttribArray(mRGBALocation);
            }
            if (mAux1Location >= 0)
            {
                glDisableVertexAttribArray(mAux1Location);
            }
            if (mAux2Location >= 0)
            {
                glDisableVertexAttribArray(mAux2Location);
            }
        }

        mProgram.makeRaster(
            R"(#version 300 es
            precision highp float;

            uniform float W, H;
            in vec4 rect;
            in vec4 incolor;
            in vec4 inaux1;
            in vec4 inaux2;
            out vec4 color;
            out vec4 aux1;
            out vec4 aux2;

            void main()
            {
                color = incolor;
                aux1 = inaux1;
                aux2 = inaux2;
                gl_Position.x = ((gl_VertexID & 1) == 0 ? rect.x : rect.z) * 2.0/W - 1.0;
                gl_Position.y = ((gl_VertexID & 2) == 0 ? rect.y : rect.w) * 2.0/H - 1.0;
                gl_Position.zw = vec2(0, 1);
            })",

            std::string(R"(#version 300 es
            #extension GL_ANGLE_shader_pixel_local_storage : require
            )")
                .append(extensions)
                .append(R"(
            precision highp float;
            in vec4 color;
            in vec4 aux1;
            in vec4 aux2;)")
                .append(fsMain)
                .c_str());

        EXPECT_TRUE(mProgram.valid());

        glUseProgram(mProgram);

        mWidthUniform = glGetUniformLocation(mProgram, "W");
        glUniform1f(mWidthUniform, W);

        mHeightUniform = glGetUniformLocation(mProgram, "H");
        glUniform1f(mHeightUniform, H);

        mLTRBLocation = glGetAttribLocation(mProgram, "rect");
        glEnableVertexAttribArray(mLTRBLocation);
        glVertexAttribDivisor(mLTRBLocation, 1);

        mRGBALocation = glGetAttribLocation(mProgram, "incolor");
        glEnableVertexAttribArray(mRGBALocation);
        glVertexAttribDivisor(mRGBALocation, 1);

        mAux1Location = glGetAttribLocation(mProgram, "inaux1");
        glEnableVertexAttribArray(mAux1Location);
        glVertexAttribDivisor(mAux1Location, 1);

        mAux2Location = glGetAttribLocation(mProgram, "inaux2");
        glEnableVertexAttribArray(mAux2Location);
        glVertexAttribDivisor(mAux2Location, 1);
    }

    GLuint get() const { return mProgram; }
    operator GLuint() { return get(); }
    operator GLuint() const { return get(); }

    void bind()
    {
        glUseProgram(mProgram);
        glBindVertexArray(mVertexArray);
        glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    }

    void drawBoxes(std::vector<Box> boxes, UseBarriers useBarriers = UseBarriers::IfNotCoherent)
    {
        uintptr_t base;
        if (mVertexBuffer == 0)
        {
            base = reinterpret_cast<uintptr_t>(boxes.data());
        }
        else
        {
            glBufferData(GL_ARRAY_BUFFER, boxes.size() * sizeof(Box), boxes.data(), GL_STATIC_DRAW);
            base = 0;
        }
        if ((useBarriers == UseBarriers::IfNotCoherent &&
             !IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent")) ||
            useBarriers == UseBarriers::Always)
        {
            for (size_t i = 0; i < boxes.size(); ++i)
            {
                glVertexAttribPointer(
                    mLTRBLocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                    reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, rect)));
                glVertexAttribPointer(
                    mRGBALocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                    reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, color)));
                glVertexAttribPointer(
                    mAux1Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                    reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, aux1)));
                glVertexAttribPointer(
                    mAux2Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                    reinterpret_cast<void *>(base + i * sizeof(Box) + offsetof(Box, aux2)));
                glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 1);
                glPixelLocalStorageBarrierANGLE();
            }
        }
        else
        {
            glVertexAttribPointer(mLTRBLocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                                  reinterpret_cast<void *>(base + offsetof(Box, rect)));
            glVertexAttribPointer(mRGBALocation, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                                  reinterpret_cast<void *>(base + offsetof(Box, color)));
            glVertexAttribPointer(mAux1Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                                  reinterpret_cast<void *>(base + offsetof(Box, aux1)));
            glVertexAttribPointer(mAux2Location, 4, GL_FLOAT, GL_FALSE, sizeof(Box),
                                  reinterpret_cast<void *>(base + offsetof(Box, aux2)));
            glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, boxes.size());
        }
    }

  private:
    GLProgram mProgram;
    GLuint mVertexArray  = 0;
    GLuint mVertexBuffer = 0;

    GLint mWidthUniform  = -1;
    GLint mHeightUniform = -1;

    GLint mLTRBLocation = -1;
    GLint mRGBALocation = -1;
    GLint mAux1Location = -1;
    GLint mAux2Location = -1;
};

class ShaderInfoLog
{
  public:
    bool compileFragmentShader(const char *source)
    {
        return compileShader(source, GL_FRAGMENT_SHADER);
    }

    bool compileShader(const char *source, GLenum shaderType)
    {
        mInfoLog.clear();

        GLuint shader = glCreateShader(shaderType);
        glShaderSource(shader, 1, &source, nullptr);
        glCompileShader(shader);

        GLint compileResult;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);

        if (compileResult == 0)
        {
            GLint infoLogLength;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
            // Info log length includes the null terminator; std::string::reserve does not.
            mInfoLog.resize(std::max(infoLogLength - 1, 0));
            glGetShaderInfoLog(shader, infoLogLength, nullptr, mInfoLog.data());
        }

        glDeleteShader(shader);
        return compileResult != 0;
    }

    bool has(const char *subStr) const
    {
        return ANGLE_UNSAFE_TODO(strstr(mInfoLog.c_str(), subStr));
    }

    const char *c_str() const { return mInfoLog.c_str(); }

  private:
    std::string mInfoLog;
};

class PixelLocalStorageTest : public ANGLETest<>
{
  public:
    PixelLocalStorageTest()
    {
        setWindowWidth(W);
        setWindowHeight(H);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setExtensionsEnabled(false);
    }

    void testTearDown() override
    {
        mProgram.reset();
        mRenderTextureProgram.reset();
        if (mScratchFBO)
        {
            glDeleteFramebuffers(1, &mScratchFBO);
        }
    }

    void testSetUp() override
    {
        if (EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"))
        {
            glGetIntegerv(GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE, &MAX_PIXEL_LOCAL_STORAGE_PLANES);
            glGetIntegerv(GL_MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE,
                          &MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
            glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &MAX_COLOR_ATTACHMENTS);
            glGetIntegerv(GL_MAX_DRAW_BUFFERS, &MAX_DRAW_BUFFERS);
        }

        // INVALID_OPERATION is generated if DITHER is enabled.
        glDisable(GL_DITHER);

        ANGLETest::testSetUp();
    }

    int isContextVersionAtLeast(int major, int minor)
    {
        return getClientMajorVersion() > major ||
               (getClientMajorVersion() == major && getClientMinorVersion() >= minor);
    }

    void attachTexture2DToScratchFBO(GLuint tex, GLint level = 0)
    {
        if (!mScratchFBO)
        {
            glGenFramebuffers(1, &mScratchFBO);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, level);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    }

    void attachTextureCubeFaceToScratchFBO(GLuint tex, GLenum target, GLint level = 0)
    {
        if (!mScratchFBO)
        {
            glGenFramebuffers(1, &mScratchFBO);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, tex, level);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    }

    void attachTextureLayerToScratchFBO(GLuint tex, int level, int layer)
    {
        if (!mScratchFBO)
        {
            glGenFramebuffers(1, &mScratchFBO);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, mScratchFBO);
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, level, layer);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    }

    // Access texture contents by rendering them into FBO 0, rather than just grabbing them with
    // glReadPixels.
    void renderTextureToDefaultFramebuffer(GLuint tex)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        // Reset the framebuffer contents to some value that might help debugging.
        glClearColor(.1f, .4f, .6f, .9f);
        glClear(GL_COLOR_BUFFER_BIT);

        GLint linked = 0;
        glGetProgramiv(mRenderTextureProgram, GL_LINK_STATUS, &linked);
        if (!linked)
        {
            constexpr char kVS[] =
                R"(#version 300 es
                precision highp float;
                out vec2 texcoord;
                void main()
                {
                    texcoord.x = (gl_VertexID & 1) == 0 ? 0.0 : 1.0;
                    texcoord.y = (gl_VertexID & 2) == 0 ? 0.0 : 1.0;
                    gl_Position = vec4(texcoord * 2.0 - 1.0, 0, 1);
                })";

            constexpr char kFS[] =
                R"(#version 300 es
                precision highp float;
                uniform highp sampler2D tex;  // FIXME! layout(binding=0) causes an ANGLE crash!
                in vec2 texcoord;
                out vec4 fragcolor;
                void main()
                {
                    fragcolor = texture(tex, texcoord);
                })";

            mRenderTextureProgram.makeRaster(kVS, kFS);
            ASSERT_TRUE(mRenderTextureProgram.valid());
            glUseProgram(mRenderTextureProgram);
            glUniform1i(glGetUniformLocation(mRenderTextureProgram, "tex"), 0);
        }

        glUseProgram(mRenderTextureProgram);
        glBindTexture(GL_TEXTURE_2D, tex);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }

    // Tests that run with and without the "noncoherent" qualifier.
    enum class CoherencyMode
    {
        Default,
        Noncoherent,        // layout(noncoherent)
        AlwaysNoncoherent,  // GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE
    };
    void doRGBA8Test(CoherencyMode);
    void doR32Test(CoherencyMode);
    void doCoherencyTest(CoherencyMode);

    // Implemented as a class members so we can run the test on ES3 and ES31 both.
    void doStateRestorationTest();
    void doDrawStateTest();
    void doImplicitDisablesTest_Framebuffer();
    void doImplicitDisablesTest_TextureAttachments();
    void doImplicitDisablesTest_RenderbufferAttachments();
    void doImplicitDisablesTest_Invalidate();

    GLint MAX_PIXEL_LOCAL_STORAGE_PLANES                           = 0;
    GLint MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES = 0;
    GLint MAX_COLOR_ATTACHMENTS                                    = 0;
    GLint MAX_DRAW_BUFFERS                                         = 0;

    PLSProgram mProgram{PLSProgram::VertexArray::Default};

    GLuint mScratchFBO = 0;
    GLProgram mRenderTextureProgram;
};

// Verify conformant implementation-dependent PLS limits.
TEST_P(PixelLocalStorageTest, ImplementationDependentLimits)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Table 6.X: Impementation Dependent Pixel Local Storage Limits.
    EXPECT_TRUE(MAX_PIXEL_LOCAL_STORAGE_PLANES >= 4);
    EXPECT_TRUE(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >= 4);

    // Logical deductions based on 6.X.
    EXPECT_TRUE(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >=
                MAX_PIXEL_LOCAL_STORAGE_PLANES);
    EXPECT_TRUE(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >= MAX_DRAW_BUFFERS);
    EXPECT_TRUE(MAX_COLOR_ATTACHMENTS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
                MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
    EXPECT_TRUE(MAX_DRAW_BUFFERS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
                MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
}

// Verify that rgba8, rgba8i, and rgba8ui pixel local storage behaves as specified.
void PixelLocalStorageTest::doRGBA8Test(CoherencyMode coherencyMode)
{
    std::ostringstream shader;
    if (coherencyMode == CoherencyMode::Default)
    {
        shader << R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane1;
        layout(rgba8i, binding=1) uniform lowp ipixelLocalANGLE plane2;
        layout(binding=2, rgba8ui) uniform lowp upixelLocalANGLE plane3;)";
    }
    else
    {
        shader << R"(
        layout(binding=0, rgba8, noncoherent) uniform lowp pixelLocalANGLE plane1;
        layout(rgba8i, noncoherent, binding=1) uniform lowp ipixelLocalANGLE plane2;
        layout(noncoherent, binding=2, rgba8ui) uniform lowp upixelLocalANGLE plane3;)";
    }
    shader << R"(
    void main()
    {
        pixelLocalStoreANGLE(plane1, color + pixelLocalLoadANGLE(plane1));
        pixelLocalStoreANGLE(plane2, ivec4(aux1) + pixelLocalLoadANGLE(plane2));
        pixelLocalStoreANGLE(plane3, uvec4(aux2) + pixelLocalLoadANGLE(plane3));
    })";
    mProgram.compile(shader.str().c_str());

    PLSTestTexture tex1(GL_RGBA8);
    PLSTestTexture tex2(GL_RGBA8I);
    PLSTestTexture tex3(GL_RGBA8UI);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    const auto usage = (coherencyMode == CoherencyMode::AlwaysNoncoherent)
                           ? GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE
                           : GL_NONE;
    glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0, usage);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0, usage);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0, usage);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

    // Accumulate R, G, B, A in 4 separate passes.
    // Store out-of-range values to ensure they are properly clamped upon storage.
    mProgram.drawBoxes({{FULLSCREEN, {2, -1, -2, -3}, {-500, 0, 0, 0}, {1, 0, 0, 0}},
                        {FULLSCREEN, {0, 1, 0, 100}, {0, -129, 0, 0}, {0, 50, 0, 0}},
                        {FULLSCREEN, {0, 0, 1, 0}, {0, 0, -70, 0}, {0, 0, 100, 0}},
                        {FULLSCREEN, {0, 0, 0, -1}, {128, 0, 0, 500}, {0, 0, 0, 300}}},
                       (coherencyMode != CoherencyMode::Default) ? UseBarriers::Always
                                                                 : UseBarriers::IfNotCoherent);

    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 255, 255, 0));

    attachTexture2DToScratchFBO(tex2);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, W, H, GLColor32I(0, -128, -70, 127));

    attachTexture2DToScratchFBO(tex3);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(1, 50, 100, 255));

    ASSERT_GL_NO_ERROR();
}

// Verify that rgba8, rgba8i, and rgba8ui pixel local storage behaves as specified.
TEST_P(PixelLocalStorageTest, RGBA8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    doRGBA8Test(PixelLocalStorageTest::CoherencyMode::Default);
}

// Verify that rgba8, rgba8i, and rgba8ui pixel local storage behaves as specified when using the
// "noncoherent" qualifier.
TEST_P(PixelLocalStorageTest, RGBA8_noncoherent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    doRGBA8Test(PixelLocalStorageTest::CoherencyMode::Noncoherent);
}

// Verify that rgba8, rgba8i, and rgba8ui pixel local storage behaves as specified when using the
// "noncoherent" qualifier and GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE.
TEST_P(PixelLocalStorageTest, RGBA8_always_noncoherent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    doRGBA8Test(PixelLocalStorageTest::CoherencyMode::AlwaysNoncoherent);
}

// Verify that r32f, r32i, and r32ui pixel local storage behaves as specified.
void PixelLocalStorageTest::doR32Test(CoherencyMode coherencyMode)
{
    std::ostringstream shader;
    if (coherencyMode == CoherencyMode::Default)
    {
        shader << R"(
        layout(r32f, binding=0) uniform highp pixelLocalANGLE plane1;
        layout(binding=1, r32i) uniform highp ipixelLocalANGLE plane2;
        layout(r32ui, binding=2) uniform highp upixelLocalANGLE plane3;)";
    }
    else
    {
        shader << R"(
        layout(noncoherent, r32f, binding=0) uniform highp pixelLocalANGLE plane1;
        layout(binding=1, noncoherent, r32i) uniform highp ipixelLocalANGLE plane2;
        layout(r32ui, binding=2, noncoherent) uniform highp upixelLocalANGLE plane3;)";
    }
    shader << R"(
    void main()
    {
        pixelLocalStoreANGLE(plane1, color + pixelLocalLoadANGLE(plane1));
        pixelLocalStoreANGLE(plane2, ivec4(aux1) + pixelLocalLoadANGLE(plane2));
        pixelLocalStoreANGLE(plane3, uvec4(aux2) + pixelLocalLoadANGLE(plane3));
    })";
    mProgram.compile(shader.str().c_str());

    PLSTestTexture tex1(GL_R32F);
    PLSTestTexture tex2(GL_R32I);
    PLSTestTexture tex3(GL_R32UI);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    const auto usage = (coherencyMode == CoherencyMode::AlwaysNoncoherent)
                           ? GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE
                           : GL_NONE;
    glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0, usage);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0, usage);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0, usage);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

    // Accumulate R in 4 separate passes.
    mProgram.drawBoxes(
        {{FULLSCREEN, {-1.5, 0, 0, 0}, {0x000000ff, 0, 0, 0}, {0x000000ff, 0, 0, 0}},
         {FULLSCREEN, {-10.25, 0, 0, 0}, {0x0000ff00, 0, 0, 0}, {0x0000ff00, 0, 0, 0}},
         {FULLSCREEN, {-100, 0, 0, 0}, {0x00ff0000, 0, 0, 0}, {0x00ff0000, 0, 0, 0}},
         {FULLSCREEN, {.25, 0, 0, 0}, {-0x1000000, 0, 0, 0}, {0xff000000, 0, 0, 22}}},
        (coherencyMode == CoherencyMode::Default) ? UseBarriers::IfNotCoherent
                                                  : UseBarriers::Always);

    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));

    // These values should be exact matches.
    //
    // GL_R32F is spec'd as a 32-bit IEEE float, GL_R32I is a 32-bit signed integer,
    // and GL_R32UI is a 32-bit unsigned integer.
    // There is some affordance for fp32 fused operations, but "a + b" is required to be
    // correctly rounded.
    //
    // From the GLSL ES 3.0 spec:
    //
    //   "Highp unsigned integers have exactly 32 bits of precision. Highp signed integers use
    //    32 bits, including a sign bit, in two's complement form."
    //
    //   "Highp floating-point variables within a shader are encoded according to the IEEE 754
    //    specification for single-precision floating-point values (logically, not necessarily
    //    physically)."
    //
    //   "Operation: a + b, a - b, a * b
    //    Precision: Correctly rounded."
    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT32F_EQ(0, 0, W, H, GLColor32F(-111.5, 0, 0, 1));

    attachTexture2DToScratchFBO(tex2);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, W, H, GLColor32I(-1, 0, 0, 1));

    attachTexture2DToScratchFBO(tex3);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0xffffffff, 0, 0, 1));

    ASSERT_GL_NO_ERROR();
}

// Verify that r32f, r32i, and r32ui pixel local storage behaves as specified.
TEST_P(PixelLocalStorageTest, R32)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));
    doR32Test(PixelLocalStorageTest::CoherencyMode::Default);
}

// Verify that r32f, r32i, and r32ui pixel local storage behaves as specified
// when using the "noncoherent" qualifier.
TEST_P(PixelLocalStorageTest, R32_noncoherent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));
    doR32Test(PixelLocalStorageTest::CoherencyMode::Noncoherent);
}

// Verify that r32f, r32i, and r32ui pixel local storage behaves as specified
// when using the "noncoherent" qualifier and GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE.
TEST_P(PixelLocalStorageTest, R32_always_noncoherent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));
    doR32Test(PixelLocalStorageTest::CoherencyMode::AlwaysNoncoherent);
}

// Test that comma operator and passing PLS planes to functions work.
TEST_P(PixelLocalStorageTest, CommaOperator)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));

    constexpr char kFS[] = R"(layout(r32f, binding=0) uniform highp pixelLocalANGLE plane1;
    layout(binding=1, r32i) uniform highp ipixelLocalANGLE plane2;
    layout(r32ui, binding=2) uniform highp upixelLocalANGLE plane3;

    highp vec4 loadPlane(highp pixelLocalANGLE p)
    {
        return pixelLocalLoadANGLE(p);
    }

    highp ivec4 iloadPlane(highp ipixelLocalANGLE p)
    {
        return pixelLocalLoadANGLE(p);
    }

    highp uvec4 uloadPlane(highp pixelLocalANGLE unused, highp upixelLocalANGLE p)
    {
        return pixelLocalLoadANGLE(p);
    }

    int j = 0;
    void incJ()
    {
        ++j;
    }

    void main()
    {
        highp int i = 0;
        pixelLocalStoreANGLE(plane1, color + loadPlane((++i, plane1)));
        pixelLocalStoreANGLE(plane2, ivec4(aux1) + iloadPlane((plane1, incJ(), plane2)));
        pixelLocalStoreANGLE(plane3, uvec4(aux2) + uloadPlane((++i, plane1), (loadPlane((++i, incJ(), plane1)), plane3)));
        if (i != 3 || j != 2)
        {
            pixelLocalStoreANGLE(plane3, uvec4(1234));
        }
    })";
    mProgram.compile(kFS);

    PLSTestTexture tex1(GL_R32F);
    PLSTestTexture tex2(GL_R32I);
    PLSTestTexture tex3(GL_R32UI);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

    // Accumulate R in 4 separate passes.
    mProgram.drawBoxes(
        {{FULLSCREEN, {-1.5, 0, 0, 0}, {0x000000ff, 0, 0, 0}, {0x000000ff, 0, 0, 0}},
         {FULLSCREEN, {-10.25, 0, 0, 0}, {0x0000ff00, 0, 0, 0}, {0x0000ff00, 0, 0, 0}},
         {FULLSCREEN, {-100, 0, 0, 0}, {0x00ff0000, 0, 0, 0}, {0x00ff0000, 0, 0, 0}},
         {FULLSCREEN, {.25, 0, 0, 0}, {-0x1000000, 0, 0, 0}, {0xff000000, 0, 0, 22}}},
        UseBarriers::IfNotCoherent);

    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));

    // Incorrectly transformed without the IR.  Test above makes sure there is no compiler crash
    // without the IR.
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr));

    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT32F_EQ(0, 0, W, H, GLColor32F(-111.5, 0, 0, 1));

    attachTexture2DToScratchFBO(tex2);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, W, H, GLColor32I(-1, 0, 0, 1));

    attachTexture2DToScratchFBO(tex3);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0xffffffff, 0, 0, 1));

    ASSERT_GL_NO_ERROR();
}

// Check proper functioning of the clear value state.
TEST_P(PixelLocalStorageTest, ClearValues_rgba8)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex8f(GL_RGBA8, 1, 1);
    PLSTestTexture tex8i(GL_RGBA8I, 1, 1);
    PLSTestTexture tex8ui(GL_RGBA8UI, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex8f, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex8i, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex8ui, 0, 0, GL_NONE);
    auto clearLoads =
        GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE});
    auto storeStores =
        GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE});

    // Clear values are initially zero.
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({0, 0, 0, 0}));
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));

    // Test custom RGBA8 clear values.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(100.5, 0, 0, 0));
    glFramebufferPixelLocalClearValueivANGLE(1, ClearI(-1, 2, -3, 4));
    glFramebufferPixelLocalClearValueuivANGLE(2, ClearUI(5, 6, 7, 8));
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({100.5, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({-1, 2, -3, 4}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({5, 6, 7, 8}));
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(255, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-1, 2, -3, 4));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(5, 6, 7, 8));

    // Rotate and test again.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex8ui, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex8f, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex8i, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    // Since each clear value type is separate state, these should all be zero again.
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(0, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(1, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(2, ({0, 0, 0, 0}));
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
    // If any component of the clear value is larger than can be represented in plane's
    // internalformat, it is clamped.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferPixelLocalClearValueuivANGLE(0, ClearUI(254, 255, 256, 257));
    glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(-1, 0, 1, 2));
    glFramebufferPixelLocalClearValueivANGLE(2, ClearI(-129, -128, 127, 128));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(0, ({254, 255, 256, 257}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(1, ({-1, 0, 1, 2}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(2, ({-129, -128, 127, 128}));
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(254, 255, 255, 255));
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 255, 255));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-128, -128, 127, 127));

    // Final rotation.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex8i, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex8ui, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex8f, 0, 0, GL_NONE);
    // Since each clear value type is separate state, these should all be zero yet again.
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(0, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(1, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(2, ({0, 0, 0, 0}));
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
    // If any component of the clear value is larger than can be represented in plane's
    // internalformat, it is clamped.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferPixelLocalClearValueivANGLE(0, ClearI(999, 999, -999, -999));
    glFramebufferPixelLocalClearValueuivANGLE(1, ClearUI(0, 0, 999, 999));
    glFramebufferPixelLocalClearValuefvANGLE(2, ClearF(999, 999, -999, -999));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(0, ({999, 999, -999, -999}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(1, ({0, 0, 999, 999}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(2, ({999, 999, -999, -999}));
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(127, 127, -128, -128));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 255, 255));
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(255, 255, 0, 0));

    // GL_LOAD_OP_ZERO_ANGLE shouldn't be affected by previous clear colors.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));

    // Cycle back to the original configuration and ensure that clear state hasn't changed.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex8f, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex8i, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex8ui, 0, 0, GL_NONE);
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({100.5f, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({-1, 2, -3, 4}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({5, 6, 7, 8}));
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(255, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-1, 2, -3, 4));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(5, 6, 7, 8));

    // Clear state is specific to the draw framebuffer; clear values on one framebuffer do not
    // affect clear values on another.
    GLFramebuffer fbo2;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex8f, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex8i, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex8ui, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(3, clearLoads);
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({0, 0, 0, 0}));
    glEndPixelLocalStorageANGLE(3, storeStores);
    attachTexture2DToScratchFBO(tex8f);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(0, 0, 0, 0));
    attachTexture2DToScratchFBO(tex8ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0, 0, 0, 0));
}

// Check clear values for r32f, r32i, and r32ui PLS format.
TEST_P(PixelLocalStorageTest, ClearValues_r32)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Test custom R32 clear values.
    PLSTestTexture tex32f(GL_R32F);
    PLSTestTexture tex32i(GL_R32I);
    PLSTestTexture tex32ui(GL_R32UI);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex32f, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex32i, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex32ui, 0, 0, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(100.5, 0, 0, 0));
    glFramebufferPixelLocalClearValueivANGLE(1, ClearI(-2, 1, 0, 0));
    glFramebufferPixelLocalClearValueuivANGLE(2, ClearUI(0xbaadbeef, 1, 1, 0));
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({100.5f, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({-2, 1, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(2, ({0xbaadbeef, 1, 1, 0}));
    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex32f);
    EXPECT_PIXEL_RECT32F_EQ(0, 0, 1, 1, GLColor32F(100.5f, 0, 0, 1));
    attachTexture2DToScratchFBO(tex32i);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, 1, 1, GLColor32I(-2, 0, 0, 1));
    attachTexture2DToScratchFBO(tex32ui);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1, GLColor32UI(0xbaadbeef, 0, 0, 1));
}

// Check type conversions of PLS state queries.
TEST_P(PixelLocalStorageTest, ClearValueConversions)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1.0f, 2.0f, 3.0f, 4.0f));
    glFramebufferPixelLocalClearValueivANGLE(0, ClearI(-5, -6, -7, -8));
    glFramebufferPixelLocalClearValueuivANGLE(
        0, ClearUI(2147483648, 2147483649, 2147483650, 2147483651));
    ASSERT_GL_NO_ERROR();

    {
        GLfloat value[4]{};
        glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
                                                          value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 1.0f);
        EXPECT_EQ(value[1], 2.0f);
        EXPECT_EQ(value[2], 3.0f);
        EXPECT_EQ(value[3], 4.0f);

        glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                          value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], -5.0f);
        EXPECT_EQ(value[1], -6.0f);
        EXPECT_EQ(value[2], -7.0f);
        EXPECT_EQ(value[3], -8.0f);

        glGetFramebufferPixelLocalStorageParameterfvANGLE(
            0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 2147483648.0f);
        EXPECT_EQ(value[1], 2147483648.0f);  // closest f32 to 2147483649
        EXPECT_EQ(value[2], 2147483648.0f);  // closest f32 to 2147483650
        EXPECT_EQ(value[3], 2147483648.0f);  // closest f32 to 2147483651
    }
    {
        GLint value[4]{};
        glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
                                                          value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 1);
        EXPECT_EQ(value[1], 2);
        EXPECT_EQ(value[2], 3);
        EXPECT_EQ(value[3], 4);

        glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                          value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], -5);
        EXPECT_EQ(value[1], -6);
        EXPECT_EQ(value[2], -7);
        EXPECT_EQ(value[3], -8);

        glGetFramebufferPixelLocalStorageParameterivANGLE(
            0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 2147483647);
        EXPECT_EQ(value[1], 2147483647);
        EXPECT_EQ(value[2], 2147483647);
        EXPECT_EQ(value[3], 2147483647);
    }
    {
        GLuint value[4]{};
        glGetFramebufferPixelLocalStorageParameteruivANGLE(
            0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 1u);
        EXPECT_EQ(value[1], 2u);
        EXPECT_EQ(value[2], 3u);
        EXPECT_EQ(value[3], 4u);

        glGetFramebufferPixelLocalStorageParameteruivANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                           value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 0u);
        EXPECT_EQ(value[1], 0u);
        EXPECT_EQ(value[2], 0u);
        EXPECT_EQ(value[3], 0u);

        glGetFramebufferPixelLocalStorageParameteruivANGLE(
            0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, value);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(value[0], 2147483648);
        EXPECT_EQ(value[1], 2147483649);
        EXPECT_EQ(value[2], 2147483650);
        EXPECT_EQ(value[3], 2147483651);
    }
}

// Check proper support of GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_LOAD_ANGLE, and GL_LOAD_OP_CLEAR_ANGLE
// loadOps. Also verify that it works do draw with GL_MAX_LOCAL_STORAGE_PLANES_ANGLE planes.
TEST_P(PixelLocalStorageTest, LoadOps)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    std::stringstream fs;
    for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        fs << "layout(binding=" << i << ", rgba8) uniform highp pixelLocalANGLE pls" << i << ";\n";
    }
    fs << "void main() {\n";
    for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        fs << "pixelLocalStoreANGLE(pls" << i << ", color + pixelLocalLoadANGLE(pls" << i
           << "));\n";
    }
    fs << "}";
    mProgram.compile(fs.str());

    // Create pls textures and clear them to red.
    glClearColor(1, 0, 0, 1);
    std::vector<PLSTestTexture> texs;
    for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        texs.emplace_back(GL_RGBA8);
        attachTexture2DToScratchFBO(texs[i]);
        glClear(GL_COLOR_BUFFER_BIT);
    }

    // Turn on scissor to try and confuse the local storage clear step.
    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, 20, H);

    // Set up pls color planes with a clear color of black. Odd units load with
    // GL_LOAD_OP_CLEAR_ANGLE (cleared to black) and even load with GL_LOAD_OP_LOAD_ANGLE (preserved
    // red).
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    std::vector<GLenum> loadOps(MAX_PIXEL_LOCAL_STORAGE_PLANES);
    for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        glFramebufferTexturePixelLocalStorageANGLE(i, texs[i], 0, 0, GL_NONE);
        glFramebufferPixelLocalClearValuefvANGLE(i, ClearF(0, 0, 0, 1));
        loadOps[i] = (i & 1) ? GL_LOAD_OP_CLEAR_ANGLE : GL_LOAD_OP_LOAD_ANGLE;
    }
    std::vector<GLenum> storeOps(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_STORE_OP_STORE_ANGLE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    // Draw transparent green into all pls attachments.
    glBeginPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, loadOps.data());
    mProgram.drawBoxes({{{FULLSCREEN}, {0, 1, 0, 0}}});
    glEndPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, storeOps.data());

    for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texs[i], 0);
        // Check that the draw buffers didn't get perturbed by local storage -- GL_COLOR_ATTACHMENT0
        // is currently off, so glClear has no effect. This also verifies that local storage planes
        // didn't get left attached to the framebuffer somewhere with draw buffers on.
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_RECT_EQ(0, 0, 20, H,
                             loadOps[i] == GL_LOAD_OP_CLEAR_ANGLE
                                 ? GLColor(0, 255, 0, 255)
                                 : /*GL_LOAD_OP_LOAD_ANGLE*/ GLColor(255, 255, 0, 255));
        // Check that the scissor didn't get perturbed by local storage.
        EXPECT_PIXEL_RECT_EQ(20, 0, W - 20, H,
                             loadOps[i] == GL_LOAD_OP_CLEAR_ANGLE
                                 ? GLColor(0, 0, 0, 255)
                                 : /*GL_LOAD_OP_LOAD_ANGLE*/ GLColor(255, 0, 0, 255));
    }

    // Detach the last read pls texture from the framebuffer.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);

    // Now test GL_LOAD_OP_ZERO_ANGLE, and leaving a plane deinitialized.
    for (int i = 0; i < 2; ++i)
    {
        loadOps[i] = GL_LOAD_OP_ZERO_ANGLE;
    }

    // Execute a pls pass without a draw.
    glBeginPixelLocalStorageANGLE(3, loadOps.data());
    glEndPixelLocalStorageANGLE(3, storeOps.data());

    for (int i = 0; i < 3; ++i)
    {
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texs[i], 0);
        if (i < 2)
        {
            ASSERT(loadOps[i] == GL_LOAD_OP_ZERO_ANGLE);
            EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(0, 0, 0, 0));
        }
        else
        {
            // Should have been left unchanged.
            EXPECT_PIXEL_RECT_EQ(0, 0, 20, H, GLColor(255, 255, 0, 255));
            EXPECT_PIXEL_RECT_EQ(20, 0, W - 20, H, GLColor(255, 0, 0, 255));
        }
    }

    // Now turn GL_COLOR_ATTACHMENT0 back on and check that the clear color and scissor didn't get
    // perturbed by local storage.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texs[1], 0);
    glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_RECT_EQ(0, 0, 20, H, GLColor(255, 0, 0, 255));
    EXPECT_PIXEL_RECT_EQ(20, 0, W - 20, H, GLColor(0, 0, 0, 0));

    ASSERT_GL_NO_ERROR();
}

// This next series of tests checks that GL utilities for rejecting fragments prevent stores to PLS:
//
//   * stencil test
//   * depth test
//   * viewport
//
// Some utilities are not legal in ANGLE_shader_pixel_local_storage:
//
//   * gl_SampleMask is disallowed by the spec
//   * discard, after potential calls to pixelLocalLoadANGLE/Store, is disallowed by the spec
//   * pixelLocalLoadANGLE/Store after a return from main is disallowed by the spec
//
// To run the tests, bind a FragmentRejectTestFBO and draw {FRAG_REJECT_TEST_BOX}:
//
//   * {0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT} should be green
//   * Fragments outside should have been rejected, leaving the pixels black
//
struct FragmentRejectTestFBO : GLFramebuffer
{
    FragmentRejectTestFBO(GLuint tex)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, *this);
        glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
        glFramebufferPixelLocalClearValuefvANGLE(0, MakeArray<float>({0, 0, 0, 1}));
        glViewport(0, 0, W, H);
        glDrawBuffers(0, nullptr);
    }
};
constexpr static int FRAG_REJECT_TEST_WIDTH  = 64;
constexpr static int FRAG_REJECT_TEST_HEIGHT = 64;
constexpr static Box FRAG_REJECT_TEST_BOX(FULLSCREEN,
                                          {0, 1, 0, 0},  // draw color
                                          {0, 0, FRAG_REJECT_TEST_WIDTH,
                                           FRAG_REJECT_TEST_HEIGHT});  // reject pixels outside aux1

// Check that the stencil test prevents stores to PLS.
TEST_P(PixelLocalStorageTest, FragmentReject_stencil)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    FragmentRejectTestFBO fbo(tex);

    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
    void main()
    {
        pixelLocalStoreANGLE(pls, color + pixelLocalLoadANGLE(pls));
    })");
    GLuint depthStencil;
    glGenRenderbuffers(1, &depthStencil);
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencil);

    // glStencilFunc(GL_NEVER, ...) should not update pls.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NEVER, 1, ~0u);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    GLint zero = 0;
    glClearBufferiv(GL_STENCIL, 0, &zero);
    mProgram.drawBoxes({{{0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    glDisable(GL_STENCIL_TEST);
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::black);

    // Stencil should be preserved after PLS, and only pixels that pass the stencil test should
    // update PLS next.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glEnable(GL_STENCIL_TEST);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
    glStencilFunc(GL_NOTEQUAL, 0, ~0u);
    glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO);
    mProgram.drawBoxes({FRAG_REJECT_TEST_BOX});
    glDisable(GL_STENCIL_TEST);
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    renderTextureToDefaultFramebuffer(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(FRAG_REJECT_TEST_WIDTH, 0, W - FRAG_REJECT_TEST_WIDTH,
                         FRAG_REJECT_TEST_HEIGHT, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, FRAG_REJECT_TEST_HEIGHT, W, H - FRAG_REJECT_TEST_HEIGHT,
                         GLColor::black);

    ASSERT_GL_NO_ERROR();
}

// Check that the depth test prevents stores to PLS.
TEST_P(PixelLocalStorageTest, FragmentReject_depth)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    FragmentRejectTestFBO fbo(tex);

    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
    void main()
    {
        pixelLocalStoreANGLE(pls, pixelLocalLoadANGLE(pls) + color);
    })");
    GLuint depth;
    glGenRenderbuffers(1, &depth);
    glBindRenderbuffer(GL_RENDERBUFFER, depth);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, W, H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    GLfloat zero = 0;
    glClearBufferfv(GL_DEPTH, 0, &zero);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT);
    GLfloat one = 1;
    glClearBufferfv(GL_DEPTH, 0, &one);
    glDisable(GL_SCISSOR_TEST);
    glDepthFunc(GL_LESS);
    mProgram.drawBoxes({FRAG_REJECT_TEST_BOX});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    glDisable(GL_DEPTH_TEST);

    renderTextureToDefaultFramebuffer(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(FRAG_REJECT_TEST_WIDTH, 0, W - FRAG_REJECT_TEST_WIDTH,
                         FRAG_REJECT_TEST_HEIGHT, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, FRAG_REJECT_TEST_HEIGHT, W, H - FRAG_REJECT_TEST_HEIGHT,
                         GLColor::black);
    ASSERT_GL_NO_ERROR();
}

// Check that restricting the viewport also restricts stores to PLS.
TEST_P(PixelLocalStorageTest, FragmentReject_viewport)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    FragmentRejectTestFBO fbo(tex);

    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
    void main()
    {
        vec4 dst = pixelLocalLoadANGLE(pls);
        pixelLocalStoreANGLE(pls, color + dst);
    })");
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    glViewport(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT);
    mProgram.drawBoxes({FRAG_REJECT_TEST_BOX});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    glViewport(0, 0, W, H);

    renderTextureToDefaultFramebuffer(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, FRAG_REJECT_TEST_WIDTH, FRAG_REJECT_TEST_HEIGHT, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(FRAG_REJECT_TEST_WIDTH, 0, W - FRAG_REJECT_TEST_WIDTH,
                         FRAG_REJECT_TEST_HEIGHT, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, FRAG_REJECT_TEST_HEIGHT, W, H - FRAG_REJECT_TEST_HEIGHT,
                         GLColor::black);
    ASSERT_GL_NO_ERROR();
}

// Check that results are only nondeterministic within predictable constraints, and that no data is
// random or leaked from other contexts when we forget to insert a barrier.
TEST_P(PixelLocalStorageTest, ForgetBarrier)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));

    mProgram.compile(R"(
    layout(binding=0, r32f) uniform highp pixelLocalANGLE framebuffer;
    void main()
    {
        vec4 dst = pixelLocalLoadANGLE(framebuffer);
        pixelLocalStoreANGLE(framebuffer, color + dst * 2.0);
    })");

    // Draw r=100, one pixel at a time, in random order.
    constexpr static int NUM_PIXELS = H * W;
    std::vector<Box> boxesA_100;
    int pixelIdx = 0;
    for (int i = 0; i < NUM_PIXELS; ++i)
    {
        int iy  = pixelIdx / W;
        float y = iy;
        int ix  = pixelIdx % W;
        float x = ix;
        pixelIdx =
            (pixelIdx + 69484171) % NUM_PIXELS;  // Prime numbers guarantee we hit every pixel once.
        boxesA_100.push_back(Box{{x, y, x + 1, y + 1}, {100, 0, 0, 0}});
    }

    // Draw r=7, one pixel at a time, in random order.
    std::vector<Box> boxesB_7;
    for (int i = 0; i < NUM_PIXELS; ++i)
    {
        int iy  = pixelIdx / W;
        float y = iy;
        int ix  = pixelIdx % W;
        float x = ix;
        pixelIdx =
            (pixelIdx + 97422697) % NUM_PIXELS;  // Prime numbers guarantee we hit every pixel once.
        boxesB_7.push_back(Box{{x, y, x + 1, y + 1}, {7, 0, 0, 0}});
    }

    PLSTestTexture tex(GL_R32F);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 0));
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    // First make sure it works properly with a barrier.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes(boxesA_100, UseBarriers::Never);
    glPixelLocalStorageBarrierANGLE();
    mProgram.drawBoxes(boxesB_7, UseBarriers::Never);
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT32F_EQ(0, 0, W, H, GLColor32F(211, 0, 0, 1));

    ASSERT_GL_NO_ERROR();

    // Vulkan generates rightful "SYNC-HAZARD-READ_AFTER_WRITE" validation errors when we omit the
    // barrier.
    ANGLE_SKIP_TEST_IF(IsVulkan() &&
                       !IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));

    // Now forget to insert the barrier and ensure our nondeterminism still falls within predictable
    // constraints.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes(boxesA_100, UseBarriers::Never);
    // OOPS! We forgot to insert a barrier!
    mProgram.drawBoxes(boxesB_7, UseBarriers::Never);
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

    float pixels[H * W * 4];
    attachTexture2DToScratchFBO(tex);
    glReadPixels(0, 0, W, H, GL_RGBA, GL_FLOAT, pixels);
    for (int r = 0; r < NUM_PIXELS * 4; r += 4)
    {
        // When two fragments, A and B, touch a pixel, there are 6 possible orderings of operations:
        //
        //   * Read A, Write A, Read B, Write B
        //   * Read B, Write B, Read A, Write A
        //   * Read A, Read B, Write A, Write B
        //   * Read A, Read B, Write B, Write A
        //   * Read B, Read A, Write B, Write A
        //   * Read B, Read A, Write A, Write B
        //
        // Which (assumimg the read and/or write operations themselves are atomic), is equivalent to
        // 1 of 4 potential effects:
        bool isAcceptableValue =
            ANGLE_UNSAFE_TODO(pixels[r]) == 211 ||  // A, then B  (  7 + (100 + 1 * 2) * 2 == 211)
            ANGLE_UNSAFE_TODO(pixels[r]) == 118 ||  // B, then A  (100 + (  7 + 1 * 2) * 2 == 118)
            ANGLE_UNSAFE_TODO(pixels[r]) == 102 ||  // A only     (100 +             1 * 2 == 102)
            ANGLE_UNSAFE_TODO(pixels[r]) == 9;
        if (!isAcceptableValue)
        {
            printf(__FILE__ "(%i): UNACCEPTABLE value at pixel location [%i, %i]\n", __LINE__,
                   (r / 4) % W, (r / 4) / W);
            printf("              Got: %f\n", ANGLE_UNSAFE_TODO(pixels[r]));
            printf("  Expected one of: { 211, 118, 102, 9 }\n");
        }
        ASSERT_TRUE(isAcceptableValue);
    }

    ASSERT_GL_NO_ERROR();
}

// Check loading and storing from memoryless local storage planes.
TEST_P(PixelLocalStorageTest, MemorylessStorage)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Bind the texture, but don't call glTexStorage until after creating the memoryless plane.
    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    // Create a memoryless plane.
    glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8, GL_NONE);
    // Define the persistent texture now, after attaching the memoryless pixel local storage. This
    // verifies that the GL_TEXTURE_2D binding doesn't get perturbed by local storage.
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

    // Draw into memoryless storage.
    // (Also validates that compiling works while PLS is active.)
    PLSProgram drawMemorylessProgram;
    drawMemorylessProgram.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE framebuffer;
    layout(binding=1, rgba8) uniform highp pixelLocalANGLE memoryless;
    void main()
    {
        pixelLocalStoreANGLE(memoryless, color + pixelLocalLoadANGLE(memoryless));
    })");

    drawMemorylessProgram.drawBoxes({{{0, 20, W, H}, {1, 0, 0, 0}},
                                     {{0, 40, W, H}, {0, 1, 0, 0}},
                                     {{0, 60, W, H}, {0, 0, 1, 0}}});

    ASSERT_GL_NO_ERROR();

    // Transfer to a texture.
    // (Also validates that compiling works while PLS is active.)
    PLSProgram transferToTextureProgram;
    transferToTextureProgram.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE framebuffer;
    layout(binding=1, rgba8) uniform highp pixelLocalANGLE memoryless;
    void main()
    {
        pixelLocalStoreANGLE(framebuffer, vec4(1) - pixelLocalLoadANGLE(memoryless));
    })");
    transferToTextureProgram.drawBoxes({{FULLSCREEN}});

    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 60, W, H - 60, GLColor(0, 0, 0, 255));
    EXPECT_PIXEL_RECT_EQ(0, 40, W, 20, GLColor(0, 0, 255, 255));
    EXPECT_PIXEL_RECT_EQ(0, 20, W, 20, GLColor(0, 255, 255, 255));
    EXPECT_PIXEL_RECT_EQ(0, 0, W, 20, GLColor(255, 255, 255, 255));

    // Ensure the GL_TEXTURE_2D binding still hasn't been perturbed by local storage.
    GLint textureBinding2D;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding2D);
    ASSERT_EQ((GLuint)textureBinding2D, tex);

    ASSERT_GL_NO_ERROR();
}

// Check that it works to render with the maximum supported data payload:
//
//   GL_MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE
//
TEST_P(PixelLocalStorageTest, MaxCombinedDrawBuffersAndPLSPlanes)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    for (int numDrawBuffers : {0, 1, MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1})
    {
        numDrawBuffers = std::min(numDrawBuffers, MAX_DRAW_BUFFERS);
        int numPLSPlanes =
            MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numDrawBuffers;
        numPLSPlanes = std::min(numPLSPlanes, MAX_PIXEL_LOCAL_STORAGE_PLANES);

        std::stringstream fs;
        for (int i = 0; i < numPLSPlanes; ++i)
        {
            fs << "layout(binding=" << i << ", rgba8ui) uniform highp upixelLocalANGLE pls" << i
               << ";\n";
        }
        for (int i = 0; i < numDrawBuffers; ++i)
        {
            if (numDrawBuffers > 1)
            {
                fs << "layout(location=" << i << ") ";
            }
            fs << "out uvec4 out" << i << ";\n";
        }
        fs << "void main() {\n";
        int c = 0;
        for (; c < std::min(numPLSPlanes, numDrawBuffers); ++c)
        {
            // Nest an expression involving a fragment output, which will have an unspecified
            // location when numDrawBuffers is 1, in pixelLocalStoreANGLE().
            fs << "pixelLocalStoreANGLE(pls" << c << ", uvec4(color) - (out" << c
               << " = uvec4(aux1) + uvec4(" << c << ")));\n";
        }
        for (int i = c; i < numPLSPlanes; ++i)
        {
            fs << "pixelLocalStoreANGLE(pls" << i << ", uvec4(color) - (uvec4(aux1) + uvec4(" << i
               << ")));\n";
        }
        for (int i = c; i < numDrawBuffers; ++i)
        {
            fs << "out" << i << " = uvec4(aux1) + uvec4(" << i << ");\n";
        }
        fs << "}";
        mProgram.compile(fs.str());

        glViewport(0, 0, W, H);

        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        std::vector<PLSTestTexture> localTexs;
        localTexs.reserve(numPLSPlanes);
        for (int i = 0; i < numPLSPlanes; ++i)
        {
            localTexs.emplace_back(GL_RGBA8UI);
            glFramebufferTexturePixelLocalStorageANGLE(i, localTexs[i], 0, 0, GL_NONE);
        }
        std::vector<PLSTestTexture> renderTexs;
        renderTexs.reserve(numDrawBuffers);
        std::vector<GLenum> drawBuffers(numDrawBuffers);
        for (int i = 0; i < numDrawBuffers; ++i)
        {
            renderTexs.emplace_back(GL_RGBA32UI);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
                                   renderTexs[i], 0);
            drawBuffers[i] = GL_COLOR_ATTACHMENT0 + i;
        }
        glDrawBuffers(drawBuffers.size(), drawBuffers.data());

        glBeginPixelLocalStorageANGLE(
            numPLSPlanes, std::vector<GLenum>(numPLSPlanes, GL_LOAD_OP_ZERO_ANGLE).data());
        mProgram.drawBoxes({{FULLSCREEN, {255, 254, 253, 252}, {0, 1, 2, 3}}});
        glEndPixelLocalStorageANGLE(
            numPLSPlanes, std::vector<GLenum>(numPLSPlanes, GL_STORE_OP_STORE_ANGLE).data());

        for (int i = 0; i < numPLSPlanes; ++i)
        {
            attachTexture2DToScratchFBO(localTexs[i]);
            EXPECT_PIXEL_RECT32UI_EQ(
                0, 0, W, H,
                GLColor32UI(255u - i - 0u, 254u - i - 1u, 253u - i - 2u, 252u - i - 3u));
        }
        for (int i = 0; i < numDrawBuffers; ++i)
        {
            attachTexture2DToScratchFBO(renderTexs[i]);
            EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0u + i, 1u + i, 2u + i, 3u + i));
        }

        ASSERT_GL_NO_ERROR();
    }
}

// Verifies that program caching works for programs that use pixel local storage.
TEST_P(PixelLocalStorageTest, ProgramCache)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    PLSTestTexture pls0(GL_RGBA8UI, 1, 1);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    PLSTestTexture pls1(GL_RGBA8UI, 1, 1);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);
    PLSTestTexture pls3(GL_RGBA8UI, 1, 1);
    glFramebufferTexturePixelLocalStorageANGLE(2, pls3, 0, 0, GL_NONE);
    glDrawBuffers(0, nullptr);
    glViewport(0, 0, 1, 1);

    // Compile the same program multiple times and verify it works each time.
    for (int j = 0; j < 10; ++j)
    {
        mProgram.compile(R"(
        layout(binding=0, rgba8ui) uniform highp upixelLocalANGLE pls0;
        layout(binding=1, rgba8ui) uniform highp upixelLocalANGLE pls1;
        layout(binding=2, rgba8ui) uniform highp upixelLocalANGLE pls3;
        void main()
        {
            pixelLocalStoreANGLE(pls0, uvec4(color) - uvec4(aux1));
            pixelLocalStoreANGLE(pls1, uvec4(color) - uvec4(aux2));
            pixelLocalStoreANGLE(pls3, uvec4(color) - uvec4(aux1) - uvec4(aux2));
        })");
        glUniform1f(mProgram.widthUniform(), 1);
        glUniform1f(mProgram.heightUniform(), 1);

        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
        mProgram.drawBoxes({{FULLSCREEN, {255, 254, 253, 252}, {0, 1, 2, 3}, {4, 5, 6, 7}}});
        glEndPixelLocalStorageANGLE(
            3, GLenumArray(
                   {GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        attachTexture2DToScratchFBO(pls0);
        EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1,
                                 GLColor32UI(255u - 0u, 254u - 1u, 253u - 2u, 252u - 3u));

        attachTexture2DToScratchFBO(pls1);
        EXPECT_PIXEL_RECT32UI_EQ(0, 0, 1, 1,
                                 GLColor32UI(255u - 4u, 254u - 5u, 253u - 6u, 252u - 7u));

        attachTexture2DToScratchFBO(pls3);
        EXPECT_PIXEL_RECT32UI_EQ(
            0, 0, 1, 1,
            GLColor32UI(255u - 0u - 4u, 254u - 1u - 5u, 253u - 2u - 6u, 252u - 3u - 7u));

        ASSERT_GL_NO_ERROR();
    }
}

// Check that pls is preserved when a shader does not call pixelLocalStoreANGLE(). (Whether that's
// because a conditional branch failed or because the shader didn't write to it at all.)
//
//   * The framebuffer fetch implementation needs to make sure every active plane's output variable
//     gets written during every invocation, or else its value will become undefined.
//
//   * The native pixel local storage implementation needs to declare a variable for every active
//     plane, even if it is unused in a particular shader invocation.
//
// Also check that a pixelLocalLoadANGLE() of an r32f texture returns (r, 0, 0, 1).
TEST_P(PixelLocalStorageTest, LoadOnly)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));

    PLSTestTexture tex(GL_RGBA8);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    // Pass 1: draw to memoryless conditionally.
    PLSProgram pass1;
    pass1.compile(R"(
    layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
    layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
    void main()
    {
        // Omit braces on the 'if' to ensure proper insertion of memoryBarriers in the translator.
        if (gl_FragCoord.x < 64.0)
            pixelLocalStoreANGLE(memoryless, vec4(1, -.1, .2, -.3));  // Only stores r.
    })");

    // Pass 2: draw to tex conditionally.
    // Don't touch memoryless -- make sure it gets preserved!
    PLSProgram pass2;
    pass2.compile(R"(
    layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
    layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
    void main()
    {
        // Omit braces on the 'if' to ensure proper insertion of memoryBarriers in the translator.
        if (gl_FragCoord.y < 64.0)
            pixelLocalStoreANGLE(tex, vec4(0, 1, 1, 0));
    })");

    // Pass 3: combine memoryless and tex.
    PLSProgram pass3;
    pass3.compile(R"(
    layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
    layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
    void main()
    {
        pixelLocalStoreANGLE(tex, pixelLocalLoadANGLE(tex) + pixelLocalLoadANGLE(memoryless));
    })");

    // Leave unit 0 with the default clear value of zero.
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 0, 0));
    glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(0, 1, 0, 0));
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));

    pass1.bind();
    pass1.drawBoxes({{FULLSCREEN}});

    pass2.bind();
    pass2.drawBoxes({{FULLSCREEN}});

    pass3.bind();
    pass3.drawBoxes({{FULLSCREEN}});

    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, 64, 64, GLColor(255, 255, 255, 255));
    EXPECT_PIXEL_RECT_EQ(64, 0, W - 64, 64, GLColor(0, 255, 255, 255));
    EXPECT_PIXEL_RECT_EQ(0, 64, 64, H - 64, GLColor(255, 255, 0, 255));
    EXPECT_PIXEL_RECT_EQ(64, 64, W - 64, H - 64, GLColor(0, 255, 0, 255));

    ASSERT_GL_NO_ERROR();

    // Now treat "tex" as entirely readonly for an entire local storage render pass.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    PLSTestTexture tex2(GL_RGBA8);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2, 0);
    glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
    glClear(GL_COLOR_BUFFER_BIT);

    mProgram.compile(R"(
        layout(binding=0, r32f) uniform highp pixelLocalANGLE memoryless;
        layout(binding=1, rgba8) uniform highp pixelLocalANGLE tex;
        out vec4 fragcolor;
        void main()
        {
            fragcolor = 1.0 - pixelLocalLoadANGLE(tex);
        })");

    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_LOAD_OP_LOAD_ANGLE}));

    mProgram.drawBoxes({{FULLSCREEN}});

    int n;
    glGetIntegerv(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, &n);
    glEndPixelLocalStorageANGLE(
        n, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

    // Ensure "tex" was properly read in the shader.
    EXPECT_PIXEL_RECT_EQ(0, 0, 64, 64, GLColor(0, 0, 0, 0));
    EXPECT_PIXEL_RECT_EQ(64, 0, W - 64, 64, GLColor(255, 0, 0, 0));
    EXPECT_PIXEL_RECT_EQ(0, 64, 64, H - 64, GLColor(0, 0, 255, 0));
    EXPECT_PIXEL_RECT_EQ(64, 64, W - 64, H - 64, GLColor(255, 0, 255, 0));

    // Ensure "tex" was preserved after the shader.
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, 64, 64, GLColor(255, 255, 255, 255));
    EXPECT_PIXEL_RECT_EQ(64, 0, W - 64, 64, GLColor(0, 255, 255, 255));
    EXPECT_PIXEL_RECT_EQ(0, 64, 64, H - 64, GLColor(255, 255, 0, 255));
    EXPECT_PIXEL_RECT_EQ(64, 64, W - 64, H - 64, GLColor(0, 255, 0, 255));

    ASSERT_GL_NO_ERROR();
}

// Check that stores and loads in a single shader invocation are coherent.
TEST_P(PixelLocalStorageTest, LoadAfterStore)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));

    // Run a fibonacci loop that stores and loads the same PLS multiple times.
    mProgram.compile(R"(
    layout(binding=0, rgba8ui) uniform highp upixelLocalANGLE fibonacci;
    void main()
    {
        pixelLocalStoreANGLE(fibonacci, uvec4(1, 0, 0, 0));  // fib(1, 0, 0, 0)
        for (int i = 0; i < 3; ++i)
        {
            uvec4 fib0 = pixelLocalLoadANGLE(fibonacci);
            uvec4 fib1;
            fib1.w = fib0.x + fib0.y;
            fib1.z = fib1.w + fib0.x;
            fib1.y = fib1.z + fib1.w;
            fib1.x = fib1.y + fib1.z;  // fib(i*4 + (5, 4, 3, 2))
            pixelLocalStoreANGLE(fibonacci, fib1);
        }
        // fib is at indices (13, 12, 11, 10)
    })");

    PLSTestTexture tex(GL_RGBA8UI);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    mProgram.drawBoxes({{FULLSCREEN}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(233, 144, 89, 55));  // fib(13, 12, 11, 10)

    ASSERT_GL_NO_ERROR();

    // Now verify that r32f, r32i, and r32ui still reload as (r, 0, 0, 1), even after an in-shader
    // store.
    mProgram.compile(R"(
        layout(binding=0, r32f) uniform highp pixelLocalANGLE pls32f;
        layout(binding=1, r32i) uniform highp ipixelLocalANGLE pls32i;
        layout(binding=2, r32ui) uniform highp upixelLocalANGLE pls32ui;

        out vec4 fragcolor;
        void main()
        {
            pixelLocalStoreANGLE(pls32f, vec4(1, .5, .5, .5));
            pixelLocalStoreANGLE(pls32i, ivec4(1, -1, -1, 0));
            pixelLocalStoreANGLE(pls32ui, uvec4(1, 1, 1, 0));
            lowp int index = int(floor(gl_FragCoord.x)) % 3;
            if (index == 0)
                fragcolor = pixelLocalLoadANGLE(pls32f);
            else if (index == 1)
                fragcolor = vec4(pixelLocalLoadANGLE(pls32i));
            else
                fragcolor = vec4(pixelLocalLoadANGLE(pls32ui));
        })");

    tex.reset(GL_RGBA8);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_R32I, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_R32UI, GL_NONE);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

    mProgram.drawBoxes({{FULLSCREEN}});

    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE}));

    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 0, 0, 255));
    ASSERT_GL_NO_ERROR();
}

// Check that PLS handles can be passed as function arguments.
TEST_P(PixelLocalStorageTest, FunctionArguments)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"));

    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE dst;
    layout(binding=1, rgba8) uniform mediump pixelLocalANGLE src1;
    void store2(lowp pixelLocalANGLE d);
    void store(highp pixelLocalANGLE d, lowp pixelLocalANGLE s)
    {
        pixelLocalStoreANGLE(d, pixelLocalLoadANGLE(s));
    }
    void main()
    {
        if (gl_FragCoord.x < 25.0)
            store(dst, src1);
        else
            store2(dst);
    }
    // Ensure inlining still works on a uniform declared after main().
    layout(binding=2, r32f) uniform highp pixelLocalANGLE src2;
    void store2(lowp pixelLocalANGLE d)
    {
        pixelLocalStoreANGLE(d, pixelLocalLoadANGLE(src2));
    })");

    PLSTestTexture dst(GL_RGBA8);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, dst, 0, 0, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_R32F, GL_NONE);

    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 1, 1, 1));  // ignored
    glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(0, 1, 1, 0));
    glFramebufferPixelLocalClearValuefvANGLE(2, ClearF(1, 0, 0, 1));
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes({{FULLSCREEN}});
    glEndPixelLocalStorageANGLE(3,
                                GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_DONT_CARE}));

    attachTexture2DToScratchFBO(dst);
    EXPECT_PIXEL_RECT_EQ(0, 0, 25, H, GLColor(0, 255, 255, 0));
    EXPECT_PIXEL_RECT_EQ(25, 0, W - 25, H, GLColor(255, 0, 0, 255));

    ASSERT_GL_NO_ERROR();
}

// Check that large amounts of overlapping draws are ordered and coherent, either implicitly via the
// "_coherent" extension, or explicitly via app-suppied barrier commands.
void PixelLocalStorageTest::doCoherencyTest(CoherencyMode coherencyMode)
{
    std::ostringstream shader;
    if (coherencyMode == CoherencyMode::Default)
    {
        shader << R"(
        layout(binding=0, rgba8ui) uniform lowp upixelLocalANGLE framebuffer;
        layout(binding=1, rgba8) uniform lowp pixelLocalANGLE tmp;)";
    }
    else
    {
        shader << R"(
        layout(binding=0, rgba8ui, noncoherent) uniform lowp upixelLocalANGLE framebuffer;
        layout(binding=1, rgba8, noncoherent) uniform lowp pixelLocalANGLE tmp;)";
    }
    shader << R"(
    // Defining a noncoherent plane shouldn't make the others noncoherent.
    layout(binding=2, rgba8ui, noncoherent) uniform lowp upixelLocalANGLE framebufferNoncoherent;
    // The application shouldn't be able to override internal synchronization functions used by
    // the compiler.
    //
    // If the compiler accidentally calls any of these functions, stomp out the framebuffer to make
    // the test fail.
    void endInvocationInterlockNV() { pixelLocalStoreANGLE(framebuffer, uvec4(0)); }
    void beginFragmentShaderOrderingINTEL() { pixelLocalStoreANGLE(framebuffer, uvec4(0)); }
    void beginInvocationInterlockARB() { pixelLocalStoreANGLE(framebuffer, uvec4(0)); }

    // Give these functions a side effect so they don't get pruned, then call them from main().
    void beginInvocationInterlockNV() { pixelLocalStoreANGLE(tmp, vec4(0)); }
    void endInvocationInterlockARB() { pixelLocalStoreANGLE(tmp, vec4(0)); }

    void main()
    {
        highp uvec4 d = pixelLocalLoadANGLE(framebuffer) >> 1;
        pixelLocalStoreANGLE(framebuffer, uvec4(color) + d);

        // NOTE: This isn't valid without barriers since the plane is noncoherent. We do this to
        // verify that if 'd' above is coherent, accessing this noncoherent plane doesn't
        // disable the interlock and make 'd' noncoherent as well.
        highp uvec4 dNoncoherent = pixelLocalLoadANGLE(framebufferNoncoherent) >> 1;
        pixelLocalStoreANGLE(framebufferNoncoherent, uvec4(color) + dNoncoherent);

        // Make sure the compiler doesn't call our overloads of GLSL functions it might be using
        // for synchronization.
        beginInvocationInterlockNV();
        endInvocationInterlockARB();
    })";
    mProgram.compile(shader.str().c_str());

    PLSTestTexture tex(GL_RGBA8UI);
    PLSTestTexture texNoncoherent(GL_RGBA8UI);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, texNoncoherent, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    std::vector<uint8_t> expected(H * W * 4);
    ANGLE_UNSAFE_TODO(memset(expected.data(), 0, H * W * 4));

    // This test times out on Swiftshader and noncoherent backends if we draw anywhere near the
    // same number of boxes as we do on coherent, hardware backends.
    int boxesPerList =
        !IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent") ||
                coherencyMode != CoherencyMode::Default ||
                ANGLE_UNSAFE_TODO(strstr((const char *)glGetString(GL_RENDERER), "SwiftShader"))
            ? 200
            : H * W * 3;

    // Prepare a ton of random sized boxes in various draws.
    std::vector<Box> boxesList[5];
    srand(17);
    uint32_t boxID = 1;
    for (auto &boxes : boxesList)
    {
        for (int i = 0; i < boxesPerList; ++i)
        {
            // Define a box.
            int w     = rand() % 10 + 1;
            int h     = rand() % 10 + 1;
            float x   = rand() % (W - w);
            float y   = rand() % (H - h);
            uint8_t r = boxID & 0x7f;
            uint8_t g = (boxID >> 7) & 0x7f;
            uint8_t b = (boxID >> 14) & 0x7f;
            uint8_t a = (boxID >> 21) & 0x7f;
            ++boxID;
            // Update expectations.
            for (int yy = y; yy < y + h; ++yy)
            {
                for (int xx = x; xx < x + w; ++xx)
                {
                    int p           = (yy * W + xx) * 4;
                    expected[p]     = r + (expected[p] >> 1);
                    expected[p + 1] = g + (expected[p + 1] >> 1);
                    expected[p + 2] = b + (expected[p + 2] >> 1);
                    expected[p + 3] = a + (expected[p + 3] >> 1);
                }
            }
            // Set up the gpu draw.
            float x0 = x;
            float x1 = x + w;
            float y0 = y;
            float y1 = y + h;
            // Allow boxes to have negative widths and heights. This adds randomness by making
            // the diagonals go in different directions.
            if (rand() & 1)
            {
                std::swap(x0, x1);
            }
            if (rand() & 1)
            {
                std::swap(y0, y1);
            }
            boxes.push_back({{x0, y0, x1, y1}, {(float)r, (float)g, (float)b, (float)a}});
        }
    }

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    for (const std::vector<Box> &boxes : boxesList)
    {
        mProgram.drawBoxes(boxes, coherencyMode != CoherencyMode::Default
                                      ? UseBarriers::Always
                                      : UseBarriers::IfNotCoherent);
    }
    glEndPixelLocalStorageANGLE(
        3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex);
    std::vector<uint8_t> actual(H * W * 4);
    glReadPixels(0, 0, W, H, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, actual.data());
    EXPECT_EQ(expected, actual);

    ASSERT_GL_NO_ERROR();
}

// Check that large amounts of overlapping draws are ordered and coherent, either implicitly via the
// "_coherent" extension, or explicitly via app-suppied barrier commands.
TEST_P(PixelLocalStorageTest, Coherency)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    doCoherencyTest(PixelLocalStorageTest::CoherencyMode::Default);
}

// Check that large amounts of overlapping draws that use the "noncoherent" qualifier are still
// ordered and coherent as long as we call glPixelLocalStorageBarrierANGLE().
TEST_P(PixelLocalStorageTest, Coherency_noncoherent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    doCoherencyTest(PixelLocalStorageTest::CoherencyMode::Noncoherent);
}

// Check that large amounts of overlapping draws that use the "noncoherent" qualifier and
// GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE are still ordered and coherent as long as we
// call glPixelLocalStorageBarrierANGLE().
TEST_P(PixelLocalStorageTest, Coherency_always_noncoherent)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    doCoherencyTest(PixelLocalStorageTest::CoherencyMode::AlwaysNoncoherent);
}

// Check that binding mipmap levels to PLS is supported.
TEST_P(PixelLocalStorageTest, MipMapLevels)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, color + pixelLocalLoadANGLE(pls));
        })");

    constexpr int LEVELS = 3;
    int levelWidth = 179, levelHeight = 313;
    std::vector<GLColor> redData(levelHeight * levelWidth, GLColor::black);
    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, LEVELS, GL_RGBA8, levelWidth, levelHeight);

    GLFramebuffer fbo;
    for (int level = 0; level < LEVELS; ++level)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);

        glUniform1f(mProgram.widthUniform(), levelWidth);
        glUniform1f(mProgram.heightUniform(), levelHeight);
        glViewport(0, 0, levelWidth, levelHeight);
        glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, levelWidth, levelHeight, GL_RGBA,
                        GL_UNSIGNED_BYTE, redData.data());

        glFramebufferTexturePixelLocalStorageANGLE(0, tex, level, 0, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
        mProgram.drawBoxes({{{0, 0, (float)levelWidth - 3, (float)levelHeight}, {0, 0, 1, 0}},
                            {{0, 0, (float)levelWidth - 2, (float)levelHeight}, {0, 1, 0, 0}},
                            {{0, 0, (float)levelWidth - 1, (float)levelHeight}, {1, 0, 0, 0}}});
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
        attachTexture2DToScratchFBO(tex, level);
        EXPECT_PIXEL_RECT_EQ(0, 0, levelWidth - 3, levelHeight, GLColor::white);
        EXPECT_PIXEL_RECT_EQ(levelWidth - 3, 0, 1, levelHeight, GLColor::yellow);
        EXPECT_PIXEL_RECT_EQ(levelWidth - 2, 0, 1, levelHeight, GLColor::red);
        EXPECT_PIXEL_RECT_EQ(levelWidth - 1, 0, 1, levelHeight, GLColor::black);

        levelWidth >>= 1;
        levelHeight >>= 1;
        ASSERT_GL_NO_ERROR();
    }

    // Delete fbo.
    // Don't delete tex -- exercise pixel local storage in a way that it has to clean itself up when
    // the context is torn down. (It has internal assertions that validate it is torn down
    // correctly.)
}

// Check that all supported texture types work at various levels and layers.
TEST_P(PixelLocalStorageTest, TextureLevelsAndLayers)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, vec4(0, 1, 0, 0) + pixelLocalLoadANGLE(pls));
        })");

    std::array<float, 4> HALFSCREEN = {0, 0, W / 2.f, H};

    int D = 5;
    std::vector<GLColor> redImg(H * W * D, GLColor::red);

    // GL_TEXTURE_2D
    {
        // Level 2.
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W * 4, H * 4);
        glTexSubImage2D(GL_TEXTURE_2D, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, redImg.data());
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexturePixelLocalStorageANGLE(0, tex, 2, 0, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
        mProgram.drawBoxes({{HALFSCREEN}});
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
        attachTexture2DToScratchFBO(tex, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W / 2.f, H, GLColor::yellow);
        EXPECT_PIXEL_RECT_EQ(W / 2.f, 0, W / 2.f, H, GLColor::red);
        ASSERT_GL_NO_ERROR();
    }

    // GL_TEXTURE_2D_ARRAY
    {
        // Level 1, layer 0.
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
        glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, W * 2, H * 2, D);
        glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, W, H, D, GL_RGBA, GL_UNSIGNED_BYTE,
                        redImg.data());
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexturePixelLocalStorageANGLE(0, tex, 1, 0, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
        mProgram.drawBoxes({{HALFSCREEN}});
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
        attachTextureLayerToScratchFBO(tex, 1, 0);
        EXPECT_PIXEL_RECT_EQ(0, 0, W / 2.f, H, GLColor::yellow);
        EXPECT_PIXEL_RECT_EQ(W / 2.f, 0, W / 2.f, H, GLColor::red);
        ASSERT_GL_NO_ERROR();

        // Level 1, layer D - 1.
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexturePixelLocalStorageANGLE(0, tex, 1, D - 1, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
        mProgram.drawBoxes({{HALFSCREEN}});
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
        attachTextureLayerToScratchFBO(tex, 1, D - 1);
        EXPECT_PIXEL_RECT_EQ(0, 0, W / 2.f, H, GLColor::yellow);
        EXPECT_PIXEL_RECT_EQ(W / 2.f, 0, W / 2.f, H, GLColor::red);
        ASSERT_GL_NO_ERROR();
    }
}

// Check that PLS works on multiple layers of the same array texture.
TEST_P(PixelLocalStorageTest, Texture2DArrayMultipleLayers)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    ASSERT_GL_NO_ERROR();

    GLTexture texArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);
    ASSERT_GL_NO_ERROR();
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_R32UI, W * 2, H * 2, 2);
    ASSERT_GL_NO_ERROR();

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, texArray, 1, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, texArray, 1, 1, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    ASSERT_GL_NO_ERROR();

    // Pass 1: seed the texArray.
    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls0;
    layout(binding=1, r32ui) uniform highp upixelLocalANGLE pls1;
    layout(binding=2, r32ui) uniform highp upixelLocalANGLE pls2;
    void main()
    {
        pixelLocalStoreANGLE(pls1, uvec4(floatBitsToUint(1.)));
        pixelLocalStoreANGLE(pls2, uvec4(floatBitsToUint(2.)));
    })");
    mProgram.drawBoxes({{FULLSCREEN}});

    // Pass 2: read the texArray.
    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls0;
    layout(binding=1, r32ui) uniform highp upixelLocalANGLE pls1;
    layout(binding=2, r32ui) uniform highp upixelLocalANGLE pls2;
    void main()
    {
        float r = uintBitsToFloat(pixelLocalLoadANGLE(pls1).r);
        float g = uintBitsToFloat(pixelLocalLoadANGLE(pls2).r) - 2.;
        pixelLocalStoreANGLE(pls0, vec4(r, g, 0, 1));
    })");
    mProgram.drawBoxes({{FULLSCREEN}});
    ASSERT_GL_NO_ERROR();

    glEndPixelLocalStorageANGLE(3,
                                GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_DONT_CARE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}

// Check that PLS works on multiple faces of the same cube texture.
TEST_P(PixelLocalStorageTest, TextureCubeMultipleFaces)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    ASSERT_GL_NO_ERROR();

    GLTexture texCube;
    glBindTexture(GL_TEXTURE_CUBE_MAP, texCube);
    ASSERT_GL_NO_ERROR();
    glTexStorage2D(GL_TEXTURE_CUBE_MAP, 2, GL_R32UI, W * 2, H * 2);
    ASSERT_GL_NO_ERROR();

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, texCube, 1, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, texCube, 1, 5, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    ASSERT_GL_NO_ERROR();

    // Pass 1: seed the texCube.
    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls0;
    layout(binding=1, r32ui) uniform highp upixelLocalANGLE pls1;
    layout(binding=2, r32ui) uniform highp upixelLocalANGLE pls2;
    void main()
    {
        pixelLocalStoreANGLE(pls1, uvec4(floatBitsToUint(1.)));
        pixelLocalStoreANGLE(pls2, uvec4(floatBitsToUint(2.)));
    })");
    mProgram.drawBoxes({{FULLSCREEN}});

    // Pass 2: read the texCube.
    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls0;
    layout(binding=1, r32ui) uniform highp upixelLocalANGLE pls1;
    layout(binding=2, r32ui) uniform highp upixelLocalANGLE pls2;
    void main()
    {
        float r = uintBitsToFloat(pixelLocalLoadANGLE(pls1).r);
        float g = uintBitsToFloat(pixelLocalLoadANGLE(pls2).r) - 2.;
        pixelLocalStoreANGLE(pls0, vec4(r, g, 0, 1));
    })");
    mProgram.drawBoxes({{FULLSCREEN}});
    ASSERT_GL_NO_ERROR();

    glEndPixelLocalStorageANGLE(3,
                                GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_DONT_CARE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}

// Check that cube map faces work as PLS planes.
TEST_P(PixelLocalStorageTest, TextureCubeFaces)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Invert the color of PLS plane 0 assuming opaque alpha, e.g., red <-> cyan
    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, vec4(1, 1, 1, 2) - pixelLocalLoadANGLE(pls));
        })");

    std::vector<GLColor> redImg(H * W, GLColor::red);
    std::vector<GLColor> greenImg(H * W, GLColor::green);
    std::vector<GLColor> blueImg(H * W, GLColor::blue);
    std::vector<GLColor> cyanImg(H * W, GLColor::cyan);
    std::vector<GLColor> magentaImg(H * W, GLColor::magenta);
    std::vector<GLColor> yellowImg(H * W, GLColor::yellow);

    {
        // Level 2.
        GLTexture tex;
        glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
        glTexStorage2D(GL_TEXTURE_CUBE_MAP, 3, GL_RGBA8, W * 4, H * 4);
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                        redImg.data());
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                        cyanImg.data());
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                        greenImg.data());
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                        magentaImg.data());
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                        blueImg.data());
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 2, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                        yellowImg.data());
        ASSERT_GL_NO_ERROR();
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        for (size_t i = 0; i < 6; ++i)
        {
            // Sequentially attach each cube map face as PLS plane 0 and invert it
            glFramebufferTexturePixelLocalStorageANGLE(0, tex, 2, i, GL_NONE);
            ASSERT_GL_NO_ERROR();
            glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
            mProgram.drawBoxes({{FULLSCREEN}});
            ASSERT_GL_NO_ERROR();
            glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
            ASSERT_GL_NO_ERROR();
        }

        // Check that all faces have inverted colors now
        attachTextureCubeFaceToScratchFBO(tex, GL_TEXTURE_CUBE_MAP_POSITIVE_X, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);
        attachTextureCubeFaceToScratchFBO(tex, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
        attachTextureCubeFaceToScratchFBO(tex, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::magenta);
        attachTextureCubeFaceToScratchFBO(tex, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
        attachTextureCubeFaceToScratchFBO(tex, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
        attachTextureCubeFaceToScratchFBO(tex, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
        ASSERT_GL_NO_ERROR();
    }
}

// Check that glFlush, glFinish, and glClientWaitSync don't invalidate PLS.
TEST_P(PixelLocalStorageTest, FlushFinishSync)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane1;
        layout(rgba8i, binding=1) uniform lowp ipixelLocalANGLE plane2;
        layout(binding=2, rgba8ui) uniform lowp upixelLocalANGLE plane3;
        void main()
        {
            pixelLocalStoreANGLE(plane1, color + pixelLocalLoadANGLE(plane1));
            pixelLocalStoreANGLE(plane2, ivec4(aux1) + pixelLocalLoadANGLE(plane2));
            pixelLocalStoreANGLE(plane3, uvec4(aux2) + pixelLocalLoadANGLE(plane3));
        })");

    PLSTestTexture tex1(GL_RGBA8);
    PLSTestTexture tex2(GL_RGBA8I);
    PLSTestTexture tex3(GL_RGBA8UI);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

    // Accumulate R, G, B, A in 4 separate passes.
    // Store out-of-range values to ensure they are properly clamped upon storage.
    mProgram.drawBoxes({{FULLSCREEN, {2, -1, -2, -3}, {-500, 0, 0, 0}, {1, 0, 0, 0}}});
    glFlush();

    mProgram.drawBoxes({{FULLSCREEN, {0, 1, 0, 100}, {0, -129, 0, 0}, {0, 50, 0, 0}}});
    glFinish();

    mProgram.drawBoxes({{FULLSCREEN, {0, 0, 1, 0}, {0, 0, -70, 0}, {0, 0, 100, 0}}});
    GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    glFlush();
    glClientWaitSync(sync, 0, std::numeric_limits<GLuint64>::max());

    mProgram.drawBoxes({{FULLSCREEN, {0, 0, 0, -1}, {128, 0, 0, 500}, {0, 0, 0, 300}}});
    glFlush();
    glFinish();

    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 255, 255, 0));

    attachTexture2DToScratchFBO(tex2);
    EXPECT_PIXEL_RECT32I_EQ(0, 0, W, H, GLColor32I(0, -128, -70, 127));

    attachTexture2DToScratchFBO(tex3);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(1, 50, 100, 255));

    ASSERT_GL_NO_ERROR();
}

void PixelLocalStorageTest::doStateRestorationTest()
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Setup state.
    PLSTestTexture plsTex(GL_RGBA8UI, 32, 33);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    glViewport(1, 1, 5, 5);
    glScissor(2, 2, 4, 4);
    glEnable(GL_SCISSOR_TEST);

    bool hasDrawBuffersIndexedOES = IsGLExtensionEnabled("GL_OES_draw_buffers_indexed");
    if (hasDrawBuffersIndexedOES)
    {
        for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
        {
            if (i % 2 == 1)
            {
                glEnableiOES(GL_BLEND, i);
            }
            glColorMaskiOES(i, i % 3 == 0, i % 3 == 1, i % 3 == 2, i % 2 == 0);
        }
    }
    else
    {
        glEnable(GL_BLEND);
        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
    }

    std::vector<GLenum> drawBuffers(MAX_DRAW_BUFFERS);
    for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
    {
        drawBuffers[i] = i % 3 == 0 ? GL_NONE : GL_COLOR_ATTACHMENT0 + i;
    }
    glDrawBuffers(MAX_DRAW_BUFFERS, drawBuffers.data());

    GLenum imageAccesses[] = {GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE};
    GLenum imageFormats[]  = {GL_RGBA8, GL_R32UI, GL_R32I, GL_R32F};
    std::vector<GLTexture> images;
    if (isContextVersionAtLeast(3, 1))
    {
        for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
        {
            GLuint tex = images.emplace_back();
            glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
            glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, 8, 8, 5);
            GLboolean layered = i % 2;
            glBindImageTexture(i, images.back(), i % 3, layered, layered == GL_FALSE ? i % 5 : 0,
                               ANGLE_UNSAFE_TODO(imageAccesses[i % 3]),
                               ANGLE_UNSAFE_TODO(imageFormats[i % 4]));
        }

        glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 17);
        glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, 1);
    }

    PLSTestTexture boundTex(GL_RGBA8, 1, 1);
    glBindTexture(GL_TEXTURE_2D, boundTex);

    // Run pixel local storage.
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, plsTex, 0, 0, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(2, ClearF(.1, .2, .3, .4));
    glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_LOAD_ANGLE,
                                                  GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE}));
    glPixelLocalStorageBarrierANGLE();
    glEndPixelLocalStorageANGLE(
        4, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE, GL_DONT_CARE}));

    // Check state.
    GLint textureBinding2D;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding2D);
    EXPECT_EQ(static_cast<GLuint>(textureBinding2D), boundTex);

    if (isContextVersionAtLeast(3, 1))
    {
        for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
        {
            GLint name, level, layer, access, format;
            GLboolean layered;
            glGetIntegeri_v(GL_IMAGE_BINDING_NAME, i, &name);
            glGetIntegeri_v(GL_IMAGE_BINDING_LEVEL, i, &level);
            glGetBooleani_v(GL_IMAGE_BINDING_LAYERED, i, &layered);
            glGetIntegeri_v(GL_IMAGE_BINDING_LAYER, i, &layer);
            glGetIntegeri_v(GL_IMAGE_BINDING_ACCESS, i, &access);
            glGetIntegeri_v(GL_IMAGE_BINDING_FORMAT, i, &format);
            EXPECT_EQ(static_cast<GLuint>(name), images[i]);
            EXPECT_EQ(level, i % 3);
            EXPECT_EQ(layered, i % 2);
            EXPECT_EQ(layer, layered == GL_FALSE ? i % 5 : 0);
            ANGLE_UNSAFE_TODO(EXPECT_EQ(static_cast<GLuint>(access), imageAccesses[i % 3]));
            ANGLE_UNSAFE_TODO(EXPECT_EQ(static_cast<GLuint>(format), imageFormats[i % 4]));
        }

        GLint defaultWidth, defaultHeight;
        glGetFramebufferParameteriv(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
                                    &defaultWidth);
        glGetFramebufferParameteriv(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
                                    &defaultHeight);
        EXPECT_EQ(defaultWidth, 17);
        EXPECT_EQ(defaultHeight, 1);
    }

    for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i)
    {
        GLint attachmentType;
        glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
                                              GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
                                              &attachmentType);
        EXPECT_EQ(attachmentType, GL_NONE);
    }

    for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
    {
        GLint drawBuffer;
        glGetIntegerv(GL_DRAW_BUFFER0 + i, &drawBuffer);
        if (i % 3 == 0)
        {
            EXPECT_EQ(drawBuffer, GL_NONE);
        }
        else
        {
            EXPECT_EQ(drawBuffer, GL_COLOR_ATTACHMENT0 + i);
        }
    }

    if (hasDrawBuffersIndexedOES)
    {
        for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
        {
            EXPECT_EQ(glIsEnablediOES(GL_BLEND, i), i % 2 == 1);
            EXPECT_GL_COLOR_MASK_INDEXED(i, i % 3 == 0, i % 3 == 1, i % 3 == 2, i % 2 == 0);
        }
    }
    else
    {
        EXPECT_TRUE(glIsEnabled(GL_BLEND));
        EXPECT_GL_INTEGER(GL_BLEND, GL_TRUE);
        EXPECT_GL_COLOR_MASK(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE)
    }

    EXPECT_TRUE(glIsEnabled(GL_SCISSOR_TEST));

    std::array<GLint, 4> scissorBox;
    glGetIntegerv(GL_SCISSOR_BOX, scissorBox.data());
    EXPECT_EQ(scissorBox, (std::array<GLint, 4>{2, 2, 4, 4}));

    std::array<GLint, 4> viewport;
    glGetIntegerv(GL_VIEWPORT, viewport.data());
    EXPECT_EQ(viewport, (std::array<GLint, 4>{1, 1, 5, 5}));

    GLint drawFramebuffer;
    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFramebuffer);
    EXPECT_EQ(static_cast<GLuint>(drawFramebuffer), fbo);

    ASSERT_GL_NO_ERROR();
}

// Check that application-facing ES3 state is not perturbed by pixel local storage.
TEST_P(PixelLocalStorageTest, StateRestoration)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doStateRestorationTest();
}

void PixelLocalStorageTest::doDrawStateTest()
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

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

    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT_AND_BACK);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_NEVER);

    glEnable(GL_POLYGON_OFFSET_FILL);
    glPolygonOffset(1e9f, 1e9f);

    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, 1, 1);

    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NEVER, 0, 0);

    if (isContextVersionAtLeast(3, 1))
    {
        glEnable(GL_SAMPLE_MASK);
        glSampleMaski(0, 0);
    }

    glViewport(0, 0, 1, 1);

    glClearColor(.1f, .2f, .3f, .4f);

    // Issue a draw to ensure GL state gets synced.
    PLSTestTexture tex(GL_RGBA8);
    renderTextureToDefaultFramebuffer(tex);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLuint depthStencil;
    glGenRenderbuffers(1, &depthStencil);
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencil);

    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, vec4(1, 0, 0, 1));
        })");

    // Clear to green. This should work regardless of draw state.
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));

    // Draw a red box. This should not go through because the blocking draw state should have been
    // restored after the clear.
    mProgram.drawBoxes({{FULLSCREEN}});

    // Store PLS to the texture. This should work again even though the blocking draw state was
    // synced for the previous draw.
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(0, 255, 0, 255));

    ASSERT_GL_NO_ERROR();
}

#define SETUP_IMPLICIT_DISABLES_TEST(framebufferTarget)                                       \
    GLFramebuffer fbo, readFBO;                                                               \
    glBindFramebuffer(framebufferTarget, fbo);                                                \
                                                                                              \
    PLSTestTexture tex0(GL_RGBA8);                                                            \
    PLSTestTexture tex1(GL_RGBA8);                                                            \
    PLSTestTexture tex2(GL_RGBA8);                                                            \
    PLSTestTexture tex3(GL_RGBA8);                                                            \
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);                       \
    glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);                       \
    glFramebufferTexturePixelLocalStorageANGLE(2, tex2, 0, 0, GL_NONE);                       \
    glFramebufferTexturePixelLocalStorageANGLE(3, tex3, 0, 0, GL_NONE);                       \
    GLenum loadOps[4] = {GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, \
                         GL_LOAD_OP_ZERO_ANGLE};                                              \
                                                                                              \
    glDrawBuffers(0, nullptr);                                                                \
                                                                                              \
    EXPECT_GL_NO_ERROR()

#define CHECK_PLS_ENABLED() \
    EXPECT_GL_NO_ERROR();   \
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 4)

#define CHECK_PLS_DISABLED() \
    EXPECT_GL_NO_ERROR();    \
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0)

#define BEGIN_PLS()                            \
    glBeginPixelLocalStorageANGLE(4, loadOps); \
    CHECK_PLS_ENABLED()

#define CHECK_ENDS_PLS(cmd) \
    BEGIN_PLS();            \
    cmd;                    \
    CHECK_PLS_DISABLED()

#define CHECK_ENDS_PLS_WITH_READ_FBO(cmd)            \
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO); \
    BEGIN_PLS();                                     \
    CHECK_PLS_ENABLED();                             \
    cmd;                                             \
    CHECK_PLS_DISABLED();

#define CHECK_DOES_NOT_END_PLS(cmd)                                                               \
    BEGIN_PLS();                                                                                  \
    cmd;                                                                                          \
    CHECK_PLS_ENABLED();                                                                          \
    glEndPixelLocalStorageANGLE(4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, \
                                                GL_DONT_CARE, GL_DONT_CARE}));                    \
    CHECK_PLS_DISABLED()

void PixelLocalStorageTest::doImplicitDisablesTest_Framebuffer()
{
    SETUP_IMPLICIT_DISABLES_TEST(GL_DRAW_FRAMEBUFFER);

    // Binding a READ_FRAMEBUFFER still ends PLS.
    CHECK_ENDS_PLS(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));
    EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, 0);
    EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    // Binding the same framebuffer still ends PLS.
    CHECK_ENDS_PLS(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo));
    EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, fbo);
    EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    CHECK_ENDS_PLS(glBindFramebuffer(GL_FRAMEBUFFER, fbo));
    EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, fbo);
    EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, fbo);
    CHECK_ENDS_PLS(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
    EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, 0);
    EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    CHECK_ENDS_PLS(glBindFramebuffer(GL_FRAMEBUFFER, 0));
    EXPECT_GL_INTEGER(GL_DRAW_FRAMEBUFFER_BINDING, 0);
    EXPECT_GL_INTEGER(GL_READ_FRAMEBUFFER_BINDING, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    CHECK_ENDS_PLS(glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8, GL_NONE));
    GLuint tex3ID = tex3;
    CHECK_DOES_NOT_END_PLS(glDeleteTextures(1, &tex3ID));

    CHECK_ENDS_PLS(glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0})));
    glDrawBuffers(0, nullptr);

    CHECK_ENDS_PLS(glDrawBuffers(0, nullptr));

    if (EnsureGLExtensionEnabled("GL_MESA_framebuffer_flip_y"))
    {
        CHECK_ENDS_PLS(
            glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
        EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE);
        glFramebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_FALSE);
        CHECK_ENDS_PLS(
            glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
        EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
                                              GL_TRUE);
        glFramebufferParameteriMESA(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_FALSE);
        CHECK_ENDS_PLS(
            glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
        EXPECT_FRAMEBUFFER_PARAMETER_INT_MESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
                                              GL_TRUE);
        glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_FALSE);
        CHECK_ENDS_PLS_WITH_READ_FBO(
            glFramebufferParameteriMESA(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, GL_TRUE));
    }

    if (isContextVersionAtLeast(3, 1))
    {
        CHECK_ENDS_PLS(glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 23));
        EXPECT_FRAMEBUFFER_PARAMETER_INT(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 23);
        glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
        CHECK_ENDS_PLS(
            glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 24));
        EXPECT_FRAMEBUFFER_PARAMETER_INT(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 24);
        glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
        CHECK_ENDS_PLS(
            glFramebufferParameteri(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 25));
        EXPECT_FRAMEBUFFER_PARAMETER_INT(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 25);
        glFramebufferParameteri(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 0);
        CHECK_ENDS_PLS_WITH_READ_FBO(
            glFramebufferParameteri(GL_READ_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, 23));
    }

    glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8, GL_NONE);
    CHECK_ENDS_PLS(glFramebufferTexturePixelLocalStorageANGLE(2, tex2, 0, 0, GL_NONE););
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex2);

    GLuint tex2ID = tex2;
    CHECK_ENDS_PLS(glDeleteTextures(1, &tex2ID));
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
    glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8, GL_NONE);

    GLuint fboID = fbo;
    CHECK_ENDS_PLS(glDeleteFramebuffers(1, &fboID));
}

void PixelLocalStorageTest::doImplicitDisablesTest_TextureAttachments()
{
    SETUP_IMPLICIT_DISABLES_TEST(GL_FRAMEBUFFER);

    GLTexture tex2D;
    glBindTexture(GL_TEXTURE_2D, tex2D);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    CHECK_ENDS_PLS(
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS(
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS(
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2D);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS_WITH_READ_FBO(
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0));

    GLTexture tex2DArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, tex2DArray);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, W, H, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    CHECK_ENDS_PLS(
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS(
        glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS(
        glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS_WITH_READ_FBO(
        glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0));

    GLTexture tex3d;
    glBindTexture(GL_TEXTURE_3D, tex3d);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, W, H, 10, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    CHECK_ENDS_PLS(glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS(
        glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS(
        glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    CHECK_ENDS_PLS_WITH_READ_FBO(
        glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d, 0, 0));

    if (EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"))
    {
        PLSTestTexture texMSAA(GL_RGBA8);
        CHECK_ENDS_PLS(glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                            GL_TEXTURE_2D, texMSAA, 0, 4));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texMSAA);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS(glFramebufferTexture2DMultisampleEXT(
            GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texMSAA, 0, 4));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texMSAA);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS(glFramebufferTexture2DMultisampleEXT(
            GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texMSAA, 0, 4));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texMSAA);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferTexture2DMultisampleEXT(
            GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texMSAA, 0, 4));
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        ASSERT_GL_NO_ERROR();
    }

    if (EnsureGLExtensionEnabled("GL_OVR_multiview2"))
    {
        CHECK_ENDS_PLS(glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                        tex2DArray, 0, 0, 1));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                        tex2DArray, 0, 0, 1));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS(glFramebufferTextureMultiviewOVR(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                        tex2DArray, 0, 0, 1));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferTextureMultiviewOVR(
            GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0, 1));
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    }

    if (EnsureGLExtensionEnabled("GL_OES_texture_3D"))
    {
        CHECK_ENDS_PLS(glFramebufferTexture3DOES(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                 GL_TEXTURE_3D, tex3d, 0, 0));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS(glFramebufferTexture3DOES(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                 GL_TEXTURE_3D, tex3d, 0, 0));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS(glFramebufferTexture3DOES(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                                 GL_TEXTURE_3D, tex3d, 0, 0));
        EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex3d);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferTexture3DOES(
            GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, tex3d, 0, 0));
    }
}

void PixelLocalStorageTest::doImplicitDisablesTest_RenderbufferAttachments()
{
    SETUP_IMPLICIT_DISABLES_TEST(GL_FRAMEBUFFER);

    GLuint colorBuffer;
    glGenRenderbuffers(1, &colorBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
    CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                             colorBuffer));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
    CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                             GL_RENDERBUFFER, colorBuffer));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorBuffer);
    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
    CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                             GL_RENDERBUFFER, colorBuffer));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorBuffer);
    glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, 0);
    CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferRenderbuffer(
        GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer));

    GLuint depthStencilBuffer;
    glGenRenderbuffers(1, &depthStencilBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
    CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                             GL_RENDERBUFFER, depthStencilBuffer));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                       depthStencilBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
    CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                             GL_RENDERBUFFER, depthStencilBuffer));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                       depthStencilBuffer);
    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
    CHECK_ENDS_PLS(glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                             GL_RENDERBUFFER, depthStencilBuffer));
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
                                       depthStencilBuffer);
    glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
    CHECK_ENDS_PLS_WITH_READ_FBO(glFramebufferRenderbuffer(
        GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilBuffer));
}

void PixelLocalStorageTest::doImplicitDisablesTest_Invalidate()
{
    SETUP_IMPLICIT_DISABLES_TEST(GL_FRAMEBUFFER);

    // "... Commands that flush or invalidate tiled memory.
    //
    // "e.g., DiscardFramebufferEXT, InvalidateFramebuffer,
    // InvalidateSubFramebuffer, etc."
    if (EnsureGLExtensionEnabled("GL_EXT_discard_framebuffer"))
    {
        CHECK_ENDS_PLS(glDiscardFramebufferEXT(GL_FRAMEBUFFER, 0, nullptr));
    }
    CHECK_ENDS_PLS(glInvalidateFramebuffer(GL_FRAMEBUFFER, 0, nullptr));
    CHECK_ENDS_PLS(glInvalidateSubFramebuffer(GL_FRAMEBUFFER, 0, nullptr, 0, 0, 1, 1));
}

#undef CHECK_ENDS_PLS_WITH_READ_FBO
#undef CHECK_DOES_NOT_END_PLS
#undef BEGIN_PLS
#undef CHECK_PLS_DISABLED
#undef CHECK_PLS_ENABLED
#undef CHECK_ENDS_PLS

// Check that draw state does not affect PLS loads and stores, particularly for
// EXT_shader_pixel_local_storage, where they are implemented as fullscreen draws.
TEST_P(PixelLocalStorageTest, DrawStateReset)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doDrawStateTest();
}

// Check that blend & color mask state and glClearBuffers{u,i,ui}v do not affect
// pixel local storage, and that PLS does not affect blend or color mask on the
// application's draw buffers.
TEST_P(PixelLocalStorageTest, BlendColorMaskAndClear)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex1(GL_RGBA8);
    PLSTestTexture tex2(GL_RGBA8);
    PLSTestTexture tex3(GL_RGBA8);
    PLSTestTexture tex4(GL_RGBA8);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glViewport(0, 0, W, H);

    const bool drawBuffersIndexed = IsGLExtensionEnabled("GL_OES_draw_buffers_indexed");
    GLint firstClearBuffer        = 0;
    if (!drawBuffersIndexed)
    {
        // Blend should not affect pixel local storage.
        glBlendFunc(GL_ZERO, GL_ZERO);
        // Color mask should not affect pixel local storage.
        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);

        mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls1;
        layout(binding=1, rgba8) uniform lowp pixelLocalANGLE pls2;
        layout(binding=2, rgba8) uniform lowp pixelLocalANGLE pls3;
        layout(binding=3, rgba8) uniform lowp pixelLocalANGLE pls4;
        void main()
        {
            pixelLocalStoreANGLE(pls1, vec4(1, 0, 0, 1));
            pixelLocalStoreANGLE(pls2, vec4(0, 1, 0, 1));
            pixelLocalStoreANGLE(pls3, vec4(0, 0, 1, 1));
            pixelLocalStoreANGLE(pls4, vec4(0, 0, 0, 1));
        })");

        glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(3, tex4, 0, 0, GL_NONE);

        glBeginPixelLocalStorageANGLE(
            4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
                            GL_LOAD_OP_ZERO_ANGLE}));

        // Blend should not affect pixel local storage.
        EXPECT_FALSE(glIsEnabled(GL_BLEND));
        EXPECT_GL_INTEGER(GL_BLEND, GL_FALSE);
        glEnable(GL_BLEND);

        // Enabling GL_BLEND works from the client perspective even though PLS is overriding it.
        EXPECT_TRUE(glIsEnabled(GL_BLEND));
        EXPECT_GL_INTEGER(GL_BLEND, GL_TRUE);

        // BlendFunc has no effect as long as GL_BLEND stays disabled on our PLS draw buffers.
        glBlendFunc(GL_ZERO, GL_ONE);
        EXPECT_GL_INTEGER(GL_BLEND_SRC_RGB, GL_ZERO);
        EXPECT_GL_INTEGER(GL_BLEND_SRC_ALPHA, GL_ZERO);
        EXPECT_GL_INTEGER(GL_BLEND_DST_RGB, GL_ONE);
        EXPECT_GL_INTEGER(GL_BLEND_DST_ALPHA, GL_ONE);

        EXPECT_GL_NO_ERROR();

        // Color mask should not affect PLS.
        EXPECT_GL_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
        glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
        EXPECT_GL_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);

        EXPECT_GL_NO_ERROR();
    }
    else
    {
        mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls1;
        layout(binding=1, rgba8) uniform lowp pixelLocalANGLE pls2;
        layout(location=0) out lowp vec4 out1;
        layout(location=1) out lowp vec4 out2;
        void main()
        {
            out1 = vec4(0, 1, 1, 0);
            out2 = vec4(1, 1, 0, 0);
            pixelLocalStoreANGLE(pls1, vec4(0, 0, 1, 1));
            pixelLocalStoreANGLE(pls2, vec4(0, 0, 0, 1));
        })");

        std::vector<GLColor> whiteData(H * W, GLColor::white);
        glBindTexture(GL_TEXTURE_2D, tex1);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, whiteData.data());

        // Blend should not affect pixel local storage.
        glEnableiOES(GL_BLEND, 0);
        glBlendEquationiOES(0, GL_FUNC_REVERSE_SUBTRACT);
        glBlendFunciOES(0, GL_ONE, GL_ONE);
        glEnableiOES(GL_BLEND, 2);

        std::vector<GLColor> blackData(H * W, GLColor::black);
        glBindTexture(GL_TEXTURE_2D, tex2);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, blackData.data());

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex1, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex2, 0);
        glFramebufferTexturePixelLocalStorageANGLE(0, tex3, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(1, tex4, 0, 0, GL_NONE);
        glDrawBuffers(2, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}));
        firstClearBuffer = 2;

        glBeginPixelLocalStorageANGLE(2,
                                      GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));

        // Color mask should not affect pixel local storage.
        glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
        glColorMaskiOES(2, GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
        EXPECT_GL_COLOR_MASK_INDEXED(0, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
        EXPECT_GL_COLOR_MASK_INDEXED(1, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
        EXPECT_GL_COLOR_MASK_INDEXED(2, GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);

        EXPECT_TRUE(glIsEnablediOES(GL_BLEND, 0));
        EXPECT_FALSE(glIsEnablediOES(GL_BLEND, 1));
        for (int i = 2; i < MAX_DRAW_BUFFERS; ++i)
        {
            // Enabling blend with PLS active still works from the client perspective, even though
            // it's overridden by PLS.
            if (i == 2)
            {
                EXPECT_TRUE(glIsEnablediOES(GL_BLEND, i));
            }
            else
            {
                EXPECT_FALSE(glIsEnablediOES(GL_BLEND, i));
            }
            glEnableiOES(GL_BLEND, i);
            EXPECT_TRUE(glIsEnablediOES(GL_BLEND, i));
            glBlendFunciOES(i, GL_ZERO, GL_ONE);

            // Changing the color mask with PLS active still works from the client perspective, even
            // though it's overridden by PLS.
            if (i == 2)
            {
                EXPECT_GL_COLOR_MASK_INDEXED(i, GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
            }
            else
            {
                EXPECT_GL_COLOR_MASK_INDEXED(i, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
            }
            glColorMaskiOES(i, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
            EXPECT_GL_COLOR_MASK_INDEXED(i, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
        }

        ASSERT_GL_NO_ERROR();
    }

    mProgram.drawBoxes({{FULLSCREEN}});

    // glClearBuffer{f,i,ui}v are ignored for reserved PLS draw buffers when PLS is active.
    for (GLint i = firstClearBuffer; i < MAX_DRAW_BUFFERS; ++i)
    {
        constexpr static GLfloat zerof[4] = {0, 0, 0, 0};
        constexpr static GLint zeroi[4]   = {0, 0, 0, 0};
        constexpr static GLuint zeroui[4] = {0, 0, 0, 0};
        glClearBufferfv(GL_COLOR, i, zerof);
        glClearBufferiv(GL_COLOR, i, zeroi);
        glClearBufferuiv(GL_COLOR, i, zeroui);
        ASSERT_GL_NO_ERROR();
    }

    int n;
    glGetIntegerv(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, &n);
    glEndPixelLocalStorageANGLE(n, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);

    attachTexture2DToScratchFBO(tex2);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);

    attachTexture2DToScratchFBO(tex3);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);

    attachTexture2DToScratchFBO(tex4);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::black);

    // Blend state that was set on overridden draw buffers during PLS should still be in effect
    // after PLS ends.
    if (!drawBuffersIndexed)
    {
        EXPECT_TRUE(glIsEnabled(GL_BLEND));
        EXPECT_GL_INTEGER(GL_BLEND, GL_TRUE);
        EXPECT_GL_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
    }
    else
    {
        EXPECT_TRUE(glIsEnablediOES(GL_BLEND, 0));
        EXPECT_FALSE(glIsEnablediOES(GL_BLEND, 1));
        EXPECT_GL_COLOR_MASK_INDEXED(0, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
        EXPECT_GL_COLOR_MASK_INDEXED(1, GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
        for (GLint i = 2; i < MAX_DRAW_BUFFERS; ++i)
        {
            EXPECT_TRUE(glIsEnablediOES(GL_BLEND, i));
            // EXPECT_GL_COLOR_MASK_INDEXED(i, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
        }
    }

    // Now ensure that clears *do* occur on non-PLS draw buffers.
    if (firstClearBuffer != 0)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glBeginPixelLocalStorageANGLE(
            n, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE,
                            GL_LOAD_OP_LOAD_ANGLE}));

        constexpr static GLfloat one[4] = {1, 1, 1, 1};
        glColorMaskiOES(0, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
        glClearBufferfv(GL_COLOR, 0, one);

        glColorMaskiOES(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        glClearBufferfv(GL_COLOR, 1, one);

        ASSERT_GL_NO_ERROR();

        glEndPixelLocalStorageANGLE(
            n, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                            GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        attachTexture2DToScratchFBO(tex1);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::magenta);

        attachTexture2DToScratchFBO(tex2);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
    }

    ASSERT_GL_NO_ERROR();
}

// Check that changing framebuffer state implicitly disable pixel local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_Framebuffer)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_Framebuffer();
}

// Check that changing framebuffer texture attachments implicitly disable pixel
// local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_TextureAttachments)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_TextureAttachments();
}

// Check that changing framebuffer renderbuffer attachments implicitly disable
// pixel local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_RenderbufferAttachments)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_RenderbufferAttachments();
}

// Check that invalidation commands implicitly disable pixel local storage.
TEST_P(PixelLocalStorageTest, ImplicitDisables_Invalidate)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_Invalidate();
}

// Check that gl(Copy)TexSubImage{2,3}D end PLS before running.
TEST_P(PixelLocalStorageTest, CopyTexSubImage)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex0(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));

    PLSTestTexture color0(GL_RGBA8);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color0, 0);
    glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
    glClearBufferfv(GL_COLOR, 0, MakeArray<float>({0, 0, 0, 0}));
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::transparentBlack);

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex0;
        layout(location=0) out lowp vec4 color0;
        void main()
        {
            lowp vec4 plsOut = color + pixelLocalLoadANGLE(tex0);
            pixelLocalStoreANGLE(tex0, plsOut);
            color0 = vec4(0, 1, 1, 0);
        })");

    GLTexture tex3D;
    glBindTexture(GL_TEXTURE_3D, tex3D);
    glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, W, H, 5);

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    glClearBufferfv(GL_COLOR, 0, MakeArray<float>({1, 0, 0, 1}));
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));

    glBindTexture(GL_TEXTURE_2D, tex0);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H / 2);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));

    // glTexStorage2D on a PLS plane generates an error since the texture must be immutable.
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W / 2, H / 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE, GL_ONE);
    glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
    EXPECT_GL_NO_ERROR();
    mProgram.drawBoxes({{{FULLSCREEN}, {0, 0, 1, 0}}});
    EXPECT_GL_NO_ERROR();

    glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));

    GLTexture texNonImmutable;
    glBindTexture(GL_TEXTURE_2D, texNonImmutable);
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    attachTexture2DToScratchFBO(tex0);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H / 2, GLColor::magenta);
    EXPECT_PIXEL_RECT_EQ(0, H / 2, W, H - H / 2, GLColor::cyan);

    attachTextureLayerToScratchFBO(tex3D, 0, 2);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);

    attachTexture2DToScratchFBO(texNonImmutable);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);

    ASSERT_GL_NO_ERROR();
}

// Check that glBlitFramebuffer ends PLS, while also working as expected.
TEST_P(PixelLocalStorageTest, BlitFramebuffer)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex0(GL_RGBA8);
    PLSTestTexture tex1(GL_R32UI);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);
    constexpr static int NUM_PLANES = 2;

    PLSTestTexture colorAttachment(GL_RGBA8);
    bool canHaveColorAttachment =
        MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES > NUM_PLANES;
    if (canHaveColorAttachment)
    {
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                               colorAttachment, 0);
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
    }

    // Set the to-be current PLS plane as the read framebuffer.
    GLFramebuffer readFBO;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
    EXPECT_GL_NO_ERROR();

    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
    glFramebufferPixelLocalClearValueuivANGLE(1, ClearUI(123, 0, 0, 0));
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, NUM_PLANES);

    // glBlitFramebuffer will implicitly end PLS and store the PLS plane before
    // executing.
    glBlitFramebuffer(0, 0, W / 2, H, 0, 0, W / 2, H, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    if (canHaveColorAttachment)
    {
        attachTexture2DToScratchFBO(colorAttachment);
        EXPECT_PIXEL_RECT_EQ(0, 0, W / 2, H, GLColor::green);
        EXPECT_PIXEL_RECT_EQ(W / 2, 0, W - W / 2, H, GLColor::red);
    }

    attachTexture2DToScratchFBO(tex0);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);

    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(123, 0, 0, 1));
}

// Check that GL_RASTERIZER_DISCARD disables renders to PLS, but not PLS clear operations.
TEST_P(PixelLocalStorageTest, RasterizerDiscard)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
        void main()
        {
            pixelLocalStoreANGLE(tex, vec4(0, 1, 0, 1));
        })");

    // PLS clears still pass when RASTERIZER_DISCARD is disabled.
    glDisable(GL_RASTERIZER_DISCARD);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 1, 1));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
    ASSERT_GL_NO_ERROR();

    // PLS clears still pass when RASTERIZER_DISCARD is enabled.
    glEnable(GL_RASTERIZER_DISCARD);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::transparentBlack);
    ASSERT_GL_NO_ERROR();

    // Rasterization passes when RASTERIZER_DISCARD is disabled.
    glDisable(GL_RASTERIZER_DISCARD);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
    ASSERT_GL_NO_ERROR();

    // Rasterization fails when RASTERIZER_DISCARD is enabled (but PLS still clears).
    glEnable(GL_RASTERIZER_DISCARD);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
    ASSERT_GL_NO_ERROR();

    // RASTERIZER_DISCARD can be disabled while PLS is active.
    glEnable(GL_RASTERIZER_DISCARD);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    glDisable(GL_RASTERIZER_DISCARD);
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
    ASSERT_GL_NO_ERROR();

    // RASTERIZER_DISCARD can be enabled while PLS is active.
    glDisable(GL_RASTERIZER_DISCARD);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    glEnable(GL_RASTERIZER_DISCARD);
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    attachTexture2DToScratchFBO(tex);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}

// Check that glClear does not clear PLS planes.
TEST_P(PixelLocalStorageTest, ClearWithActivePLS)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture pls0(GL_RGBA8);
    PLSTestTexture pls1(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);

    constexpr static int NUM_PLANES = 2;
    int maxColorAttachmentsWith2PLSPlanes =
        std::min(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2, MAX_DRAW_BUFFERS);
    int numColorAttachments = std::min(maxColorAttachmentsWith2PLSPlanes, 2);

    PLSTestTexture colorAttachment0(GL_RGBA8);
    PLSTestTexture colorAttachment1(GL_RGBA8);

    for (int colorAttachmentMask = 0; colorAttachmentMask < (1 << numColorAttachments);
         ++colorAttachmentMask)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        if (numColorAttachments >= 2)
        {
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
                                   colorAttachment1, 0);
        }
        if (numColorAttachments >= 1)
        {
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   colorAttachment0, 0);
        }

        // Clear color attachments.
        glDrawBuffers(2, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}));
        if (numColorAttachments >= 2)
        {
            float green[4] = {0, 1, 0, 1};
            glClearBufferfv(GL_COLOR, 1, green);
            glReadBuffer(GL_COLOR_ATTACHMENT1);
            EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
        }
        if (numColorAttachments >= 1)
        {
            float red[4] = {1, 0, 0, 1};
            glClearBufferfv(GL_COLOR, 0, red);
            glReadBuffer(GL_COLOR_ATTACHMENT0);
            EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
        }

        // Only enable draw buffers that are in colorAttachmentMask.
        GLenum drawBuffers[2];
        for (int i = 0; i < 2; ++i)
        {
            ANGLE_UNSAFE_TODO(drawBuffers[i]) =
                (colorAttachmentMask & (1 << i)) ? GL_COLOR_ATTACHMENT0 + i : GL_NONE;
        }
        glDrawBuffers(2, drawBuffers);

        if (drawBuffers[0] == GL_NONE && numColorAttachments >= 1)
        {
            // PLS doesn't allow a colorAttachment0 with a disabled draw buffer.
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
        }

        glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 1, 1));
        glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(0, 1, 1, 1));
        glBeginPixelLocalStorageANGLE(
            NUM_PLANES, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, NUM_PLANES);

        glClearColor(1, 1, 1, 1);
        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, 10, H);
        if (numColorAttachments != 0)
        {
            glClear(GL_COLOR_BUFFER_BIT);
            EXPECT_GL_NO_ERROR();
        }
        glDisable(GL_SCISSOR_TEST);
        EXPECT_GL_NO_ERROR();

        glEndPixelLocalStorageANGLE(
            NUM_PLANES, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        if (numColorAttachments >= 1)
        {
            // Restore colorAttachment0 since PLS doesn't allow a colorAttachment0 with a disabled
            // draw buffer.
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   colorAttachment0, 0);
        }

        // Make sure glClear worked on the enabled color attachments, and did not clear the disabled
        // ones.
        if (numColorAttachments >= 2)
        {
            glReadBuffer(GL_COLOR_ATTACHMENT1);
            EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
                                 (colorAttachmentMask & 2) ? GLColor::white : GLColor::green);
            EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::green);
        }
        if (numColorAttachments >= 1)
        {
            glReadBuffer(GL_COLOR_ATTACHMENT0);
            EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
                                 (colorAttachmentMask & 1) ? GLColor::white : GLColor::red);
            EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::red);
        }

        // Make sure glClear did not touch the PLS planes.
        attachTexture2DToScratchFBO(pls0);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);

        attachTexture2DToScratchFBO(pls1);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);

        // Clear to check that the draw buffers were properly restored after we cleared with PLS
        // active.
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glClearColor(0, 0, 0, 1);
        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, 10, H);
        if (numColorAttachments >= 1)
        {
            glClear(GL_COLOR_BUFFER_BIT);
        }
        glDisable(GL_SCISSOR_TEST);
        EXPECT_GL_NO_ERROR();

        // Make sure glClear worked on the enabled color attachments, and did not clear the disabled
        // ones.
        if (numColorAttachments >= 2)
        {
            glReadBuffer(GL_COLOR_ATTACHMENT1);
            EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
                                 (colorAttachmentMask & 2) ? GLColor::black : GLColor::green);
            EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::green);
        }
        if (numColorAttachments >= 1)
        {
            glReadBuffer(GL_COLOR_ATTACHMENT0);
            EXPECT_PIXEL_RECT_EQ(0, 0, 10, H,
                                 (colorAttachmentMask & 1) ? GLColor::black : GLColor::red);
            EXPECT_PIXEL_RECT_EQ(10, 0, W - 10, H, GLColor::red);
        }

        // Make sure glClear still did not touch the PLS planes.
        attachTexture2DToScratchFBO(pls0);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);

        attachTexture2DToScratchFBO(pls1);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);
        EXPECT_GL_NO_ERROR();
    }

    ASSERT_GL_NO_ERROR();
}

// Check the Intel-Mac workaround for GL_COLOR_ATTACHMENT0. On this platform, it has been necessary
// in the past to attach a texture on GL_COLOR_ATTACHMENT0 when rendering PLS only to storage
// textures.
//
// Check that:
//   1) Needed workarounds are applied and PLS draws correctly.
//   2) The workarounds do not affect GL state after PLS ends.
//
TEST_P(PixelLocalStorageTest, ColorAttachment0Workaround)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES < 3);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture pls(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls, 0, 0, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));

    // Use PLS with:
    //   1) GL_COLOR_ATTACHMENT0 not attached.
    //   2) drawbuffer 0 disabled.
    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
        void main()
        {
            pixelLocalStoreANGLE(tex, pixelLocalLoadANGLE(tex) + vec4(0, 1, 0, 1));
        })");

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    glDrawBuffers(0, GLenumArray({GL_NONE}));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();

    // Ensure our workaround didn't change the COLOR_ATTACHMENT0 binding or draw buffer.
    EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_NONE);
    EXPECT_GL_INTEGER(GL_DRAW_BUFFER0, GL_NONE);
    EXPECT_GL_NO_ERROR();

    attachTexture2DToScratchFBO(pls);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);

    // Use PLS with:
    //   1) GL_COLOR_ATTACHMENT0 still not attached.
    //   2) drawbuffer 0 enabled.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glDrawBuffers(1, GLenumArray({GL_COLOR_ATTACHMENT0}));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();

    // Ensure our workaround didn't change the COLOR_ATTACHMENT0 binding or draw buffer.
    EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_NONE);
    EXPECT_GL_INTEGER(GL_DRAW_BUFFER0, GL_COLOR_ATTACHMENT0);
    EXPECT_GL_NO_ERROR();

    attachTexture2DToScratchFBO(pls);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);

    // Use PLS with:
    //   1) GL_COLOR_ATTACHMENT0 *not* attached.
    //   2) GL_COLOR_ATTACHMENT1 attached.
    //   3) GL_COLOR_ATTACHMENT2 attached.
    //   4) drawbuffer 0 enabled.
    //   5) drawbuffer 1 enabled.
    //   6) drawbuffer 2 enabled but not written by the shader.
    PLSTestTexture tex1(GL_RGBA8), tex2(GL_RGBA8);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
        layout(location=1) out lowp vec4 tex1;
        layout(location=3) out lowp vec4 tex3;
        void main()
        {
            pixelLocalStoreANGLE(pls, pixelLocalLoadANGLE(pls) + vec4(0, 1, 0, 1));
            tex1 = vec4(0, 1, 0, 1);
            tex3 = vec4(0, 0, 1, 1);
        })");

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex1, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, tex2, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, 0, 0);
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                                  GL_COLOR_ATTACHMENT3}));

    GLfloat red[] = {1, 0, 0, 1};
    glClearBufferfv(GL_COLOR, 1, red);
    glClearBufferfv(GL_COLOR, 2, red);

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    mProgram.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();

    // Ensure our workaround didn't change the COLOR_ATTACHMENT0 binding or draw buffer.
    EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_NONE);
    EXPECT_GL_INTEGER(GL_DRAW_BUFFER0, GL_COLOR_ATTACHMENT0);
    EXPECT_GL_INTEGER(GL_DRAW_BUFFER1, GL_COLOR_ATTACHMENT1);
    EXPECT_GL_INTEGER(GL_DRAW_BUFFER2, GL_COLOR_ATTACHMENT2);
    EXPECT_GL_INTEGER(GL_DRAW_BUFFER3, GL_COLOR_ATTACHMENT3);
    EXPECT_GL_NO_ERROR();

    attachTexture2DToScratchFBO(pls);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);

    attachTexture2DToScratchFBO(tex1);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);

    ASSERT_GL_NO_ERROR();
}

// Check that PLS and EXT_shader_framebuffer_fetch can be used together.
TEST_P(PixelLocalStorageTest, ParallelFramebufferFetch)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));

    mProgram.compile("#extension GL_EXT_shader_framebuffer_fetch : require", R"(
    layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls;
    inout highp vec4 fbfetch;
    void main()
    {
        // Swap pls and fbfetch.
        vec4 tmp = pixelLocalLoadANGLE(pls);
        pixelLocalStoreANGLE(pls, fbfetch);
        fbfetch = tmp;
    })");

    PLSTestTexture pls(GL_RGBA8), fbfetch(GL_RGBA8);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls, 0, 0, GL_NONE);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbfetch, 0);

    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 1, 0, 1));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));

    GLfloat red[] = {1, 0, 0, 1};
    glClearBufferfv(GL_COLOR, 0, red);

    // Swap pls and fbfetch.
    mProgram.drawBoxes({{FULLSCREEN}});

    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);

    attachTexture2DToScratchFBO(pls);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
}

// Check that PLS gets properly cleaned up when its framebuffer and textures are never deleted.
TEST_P(PixelLocalStorageTest, LeakFramebufferAndTexture)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    GLuint fbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLuint tex0;
    glGenTextures(1, &tex0);
    glBindTexture(GL_TEXTURE_2D, tex0);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 10, 10);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);

    PLSTestTexture tex1(GL_R32UI);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);

    glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I, GL_NONE);

    // Delete tex1.
    // Don't delete tex0.
    // Don't delete fbo.

    // The PixelLocalStorage frontend implementation has internal assertions that verify all its GL
    // context objects are properly disposed of.
}

// Check that sampler, texture, and PLS bindings all work when they are used in the same shader.
TEST_P(PixelLocalStorageTest, PLSWithSamplers)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture tex0(GL_RGBA8);
    std::vector<GLColor> redData(H * H, GLColor::red);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, redData.data());

    PLSTestTexture tex1(GL_RGBA8);
    std::vector<GLColor> blueData(H * H, GLColor::blue);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, blueData.data());

    PLSTestTexture pls0(GL_RGBA8);
    std::vector<GLColor> greenData(H * W, GLColor::green);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, greenData.data());

    PLSTestTexture pls1(GL_RGBA8);
    PLSTestTexture pls2(GL_RGBA8);

    glViewport(0, 0, W, H);

    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, pls2, 0, 0, GL_NONE);

    mProgram.compile(R"(
    layout(binding=0, rgba8) uniform mediump pixelLocalANGLE pls0;
    layout(binding=1, rgba8) uniform mediump pixelLocalANGLE pls1;
    layout(binding=2, rgba8) uniform mediump pixelLocalANGLE pls2;
    uniform mediump sampler2D tex0;
    uniform mediump sampler2D tex1;
    void main()
    {
        vec4 value;
        if (gl_FragCoord.y > 50.0)
        {
            if (gl_FragCoord.x > 50.0)
                value = texture(tex1, color.xy);
            else
                value = texture(tex0, color.xy);
        }
        else
        {
            if (gl_FragCoord.x > 50.0)
                value = pixelLocalLoadANGLE(pls1);
            else
                value = pixelLocalLoadANGLE(pls0);
        }
        pixelLocalStoreANGLE(pls2, value);
        pixelLocalStoreANGLE(pls1, vec4(0, 1, 1, 1));
    })");
    glUniform1i(glGetUniformLocation(mProgram, "tex0"), 0);
    glUniform1i(glGetUniformLocation(mProgram, "tex1"), 3);

    glFramebufferPixelLocalClearValuefvANGLE(1, ClearF(1, 1, 0, 1));
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE}));

    glBindTexture(GL_TEXTURE_2D, tex0);

    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, tex1);

    mProgram.drawBoxes({{FULLSCREEN, {0, 0, 1, 1}}});
    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(pls2);
    EXPECT_PIXEL_RECT_EQ(0, 0, 50, 50, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(50, 0, W - 50, 50, GLColor::yellow);
    EXPECT_PIXEL_RECT_EQ(0, 50, 50, H - 50, GLColor::red);
    EXPECT_PIXEL_RECT_EQ(50, 50, W - 50, H - 50, GLColor::blue);

    attachTexture2DToScratchFBO(pls1);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);

    attachTexture2DToScratchFBO(pls0);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);

    ASSERT_GL_NO_ERROR();
}

// Check the PLS interruption mechanism.
TEST_P(PixelLocalStorageTest, Interrupt)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture t(GL_RGBA8);
    PLSTestTexture u0(GL_R32UI);
    PLSTestTexture u1(GL_R32UI);
    PLSTestTexture u2(GL_R32UI);

    GLFramebuffer f;
    glBindFramebuffer(GL_FRAMEBUFFER, f);
    glFramebufferTexturePixelLocalStorageANGLE(0, t, 0, 0, GL_NONE);

    GLFramebuffer g;
    glBindFramebuffer(GL_FRAMEBUFFER, g);
    glFramebufferTexturePixelLocalStorageANGLE(0, u0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, u1, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, u2, 0, 0, GL_NONE);

    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    PLSProgram p;
    p.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE t;
    void main()
    {
        pixelLocalStoreANGLE(t, color + pixelLocalLoadANGLE(t));
    })");

    PLSProgram q;
    q.compile(R"(
    layout(binding=0, r32ui) uniform highp upixelLocalANGLE u0;
    layout(binding=1, r32ui) uniform highp upixelLocalANGLE u1;
    layout(binding=2, r32ui) uniform highp upixelLocalANGLE u2;
    void main()
    {
        pixelLocalStoreANGLE(u0, (pixelLocalLoadANGLE(u1) << 8) | 0xau);
        pixelLocalStoreANGLE(u1, (pixelLocalLoadANGLE(u2) << 8) | 0xbu);
        pixelLocalStoreANGLE(u2, (pixelLocalLoadANGLE(u0) << 8) | 0xcu);
    })");

    // Interleave 2 PLS rendering passes.
    glBindFramebuffer(GL_FRAMEBUFFER, f);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    p.bind();
    p.drawBoxes({{{FULLSCREEN}, {1, 0, 0, 0}}});
    glFramebufferPixelLocalStorageInterruptANGLE();

    glBindFramebuffer(GL_FRAMEBUFFER, g);
    q.bind();
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    q.drawBoxes({{{FULLSCREEN}}});
    glFramebufferPixelLocalStorageInterruptANGLE();

    p.bind();
    glBindFramebuffer(GL_FRAMEBUFFER, f);
    glFramebufferPixelLocalStorageRestoreANGLE();
    p.drawBoxes({{{FULLSCREEN}, {0, 0, 0, 1}}});
    glFramebufferPixelLocalStorageInterruptANGLE();

    glBindFramebuffer(GL_FRAMEBUFFER, g);
    glFramebufferPixelLocalStorageRestoreANGLE();
    q.bind();
    q.drawBoxes({{{FULLSCREEN}}});
    glFramebufferPixelLocalStorageInterruptANGLE();

    glBindFramebuffer(GL_FRAMEBUFFER, f);
    glFramebufferPixelLocalStorageRestoreANGLE();
    p.bind();
    p.drawBoxes({{{FULLSCREEN}, {0, 0, 1, 0}}});
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

    q.bind();
    glBindFramebuffer(GL_FRAMEBUFFER, g);
    glFramebufferPixelLocalStorageRestoreANGLE();
    q.drawBoxes({{{FULLSCREEN}}});
    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE}));

    attachTexture2DToScratchFBO(t);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor(255, 0, 255, 255));
    ASSERT_GL_NO_ERROR();

    attachTexture2DToScratchFBO(u0);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0x0a0c0b0a, 0, 0, 1));

    attachTexture2DToScratchFBO(u1);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0x0b0a0c0b, 0, 0, 1));

    attachTexture2DToScratchFBO(u2);
    EXPECT_PIXEL_RECT32UI_EQ(0, 0, W, H, GLColor32UI(0x0c0b0a0c, 0, 0, 1));

    ASSERT_GL_NO_ERROR();
}

// Check that deleting attachments and PLS bindings on the current draw framebuffer implicitly
// deactivates pixel local storage.
TEST_P(PixelLocalStorageTest, DeleteAttachments_draw_framebuffer)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    GLuint depthStencil;
    glGenRenderbuffers(1, &depthStencil);
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencil);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthStencil);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthStencil);

    PLSTestTexture colorTex(GL_RGBA8);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTex);

    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER,
                              colorRenderbuffer);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, colorRenderbuffer);

    PLSTestTexture pls0(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, pls0);

    PLSTestTexture pls1(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, pls1);

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
    ASSERT_GL_NO_ERROR();

    // Deleting the depth/stencil will implicitly end pixel local storage.
    glDeleteRenderbuffers(1, &depthStencil);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 0);

    // Deleting the color texture will implicitly end pixel local storage.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    colorTex.reset();
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0);

    // Deleting the color renderbuffer will implicitly end pixel local storage.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    glDeleteRenderbuffers(1, &colorRenderbuffer);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0);

    // Pixel local storage can't be ended because it's already deactivated.
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);

    // Deleting an inactive PLS plane will implicitly end pixel local storage.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    pls1.reset();
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);

    // Deleting an active PLS plane will implicitly end pixel local storage.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    pls0.reset();
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);

    // Deleting the textures deinitialized the planes.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}

// Check that deleting attachments and PLS bindings on the current read framebuffer does *not*
// deactivate pixel local storage.
TEST_P(PixelLocalStorageTest, DeleteAttachments_read_framebuffer)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Deleting attachments on the read framebuffer does not turn off PLS.
    GLFramebuffer readFBO;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);

    GLuint depthStencil;
    glGenRenderbuffers(1, &depthStencil);
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, W, H);
    glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencil);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthStencil);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthStencil);

    PLSTestTexture colorTex(GL_RGBA8);
    colorTex.reset(GL_RGBA8);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTex);

    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
    glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER,
                              colorRenderbuffer);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
                                       colorRenderbuffer);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFBO);

    PLSTestTexture inactivePLS0(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(0, inactivePLS0, 0, 0, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, inactivePLS0);

    PLSTestTexture inactivePLS1(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(1, inactivePLS1, 0, 0, GL_NONE);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, inactivePLS1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    PLSTestTexture activePLS(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(0, activePLS, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

    glDeleteRenderbuffers(1, &depthStencil);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 0);
    colorTex.reset();
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0);

    glDeleteRenderbuffers(1, &colorRenderbuffer);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0);
    inactivePLS0.reset();
    inactivePLS1.reset();

    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_NO_ERROR();

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFBO);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
}

// Check that glBeginPixelLocalStorageANGLE() is banned while tiled rendering is
// active, and that glStartTilingQCOM() implicitly disables PLS.
TEST_P(PixelLocalStorageTest, TiledRenderingInteractions)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_QCOM_tiled_rendering"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    PLSTestTexture color0(GL_RGBA8, 100, 100);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color0, 0);

    glStartTilingQCOM(8, 8, 8, 8, GL_COLOR_BUFFER_BIT0_QCOM);
    ASSERT_GL_NO_ERROR();

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
    ASSERT_GL_NO_ERROR();

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

    // glStartTilingQCOM implicitly disables PLS.
    glStartTilingQCOM(8, 8, 8, 8, GL_COLOR_BUFFER_BIT0_QCOM);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);

    glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM);
}

// Regression test for the Metal backend not resetting the MTLRenderPassDesc defaultWidth/Height
// when switching between PLS and no PLS. Checks the state leak that happens when switching FBOs.
TEST_P(PixelLocalStorageTest, DefaultRPDescSizeLeak_BetweenFBOs)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, color + pixelLocalLoadANGLE(pls));
        })");

    // Render to a large-sized PLS.
    {
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);

        PLSTestTexture texture(GL_RGBA8, 8192, 8192);
        glFramebufferTexturePixelLocalStorageANGLE(0, texture, 0, 0, GL_NONE);

        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        mProgram.drawBoxes({{FULLSCREEN}});
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    }

    // Render to a small texture. If the state leaks, the Metal debug device complains that the
    // defaultWidth/Height is larger than the attachment size.
    {
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);

        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    }
}

// Regression test for the Metal backend not resetting the MTLRenderPassDesc defaultWidth/Height
// when switching between PLS and no PLS. Checks the state leak that happens when reusing the same
// FBO.
TEST_P(PixelLocalStorageTest, DefaultRPDescSizeLeak_SameFBO)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, color + pixelLocalLoadANGLE(pls));
        })");

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Render to a large-sized PLS.
    {

        PLSTestTexture texture(GL_RGBA8, 8192, 8192);
        glFramebufferTexturePixelLocalStorageANGLE(0, texture, 0, 0, GL_NONE);

        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        mProgram.drawBoxes({{FULLSCREEN}});
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));

        glFramebufferTexturePixelLocalStorageANGLE(0, 0, 0, 0, GL_NONE);
    }

    // Render to a small texture. If the state leaks, the Metal debug device complains that the
    // defaultWidth/Height is larger than the attachment size.
    {
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    }
}

// Test potential UAF when delete a texture while attached to FBO and used as input attachment.
TEST_P(PixelLocalStorageTest, DeleteTextureUsedAsInputAttachment)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    const char kVSSource[] = R"( #version 300 es
        void main() {
          vec2 p = vec2(float((gl_VertexID & 1) * 2 - 1),
                        float((gl_VertexID & 2) - 1));
          gl_Position = vec4(p, 0.0, 1.0);
        })";

    const char kFSSource[] = R"(#version 300 es
        #extension GL_ANGLE_shader_pixel_local_storage : require
        precision mediump float;
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane0;
        layout(location=0) out lowp vec4 o0;
        layout(location=1) out lowp vec4 o1;
        void main() {
          vec4 v = pixelLocalLoadANGLE(plane0);
          pixelLocalStoreANGLE(plane0, v + vec4(0.1));
          o0 = vec4(1.0, 0.0, 0.0, 1.0);
          o1 = vec4(0.0, 1.0, 0.0, 1.0);
        })";

    ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
    glUseProgram(program);

    const int kWdith = 16, kHeight = 16;
    glViewport(0, 0, kWdith, kHeight);
    glDisable(GL_DITHER);

    // ---- Textures -----------------------------------------------------------
    PLSTestTexture tc0a(GL_RGBA8, kWdith, kHeight);
    PLSTestTexture tc0b(GL_RGBA8, kWdith, kHeight);
    PLSTestTexture tc1(GL_RGBA8, kWdith, kHeight);
    PLSTestTexture tpls(GL_RGBA8, kWdith, kHeight);

    // ---- FBO setup ----------------------------------------------------------
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tc0a, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tc1, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glFramebufferTexturePixelLocalStorageANGLE(0, tpls, 0, 0, GL_NONE);
    GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
    glDrawBuffers(2, drawBuffers);

    // ---- Draw 1: populate input-attachment descriptors for {0, 1, plsSlot} --
    GLenum loadOpZero = GL_LOAD_OP_ZERO_ANGLE;
    glBeginPixelLocalStorageANGLE(1, &loadOpZero);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    GLenum storeOpStore = GL_STORE_OP_STORE_ANGLE;
    glEndPixelLocalStorageANGLE(1, &storeOpStore);

    // ---- Detach + delete the victim, then force ANGLE finishImpl ------------
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
    tc1.reset();

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor::red);

    // ---- Replace COLOR_ATTACHMENT0 to force a descriptor-set cache miss -----
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tc0b, 0);
    GLenum singleDrawBuffer[] = {GL_COLOR_ATTACHMENT0};
    glDrawBuffers(1, singleDrawBuffer);

    // ---- Draw 2: stale slot for color1 is emitted to vkUpdateDescriptorSets -
    GLenum loadOpLoad = GL_LOAD_OP_LOAD_ANGLE;
    glBeginPixelLocalStorageANGLE(1, &loadOpLoad);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glEndPixelLocalStorageANGLE(1, &storeOpStore);

    // Force render-pass end + flushDescriptorSetUpdates
    EXPECT_PIXEL_RECT_EQ(0, 0, 1, 1, GLColor::red);
}

// Checks that draw commands validate current PLS state against the shader's PLS uniforms.
class DrawCommandValidationTest
{
  public:
    DrawCommandValidationTest()
    {
        mHasDebugKHR = EnsureGLExtensionEnabled("GL_KHR_debug");
        if (mHasDebugKHR)
        {
            glDebugMessageControlKHR(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR,
                                     GL_DEBUG_SEVERITY_HIGH, 0, NULL, GL_TRUE);
            glDebugMessageCallbackKHR(&ErrorMessageCallback, this);
            glEnable(GL_DEBUG_OUTPUT);
            glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        }
    }

    void run()
    {
        PLSProgram p2;
        p2.compile(R"(
            layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane0;
            layout(binding=2, r32ui) uniform highp upixelLocalANGLE plane2;
            void main()
            {
                pixelLocalStoreANGLE(plane0, vec4(pixelLocalLoadANGLE(plane2).r));
            })");

        PLSProgram p;
        p.compile(R"(
            layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane0;
            layout(binding=1, rgba8) uniform lowp pixelLocalANGLE plane1;
            layout(binding=2, r32ui) uniform highp upixelLocalANGLE plane2;
            void main()
            {
                pixelLocalStoreANGLE(plane0, pixelLocalLoadANGLE(plane1) *
                                             vec4(pixelLocalLoadANGLE(plane2).r));
            })");

        ForAllDrawCalls([this]() {
            // INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
            // pixel local uniform bound to an inactive pixel local storage plane.
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Draw program references pixel local storage plane(s) that are not currently "
                "active.");
        });

        PLSTestTexture tex0(GL_RGBA8);
        PLSTestTexture tex1(GL_RGBA8);
        PLSTestTexture tex2(GL_R32UI);
        PLSTestTexture tex3(GL_R32UI);

        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(2, tex2, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(3, tex3, 0, 0, GL_NONE);
        glViewport(0, 0, W, H);
        glDrawBuffers(0, nullptr);

        glBeginPixelLocalStorageANGLE(2,
                                      GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        ForAllDrawCalls([this]() {
            // INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
            // pixel local uniform bound to an inactive pixel local storage plane.
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Draw program references pixel local storage plane(s) that are not currently "
                "active.");
        });

        glEndPixelLocalStorageANGLE(
            2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        ASSERT_GL_NO_ERROR();

        glBeginPixelLocalStorageANGLE(
            4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
                            GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        ForAllDrawCalls([this]() {
            // INVALID_OPERATION is generated if a draw is issued with a fragment shader that does
            // _not_ have a pixel local uniform bound to an _active_ pixel local storage plane
            // (i.e., the fragment shader must declare uniforms bound to every single active pixel
            // local storage plane).
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Active pixel local storage plane(s) are not referenced by the draw program.");
        });

        glEndPixelLocalStorageANGLE(
            4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                            GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        ASSERT_GL_NO_ERROR();

        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        p2.bind();
        ForAllDrawCalls([this]() {
            // INVALID_OPERATION is generated if a draw is issued with a fragment shader that does
            // _not_ have a pixel local uniform bound to an _active_ pixel local storage plane
            // (i.e., the fragment shader must declare uniforms bound to every single active pixel
            // local storage plane).
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Active pixel local storage plane(s) are not referenced by the draw program.");
        });
        p.bind();

        glEndPixelLocalStorageANGLE(
            3, GLenumArray(
                   {GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        PLSTestTexture texWrongFormat(GL_RGBA8I);
        glFramebufferTexturePixelLocalStorageANGLE(2, texWrongFormat, 0, 0, GL_NONE);

        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        ForAllDrawCalls([this]() {
            // INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
            // pixel local storage uniform whose format layout qualifier does not identically match
            // the internalformat of its associated pixel local storage plane on the current draw
            // framebuffer.
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Pixel local storage formats in the draw program do not match actively bound "
                "planes.");
        });

        glEndPixelLocalStorageANGLE(
            3, GLenumArray(
                   {GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        // Now create a noncoherency mismatch.
        glFramebufferTexturePixelLocalStorageANGLE(
            2, tex2, 0, 0, GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE);

        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        ForAllDrawCalls([this]() {
            // INVALID_OPERATION is generated if a draw is issued with a fragment shader that has a
            // pixel local storage uniform whose format layout qualifier does not identically match
            // the internalformat of its associated pixel local storage plane on the current draw
            // framebuffer.
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Pixel local storage planes with GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE "
                "do not have noncoherent bindings in the draw program.");
        });

        glEndPixelLocalStorageANGLE(
            3, GLenumArray(
                   {GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

        ASSERT_GL_NO_ERROR();
    }

  private:
    static void ForAllDrawCalls(std::function<void()> checks)
    {
        glDrawArrays(GL_TRIANGLES, 0, 3);
        checks();
        ASSERT_GL_NO_ERROR();

        glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_SHORT, nullptr);
        checks();
        ASSERT_GL_NO_ERROR();

        glDrawRangeElements(GL_TRIANGLES, 0, 0, 0, GL_UNSIGNED_SHORT, nullptr);
        checks();
        ASSERT_GL_NO_ERROR();

        glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 1);
        checks();
        ASSERT_GL_NO_ERROR();

        glDrawElementsInstanced(GL_TRIANGLES, 0, GL_UNSIGNED_SHORT, nullptr, 0);
        checks();
        ASSERT_GL_NO_ERROR();
    }

    static void GL_APIENTRY ErrorMessageCallback(GLenum source,
                                                 GLenum type,
                                                 GLuint id,
                                                 GLenum severity,
                                                 GLsizei length,
                                                 const GLchar *message,
                                                 const void *userParam)
    {
        auto test = const_cast<DrawCommandValidationTest *>(
            static_cast<const DrawCommandValidationTest *>(userParam));
        test->mErrorMessages.emplace_back(message);
    }

    bool mHasDebugKHR;
    std::vector<std::string> mErrorMessages;
};

// Check that draw commands validate current PLS state against the shader's PLS uniforms.
TEST_P(PixelLocalStorageTest, DrawCommandValidation)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    DrawCommandValidationTest().run();
}

// Check that calling the implicit-end PLS API without PLS being active works.
TEST_P(PixelLocalStorageTest, ImplicitEndWithoutPLS)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    glEndPixelLocalStorageImplicitANGLE();
    ASSERT_GL_NO_ERROR();
}

// Check that redefining textures or renderbuffers bound to the current framebuffer
// while pixel local storage is active generates GL_INVALID_OPERATION.
TEST_P(PixelLocalStorageTest, RedefineBoundAttachmentsConflict)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Case 1: Texture attachment
    {
        GLTexture texFB;
        glBindTexture(GL_TEXTURE_2D, texFB);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();

        GLTexture texNonFB;
        glBindTexture(GL_TEXTURE_2D, texNonFB);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();

        GLTexture texNonFBStorage;
        // Bind it to initialize it as a 2D texture, but don't define images yet.
        glBindTexture(GL_TEXTURE_2D, texNonFBStorage);
        ASSERT_GL_NO_ERROR();

        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texFB, 0);
        ASSERT_GL_NO_ERROR();

        // Set up PLS on plane 0 (using a different texture)
        GLTexture texPLS;
        glBindTexture(GL_TEXTURE_2D, texPLS);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
        ASSERT_GL_NO_ERROR();
        glFramebufferTexturePixelLocalStorageANGLE(0, texPLS, 0, 0, GL_NONE);
        ASSERT_GL_NO_ERROR();

        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Begin PLS
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        // Attempt to redefine texFB (bound to FB)
        glBindTexture(GL_TEXTURE_2D, texFB);

        // 1. glTexImage2D
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // 2. glTexStorage2D
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // 3. glGenerateMipmap
        glGenerateMipmap(GL_TEXTURE_2D);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Attempt to redefine texNonFB (NOT bound to FB) - should succeed
        glBindTexture(GL_TEXTURE_2D, texNonFB);

        // 1. glTexImage2D
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_NO_ERROR();

        // 2. glGenerateMipmap
        glGenerateMipmap(GL_TEXTURE_2D);
        EXPECT_GL_NO_ERROR();

        // 3. glTexStorage2D on texNonFBStorage (NOT bound to FB) - should succeed
        glBindTexture(GL_TEXTURE_2D, texNonFBStorage);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
        EXPECT_GL_NO_ERROR();

        // End PLS
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
        ASSERT_GL_NO_ERROR();
    }

    // Case 2: Renderbuffer attachment
    {
        GLRenderbuffer rboFB;
        glBindRenderbuffer(GL_RENDERBUFFER, rboFB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
        ASSERT_GL_NO_ERROR();

        GLRenderbuffer rboNonFB;
        glBindRenderbuffer(GL_RENDERBUFFER, rboNonFB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
        ASSERT_GL_NO_ERROR();

        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboFB);
        ASSERT_GL_NO_ERROR();

        // Set up PLS on plane 0
        GLTexture texPLS;
        glBindTexture(GL_TEXTURE_2D, texPLS);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, W, H);
        ASSERT_GL_NO_ERROR();
        glFramebufferTexturePixelLocalStorageANGLE(0, texPLS, 0, 0, GL_NONE);
        ASSERT_GL_NO_ERROR();

        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Begin PLS
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();

        // Attempt to redefine rboFB (bound to FB)
        glBindRenderbuffer(GL_RENDERBUFFER, rboFB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Attempt to redefine rboNonFB (NOT bound to FB) - should succeed
        glBindRenderbuffer(GL_RENDERBUFFER, rboNonFB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, W, H);
        EXPECT_GL_NO_ERROR();

        // End PLS
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
        ASSERT_GL_NO_ERROR();
    }
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTest);
#define PLATFORM(API, BACKEND) API##_##BACKEND()
#define PLS_INSTANTIATE_RENDERING_TEST_AND(TEST, API, ...)                                \
    ANGLE_INSTANTIATE_TEST(                                                               \
        TEST,                                                                             \
        PLATFORM(API, OPENGL) /* OpenGL coherent. */                                      \
            .enable(Feature::EmulatePixelLocalStorage),                                   \
        PLATFORM(API, OPENGL) /* OpenGL noncoherent. */                                   \
            .enable(Feature::EmulatePixelLocalStorage)                                    \
            .disable(Feature::SupportsFragmentShaderInterlockNV)                          \
            .disable(Feature::SupportsFragmentShaderOrderingINTEL)                        \
            .disable(Feature::SupportsFragmentShaderInterlockARB),                        \
        PLATFORM(API, OPENGLES) /* OpenGL ES coherent */                                  \
            .enable(Feature::EmulatePixelLocalStorage),                                   \
        PLATFORM(API, OPENGLES) /* Test PLS not having access to                          \
                                             glEnablei/glDisablei/glColorMaski. */        \
            .enable(Feature::EmulatePixelLocalStorage)                                    \
            .enable(Feature::DisableDrawBuffersIndexed),                                  \
        PLATFORM(API, OPENGLES) /* OpenGL ES noncoherent                                  \
                                   (EXT_shader_framebuffer_fetch_non_coherent). */        \
            .enable(Feature::EmulatePixelLocalStorage)                                    \
            .disable(Feature::SupportsShaderFramebufferFetchEXT),                         \
        PLATFORM(API, OPENGLES) /* OpenGL ES noncoherent (shader images). */              \
            .enable(Feature::EmulatePixelLocalStorage)                                    \
            .disable(Feature::SupportsShaderFramebufferFetchEXT)                          \
            .disable(Feature::SupportsShaderFramebufferFetchNonCoherentEXT),              \
        PLATFORM(API, VULKAN) /* Vulkan coherent. */                                      \
            .enable(Feature::EmulatePixelLocalStorage),                                   \
        PLATFORM(API, VULKAN) /* Vulkan noncoherent. */                                   \
            .disable(Feature::SupportsShaderFramebufferFetch)                             \
            .disable(Feature::SupportsFragmentShaderPixelInterlock)                       \
            .enable(Feature::EmulatePixelLocalStorage),                                   \
        PLATFORM(API, VULKAN_SWIFTSHADER) /* Swiftshader coherent (framebuffer fetch). */ \
            .enable(Feature::EmulatePixelLocalStorage),                                   \
        PLATFORM(API, VULKAN_SWIFTSHADER) /* Swiftshader noncoherent. */                  \
            .disable(Feature::SupportsShaderFramebufferFetch)                             \
            .disable(Feature::SupportsFragmentShaderPixelInterlock)                       \
            .enable(Feature::EmulatePixelLocalStorage),                                   \
        __VA_ARGS__)

#define PLS_INSTANTIATE_RENDERING_TEST_ES3(TEST)                                                   \
    PLS_INSTANTIATE_RENDERING_TEST_AND(                                                            \
        TEST, ES3,                                                                                 \
                                                                                                   \
        ES3_D3D11().enable(Feature::EmulatePixelLocalStorage), /* D3D coherent. */                 \
                                                                                                   \
        ES3_D3D11() /* D3D noncoherent. */                                                         \
            .enable(Feature::DisableRasterizerOrderViews)                                          \
            .enable(Feature::EmulatePixelLocalStorage),                                            \
                                                                                                   \
        ES3_METAL().enable(Feature::EmulatePixelLocalStorage), /* Metal, coherent (in tiled memory \
                                                                  on Apple Silicon).*/             \
        ES3_METAL() /* Metal, coherent via raster order groups + read_write textures.*/            \
            .enable(Feature::EmulatePixelLocalStorage)                                             \
            .enable(Feature::DisableProgrammableBlending), /* Metal, coherent, r32 packed          \
                                                              read_write texture formats.*/        \
        ES3_METAL()                                                                                \
            .enable(Feature::EmulatePixelLocalStorage)                                             \
            .enable(Feature::DisableProgrammableBlending)                                          \
            .enable(Feature::DisableRWTextureTier2Support), /* Metal, noncoherent if not on Apple  \
                                                               Silicon. (Apple GPUs don't support  \
                                                               fragment-to-fragment memory         \
                                                               barriers.)*/                        \
        ES3_METAL()                                                                                \
            .enable(Feature::EmulatePixelLocalStorage)                                             \
            .enable(Feature::DisableRasterOrderGroups))

#define PLS_INSTANTIATE_RENDERING_TEST_ES31(TEST) PLS_INSTANTIATE_RENDERING_TEST_AND(TEST, ES31)

PLS_INSTANTIATE_RENDERING_TEST_ES3(PixelLocalStorageTest);

class PixelLocalStorageTestES31 : public PixelLocalStorageTest
{};

// Check that cube map array faces work as PLS planes.
TEST_P(PixelLocalStorageTestES31, TextureCubeArrayFaces)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_texture_cube_map_array") &&
                       !EnsureGLExtensionEnabled("GL_OES_texture_cube_map_array"));

    // Invert the color of PLS plane 0 assuming opaque alpha, e.g., red <-> cyan
    mProgram.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
        void main()
        {
            pixelLocalStoreANGLE(pls, vec4(1, 1, 1, 2) - pixelLocalLoadANGLE(pls));
        })");

    std::vector<GLColor> redImg(H * W, GLColor::red);
    std::vector<GLColor> greenImg(H * W, GLColor::green);
    std::vector<GLColor> blueImg(H * W, GLColor::blue);
    std::vector<GLColor> cyanImg(H * W, GLColor::cyan);
    std::vector<GLColor> magentaImg(H * W, GLColor::magenta);
    std::vector<GLColor> yellowImg(H * W, GLColor::yellow);

    {
        // Level 2, layer 1 (layer-faces 6-11)
        GLTexture tex;
        glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
        glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 3, GL_RGBA8, W * 4, H * 4, 12);
        glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 2, 0, 0, 6, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        redImg.data());
        glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 2, 0, 0, 7, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        cyanImg.data());
        glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 2, 0, 0, 8, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        greenImg.data());
        glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 2, 0, 0, 9, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        magentaImg.data());
        glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 2, 0, 0, 10, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        blueImg.data());
        glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 2, 0, 0, 11, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        yellowImg.data());
        ASSERT_GL_NO_ERROR();
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        for (size_t i = 6; i < 12; ++i)
        {
            // Sequentially attach cube map layer-faces 6-11 as PLS plane 0 and invert them
            glFramebufferTexturePixelLocalStorageANGLE(0, tex, 2, i, GL_NONE);
            ASSERT_GL_NO_ERROR();
            glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
            mProgram.drawBoxes({{FULLSCREEN}});
            ASSERT_GL_NO_ERROR();
            glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
            ASSERT_GL_NO_ERROR();
        }

        // Check that layer-faces 6-11 have inverted colors now
        attachTextureLayerToScratchFBO(tex, 2, 6);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::cyan);
        attachTextureLayerToScratchFBO(tex, 2, 7);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);
        attachTextureLayerToScratchFBO(tex, 2, 8);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::magenta);
        attachTextureLayerToScratchFBO(tex, 2, 9);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);
        attachTextureLayerToScratchFBO(tex, 2, 10);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::yellow);
        attachTextureLayerToScratchFBO(tex, 2, 11);
        EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::blue);
        ASSERT_GL_NO_ERROR();
    }
}

// Check that early_fragment_tests are not triggered when PLS uniforms are not declared.
TEST_P(PixelLocalStorageTestES31, EarlyFragmentTests)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture tex(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    GLuint stencil;
    glGenRenderbuffers(1, &stencil);
    glBindRenderbuffer(GL_RENDERBUFFER, stencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, W, H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, stencil);
    glClearStencil(0);
    glClear(GL_STENCIL_BUFFER_BIT);

    glEnable(GL_STENCIL_TEST);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

    // Emits a fullscreen quad.
    constexpr char kFullscreenVS[] = R"(#version 310 es
    precision highp float;
    void main()
    {
        gl_Position.x = (gl_VertexID & 1) == 0 ? -1.0 : 1.0;
        gl_Position.y = (gl_VertexID & 2) == 0 ? -1.0 : 1.0;
        gl_Position.zw = vec2(0, 1);
    })";

    // Renders green to the framebuffer.
    constexpr char kDrawRed[] = R"(#version 310 es
    out mediump vec4 fragColor;
    void main()
    {
        fragColor = vec4(1, 0, 0, 1);
    })";

    ANGLE_GL_PROGRAM(drawGreen, kFullscreenVS, kDrawRed);

    // Render to stencil without PLS uniforms and with a discard. Since we discard, and since the
    // shader shouldn't enable early_fragment_tests, stencil should not be affected.
    constexpr char kNonPLSDiscard[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : enable
    void f(highp ipixelLocalANGLE pls)
    {
        // Function arguments don't trigger PLS restrictions.
        pixelLocalStoreANGLE(pls, ivec4(8));
    }
    void main()
    {
        discard;
    })";
    ANGLE_GL_PROGRAM(lateDiscard, kFullscreenVS, kNonPLSDiscard);
    glUseProgram(lateDiscard);
    glStencilFunc(GL_ALWAYS, 1, ~0u);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Clear the framebuffer to green.
    glClearColor(0, 1, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    // Render red to the framebuffer with a stencil test. This should have no effect because the
    // stencil buffer should be all zeros.
    glUseProgram(drawGreen);
    glStencilFunc(GL_NOTEQUAL, 0, ~0u);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::green);

    // Now double check that this test would have failed if the shader had enabled
    // early_fragment_tests. Render to stencil *with* early_fragment_tests and a discard. Stencil
    // should be affected this time even though we discard.
    ANGLE_GL_PROGRAM(earlyDiscard, kFullscreenVS,
                     (std::string(kNonPLSDiscard) + "layout(early_fragment_tests) in;").c_str());
    glUseProgram(earlyDiscard);
    glStencilFunc(GL_ALWAYS, 1, ~0u);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Clear the framebuffer to green.
    glClearColor(0, 1, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    // Render red to the framebuffer again. This time the stencil test should pass because the
    // stencil buffer should be all ones.
    glUseProgram(drawGreen);
    glStencilFunc(GL_NOTEQUAL, 0, ~0u);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_PIXEL_RECT_EQ(0, 0, W, H, GLColor::red);

    ASSERT_GL_NO_ERROR();
}

// Check that application-facing ES31 state is not perturbed by pixel local storage.
TEST_P(PixelLocalStorageTestES31, StateRestoration)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doStateRestorationTest();
}

// Check that draw state does not affect PLS loads and stores, particularly for
// EXT_shader_pixel_local_storage, where they are implemented as fullscreen draws.
TEST_P(PixelLocalStorageTestES31, DrawStateReset)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doDrawStateTest();
}

// Check that changing framebuffer state implicitly disable pixel local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_Framebuffer)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_Framebuffer();
}

// Check that changing framebuffer texture attachments implicitly disable pixel
// local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_TextureAttachments)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_TextureAttachments();
}

// Check that changing framebuffer renderbuffer attachments implicitly disable
// pixel local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_RenderbufferAttachments)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_RenderbufferAttachments();
}

// Check that invalidation commands implicitly disable pixel local storage.
TEST_P(PixelLocalStorageTestES31, ImplicitDisables_Invalidate)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    doImplicitDisablesTest_Invalidate();
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTestES31);
PLS_INSTANTIATE_RENDERING_TEST_ES31(PixelLocalStorageTestES31);

class PixelLocalStorageRequestableExtensionTest : public ANGLETest<>
{
  protected:
    PixelLocalStorageRequestableExtensionTest() { setExtensionsEnabled(false); }
};

static void do_implicitly_enabled_extensions_test(const char *plsExtensionToRequest)
{
    bool hasDrawBuffersIndexedOES = IsGLExtensionRequestable("GL_OES_draw_buffers_indexed");
    bool hasDrawBuffersIndexedEXT = IsGLExtensionRequestable("GL_EXT_draw_buffers_indexed");
    bool hasCoherent = IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage_coherent");

    EXPECT_TRUE(!IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
    EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
    EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
    EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    glRequestExtensionANGLE(plsExtensionToRequest);
    EXPECT_GL_NO_ERROR();

    if (hasDrawBuffersIndexedOES)
    {
        EXPECT_TRUE(IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
    }
    if (hasDrawBuffersIndexedEXT)
    {
        EXPECT_TRUE(IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
    }
    if (hasCoherent)
    {
        EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
    }
    EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
}

// Check that ANGLE_shader_pixel_local_storage implicitly enables its dependency extensions.
TEST_P(PixelLocalStorageRequestableExtensionTest, ImplicitlyEnabledExtensions)
{
    EXPECT_TRUE(IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
                                             "EGL_ANGLE_create_context_extensions_enabled"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage"));
    do_implicitly_enabled_extensions_test("GL_ANGLE_shader_pixel_local_storage");
}

// Check that ANGLE_shader_pixel_local_storage_coherent implicitly enables its
// dependency extensions.
TEST_P(PixelLocalStorageRequestableExtensionTest, ImplicitlyEnabledExtensionsCoherent)
{
    EXPECT_TRUE(IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
                                             "EGL_ANGLE_create_context_extensions_enabled"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage"));
    if (!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage_coherent"))
    {
        // Requesting GL_ANGLE_shader_pixel_local_storage_coherent should not implicitly enable any
        // other extensions if the extension itself is not supported.
        glRequestExtensionANGLE("GL_ANGLE_shader_pixel_local_storage_coherent");
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        EXPECT_TRUE(!IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"));
        EXPECT_TRUE(!IsGLExtensionEnabled("GL_EXT_draw_buffers_indexed"));
        EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
        EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    }
    else
    {
        do_implicitly_enabled_extensions_test("GL_ANGLE_shader_pixel_local_storage_coherent");
    }
}

// Check that the dependency extensions of ANGLE_shader_pixel_local_storage do not enable it.
TEST_P(PixelLocalStorageRequestableExtensionTest, ANGLEShaderPixelLocalStorageNotImplicitlyEnabled)
{
    EXPECT_TRUE(IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
                                             "EGL_ANGLE_create_context_extensions_enabled"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage"));

    EnsureGLExtensionEnabled("GL_OES_draw_buffers_indexed");
    EnsureGLExtensionEnabled("GL_EXT_draw_buffers_indexed");
    EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
    EXPECT_TRUE(!IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    if (IsGLExtensionRequestable("GL_ANGLE_shader_pixel_local_storage_coherent"))
    {
        glRequestExtensionANGLE("GL_ANGLE_shader_pixel_local_storage_coherent");
        EXPECT_GL_NO_ERROR();
        EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));
        EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    }
    else
    {
        glRequestExtensionANGLE("GL_ANGLE_shader_pixel_local_storage");
        EXPECT_GL_NO_ERROR();
        EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    }
}

PLS_INSTANTIATE_RENDERING_TEST_ES3(PixelLocalStorageRequestableExtensionTest);

class PixelLocalStorageValidationTest : public ANGLETest<>
{
  public:
    PixelLocalStorageValidationTest() { setExtensionsEnabled(false); }

  protected:
    void testSetUp() override
    {
        ASSERT_TRUE(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
        glGetIntegerv(GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE, &MAX_PIXEL_LOCAL_STORAGE_PLANES);
        glGetIntegerv(GL_MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE,
                      &MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES);
        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &MAX_COLOR_ATTACHMENTS);
        glGetIntegerv(GL_MAX_DRAW_BUFFERS, &MAX_DRAW_BUFFERS);

        mHasDebugKHR = EnsureGLExtensionEnabled("GL_KHR_debug");
        if (mHasDebugKHR)
        {
            glDebugMessageControlKHR(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR,
                                     GL_DEBUG_SEVERITY_HIGH, 0, NULL, GL_TRUE);
            glDebugMessageCallbackKHR(&ErrorMessageCallback, this);
            glEnable(GL_DEBUG_OUTPUT);
            glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        }

        // INVALID_OPERATION is generated if DITHER is enabled.
        glDisable(GL_DITHER);

        ANGLETest::testSetUp();
    }

    int isContextVersionAtLeast(int major, int minor)
    {
        return getClientMajorVersion() > major ||
               (getClientMajorVersion() == major && getClientMinorVersion() >= minor);
    }

    static void GL_APIENTRY ErrorMessageCallback(GLenum source,
                                                 GLenum type,
                                                 GLuint id,
                                                 GLenum severity,
                                                 GLsizei length,
                                                 const GLchar *message,
                                                 const void *userParam)
    {
        auto test = const_cast<PixelLocalStorageValidationTest *>(
            static_cast<const PixelLocalStorageValidationTest *>(userParam));
        test->mErrorMessages.emplace_back(message);
    }

    GLint MAX_PIXEL_LOCAL_STORAGE_PLANES                           = 0;
    GLint MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES = 0;
    GLint MAX_COLOR_ATTACHMENTS                                    = 0;
    GLint MAX_DRAW_BUFFERS                                         = 0;

    bool mHasDebugKHR;
    std::vector<std::string> mErrorMessages;
};

class PixelLocalStorageWebGLValidationTest : public PixelLocalStorageValidationTest
{
  public:
    PixelLocalStorageWebGLValidationTest() { setWebGLCompatibilityEnabled(true); }

  protected:
    void testSetUp() override { PixelLocalStorageValidationTest::testSetUp(); }
};

class ScopedEnable
{
  public:
    ScopedEnable(GLenum feature) : mFeature(feature) { glEnable(mFeature); }
    ~ScopedEnable() { glDisable(mFeature); }

  private:
    GLenum mFeature;
};

// Check that PLS state has the correct initial values.
TEST_P(PixelLocalStorageValidationTest, InitialValues)
{
    // It's valid to query GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE even when fbo 0 is bound.
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_GL_NO_ERROR();

    // Table 6.Y: Pixel Local Storage State
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
        EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
        EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
        EXPECT_PLS_INTEGER(i, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
    }
    EXPECT_GL_NO_ERROR();
}

// Check that glFramebufferMemorylessPixelLocalStorageANGLE validates as specified.
TEST_P(PixelLocalStorageValidationTest, FramebufferMemorylessPixelLocalStorageANGLE)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32UI, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_R32UI);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, GL_NONE);

    // If <internalformat> is NONE, the pixel local storage plane at index <plane> is deinitialized
    // and any internal storage is released.
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_NONE, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, GL_NONE);

    // Set back to GL_RGBA8I.
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8I, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);

    // INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
    // to DRAW_FRAMEBUFFER.
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32UI, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);

    // INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
    // in an interrupted state.
    EXPECT_GL_NO_ERROR();
    glFramebufferPixelLocalStorageInterruptANGLE();
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32UI, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);

    // INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
    glFramebufferMemorylessPixelLocalStorageANGLE(-1, GL_R32UI, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
    glFramebufferMemorylessPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_R32UI,
                                                  GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
    glFramebufferMemorylessPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_R32UI,
                                                  GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                       GL_R32UI);

    // INVALID_ENUM is generated if <internalformat> is not one of the acceptable values in Table
    // X.2, or NONE.
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA16F, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA32UI, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8_SNORM, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");

    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, GL_NONE);

    // INVALID_OPERATION is generated if <internalformat> is not color-renderable.
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Internal format is not renderable.");
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);

    // Repeat after enabling the extension
    if (EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"))
    {
        glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_R32F, GL_NONE);
        EXPECT_GL_NO_ERROR();
        EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_R32F);
    }

    // INVALID_VALUE is generated if any bit other than
    // PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE is set in <usage>.
    glFramebufferMemorylessPixelLocalStorageANGLE(
        0, GL_RGBA8, GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE << 1);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage usage.");

    ASSERT_GL_NO_ERROR();
}

// Check that glFramebufferTexturePixelLocalStorageANGLE validates as specified.
TEST_P(PixelLocalStorageValidationTest, FramebufferTexturePixelLocalStorageANGLE)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Initially, pixel local storage planes are in a deinitialized state and are unusable.
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8UI, 10, 10);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8UI);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);

    // If <backingtexture> is 0, <level> and <layer> are ignored and the pixel local storage plane
    // <plane> is deinitialized.
    glFramebufferTexturePixelLocalStorageANGLE(1, 0, 1, 2, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);

    // Set back to GL_RGBA8I.
    glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8UI);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);

    {
        // When a texture object is deleted, any pixel local storage plane to which it was bound is
        // automatically deinitialized.
        GLFramebuffer keepalive;  // Keep the underlying texture alive after deleting its ID by
                                  // binding it to a framebuffer.
        glBindFramebuffer(GL_FRAMEBUFFER, keepalive);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
        ASSERT_GL_NO_ERROR();
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        tex.reset();
        PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
        EXPECT_GL_NO_ERROR();
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
    }
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8UI, 10, 10);

    // Same as above, but with orphaning.
    glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8UI);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    {
        GLFramebuffer keepalive;  // Keep the underlying texture alive after deleting its ID by
                                  // binding it to a framebuffer.
        glBindFramebuffer(GL_FRAMEBUFFER, keepalive);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
        ASSERT_GL_NO_ERROR();
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        tex.reset();
        PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
        EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
    }
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8UI, 10, 10);

    // INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
    // to DRAW_FRAMEBUFFER.
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
    // in an interrupted state.
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(1, tex, 1, 0, GL_NONE);
    glFramebufferPixelLocalStorageInterruptANGLE();
    glFramebufferTexturePixelLocalStorageANGLE(1, 0, 0, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_PLS_INTEGER(1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);

    // INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
    glFramebufferTexturePixelLocalStorageANGLE(-1, tex, 1, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
    glFramebufferTexturePixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, tex, 1, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
    glFramebufferTexturePixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, tex, 2, 0,
                                               GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex);
    EXPECT_PLS_INTEGER(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 2);

    // INVALID_OPERATION is generated if <backingtexture> is not the name of an existing immutable
    // texture object, or zero.
    GLTexture badTex;
    glFramebufferTexturePixelLocalStorageANGLE(2, badTex, 0, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Not a valid texture object name.");
    glBindTexture(GL_TEXTURE_2D, badTex);
    glFramebufferTexturePixelLocalStorageANGLE(2, badTex, 0, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Texture is not immutable.");
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 10, 10, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(2, badTex, 0, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Texture is not immutable.");

    // INVALID_OPERATION is generated if <backingtexture> is nonzero
    // and of type TEXTURE_2D_MULTISAMPLE or TEXTURE_3D.
    GLTexture tex2DMultisample;
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex2DMultisample);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 1, GL_RGBA8, 10, 10, 1);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(0, tex2DMultisample, 0, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage texture type.");
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);

    GLTexture tex3D;
    glBindTexture(GL_TEXTURE_3D, tex3D);
    glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 5, 5, 5);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(0, tex3D, 0, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage texture type.");
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
    EXPECT_PLS_INTEGER(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);

    // INVALID_VALUE is generated if <level> < 0.
    tex.reset();
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_R32UI, 10, 10);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex, -1, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Negative level.");

    // GL_INVALID_VALUE is generated if <backingtexture> is nonzero and <level> >= the immutable
    // number of mipmap levels in <backingtexture>.
    glFramebufferTexturePixelLocalStorageANGLE(2, tex, 3, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Level is larger than texture level count.");

    // INVALID_VALUE is generated if <layer> < 0.
    glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, -1, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Negative layer.");

    // GL_INVALID_VALUE is generated if <backingtexture> is nonzero and <layer> >= the immutable
    // number of texture layers in <backingtexture>.
    glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, 1, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Layer is larger than texture depth.");

    GLTexture tex2DArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, tex2DArray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8I, 10, 10, 7);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(2, tex2DArray, 1, 7, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Layer is larger than texture depth.");
    glFramebufferTexturePixelLocalStorageANGLE(2, tex2DArray, 2, 6, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Level is larger than texture level count.");
    glFramebufferTexturePixelLocalStorageANGLE(2, tex2DArray, 1, 6, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, tex2DArray);
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 6);
    // When a texture object is deleted, any pixel local storage plane to which it was bound is
    // automatically deinitialized.
    {
        GLFramebuffer keepalive;  // Keep the underlying texture alive after deleting its ID by
                                  // binding it to a framebuffer.
        glBindFramebuffer(GL_FRAMEBUFFER, keepalive);
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex2DArray, 0, 0);
        ASSERT_GL_NO_ERROR();
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        tex2DArray.reset();
        PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
        EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_NONE);
        EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0);
        EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 0);
        EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 0);
    }

    GLTexture texCubeMap;
    glBindTexture(GL_TEXTURE_CUBE_MAP, texCubeMap);
    glTexStorage2D(GL_TEXTURE_CUBE_MAP, 2, GL_RGBA8I, 10, 10);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(2, texCubeMap, 1, 6, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Layer is larger than texture depth.");
    glFramebufferTexturePixelLocalStorageANGLE(2, texCubeMap, 2, 5, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Level is larger than texture level count.");
    glFramebufferTexturePixelLocalStorageANGLE(2, texCubeMap, 1, 5, GL_NONE);
    EXPECT_GL_NO_ERROR();
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, texCubeMap);
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1);
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE, 5);

    // INVALID_ENUM is generated if <backingtexture> is nonzero and its internalformat is not
    // one of the acceptable values in Table X.2.
    tex.reset();
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RG32F, 10, 10);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage internal format.");

    // INVALID_OPERATION is generated if <backingtexture> is nonzero and its internalformat is not
    // color-renderable.
    tex.reset();
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_R32F, 10, 10);
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, 0, GL_NONE);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Internal format is not renderable.");
    EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_RGBA8I);

    // Repeat after enabling the extension
    if (EnsureGLExtensionEnabled("GL_EXT_color_buffer_float"))
    {
        glFramebufferTexturePixelLocalStorageANGLE(2, tex, 1, 0, GL_NONE);
        EXPECT_GL_NO_ERROR();
        EXPECT_PLS_INTEGER(2, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE, GL_R32F);
    }

    // INVALID_VALUE is generated if any bit other than
    // PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE is set in <usage>.
    glFramebufferTexturePixelLocalStorageANGLE(
        1, tex, 1, 0, GL_PIXEL_LOCAL_USAGE_ALWAYS_NONCOHERENT_BIT_ANGLE << 1);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage usage.");

    ASSERT_GL_NO_ERROR();
}

// Check that FramebufferPixelLocalClearValue{f,i,ui}vANGLE validate as specified.
TEST_P(PixelLocalStorageValidationTest, glFramebufferPixelLocalClearValuesANGLE)
{
    // INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
    // to DRAW_FRAMEBUFFER.
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");

    glFramebufferPixelLocalClearValueivANGLE(1, ClearI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");

    glFramebufferPixelLocalClearValueuivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1,
                                              ClearUI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");

    ASSERT_GL_NO_ERROR();

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
    // in an interrupted state.
    glFramebufferPixelLocalStorageInterruptANGLE();

    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 1, 1, 1));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");

    glFramebufferPixelLocalClearValueivANGLE(1, ClearI(1, 1, 1, 1));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");

    glFramebufferPixelLocalClearValueuivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1,
                                              ClearUI(1, 1, 1, 1));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");

    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_FLOAT(0, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_INT(1, ({0, 0, 0, 0}));
    EXPECT_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1, ({0, 0, 0, 0}));
    ASSERT_GL_NO_ERROR();

    // INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
    glFramebufferPixelLocalClearValuefvANGLE(-1, ClearF(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");

    glFramebufferPixelLocalClearValueivANGLE(-1, ClearI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");

    glFramebufferPixelLocalClearValueuivANGLE(-1, ClearUI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");

    glFramebufferPixelLocalClearValuefvANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, ClearF(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    glFramebufferPixelLocalClearValueivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, ClearI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    glFramebufferPixelLocalClearValueuivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, ClearUI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    ASSERT_GL_NO_ERROR();

    // INVALID_OPERATION is generated if PIXEL_LOCAL_STORAGE_ACTIVE_ANGLE is TRUE.
    PLSTestTexture tex(GL_R32UI);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    ASSERT_GL_NO_ERROR();

    glFramebufferPixelLocalClearValuefvANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 1,
                                             ClearF(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");

    glFramebufferPixelLocalClearValuefvANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES - 2,
                                             ClearF(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");

    glFramebufferPixelLocalClearValueuivANGLE(0, ClearUI(0, 0, 0, 0));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");

    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    ASSERT_GL_NO_ERROR();
}

#define EXPECT_BANNED(cmd, msg)                   \
    cmd;                                          \
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION); \
    EXPECT_GL_SINGLE_ERROR_MSG(msg)

#define EXPECT_BANNED_DEFAULT_MSG(cmd) \
    EXPECT_BANNED(cmd, "Operation not permitted while pixel local storage is active.")

static std::vector<char> FormatBannedCapMsg(GLenum cap)
{
    constexpr char format[] =
        "Cap 0x%04X cannot be enabled or disabled while pixel local storage is active.";
    std::vector<char> msg(ANGLE_UNSAFE_TODO(std::snprintf(nullptr, 0, format, cap) + 1));
    ANGLE_UNSAFE_TODO(std::snprintf(msg.data(), msg.size(), format, cap));
    return msg;
}

#define EXPECT_ALLOWED_CAP(cap) \
    {                           \
        glEnable(cap);          \
        glDisable(cap);         \
        glIsEnabled(cap);       \
        EXPECT_GL_NO_ERROR();   \
    }

#define EXPECT_BANNED_CAP(cap)                           \
    {                                                    \
        std::vector<char> msg = FormatBannedCapMsg(cap); \
        EXPECT_BANNED(glEnable(cap), msg.data());        \
        EXPECT_BANNED(glDisable(cap), msg.data());       \
        glIsEnabled(cap);                                \
        EXPECT_GL_NO_ERROR();                            \
    }

#define EXPECT_BANNED_CAP_INDEXED(cap)                    \
    {                                                     \
        std::vector<char> msg = FormatBannedCapMsg(cap);  \
        EXPECT_BANNED(glEnableiOES(cap, 0), msg.data());  \
        EXPECT_BANNED(glDisableiOES(cap, 0), msg.data()); \
        EXPECT_GL_NO_ERROR();                             \
    }

// Check that glBeginPixelLocalStorageANGLE validates non-PLS context state as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_context_state)
{
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
    // to DRAW_FRAMEBUFFER.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
    // in an interrupted state.
    glFramebufferPixelLocalStorageInterruptANGLE();
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
    glFramebufferPixelLocalStorageRestoreANGLE();
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    ASSERT_GL_NO_ERROR();

    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

    // INVALID_OPERATION is generated if PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE is nonzero.
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Operation not permitted while pixel local storage is active.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_NO_ERROR();
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // INVALID_OPERATION is generated if the value of SAMPLE_BUFFERS is 1 (i.e., if rendering to a
    // multisampled framebuffer).
    {
        GLRenderbuffer msaa;
        glBindRenderbuffer(GL_RENDERBUFFER, msaa);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 2, GL_RGBA8, 100, 100);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaa);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to begin pixel local storage with a multisampled framebuffer.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }

    // INVALID_OPERATION is generated if DITHER is enabled.
    {
        ScopedEnable scopedEnable(GL_DITHER);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to begin pixel local storage with GL_DITHER enabled.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }
}

// Check that glBeginPixelLocalStorageANGLE() is banned while transform feedback
// is active, and that glBeginTransformFeedback() implicitly disables PLS.
TEST_P(PixelLocalStorageValidationTest, TransformFeedbackInteractions)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    // INVALID_OPERATION is generated if TRANSFORM_FEEDBACK_ACTIVE is true.
    constexpr char kFS[] = R"(#version 300 es
        out mediump vec4 color;
        void main()
        {
            color = vec4(0.6, 0.0, 0.0, 1.0);
        })";
    std::vector<std::string> xfVaryings;
    xfVaryings.push_back("gl_Position");
    ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(xfProgram, essl3_shaders::vs::Simple(), kFS, xfVaryings,
                                        GL_INTERLEAVED_ATTRIBS);
    glUseProgram(xfProgram);
    GLuint xfBuffer;
    glGenBuffers(1, &xfBuffer);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 1024, nullptr, GL_STATIC_DRAW);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfBuffer);
    glBeginTransformFeedback(GL_TRIANGLES);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_INTEGER(GL_TRANSFORM_FEEDBACK_ACTIVE, 1);

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Attempted to begin pixel local storage with transform feedback active.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glEndTransformFeedback();

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

    // glBeginTransformFeedback implicitly disables PLS.
    glBeginTransformFeedback(GL_TRIANGLES);
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_TRANSFORM_FEEDBACK_ACTIVE, 1);
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    glEndTransformFeedback();
    EXPECT_GL_NO_ERROR();

    glUseProgram(0);
    EXPECT_GL_NO_ERROR();
}

// Check that EXT_blend_func_extended is banned when PLS is active.
TEST_P(PixelLocalStorageValidationTest, PLSActive_bans_blend_func_extended)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    // INVALID_OPERATION is generated if BLEND_DST_ALPHA, BLEND_DST_RGB, BLEND_SRC_ALPHA, or
    // BLEND_SRC_RGB, for any draw buffer, is a blend function requiring the secondary color input,
    // as specified in EXT_blend_func_extended (SRC1_COLOR_EXT, ONE_MINUS_SRC1_COLOR_EXT,
    // SRC1_ALPHA_EXT, ONE_MINUS_SRC1_ALPHA_EXT).
    for (auto blendFunc : {GL_SRC1_COLOR_EXT, GL_ONE_MINUS_SRC1_COLOR_EXT, GL_SRC1_ALPHA_EXT,
                           GL_ONE_MINUS_SRC1_ALPHA_EXT})
    {
        glBlendFunc(blendFunc, GL_ONE);
        ASSERT_GL_NO_ERROR();
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to begin pixel local storage with a blend function requiring the secondary "
            "color input.");
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        glBlendFunc(GL_ONE, GL_ZERO);

        if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
        {
            glBlendFunciOES(MAX_DRAW_BUFFERS - 1, GL_ZERO, blendFunc);
            ASSERT_GL_NO_ERROR();
            glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Attempted to begin pixel local storage with a blend function requiring the "
                "secondary color input.");
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
            glBlendFunc(GL_ONE, GL_ZERO);
        }

        glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, blendFunc);
        ASSERT_GL_NO_ERROR();
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to begin pixel local storage with a blend function requiring the secondary "
            "color input.");
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        glBlendFunc(GL_ONE, GL_ZERO);

        if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
        {
            glBlendFuncSeparateiOES(MAX_DRAW_BUFFERS - 1, blendFunc, GL_ONE, GL_ONE, GL_ONE);
            ASSERT_GL_NO_ERROR();
            glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
            EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
            EXPECT_GL_SINGLE_ERROR_MSG(
                "Attempted to begin pixel local storage with a blend function requiring the "
                "secondary color input.");
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
            glBlendFunc(GL_ONE, GL_ZERO);
        }

        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        ASSERT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

        // INVALID_OPERATION is generated by BlendFunc*() if <srcRGB>, <dstRGB>,
        // <srcAlpha>, or <dstAlpha> is a blend function requiring the secondary
        // color input, as specified in EXT_blend_func_extended (SRC1_COLOR_EXT,
        // ONE_MINUS_SRC1_COLOR_EXT, SRC1_ALPHA_EXT, ONE_MINUS_SRC1_ALPHA_EXT).
        constexpr char kErrMsg[] =
            "Blend functions requiring the secondary color input are not supported when pixel "
            "local storage is active.";

        glBlendFunc(GL_ONE, blendFunc);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);

        glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, blendFunc);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);

        if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
        {
            for (GLint i : {0, MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
                            MAX_DRAW_BUFFERS - 1})
            {
                glBlendFunciOES(i, blendFunc, GL_ZERO);
                EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);

                glBlendFuncSeparateiOES(i, GL_ONE, GL_ONE, GL_ONE, blendFunc);
                EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                EXPECT_GL_SINGLE_ERROR_MSG(kErrMsg);
            }
        }

        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
        EXPECT_GL_NO_ERROR();
    }

    // GL_SRC_ALPHA_SATURATE_EXT is ok.
    glBlendFuncSeparate(GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT,
                        GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);
    glBlendFuncSeparate(GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT,
                        GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT);
    EXPECT_GL_NO_ERROR();
    if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
    {
        for (GLint i = 0; i < MAX_DRAW_BUFFERS; ++i)
        {
            glBlendFuncSeparateiOES(i, GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT,
                                    GL_SRC_ALPHA_SATURATE_EXT, GL_SRC_ALPHA_SATURATE_EXT);
            EXPECT_GL_NO_ERROR();
        }
    }
    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_NO_ERROR();
}

// Check that KHR_blend_equation_advanced is banned when PLS is active.
TEST_P(PixelLocalStorageValidationTest, PLSActive_bans_blend_equation_advanced)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_blend_equation_advanced"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    // INVALID_OPERATION is generated if BLEND_EQUATION_RGB and/or BLEND_EQUATION_ALPHA are one of
    // the advanced blend equations defined in KHR_blend_equation_advanced.
    for (auto blendEquation : {
             GL_MULTIPLY_KHR,
             GL_SCREEN_KHR,
             GL_OVERLAY_KHR,
             GL_DARKEN_KHR,
             GL_LIGHTEN_KHR,
             GL_COLORDODGE_KHR,
             GL_COLORBURN_KHR,
             GL_HARDLIGHT_KHR,
             GL_SOFTLIGHT_KHR,
             GL_DIFFERENCE_KHR,
             GL_EXCLUSION_KHR,
             GL_HSL_HUE_KHR,
             GL_HSL_SATURATION_KHR,
             GL_HSL_COLOR_KHR,
             GL_HSL_LUMINOSITY_KHR,
         })
    {
        glBlendEquation(blendEquation);
        ASSERT_GL_NO_ERROR();

        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to begin pixel local storage with an advanced blend equation enabled.");
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        glBlendEquation(GL_FUNC_ADD);
        if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
        {
            glBlendEquationiOES(0, blendEquation);
        }
        else
        {
            glBlendEquation(blendEquation);
        }
        ASSERT_GL_NO_ERROR();
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to begin pixel local storage with an advanced blend equation enabled.");

        if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
        {
            glBlendEquationiOES(0, GL_FUNC_ADD);
        }
        else
        {
            glBlendEquation(GL_FUNC_ADD);
        }
        ASSERT_GL_NO_ERROR();

        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

        // INVALID_OPERATION is generated by BlendEquation*() if <mode> is one of the advanced
        // blend equations defined in KHR_blend_equation_advanced.
        glBlendEquation(blendEquation);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Advanced blend equations are not supported when pixel local storage is "
            "active.");

        if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
        {
            for (GLint i : {0,

                            MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
                            MAX_DRAW_BUFFERS - 1})
            {
                glBlendEquationiOES(i, blendEquation);
                EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                EXPECT_GL_SINGLE_ERROR_MSG(
                    "Advanced blend equations are not supported when pixel local storage is "
                    "active.");
            }
        }

        EXPECT_GL_INTEGER(GL_BLEND_EQUATION_RGB, GL_FUNC_ADD);
        EXPECT_GL_INTEGER(GL_BLEND_EQUATION_ALPHA, GL_FUNC_ADD);
        ASSERT_GL_NO_ERROR();

        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
        EXPECT_GL_NO_ERROR();
    }
}

// Check that glBeginPixelLocalStorageANGLE validates the draw framebuffer's state as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_framebuffer_state)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    // INVALID_VALUE is generated if <n> < 1 or <n> > MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
    glBeginPixelLocalStorageANGLE(0, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Planes must be greater than 0.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glBeginPixelLocalStorageANGLE(
        MAX_PIXEL_LOCAL_STORAGE_PLANES + 1,
        std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES + 1, GL_DONT_CARE).data());
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Planes must be less than or equal to GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // INVALID_FRAMEBUFFER_OPERATION is generated if the draw framebuffer has an image attached to
    // any color attachment point on or after:
    //
    //   COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE - <n>
    //
    int maxColorAttachmentsWithMaxPLSPlanes =
        MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - MAX_PIXEL_LOCAL_STORAGE_PLANES;
    for (int reservedAttachment : {maxColorAttachmentsWithMaxPLSPlanes, MAX_DRAW_BUFFERS - 1})
    {
        PLSTestTexture tmp(GL_RGBA8, 100, 100);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + reservedAttachment,
                               GL_TEXTURE_2D, tmp, 0);
        glBeginPixelLocalStorageANGLE(
            MAX_PIXEL_LOCAL_STORAGE_PLANES,
            std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_DONT_CARE).data());
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Framebuffer cannot have images attached to color attachment points on or after "
            "COLOR_ATTACHMENT0 + "
            "MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_ANGLE - <n>.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }
}

// Check that glBeginPixelLocalStorageANGLE validates its loadops as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_loadops)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    // INVALID_VALUE is generated if <loadops> is NULL.
    glBeginPixelLocalStorageANGLE(1, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("<loadops> cannot be null.");

    // INVALID_ENUM is generated if <loadops>[0..<n>-1] is not one of the Load Operations enumerated
    // in Table X.1.
    {
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_REPLACE}));

        EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
        EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Load Operation: 0x1E01.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        for (int i = 1; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
        {
            glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
        }
        std::vector<GLenum> loadops(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_DONT_CARE);
        loadops.back() = GL_SCISSOR_BOX;
        glBeginPixelLocalStorageANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES, loadops.data());
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
        EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Load Operation: 0x0C10.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        for (int i = 1; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
        {
            glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_NONE, GL_NONE);
        }
    }

    // INVALID_OPERATION is generated if <loadops>[0..<n>-1] is not DISABLE_ANGLE, and the pixel
    // local storage plane at that same index is is in a deinitialized state.
    {
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_NO_ERROR();
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
        EXPECT_GL_NO_ERROR();

        glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to enable a pixel local storage plane that is in a deinitialized state.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        // If <backingtexture> is 0, <level> and <layer> are ignored and the pixel local storage
        // plane <plane> is deinitialized.
        glFramebufferTexturePixelLocalStorageANGLE(0, 0, -1, 999, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to enable a pixel local storage plane that is in a deinitialized state.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
        EXPECT_GL_NO_ERROR();
        glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
        EXPECT_GL_NO_ERROR();

        // If <internalformat> is NONE, the pixel local storage plane at index <plane> is
        // deinitialized and any internal storage is released.
        glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_NONE, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to enable a pixel local storage plane that is in a deinitialized state.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 100, 100);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    // INVALID_OPERATION is generated if <loadops>[0..<n>-1] is LOAD_OP_LOAD_ANGLE and
    // the pixel local storage plane at that same index is memoryless.
    glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8, GL_NONE);
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_LOAD_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Load Operation GL_LOAD_OP_LOAD_ANGLE is invalid for memoryless planes.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}

// Check that glBeginPixelLocalStorageANGLE validates the pixel local storage planes as specified.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_planes)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    PLSTestTexture pls0(GL_RGBA8, 100, 100);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);

    // INVALID_OPERATION is generated if all enabled, texture-backed pixel local storage planes do
    // not have the same width and height.
    {
        PLSTestTexture pls1(GL_RGBA8, 100, 100);
        GLTexture pls2;
        glBindTexture(GL_TEXTURE_2D, pls2);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 100, 101);

        glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);
        glFramebufferTexturePixelLocalStorageANGLE(2, pls2, 0, 0, GL_NONE);

        // Disabling the mismatched size plane is fine.
        glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_DONT_CARE}));
        EXPECT_GL_NO_ERROR();
        glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));
        EXPECT_GL_NO_ERROR();

        // Enabling the mismatched size plane errors.
        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_LOAD_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG("Mismatched pixel local storage backing texture sizes.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        // Deleting a texture deinitializes the plane.
        pls2.reset();
        PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to enable a pixel local storage plane that is in a deinitialized state.");

        // Converting the mismatched size plane to memoryless also works.
        glFramebufferMemorylessPixelLocalStorageANGLE(2, GL_RGBA8, GL_NONE);
        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_NO_ERROR();
        glEndPixelLocalStorageANGLE(
            3, GLenumArray(
                   {GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
        EXPECT_GL_NO_ERROR();

        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }

    {
        // pls1 going out of scope deinitialized the PLS plane.
        PLSTestTexture newTextureMaybeRecycledID(GL_RGBA8, 1, 1);
        glBeginPixelLocalStorageANGLE(
            2, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Attempted to enable a pixel local storage plane that is in a deinitialized state.");
    }

    // Convert to memoryless.
    glFramebufferMemorylessPixelLocalStorageANGLE(1, GL_RGBA8, GL_NONE);

    // INVALID_OPERATION is generated if the draw framebuffer has other attachments, and its
    // enabled, texture-backed pixel local storage planes do not have identical dimensions with the
    // rendering area.
    {
        PLSTestTexture rt0(GL_RGBA8, 200, 100);
        PLSTestTexture rt1(GL_RGBA8, 100, 200);

        // rt0 is wider than the PLS extents.
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt0, 0);
        glBeginPixelLocalStorageANGLE(2,
                                      GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Pixel local storage backing texture dimensions not equal to the rendering area.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        // rt1 is taller than the PLS extents.
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt1, 0);
        glBeginPixelLocalStorageANGLE(2,
                                      GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Pixel local storage backing texture dimensions not equal to the rendering area.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        // The intersection of rt0 and rt1 is equal to the PLS extents.
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, rt0, 0);
        glBeginPixelLocalStorageANGLE(2,
                                      GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
        EXPECT_GL_NO_ERROR();
        glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_DONT_CARE}));

        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }

    // INVALID_OPERATION is generated if the draw framebuffer has no attachments and no enabled,
    // texture-backed pixel local storage planes.
    {
        for (int i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
        {
            glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
        }
        glBeginPixelLocalStorageANGLE(
            MAX_PIXEL_LOCAL_STORAGE_PLANES,
            std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_LOAD_OP_ZERO_ANGLE).data());
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Draw framebuffer has no attachments and no enabled, texture-backed pixel local "
            "storage planes.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

        glFramebufferMemorylessPixelLocalStorageANGLE(0, GL_RGBA8, GL_NONE);
        glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
        EXPECT_GL_SINGLE_ERROR_MSG(
            "Draw framebuffer has no attachments and no enabled, texture-backed pixel local "
            "storage planes.");
        ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    }
}

// Check that glBeginPixelLocalStorageANGLE validates GL_TEXTURE_BASE_LEVEL and
// GL_TEXTURE_MAX_LEVEL.
//
// INVALID_OPERATION is generated if, for any active backing texture, the mipmap level bound to
// pixel local storage is outside the effective base/max range of that texture.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_base_max_level)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    ASSERT_GL_NO_ERROR();

    PLSTestTexture pls0(GL_RGBA8, 256, 256, 9);
    PLSTestTexture pls1(GL_RGBA8, 256, 256, 9);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 2, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 2, 0, GL_NONE);

    GLenum dontCare[2] = {GL_DONT_CARE, GL_DONT_CARE};
    glBeginPixelLocalStorageANGLE(2, dontCare);
    glEndPixelLocalStorageANGLE(2, dontCare);
    ASSERT_GL_NO_ERROR();

    // GL_TEXTURE_BASE_LEVEL == GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: pass.
    glBindTexture(GL_TEXTURE_2D, pls0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
    glBeginPixelLocalStorageANGLE(2, dontCare);
    glEndPixelLocalStorageANGLE(2, dontCare);
    ASSERT_GL_NO_ERROR();

    // GL_TEXTURE_BASE_LEVEL > GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: fail.
    glBindTexture(GL_TEXTURE_2D, pls1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3);
    glBeginPixelLocalStorageANGLE(2, dontCare);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Mipmap level for PLS backing texture outside the effective base/max range.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // GL_TEXTURE_BASE_LEVEL > GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, but plane not enabled: pass.
    glBindTexture(GL_TEXTURE_2D, pls1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3);
    glBeginPixelLocalStorageANGLE(1, dontCare);
    glEndPixelLocalStorageANGLE(1, dontCare);
    ASSERT_GL_NO_ERROR();

    // GL_TEXTURE_BASE_LEVEL == GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: pass again.
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 3, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 3, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(2, dontCare);
    glEndPixelLocalStorageANGLE(2, dontCare);
    ASSERT_GL_NO_ERROR();

    // GL_TEXTURE_MAX_LEVEL == GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: pass.
    glBindTexture(GL_TEXTURE_2D, pls1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 3);
    glBeginPixelLocalStorageANGLE(2, dontCare);
    glEndPixelLocalStorageANGLE(2, dontCare);
    ASSERT_GL_NO_ERROR();

    // GL_TEXTURE_MAX_LEVEL < GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE: fail.
    glBindTexture(GL_TEXTURE_2D, pls0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
    glBeginPixelLocalStorageANGLE(2, dontCare);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Mipmap level for PLS backing texture outside the effective base/max range.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
}

// Test that operations performed during interrupted PLS which make PLS invalid are validated on
// resume.
TEST_P(PixelLocalStorageValidationTest, ResumeValidatesFramebufferState)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    // Small PLS backing texture (4x4) and large color attachment (WxH = 128x128).
    PLSTestTexture smallPLS(GL_RGBA8, 4, 4);
    PLSTestTexture largeAttachment(GL_RGBA8, W, H);

    std::vector<GLubyte> zeros(W * H * 4, 0);
    glBindTexture(GL_TEXTURE_2D, largeAttachment);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, zeros.data());
    ASSERT_GL_NO_ERROR();

    // FBO with NO attachments, only a PLS plane.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, smallPLS, 0, 0, GL_NONE);
    ASSERT_GL_NO_ERROR();

    // Shader that writes to both color attachment 0 and PLS plane 0.
    PLSProgram p;
    p.compile(R"(
    layout(binding=0, rgba8) uniform highp pixelLocalANGLE pls;
    layout(location=0) out vec4 fragColor;
    void main()
    {
        pixelLocalStoreANGLE(pls, vec4(0, 1, 0, 1));
        fragColor = vec4(1, 0, 0, 1);
    })");

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glViewport(0, 0, W, H);

    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_ZERO_ANGLE}));
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 1);

    glFramebufferPixelLocalStorageInterruptANGLE();
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                           largeAttachment, 0);
    ASSERT_GL_NO_ERROR();

    // Invalid because fbo attachment and PLS plane now have different sizes.
    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that state changes which are invalid for PLS are validated on restore
TEST_P(PixelLocalStorageValidationTest, RestoreValidatesSameStatesAsBegin)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    ASSERT_GL_NO_ERROR();

    PLSTestTexture pls0(GL_RGBA8, 256, 256, 9);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 2, 0, GL_NONE);

    GLenum dontCare[1] = {GL_DONT_CARE};
    glBeginPixelLocalStorageANGLE(1, dontCare);
    ASSERT_GL_NO_ERROR();

    glFramebufferPixelLocalStorageInterruptANGLE();
    EXPECT_GL_NO_ERROR();

    glEnable(GL_DITHER);
    EXPECT_GL_NO_ERROR();

    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glDisable(GL_DITHER);
    glFramebufferPixelLocalStorageRestoreANGLE();
    glEndPixelLocalStorageANGLE(1, dontCare);
    EXPECT_GL_NO_ERROR();
}

// Restoring PLS is always valid if begin has not been called.
TEST_P(PixelLocalStorageValidationTest, RestoreAlwaysValidIfNotBegun)
{
    glFramebufferPixelLocalStorageInterruptANGLE();
    EXPECT_GL_NO_ERROR();

    glEnable(GL_DITHER);
    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_NO_ERROR();
}

// Check that glBeginPixelLocalStorageANGLE validates feedback loops with GL_TEXTURE_2D as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one pixel local
// storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_tex2d)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                                  GL_COLOR_ATTACHMENT3}));
    ASSERT_GL_NO_ERROR();

    // Check for simple tex2D collisions.
    PLSTestTexture pls0(GL_RGBA8, W, H);
    PLSTestTexture pls1(GL_RGBA8, W, H);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(2, pls0, 0, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is bound to multiple active pixel local storage planes.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Now attach it to the framebuffer and check for a collision there.
    PLSTestTexture color(GL_RGBA8, W, H);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls0, 0);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is simultaneously bound to an active pixel local storage plane and "
        "attached to an enabled drawbuffer.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Turning off the draw buffer removes the collision.
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_NONE, GL_COLOR_ATTACHMENT2, GL_NONE}));
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    ASSERT_GL_NO_ERROR();
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                                  GL_COLOR_ATTACHMENT3}));

    // Double-binding the other plane also causes a collision.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls1, 0);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is simultaneously bound to an active pixel local storage plane and "
        "attached to an enabled drawbuffer.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // A PLS collision is fine as long as only one of the planes is active.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    ASSERT_GL_NO_ERROR();
}

// Check that glBeginPixelLocalStorageANGLE validates feedback loops with GL_TEXTURE_2D_ARRAY as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one active pixel
// local storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_tex2darray)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                                  GL_COLOR_ATTACHMENT3}));
    ASSERT_GL_NO_ERROR();

    // Attach different array indices of the same texture to PLS.
    GLTexture pls2darray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, pls2darray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, W, H, 4);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls2darray, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls2darray, 0, 1, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, pls2darray, 0, 2, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(3, pls2darray, 0, 3, GL_NONE);
    glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
                                                  GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();

    // Now attach the same array index twice.
    glFramebufferTexturePixelLocalStorageANGLE(2, pls2darray, 0, 3, GL_NONE);
    glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
                                                  GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is bound to multiple active pixel local storage planes.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Now attach the same array index to the framebuffer and PLS.
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, pls2darray, 0, 3);
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is simultaneously bound to an active pixel local storage plane and "
        "attached to an enabled drawbuffer.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Attach the sidelined layer to the framebuffer and use fewer PLS planes so there is no
    // longer a collision.
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, pls2darray, 0, 2);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    ASSERT_GL_NO_ERROR();
}

// Check that glBeginPixelLocalStorageANGLE validates feedback loops with GL_TEXTURE_CUBE_MAP as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one active pixel
// local storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_texcubemap)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                                  GL_COLOR_ATTACHMENT3}));
    ASSERT_GL_NO_ERROR();

    // Attach different faces of the same cube map texture to PLS.
    GLTexture plscubemap;
    glBindTexture(GL_TEXTURE_CUBE_MAP, plscubemap);
    glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, W, H);
    ASSERT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(0, plscubemap, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, plscubemap, 0, 1, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, plscubemap, 0, 2, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(3, plscubemap, 0, 3, GL_NONE);
    glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
                                                  GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(4, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE,
                                                GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();

    // Now attach the same face index twice.
    glFramebufferTexturePixelLocalStorageANGLE(2, plscubemap, 0, 3, GL_NONE);
    glBeginPixelLocalStorageANGLE(4, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE,
                                                  GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is bound to multiple active pixel local storage planes.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Now attach the same face index to the framebuffer and PLS (-Y face has index 3).
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
                           plscubemap, 0);
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is simultaneously bound to an active pixel local storage plane and "
        "attached to an enabled drawbuffer.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Attach the sidelined face to the framebuffer and use fewer PLS planes so there is no
    // longer a collision (+Y face has index 2).
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                           plscubemap, 0);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    ASSERT_GL_NO_ERROR();
}

// Check that glBeginPixelLocalStorageANGLE validates feedback loops with mipmap levels as
// specified:
//
// INVALID_OPERATION is generated if a single texture slice is bound to more than one active pixel
// local storage plane.
//
// INVALID_OPERATION is generated if a single texture slice is simultaneously bound to an active
// pixel local storage plane and attached to an enabled drawbuffer.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_pls_collisions_mipmap)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glDrawBuffers(4, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                                  GL_COLOR_ATTACHMENT3}));
    ASSERT_GL_NO_ERROR();

    // Check collisions with mipmaps.
    GLTexture pls8;
    glBindTexture(GL_TEXTURE_2D, pls8);
    glTexStorage2D(GL_TEXTURE_2D, 4, GL_RGBA8, 8, 8);
    GLTexture pls16;
    glBindTexture(GL_TEXTURE_2D, pls16);
    glTexStorage2D(GL_TEXTURE_2D, 5, GL_RGBA8, 16, 16);
    ASSERT_GL_NO_ERROR();
    glFramebufferTexturePixelLocalStorageANGLE(0, pls8, 2, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls16, 3, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_NO_ERROR();

    // Can't have the same mipmap level attached to different PLS planes.
    glFramebufferTexturePixelLocalStorageANGLE(2, pls16, 3, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is bound to multiple active pixel local storage planes.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Can't attach different levels to PLS because they're different sizes.
    glFramebufferTexturePixelLocalStorageANGLE(2, pls16, 2, 0, GL_NONE);
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Mismatched pixel local storage backing texture sizes.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Can't have the same mipmap level attached to PLS and the framebuffer.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls16, 3);
    glBeginPixelLocalStorageANGLE(
        2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "A single texture slice is simultaneously bound to an active pixel local storage plane and "
        "attached to an enabled drawbuffer.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Can't attach different levels to the framebuffer because they're different sizes.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, pls16, 2);
    glBeginPixelLocalStorageANGLE(
        2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Pixel local storage backing texture dimensions not equal to the rendering area.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    // Removing the attachment causes PLS to pass again.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, 0, 0);
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_NO_ERROR();
    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    ASSERT_GL_NO_ERROR();
}

// Check that glBeginPixelLocalStorageANGLE validates the Mac/AMD constraint as specified:
//
// INVALID_OPERATION is generated if color attachment zero is present, but draw buffer zero is
// disabled.
TEST_P(PixelLocalStorageValidationTest, BeginPixelLocalStorageANGLE_color_attachment_0_constraint)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    PLSTestTexture pls(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls, 0, 0, GL_NONE);
    glFramebufferPixelLocalClearValuefvANGLE(0, ClearF(1, 0, 0, 1));

    GLTexture attachment0;
    glBindTexture(GL_TEXTURE_2D_ARRAY, attachment0);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, W * 2, H * 2, 3);

    // Use PLS with:
    //   1) GL_COLOR_ATTACHMENT0 attached.
    //   2) drawbuffer 0 disabled.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, attachment0, 1, 2);
    glDrawBuffers(1, GLenumArray({GL_NONE}));
    glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Color attachment zero is present, but draw buffer zero is disabled.");

    // Ensure PLS didn't change the COLOR_ATTACHMENT0 binding.
    EXPECT_FRAMEBUFFER_ATTACHMENT_TYPE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE);
    EXPECT_FRAMEBUFFER_ATTACHMENT_NAME(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, attachment0);
    EXPECT_FRAMEBUFFER_ATTACHMENT_LEVEL(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 1);
    EXPECT_FRAMEBUFFER_ATTACHMENT_LAYER(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 2);

    // INVALID_OPERATION is generated if a draw is issued with a fragment shader that declares index
    // zero output, but color attachment zero is not present or has an incompatible numeric type.
    PLSProgram programImplicitLocation0;
    programImplicitLocation0.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
        out lowp vec4 out0;
        void main() { out0 = vec4(0); })");

    PLSProgram programExplicitLocation0;
    programExplicitLocation0.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
        layout(location=0) out lowp vec4 out0;
        void main() { out0 = vec4(0); })");

    PLSProgram programMultipleOutputs;
    programMultipleOutputs.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
        layout(location=1) out lowp vec4 out1;
        layout(location=0) out lowp vec4 out0;
        void main()
        {
            out0 = vec4(0);
            out1 = vec4(1);
        })");

    PLSProgram programNoLocation0;
    programNoLocation0.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE tex;
        layout(location=1) out lowp vec4 out1;
        void main() { out1 = vec4(1); })");

    PLSTestTexture texIncompatible(GL_RGBA8UI);
    for (bool drawBuffer0Enabled : {false, true})
    {
        for (bool hasAttachment0 : {false, true})
        {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   hasAttachment0 ? texIncompatible.id() : 0, 0);
            glDrawBuffers(drawBuffer0Enabled ? 1 : 0, GLenumArray({GL_COLOR_ATTACHMENT0}));

            {
                programImplicitLocation0.bind();
                glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
                if (hasAttachment0 && !drawBuffer0Enabled)
                {
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Color attachment zero is present, but draw buffer zero is disabled.");
                }
                else
                {
                    EXPECT_GL_NO_ERROR();
                    programImplicitLocation0.drawBoxes({{{FULLSCREEN}}});
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Fragment shader declares index zero output, but color attachment zero is "
                        "not "
                        "present or of incompatible numeric type.");
                    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
                }
                ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
                EXPECT_GL_NO_ERROR();
            }

            {
                programExplicitLocation0.bind();
                glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
                if (hasAttachment0 && !drawBuffer0Enabled)
                {
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Color attachment zero is present, but draw buffer zero is disabled.");
                }
                else
                {
                    EXPECT_GL_NO_ERROR();
                    programExplicitLocation0.drawBoxes({{{FULLSCREEN}}});
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Fragment shader declares index zero output, but color attachment zero is "
                        "not "
                        "present or of incompatible numeric type.");
                    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
                }
                ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
                EXPECT_GL_NO_ERROR();
            }

            {
                programMultipleOutputs.bind();
                glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
                if (hasAttachment0 && !drawBuffer0Enabled)
                {
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Color attachment zero is present, but draw buffer zero is disabled.");
                }
                else
                {
                    EXPECT_GL_NO_ERROR();
                    programMultipleOutputs.drawBoxes({{{FULLSCREEN}}});
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Fragment shader declares index zero output, but color attachment zero is "
                        "not "
                        "present or of incompatible numeric type.");
                    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
                }
                ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
                EXPECT_GL_NO_ERROR();
            }

            {
                programNoLocation0.bind();
                glBeginPixelLocalStorageANGLE(1, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE}));
                if (hasAttachment0 && !drawBuffer0Enabled)
                {
                    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
                    EXPECT_GL_SINGLE_ERROR_MSG(
                        "Color attachment zero is present, but draw buffer zero is disabled.");
                }
                else
                {
                    EXPECT_GL_NO_ERROR();
                    programNoLocation0.drawBoxes({{{FULLSCREEN}}});
                    // Location 1 doesn't care if the color attachment is disabled.
                    EXPECT_GL_NO_ERROR();
                    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_STORE_OP_STORE_ANGLE}));
                }
                ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
                EXPECT_GL_NO_ERROR();
            }
        }
    }

    ASSERT_GL_NO_ERROR();
}

// Check that glEndPixelLocalStorageANGLE and glPixelLocalStorageBarrierANGLE validate as specified.
TEST_P(PixelLocalStorageValidationTest, EndAndBarrierANGLE)
{
    // INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
    // in an interrupted state.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glFramebufferPixelLocalStorageInterruptANGLE();
    glEndPixelLocalStorageANGLE(0, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is interrupted.");
    glFramebufferPixelLocalStorageRestoreANGLE();
    ASSERT_GL_NO_ERROR();

    // INVALID_OPERATION is generated if PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE is zero.
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glEndPixelLocalStorageANGLE(0, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glPixelLocalStorageBarrierANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glEndPixelLocalStorageANGLE(1, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    glPixelLocalStorageBarrierANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    glBeginPixelLocalStorageANGLE(0, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Planes must be greater than 0.");
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);

    glPixelLocalStorageBarrierANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    glEndPixelLocalStorageANGLE(2, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    PLSTestTexture tex(GL_RGBA8);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    for (int i = 1; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
    {
        glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
    }
    glBeginPixelLocalStorageANGLE(
        MAX_PIXEL_LOCAL_STORAGE_PLANES,
        std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_LOAD_OP_ZERO_ANGLE).data());
    EXPECT_GL_NO_ERROR();
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, MAX_PIXEL_LOCAL_STORAGE_PLANES);

    glPixelLocalStorageBarrierANGLE();
    EXPECT_GL_NO_ERROR();

    glEndPixelLocalStorageANGLE(
        MAX_PIXEL_LOCAL_STORAGE_PLANES,
        std::vector<GLenum>(MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_DONT_CARE).data());
    EXPECT_GL_NO_ERROR();

    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    EXPECT_GL_NO_ERROR();

    glEndPixelLocalStorageANGLE(0, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    glPixelLocalStorageBarrierANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage is not active.");

    ASSERT_GL_NO_ERROR();

    // INVALID_VALUE is generated if <n> != PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE.
    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
    ASSERT_GL_NO_ERROR();

    glEndPixelLocalStorageANGLE(3, GLenumArray({GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("<n> != ACTIVE_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    glEndPixelLocalStorageANGLE(1, GLenumArray({GL_DONT_CARE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("<n> != ACTIVE_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
    ASSERT_GL_NO_ERROR();

    // INVALID_ENUM is generated if <storeops>[0..PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE-1] is not
    // one of the Store Operations enumerated in Table X.2.
    glBeginPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_LOAD_ANGLE, GL_DONT_CARE, GL_LOAD_OP_CLEAR_ANGLE}));
    ASSERT_GL_NO_ERROR();

    glEndPixelLocalStorageANGLE(
        3, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x96E4.");

    glEndPixelLocalStorageANGLE(
        3, GLenumArray({GL_DONT_CARE, GL_LOAD_OP_LOAD_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x96E5.");

    glEndPixelLocalStorageANGLE(
        3, GLenumArray({GL_DONT_CARE, GL_STORE_OP_STORE_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x96E3.");

    glEndPixelLocalStorageANGLE(
        3, GLenumArray({GL_DONT_CARE, GL_SCISSOR_BOX, GL_STORE_OP_STORE_ANGLE}));
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("Invalid pixel local storage Store Operation: 0x0C10.");

    ASSERT_GL_NO_ERROR();
}

// Check that FramebufferPixelLocalStorageInterruptANGLE validates as specified.
TEST_P(PixelLocalStorageValidationTest, InterruptMechanism)
{
    // These commands are ignored when the default framebuffer object name 0 is bound.
    glFramebufferPixelLocalStorageRestoreANGLE();
    glFramebufferPixelLocalStorageInterruptANGLE();
    glFramebufferPixelLocalStorageInterruptANGLE();
    glFramebufferPixelLocalStorageInterruptANGLE();
    glFramebufferPixelLocalStorageRestoreANGLE();
    glFramebufferPixelLocalStorageRestoreANGLE();
    glFramebufferPixelLocalStorageRestoreANGLE();
    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_NO_ERROR();

    for (size_t i = 0; i < 256; ++i)
    {
        glFramebufferPixelLocalStorageInterruptANGLE();
    }
    EXPECT_GL_NO_ERROR();

    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    // INVALID_FRAMEBUFFER_OPERATION is generated if pixel local storage on the draw framebuffer is
    // not in an interrupted state.
    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is not interrupted.");

    for (size_t i = 0; i < 255; ++i)
    {
        glFramebufferPixelLocalStorageInterruptANGLE();
    }
    EXPECT_GL_NO_ERROR();

    // INVALID_FRAMEBUFFER_OPERATION is generated if the current interrupt count on the draw
    // framebuffer is greater than or equal to 255.
    glFramebufferPixelLocalStorageInterruptANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Pixel local storage does not support more than 255 nested interruptions.");

    for (size_t i = 0; i < 255; ++i)
    {
        glFramebufferPixelLocalStorageRestoreANGLE();
    }
    EXPECT_GL_NO_ERROR();

    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is not interrupted.");

    glFramebufferPixelLocalStorageInterruptANGLE();
    glFramebufferPixelLocalStorageInterruptANGLE();
    ASSERT_GL_NO_ERROR();

    glFramebufferPixelLocalStorageRestoreANGLE();
    glFramebufferPixelLocalStorageRestoreANGLE();
    ASSERT_GL_NO_ERROR();

    glFramebufferPixelLocalStorageRestoreANGLE();
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Pixel local storage on the draw framebuffer is not interrupted.");
}

// Check that glGetFramebufferPixelLocalStorageParameter(f|i|ui)(Robust)?ANGLE validate as
// specified.
TEST_P(PixelLocalStorageValidationTest, GetFramebufferPixelLocalStorageParametersANGLE)
{
    // The "Get.*Robust" variants require ANGLE_robust_client_memory.
    //
    // If ANGLE ever does find itself in a situation where ANGLE_robust_client_memory is not
    // supported, we will need to hide the "Get.*Robust" variants in order to be spec compliant.
    EXPECT_TRUE(IsGLExtensionEnabled("GL_ANGLE_robust_client_memory"));

    GLfloat floats[5];
    GLint ints[5];
    GLsizei length;

    // INVALID_FRAMEBUFFER_OPERATION is generated if the default framebuffer object name 0 is bound
    // to DRAW_FRAMEBUFFER.
    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");
    glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
                                                      floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                            4, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                      ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG(
        "Default framebuffer object name 0 does not support pixel local storage.");

    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    // No error is generated if pixel local storage on the draw framebuffer is
    // in an interrupted state.
    glFramebufferPixelLocalStorageInterruptANGLE();
    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
    EXPECT_GL_NO_ERROR();
    glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
                                                      floats);
    EXPECT_GL_NO_ERROR();
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                            4, &length, ints);
    EXPECT_GL_NO_ERROR();
    glGetFramebufferPixelLocalStorageParameterivANGLE(1, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                      ints);
    EXPECT_GL_NO_ERROR();
    glFramebufferPixelLocalStorageRestoreANGLE();
    ASSERT_GL_NO_ERROR();

    // INVALID_VALUE is generated if <plane> < 0 or <plane> >= MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.
    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        -1, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
    glGetFramebufferPixelLocalStorageParameterfvANGLE(-1, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
                                                      floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");

    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(-1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE,
                                                            4, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(-1, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane cannot be less than 0.");

    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 4, &length, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
    glGetFramebufferPixelLocalStorageParameterfvANGLE(
        MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
        MAX_PIXEL_LOCAL_STORAGE_PLANES, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, 1, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(MAX_PIXEL_LOCAL_STORAGE_PLANES,
                                                      GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("Plane must be less than GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE.");

    // No error for known enums
    const GLenum params[] = {
        GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
        GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE,
        GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
        GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
        GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
        GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
        GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE,
    };
    for (GLenum param : params)
    {
        glGetFramebufferPixelLocalStorageParameterfvANGLE(0, param, floats);
        EXPECT_GL_NO_ERROR();
        glGetFramebufferPixelLocalStorageParameterivANGLE(0, param, ints);
        EXPECT_GL_NO_ERROR();
        glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(0, param, 4, &length, floats);
        EXPECT_GL_NO_ERROR();
        glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, param, 4, &length, ints);
        EXPECT_GL_NO_ERROR();
    }

    // INVALID_ENUM is generated if <pname> is not in Table 6.Y.
    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        1, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, 1, &length, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("The <pname> is unknown.");
    glGetFramebufferPixelLocalStorageParameterfvANGLE(
        1, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("The <pname> is unknown.");
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
        1, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, 1, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("The <pname> is unknown.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(
        1, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_ENUM);
    EXPECT_GL_SINGLE_ERROR_MSG("The <pname> is unknown.");

    // INVALID_OPERATION is generated if <bufSize> is not large enough to receive the requested
    // parameter.
    //
    // ... When an error is generated, nothing is written to <length>.
    constexpr GLsizei kLengthInitValue = 0xbaadc0de;
    length                             = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                            0, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 0,
                                                            &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
                                                            0, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
                                                            0, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                            3, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 3, &length, ints);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 3, &length, floats);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("The paramCount is less than the number of returned values.");
    EXPECT_EQ(length, kLengthInitValue);

    // Not quite pure validation, but also ensure that <length> gets written properly.
    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                            5, &length, ints);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 1);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 5,
                                                            &length, ints);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 1);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
                                                            5, &length, ints);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 1);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
                                                            5, &length, ints);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 1);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                            5, &length, ints);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 4);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_ANGLE, 5, &length, ints);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 4);

    length = kLengthInitValue;
    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 5, &length, floats);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(length, 4);

    // INVALID_VALUE is generated if <params> is NULL.
    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                            5, &length, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_INTERNAL_FORMAT_ANGLE,
                                                      nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");

    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE, 5,
                                                            &length, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_TEXTURE_NAME_ANGLE,
                                                      nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");

    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
                                                            5, &length, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LEVEL_ANGLE,
                                                      nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");

    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
                                                            5, &length, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_TEXTURE_LAYER_ANGLE,
                                                      nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");

    glGetFramebufferPixelLocalStorageParameterivRobustANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                            5, &length, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
    glGetFramebufferPixelLocalStorageParameterivANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_INT_ANGLE,
                                                      nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");

    glGetFramebufferPixelLocalStorageParameterfvRobustANGLE(
        0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE, 5, &length, nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
    glGetFramebufferPixelLocalStorageParameterfvANGLE(0, GL_PIXEL_LOCAL_CLEAR_VALUE_FLOAT_ANGLE,
                                                      nullptr);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_VALUE);
    EXPECT_GL_SINGLE_ERROR_MSG("The <params> is null.");
}

// Check command-specific errors that go into effect when PLS is active, as well as commands
// specifically called out by EXT_shader_pixel_local_storage for flushing tiled memory.
TEST_P(PixelLocalStorageValidationTest, BannedCommands)
{
    PLSTestTexture tex(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
    for (int i = 1; i < numActivePlanes; ++i)
    {
        glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
    }
    glBeginPixelLocalStorageANGLE(
        numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
    ASSERT_GL_NO_ERROR();

    // INVALID_OPERATION is generated by Enable(), Disable() if <cap> is not one of: CULL_FACE,
    // DEPTH_CLAMP_EXT, DEPTH_TEST, POLYGON_OFFSET_POINT_NV, POLYGON_OFFSET_LINE_NV,
    // POLYGON_OFFSET_LINE_ANGLE, POLYGON_OFFSET_FILL, PRIMITIVE_RESTART_FIXED_INDEX,
    // SCISSOR_TEST, STENCIL_TEST, CLIP_DISTANCE[0..7]_EXT
    EXPECT_ALLOWED_CAP(GL_CULL_FACE);
    if (EnsureGLExtensionEnabled("GL_KHR_debug"))
    {
        EXPECT_ALLOWED_CAP(GL_DEBUG_OUTPUT);
        EXPECT_ALLOWED_CAP(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        // Now turn them back on since the test actually uses these caps...
        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    }
    EXPECT_ALLOWED_CAP(GL_DEPTH_TEST);
    if (EnsureGLExtensionEnabled("GL_EXT_depth_clamp"))
    {
        EXPECT_ALLOWED_CAP(GL_DEPTH_CLAMP_EXT);
    }
    if (EnsureGLExtensionEnabled("GL_ANGLE_polygon_mode"))
    {
        EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_LINE_ANGLE);
        glPolygonModeANGLE(GL_FRONT_AND_BACK, GL_FILL_ANGLE);
        EXPECT_GL_NO_ERROR();
    }
    if (EnsureGLExtensionEnabled("GL_NV_polygon_mode"))
    {
        EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_POINT_NV);
        EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_LINE_NV);
        glPolygonModeNV(GL_FRONT_AND_BACK, GL_FILL_NV);
        EXPECT_GL_NO_ERROR();
    }
    if (EnsureGLExtensionEnabled("GL_EXT_polygon_offset_clamp"))
    {
        glPolygonOffsetClampEXT(0.0f, 0.0f, 0.0f);
        EXPECT_GL_NO_ERROR();
    }
    EXPECT_ALLOWED_CAP(GL_POLYGON_OFFSET_FILL);
    EXPECT_ALLOWED_CAP(GL_PRIMITIVE_RESTART_FIXED_INDEX);
    EXPECT_ALLOWED_CAP(GL_RASTERIZER_DISCARD);
    EXPECT_ALLOWED_CAP(GL_SCISSOR_TEST);
    EXPECT_ALLOWED_CAP(GL_STENCIL_TEST);
    if (EnsureGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        for (GLenum i = 0; i < 8; ++i)
        {
            EXPECT_ALLOWED_CAP(GL_CLIP_DISTANCE0_EXT + i);
        }
    }
    EXPECT_BANNED_CAP(GL_SAMPLE_ALPHA_TO_COVERAGE);
    EXPECT_BANNED_CAP(GL_SAMPLE_COVERAGE);
    EXPECT_BANNED_CAP(GL_DITHER);
    if (isContextVersionAtLeast(3, 1))
    {
        EXPECT_BANNED_CAP(GL_SAMPLE_MASK);
    }
    if (EnsureGLExtensionEnabled("GL_EXT_multisample_compatibility"))
    {
        EXPECT_BANNED_CAP(GL_MULTISAMPLE_EXT);
        EXPECT_BANNED_CAP(GL_SAMPLE_ALPHA_TO_ONE_EXT);
    }
    if (EnsureGLExtensionEnabled("GL_OES_sample_shading"))
    {
        EXPECT_BANNED_CAP(GL_SAMPLE_SHADING);
    }
    if (EnsureGLExtensionEnabled("GL_QCOM_shading_rate"))
    {
        EXPECT_BANNED_CAP(GL_SHADING_RATE_PRESERVE_ASPECT_RATIO_QCOM);
    }
    if (EnsureGLExtensionEnabled("GL_ANGLE_logic_op"))
    {
        EXPECT_BANNED_CAP(GL_COLOR_LOGIC_OP);
    }
    // BLEND is the only indexed capability in ANGLE, but we can at least use SHADING_RATE_IMAGE_NV
    // to check the debug output and verify that PLS validation is banning it, which ensures it is
    // future proof against hypothetical future extensions that add indexed capabilities.
    EXPECT_BANNED_CAP_INDEXED(GL_SHADING_RATE_IMAGE_NV);

    // The validation tests are expected to run in a configuration where the draw buffers are
    // restricted by PLS (e.g., not shader images).
    int bannedCombinedAttachment =
        MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numActivePlanes;
    ASSERT(bannedCombinedAttachment < MAX_DRAW_BUFFERS);

    // glClearBuffer{f,i,ui}v are ignored for all attachments at or beyond
    // MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numActivePlanes.
    GLfloat clearf[4]{};
    GLint cleari[4]{};
    GLuint clearui[4]{};

    glClearBufferfv(GL_COLOR, MAX_DRAW_BUFFERS - 1, clearf);
    glClearBufferiv(GL_COLOR, MAX_DRAW_BUFFERS - 1, cleari);
    glClearBufferuiv(GL_COLOR, MAX_DRAW_BUFFERS - 1, clearui);
    EXPECT_GL_NO_ERROR();

    glClearBufferfv(GL_COLOR, bannedCombinedAttachment, clearf);
    glClearBufferiv(GL_COLOR, bannedCombinedAttachment, cleari);
    glClearBufferuiv(GL_COLOR, bannedCombinedAttachment, clearui);
    EXPECT_GL_NO_ERROR();

    glClearBufferfv(GL_DEPTH, 0, clearf);
    EXPECT_GL_NO_ERROR();
    glClearBufferiv(GL_STENCIL, 0, cleari);
    EXPECT_GL_NO_ERROR();

    if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
    {
        // Enabling and disabling GL_BLEND is valid while PLS is active.
        glEnableiOES(GL_BLEND, MAX_DRAW_BUFFERS - 1);
        glDisableiOES(GL_BLEND, MAX_DRAW_BUFFERS - 1);
        EXPECT_GL_NO_ERROR();

        glEnableiOES(GL_BLEND, bannedCombinedAttachment);
        glDisableiOES(GL_BLEND, bannedCombinedAttachment);
        EXPECT_GL_NO_ERROR();
    }

    ASSERT_GL_NO_ERROR();
}

// Check that glBlendFunc and glBlendEquation updates are valid while PLS is active, and still take
// effect after PLS is ended.
TEST_P(PixelLocalStorageValidationTest, BlendFuncDuringPLS)
{
    const GLint drawBufferIndexCount =
        IsGLExtensionEnabled("GL_OES_draw_buffers_indexed") ? MAX_DRAW_BUFFERS : 1;
    PLSTestTexture tex(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
    for (int i = 1; i < numActivePlanes; ++i)
    {
        glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
    }

    glBeginPixelLocalStorageANGLE(
        numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
    ASSERT_GL_NO_ERROR();

    for (GLint i = 0; i < drawBufferIndexCount; ++i)
    {
        if (drawBufferIndexCount > 1)
        {
            glBlendEquationiOES(i, GL_FUNC_SUBTRACT);
            glBlendFunciOES(i, GL_ONE, GL_CONSTANT_COLOR);
        }
        else
        {
            glBlendEquation(GL_FUNC_SUBTRACT);
            glBlendFunc(GL_ONE, GL_CONSTANT_COLOR);
        }
        ASSERT_GL_NO_ERROR();
    }

    glEndPixelLocalStorageANGLE(numActivePlanes,
                                std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
    ASSERT_GL_NO_ERROR();

    for (GLint i = 0; i < drawBufferIndexCount; ++i)
    {
        GLint blendEquationRGB, blendEquationAlpha;
        if (drawBufferIndexCount > 1)
        {
            glGetIntegeri_v(GL_BLEND_EQUATION_RGB, i, &blendEquationRGB);
            glGetIntegeri_v(GL_BLEND_EQUATION_ALPHA, i, &blendEquationAlpha);
        }
        else
        {
            glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB);
            glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha);
        }
        EXPECT_EQ(blendEquationRGB, GL_FUNC_SUBTRACT);
        EXPECT_EQ(blendEquationAlpha, GL_FUNC_SUBTRACT);

        GLint blendSrcRGB, blendSrcAlpha, blendDstRGB, blendDstAlpha;
        if (drawBufferIndexCount > 1)
        {
            glGetIntegeri_v(GL_BLEND_SRC_RGB, i, &blendSrcRGB);
            glGetIntegeri_v(GL_BLEND_SRC_ALPHA, i, &blendSrcAlpha);
            glGetIntegeri_v(GL_BLEND_DST_RGB, i, &blendDstRGB);
            glGetIntegeri_v(GL_BLEND_DST_ALPHA, i, &blendDstAlpha);
        }
        else
        {
            glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB);
            glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha);
            glGetIntegerv(GL_BLEND_DST_RGB, &blendDstRGB);
            glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDstAlpha);
        }
        EXPECT_EQ(blendSrcRGB, GL_ONE);
        EXPECT_EQ(blendSrcAlpha, GL_ONE);
        EXPECT_EQ(blendDstRGB, GL_CONSTANT_COLOR);
        EXPECT_EQ(blendDstAlpha, GL_CONSTANT_COLOR);
    }

    glBeginPixelLocalStorageANGLE(
        numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
    ASSERT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
    ASSERT_GL_NO_ERROR();

    for (GLint i = 0; i < drawBufferIndexCount; ++i)
    {
        if (drawBufferIndexCount > 1)
        {
            glBlendEquationSeparateiOES(i, GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD);
            glBlendFuncSeparateiOES(i, GL_ZERO, GL_DST_COLOR, GL_DST_ALPHA, GL_SRC_COLOR);
        }
        else
        {
            glBlendEquationSeparate(GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_ADD);
            glBlendFuncSeparate(GL_ZERO, GL_DST_COLOR, GL_DST_ALPHA, GL_SRC_COLOR);
        }
        ASSERT_GL_NO_ERROR();
    }

    glEndPixelLocalStorageANGLE(numActivePlanes,
                                std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
    ASSERT_GL_NO_ERROR();

    for (GLint i = 0; i < drawBufferIndexCount; ++i)
    {
        GLint blendEquationRGB, blendEquationAlpha;
        if (drawBufferIndexCount > 1)
        {
            glGetIntegeri_v(GL_BLEND_EQUATION_RGB, i, &blendEquationRGB);
            glGetIntegeri_v(GL_BLEND_EQUATION_ALPHA, i, &blendEquationAlpha);
        }
        else
        {
            glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB);
            glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha);
        }
        EXPECT_EQ(blendEquationRGB, GL_FUNC_REVERSE_SUBTRACT);
        EXPECT_EQ(blendEquationAlpha, GL_FUNC_ADD);

        GLint blendSrcRGB, blendDstRGB, blendSrcAlpha, blendDstAlpha;
        if (drawBufferIndexCount > 1)
        {
            glGetIntegeri_v(GL_BLEND_SRC_RGB, i, &blendSrcRGB);
            glGetIntegeri_v(GL_BLEND_SRC_ALPHA, i, &blendSrcAlpha);
            glGetIntegeri_v(GL_BLEND_DST_RGB, i, &blendDstRGB);
            glGetIntegeri_v(GL_BLEND_DST_ALPHA, i, &blendDstAlpha);
        }
        else
        {
            glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB);
            glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha);
            glGetIntegerv(GL_BLEND_DST_RGB, &blendDstRGB);
            glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDstAlpha);
        }
        EXPECT_EQ(blendSrcRGB, GL_ZERO);
        EXPECT_EQ(blendDstRGB, GL_DST_COLOR);
        EXPECT_EQ(blendSrcAlpha, GL_DST_ALPHA);
        EXPECT_EQ(blendDstAlpha, GL_SRC_COLOR);
    }
}

// Check that glGetFramebufferAttachmentParameteriv() generates errors when
// querying overridden PLS attachments.
TEST_P(PixelLocalStorageValidationTest, FramebufferQueries)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture pls0(GL_RGBA8);
    PLSTestTexture pls1(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);

    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
    EXPECT_GL_NO_ERROR();

    int firstOverriddenDrawBuffer = MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2;
    // The validation tests are expected to run in a configuration where the draw buffers are
    // restricted by PLS (e.g., not shader images).
    ASSERT(firstOverriddenDrawBuffer < MAX_DRAW_BUFFERS);

    GLint intValue;
    glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
                                          GL_COLOR_ATTACHMENT0 + firstOverriddenDrawBuffer,
                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");

    glGetFramebufferAttachmentParameteriv(
        GL_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2,
        GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");

    glGetFramebufferAttachmentParameteriv(
        GL_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
        GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");

    glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
                                          GL_COLOR_ATTACHMENT0 + MAX_COLOR_ATTACHMENTS - 1,
                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Color attachment is actively reserved for pixel local storage.");

    if (firstOverriddenDrawBuffer != 0)
    {
        glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                              GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
        EXPECT_GL_NO_ERROR();
        glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
                                              GL_COLOR_ATTACHMENT0 + firstOverriddenDrawBuffer - 1,
                                              GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
        EXPECT_GL_NO_ERROR();
    }

    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
    EXPECT_GL_NO_ERROR();

    glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
                                          GL_COLOR_ATTACHMENT0 + firstOverriddenDrawBuffer,
                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_NO_ERROR();

    glGetFramebufferAttachmentParameteriv(
        GL_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 2,
        GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_NO_ERROR();

    glGetFramebufferAttachmentParameteriv(
        GL_FRAMEBUFFER,
        GL_COLOR_ATTACHMENT0 + MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - 1,
        GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_NO_ERROR();

    glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
                                          GL_COLOR_ATTACHMENT0 + MAX_COLOR_ATTACHMENTS - 1,
                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &intValue);
    EXPECT_GL_NO_ERROR();
}

// Test that binding overlapping regions of a texture to a sampler and a PLS plane is an error.
TEST_P(PixelLocalStorageWebGLValidationTest, FeedbackLoopValidation)
{
    PLSProgram program;
    program.compile(R"(
        layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane1;
        layout(rgba8i, binding=1) uniform lowp ipixelLocalANGLE plane2;
        layout(binding=2, rgba8ui) uniform lowp upixelLocalANGLE plane3;
        uniform sampler2D tex2d;
        uniform highp sampler2DArray tex2dArray;
        void main()
        {
            vec3 texcoord = vec3(0, 0, 0);
            vec4 sampledValue = texture(tex2d, texcoord.xy) + texture(tex2dArray, texcoord.xyz);
            pixelLocalStoreANGLE(plane1, sampledValue + color + pixelLocalLoadANGLE(plane1));
            pixelLocalStoreANGLE(plane2, ivec4(aux1) + pixelLocalLoadANGLE(plane2));
            pixelLocalStoreANGLE(plane3, uvec4(aux2) + pixelLocalLoadANGLE(plane3));
        })");

    GLuint sampler2dLocation = glGetUniformLocation(program, "tex2d");
    glUniform1i(sampler2dLocation, 0);

    GLuint sampler2dArrayLocation = glGetUniformLocation(program, "tex2dArray");
    glUniform1i(sampler2dArrayLocation, 1);

    PLSTestTexture tex1(GL_RGBA8);
    PLSTestTexture tex2(GL_RGBA8I);
    PLSTestTexture tex3(GL_RGBA8UI);

    // 2D_ARRAY texture for coverage of more texture types
    GLTexture tex4;
    glBindTexture(GL_TEXTURE_2D_ARRAY, tex4);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, W, H, 16);

    // Attached to plane 3 but unrefernced/inactive
    PLSTestTexture tex5(GL_RGBA8);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex2, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(2, tex3, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(3, tex5, 0, 0, GL_NONE);
    glViewport(0, 0, W, H);
    glDrawBuffers(0, nullptr);

    auto doSinglePLSDraw = [&]() {
        glBeginPixelLocalStorageANGLE(
            3, GLenumArray({GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE, GL_LOAD_OP_ZERO_ANGLE}));
        program.drawBoxes({{FULLSCREEN, {-1.5, 0, 0, 0}, {0x000000ff, 0, 0, 0}}});
        glEndPixelLocalStorageANGLE(
            3, GLenumArray(
                   {GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));
    };

    // Bind tex1 and tex4 for sampling
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D_ARRAY, tex4);
    ASSERT_GL_NO_ERROR();

    // Bind tex1 as a PLS plane, should generate a feedback loop error
    glFramebufferTexturePixelLocalStorageANGLE(0, tex1, 0, 0, GL_NONE);
    ASSERT_GL_NO_ERROR();
    doSinglePLSDraw();
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Feedback loop formed between Framebuffer and active Texture.");

    // Bind tex5 for sampling. Should not generate feedback loop errors because it's bound to an
    // inactive plane
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex5);
    ASSERT_GL_NO_ERROR();
    doSinglePLSDraw();
    EXPECT_GL_NO_ERROR();

    // Bind the 2D array texture as a PLS plane and expect that it generates a feedback loop because
    // it's also bound for sampling.
    glFramebufferTexturePixelLocalStorageANGLE(0, tex4, 0, 0, GL_NONE);
    ASSERT_GL_NO_ERROR();
    doSinglePLSDraw();
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    EXPECT_GL_SINGLE_ERROR_MSG("Feedback loop formed between Framebuffer and active Texture.");
}

// Check that glEnable(GL_BLEND) and glColorMask() do not generate errors, and are ignored for
// overridden draw buffers during PLS.
TEST_P(PixelLocalStorageValidationTest, BlendMaskDuringPLS)
{
    // Make sure we're given a context where this test is relevant.
    ASSERT(MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES <= MAX_DRAW_BUFFERS);

    PLSTestTexture tex(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
    for (int i = 1; i < numActivePlanes; ++i)
    {
        glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
    }
    auto beginPLS = [=]() {
        glBeginPixelLocalStorageANGLE(
            numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
    };
    auto endPLS = [=]() {
        glEndPixelLocalStorageANGLE(numActivePlanes,
                                    std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());
    };

#define EXPECT_UNIFORM_BLEND_MASK(ENABLED)                    \
    EXPECT_EQ(glIsEnabled(GL_BLEND), ENABLED);                \
    EXPECT_GL_INTEGER(GL_BLEND, ENABLED);                     \
    if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))  \
    {                                                         \
        for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)            \
        {                                                     \
            EXPECT_EQ(glIsEnablediOES(GL_BLEND, i), ENABLED); \
        }                                                     \
    }

#define EXPECT_UNIFORM_COLOR_MASK(R, G, B, A)                \
    EXPECT_GL_COLOR_MASK(R, G, B, A);                        \
    if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed")) \
    {                                                        \
        for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)           \
        {                                                    \
            EXPECT_GL_COLOR_MASK_INDEXED(i, R, G, B, A);     \
        }                                                    \
    }

    // Set before.
    glEnable(GL_BLEND);
    EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
    beginPLS();
    EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
    glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
    EXPECT_GL_NO_ERROR();
    EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
    endPLS();
    // Blend & color mask shouldn't change after endPLS().
    EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
    EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
    glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_FALSE);
    glDisable(GL_BLEND);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    EXPECT_GL_NO_ERROR();

    // Set during.
    beginPLS();
    EXPECT_UNIFORM_BLEND_MASK(GL_FALSE);
    EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glEnable(GL_BLEND);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
    EXPECT_GL_NO_ERROR();
    EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
    EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
    endPLS();
    // Blend & color mask shouldn't change after endPLS().
    EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
    EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
    glDisable(GL_BLEND);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    EXPECT_GL_NO_ERROR();

    if (IsGLExtensionEnabled("GL_OES_draw_buffers_indexed"))
    {
        // Indexed before, non-indexed during.
        glEnableiOES(GL_BLEND, 0);
        glEnableiOES(GL_BLEND, MAX_DRAW_BUFFERS - 1);
        glColorMaskiOES(0, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
        glColorMaskiOES(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);

        beginPLS();

        EXPECT_TRUE(glIsEnablediOES(GL_BLEND, 0));
        for (GLint i = 1; i < MAX_DRAW_BUFFERS - 1; ++i)
        {
            EXPECT_FALSE(glIsEnablediOES(GL_BLEND, i));
        }
        EXPECT_TRUE(glIsEnablediOES(GL_BLEND, MAX_DRAW_BUFFERS - 1));

        EXPECT_GL_COLOR_MASK_INDEXED(0, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
        for (GLint i = 1; i < MAX_DRAW_BUFFERS - 1; ++i)
        {
            EXPECT_GL_COLOR_MASK_INDEXED(i, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        }
        EXPECT_GL_COLOR_MASK_INDEXED(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);

        glEnable(GL_BLEND);
        EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);

        glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_UNIFORM_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);

        endPLS();

        EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
        EXPECT_UNIFORM_COLOR_MASK(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE);

        glDisable(GL_BLEND);
        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

        EXPECT_GL_NO_ERROR();

        // Non-indexed before, indexed during.
        glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        glEnable(GL_BLEND);

        beginPLS();

        EXPECT_UNIFORM_BLEND_MASK(GL_TRUE);
        EXPECT_UNIFORM_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);

        glDisableiOES(GL_BLEND, 1);
        glDisableiOES(GL_BLEND, MAX_DRAW_BUFFERS - 1);
        EXPECT_TRUE(glIsEnabled(GL_BLEND));
        EXPECT_TRUE(glIsEnablediOES(GL_BLEND, 0));
        EXPECT_FALSE(glIsEnablediOES(GL_BLEND, 1));
        for (GLint i = 2; i < MAX_DRAW_BUFFERS - 1; ++i)
        {
            EXPECT_TRUE(glIsEnablediOES(GL_BLEND, i));
        }
        EXPECT_FALSE(glIsEnablediOES(GL_BLEND, MAX_DRAW_BUFFERS - 1));

        glColorMaskiOES(2, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
        glColorMaskiOES(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
        EXPECT_GL_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_GL_COLOR_MASK_INDEXED(0, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_GL_COLOR_MASK_INDEXED(2, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
        for (GLint i = 3; i < MAX_DRAW_BUFFERS - 1; ++i)
        {
            EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        }
        EXPECT_GL_COLOR_MASK_INDEXED(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

        endPLS();

        EXPECT_TRUE(glIsEnabled(GL_BLEND));
        EXPECT_TRUE(glIsEnablediOES(GL_BLEND, 0));
        EXPECT_FALSE(glIsEnablediOES(GL_BLEND, 1));
        for (GLint i = 2; i < MAX_DRAW_BUFFERS - 1; ++i)
        {
            EXPECT_TRUE(glIsEnablediOES(GL_BLEND, i));
        }
        EXPECT_FALSE(glIsEnablediOES(GL_BLEND, MAX_DRAW_BUFFERS - 1));

        EXPECT_GL_COLOR_MASK(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_GL_COLOR_MASK_INDEXED(0, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        EXPECT_GL_COLOR_MASK_INDEXED(2, GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
        for (GLint i = 3; i < MAX_DRAW_BUFFERS - 1; ++i)
        {
            EXPECT_GL_COLOR_MASK_INDEXED(1, GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
        }
        EXPECT_GL_COLOR_MASK_INDEXED(MAX_DRAW_BUFFERS - 1, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

        glDisable(GL_BLEND);
        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

        EXPECT_GL_NO_ERROR();
    }

    EXPECT_GL_NO_ERROR();

#undef EXPECT_NON_OVERRIDDEN_COLOR_MASK
#undef EXPECT_UNIFORM_BLEND_MASK
}

// Check that modifications to PLS textures bounce while it's active.
TEST_P(PixelLocalStorageValidationTest, ModifyTextureDuringPLS)
{
    GLTexture pls2d;
    glBindTexture(GL_TEXTURE_2D, pls2d);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W, H);

    GLTexture nonpls2d;
    glBindTexture(GL_TEXTURE_2D, nonpls2d);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, W, H);

    GLTexture pls2darray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, pls2darray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, W, H, 5);

    GLTexture nonpls2darray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, nonpls2darray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 3, GL_RGBA8, W, H, 5);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls2d, 1, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls2darray, 1, 4, GL_NONE);

    glBeginPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
    ASSERT_GL_NO_ERROR();

#define CHECK_TEXTURE_2D_MODIFICATION_MSG(FN, MSG) \
    glBindTexture(GL_TEXTURE_2D, pls2d);           \
    FN;                                            \
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);  \
    EXPECT_GL_SINGLE_ERROR_MSG(MSG)                \
    glBindTexture(GL_TEXTURE_2D, nonpls2d);        \
    FN;                                            \
    EXPECT_GL_NO_ERROR();

#define CHECK_TEXTURE_2D_MODIFICATION(FN) \
    CHECK_TEXTURE_2D_MODIFICATION_MSG(    \
        FN, "Operation not permitted on an active pixel local storage backing texture.")

#define CHECK_TEXTURE_2D_ARRAY_MODIFICATION_MSG(FN, MSG) \
    glBindTexture(GL_TEXTURE_2D_ARRAY, pls2darray);      \
    FN;                                                  \
    EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);        \
    EXPECT_GL_SINGLE_ERROR_MSG(MSG)                      \
    glBindTexture(GL_TEXTURE_2D_ARRAY, nonpls2darray);   \
    FN;                                                  \
    EXPECT_GL_NO_ERROR();

#define CHECK_TEXTURE_2D_ARRAY_MODIFICATION(FN) \
    CHECK_TEXTURE_2D_ARRAY_MODIFICATION_MSG(    \
        FN, "Operation not permitted on an active pixel local storage backing texture.")

    std::vector<uint8_t> imageData(H * W * 4);

    CHECK_TEXTURE_2D_MODIFICATION(
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()));

    CHECK_TEXTURE_2D_ARRAY_MODIFICATION(glTexSubImage3D(
        GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()));

    CHECK_TEXTURE_2D_MODIFICATION_MSG(
        glGenerateMipmap(GL_TEXTURE_2D),
        "Operation not permitted while pixel local storage is active.");

    CHECK_TEXTURE_2D_ARRAY_MODIFICATION_MSG(
        glGenerateMipmap(GL_TEXTURE_2D_ARRAY),
        "Operation not permitted while pixel local storage is active.");

    if (EnsureGLExtensionEnabled("GL_ANGLE_robust_client_memory"))
    {
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexSubImage2DRobustANGLE(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                                       imageData.size(), imageData.data()));

        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexSubImage3DRobustANGLE(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, W, H, 1, GL_RGBA,
                                       GL_UNSIGNED_BYTE, imageData.size(), imageData.data()));
    }

    if (EnsureGLExtensionEnabled("GL_OES_texture_3D"))
    {
        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(glTexSubImage3DOES(
            GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, W, H, 1, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()));
    }

    GLfloat zerof[4] = {};
    GLint zeroi[4]   = {};
    GLuint zeroui[4] = {};
    CHECK_TEXTURE_2D_MODIFICATION(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
    CHECK_TEXTURE_2D_MODIFICATION(glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, zeroi));
    CHECK_TEXTURE_2D_MODIFICATION(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0.f));
    CHECK_TEXTURE_2D_MODIFICATION(glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, zerof));

    CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0));
    CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
        glTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, zeroi));
    CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
        glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LOD, 0.f));
    CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
        glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LOD, zerof));

    if (EnsureGLExtensionEnabled("GL_ANGLE_robust_client_memory"))
    {
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexParameterivRobustANGLE(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1, zeroi));
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexParameterfvRobustANGLE(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 1, zerof));

        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexParameterfvRobustANGLE(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LOD, 1, zerof));
        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexParameterivRobustANGLE(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1, zeroi));
    }

    if (EnsureGLExtensionEnabled("GL_EXT_texture_border_clamp"))
    {
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexParameterIivEXT(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroi));
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexParameterIuivEXT(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroui));

        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexParameterIivEXT(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroi));
        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexParameterIuivEXT(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroui));
    }

    if (EnsureGLExtensionEnabled("GL_OES_texture_border_clamp"))
    {
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroi));
        CHECK_TEXTURE_2D_MODIFICATION(
            glTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, zeroui));

        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexParameterIivOES(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroi));
        CHECK_TEXTURE_2D_ARRAY_MODIFICATION(
            glTexParameterIuivOES(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, zeroui));
    }

#undef CHECK_TEXTURE_2D_MODIFICATION
#undef CHECK_TEXTURE_2D_ARRAY_MODIFICATION

    glEndPixelLocalStorageANGLE(2, GLenumArray({GL_DONT_CARE, GL_DONT_CARE}));
    ASSERT_GL_NO_ERROR();
}

// Check that glClear with active PLS doesn't accidentally turn off draw buffers when there is no
// color attachment.
TEST_P(PixelLocalStorageValidationTest, ClearDuringPLSDoesntAffectDrawBuffers)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

    PLSTestTexture pls0(GL_RGBA8);
    PLSTestTexture pls1(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, pls0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, pls1, 0, 0, GL_NONE);

    constexpr static int NUM_PLANES = 2;
    int maxColorAttachmentsWith2PLSPlanes =
        MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - NUM_PLANES;
    // The validation tests are expected to run in a configuration where the draw buffers are
    // restricted by PLS (e.g., not shader images).
    ASSERT(maxColorAttachmentsWith2PLSPlanes < MAX_DRAW_BUFFERS);

    int numColorAttachments = std::min(maxColorAttachmentsWith2PLSPlanes, 2);

    PLSTestTexture colorAttachment0(GL_RGBA8);
    PLSTestTexture colorAttachment1(GL_RGBA8);

    for (int colorAttachmentMask = 0; colorAttachmentMask < (1 << numColorAttachments);
         ++colorAttachmentMask)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        if (numColorAttachments >= 2)
        {
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
                                   colorAttachment1, 0);
        }
        if (numColorAttachments >= 1)
        {
            glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   colorAttachment0, 0);
        }

        // Only enable draw buffers that are in colorAttachmentMask.
        GLenum drawBuffers[2];
        for (int i = 0; i < 2; ++i)
        {
            ANGLE_UNSAFE_TODO(drawBuffers[i]) =
                (colorAttachmentMask & (1 << i)) ? GL_COLOR_ATTACHMENT0 + i : GL_NONE;
        }
        glDrawBuffers(2, drawBuffers);

        if (colorAttachmentMask != 0 && colorAttachmentMask != 3)
        {
            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            glDrawBuffers(2, GLenumArray({GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}));
            if (numColorAttachments >= 2)
            {
                glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                       (colorAttachmentMask & 2) ? colorAttachment1.id() : 0, 0);
            }
            if (numColorAttachments >= 1)
            {
                glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
                                       (colorAttachmentMask & 1) ? colorAttachment0.id() : 0, 0);
            }

            glBeginPixelLocalStorageANGLE(
                NUM_PLANES, GLenumArray({GL_LOAD_OP_CLEAR_ANGLE, GL_LOAD_OP_CLEAR_ANGLE}));
            EXPECT_GL_NO_ERROR();
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, NUM_PLANES);
            EXPECT_GL_INTEGER(GL_DRAW_BUFFER1,
                              numColorAttachments >= 2 ? GL_COLOR_ATTACHMENT1 : GL_NONE);
            EXPECT_GL_INTEGER(GL_DRAW_BUFFER0,
                              numColorAttachments >= 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE);

            // Check that glClear doesn't turn off any draw buffers. We have to do this check now
            // because glEndPixelLocalStorageANGLE restores them anyway.
            glClear(GL_COLOR_BUFFER_BIT);
            EXPECT_GL_INTEGER(GL_DRAW_BUFFER1,
                              numColorAttachments >= 2 ? GL_COLOR_ATTACHMENT1 : GL_NONE);
            EXPECT_GL_INTEGER(GL_DRAW_BUFFER0,
                              numColorAttachments >= 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE);

            glEndPixelLocalStorageANGLE(
                NUM_PLANES, GLenumArray({GL_STORE_OP_STORE_ANGLE, GL_STORE_OP_STORE_ANGLE}));

            // Make sure the draw buffers stuck after disabling PLS.
            EXPECT_GL_INTEGER(GL_DRAW_BUFFER1,
                              numColorAttachments >= 2 ? GL_COLOR_ATTACHMENT1 : GL_NONE);
            EXPECT_GL_INTEGER(GL_DRAW_BUFFER0,
                              numColorAttachments >= 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE);
        }
    }

    ASSERT_GL_NO_ERROR();
}

// Check that glReadPixels and glCopyTex*Image* end PLS before running.
TEST_P(PixelLocalStorageValidationTest, ReadPixels)
{
    PLSTestTexture tex(GL_RGBA8);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0, GL_NONE);
    int numActivePlanes = MAX_PIXEL_LOCAL_STORAGE_PLANES - 1;
    for (int i = 1; i < numActivePlanes; ++i)
    {
        glFramebufferMemorylessPixelLocalStorageANGLE(i, GL_RGBA8, GL_NONE);
    }

    int firstOverriddenDrawBuffer =
        MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES - numActivePlanes;
    // The validation tests are expected to run in a configuration where the draw buffers are
    // restricted by PLS (e.g., not shader images).
    ASSERT(firstOverriddenDrawBuffer < MAX_DRAW_BUFFERS);

    std::vector<PLSTestTexture> colorAttachments;
    std::vector<GLenum> drawBuffers;
    for (int i = 0; i < firstOverriddenDrawBuffer; ++i)
    {
        colorAttachments.emplace_back(GL_RGBA8);
        drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i);
        glFramebufferTexture2D(GL_FRAMEBUFFER, drawBuffers.back(), GL_TEXTURE_2D,
                               colorAttachments.back(), 0);
    }
    for (int i = firstOverriddenDrawBuffer; i < MAX_DRAW_BUFFERS; ++i)
    {
        colorAttachments.emplace_back(GL_RGBA8);
    }
    glDrawBuffers(firstOverriddenDrawBuffer, drawBuffers.data());

    GLTexture tex2d;
    glBindTexture(GL_TEXTURE_2D, tex2d);

    GLTexture tex3D;
    glBindTexture(GL_TEXTURE_3D, tex3D);
    glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, W, H, 5);

    auto beginPLS = [=]() {
        glBeginPixelLocalStorageANGLE(
            numActivePlanes, std::vector<GLenum>(numActivePlanes, GL_LOAD_OP_ZERO_ANGLE).data());
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
    };
    beginPLS();
    std::vector<uint8_t> pixelData(H * W * 4);

    // glReadPixels can't access the overridden PLS draw buffers.
    for (int i = 0; i < firstOverriddenDrawBuffer; ++i)
    {
        glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
        EXPECT_GL_NO_ERROR();
        glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        if (IsGLExtensionEnabled("GL_ANGLE_robust_client_memory"))
        {
            glReadPixelsRobustANGLE(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE,
                                    static_cast<GLsizei>(pixelData.size()), nullptr, nullptr,
                                    nullptr, pixelData.data());
            EXPECT_GL_NO_ERROR();
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
            beginPLS();
        }
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        if (EnsureGLExtensionEnabled("GL_OES_texture_3D"))
        {
            beginPLS();
            glCopyTexSubImage3DOES(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
            EXPECT_GL_NO_ERROR();
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        }
        beginPLS();
    }
    for (int i = firstOverriddenDrawBuffer; i < MAX_DRAW_BUFFERS; ++i)
    {
#define EXPECT_BLIT_FRAMEBUFFER_ERROR()                                             \
    if (firstOverriddenDrawBuffer == 0)                                             \
    {                                                                               \
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);                   \
        EXPECT_GL_SINGLE_ERROR_MSG(                                                 \
            "Framebuffer is incomplete: No attachments and default size is zero."); \
    }                                                                               \
    else                                                                            \
    {                                                                               \
        EXPECT_GL_SINGLE_ERROR(GL_INVALID_OPERATION);                               \
        EXPECT_GL_SINGLE_ERROR_MSG("Missing read attachment.");                     \
    }
        // PLS gets disabled even if there was an error.
        glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
        EXPECT_GL_NO_ERROR();
        glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());
        EXPECT_BLIT_FRAMEBUFFER_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
        EXPECT_BLIT_FRAMEBUFFER_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H);
        EXPECT_BLIT_FRAMEBUFFER_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
        EXPECT_BLIT_FRAMEBUFFER_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        if (EnsureGLExtensionEnabled("GL_OES_texture_3D"))
        {
            beginPLS();
            glCopyTexSubImage3DOES(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
            EXPECT_BLIT_FRAMEBUFFER_ERROR();
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        }
        beginPLS();
#undef EXPECT_BLIT_FRAMEBUFFER_ERROR
    }

    // When a different READ_FRAMEBUFFER is bound, glReadPixels can access all the draw buffers.
    GLFramebuffer readFBO;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO);
    EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
    beginPLS();
    for (int i = 0; i < MAX_DRAW_BUFFERS; ++i)
    {
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
                               colorAttachments[i], 0);
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glReadBuffer(GL_COLOR_ATTACHMENT0 + i);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, numActivePlanes);
        glReadPixels(0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, W, H, 0);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, W, H);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        beginPLS();
        glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
        EXPECT_GL_NO_ERROR();
        EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        if (EnsureGLExtensionEnabled("GL_OES_texture_3D"))
        {
            beginPLS();
            glCopyTexSubImage3DOES(GL_TEXTURE_3D, 0, 0, 0, 2, 0, 0, W, H);
            EXPECT_GL_NO_ERROR();
            EXPECT_GL_INTEGER(GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_ANGLE, 0);
        }
        beginPLS();
    }

    glEndPixelLocalStorageANGLE(numActivePlanes,
                                std::vector<GLenum>(numActivePlanes, GL_DONT_CARE).data());

    ASSERT_GL_NO_ERROR();
}

// Check that draw commands validate current PLS state against the shader's PLS uniforms.
TEST_P(PixelLocalStorageValidationTest, DrawCommandValidation)
{
    DrawCommandValidationTest().run();
}

// Check that PLS gets properly cleaned up when its framebuffer and textures are never deleted.
TEST_P(PixelLocalStorageValidationTest, LeakFramebufferAndTexture)
{
    GLuint fbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLuint tex0;
    glGenTextures(1, &tex0);
    glBindTexture(GL_TEXTURE_2D, tex0);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 10, 10);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);

    PLSTestTexture tex1(GL_R32UI);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);

    glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I, GL_NONE);

    // Delete tex1.
    // Don't delete tex0.
    // Don't delete fbo.

    // The PixelLocalStorage frontend implementation has internal assertions that verify all its GL
    // context objects are properly disposed of.
}

// Check that PLS gets properly cleaned up when the context is lost.
TEST_P(PixelLocalStorageValidationTest, LoseContext)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_CHROMIUM_lose_context"));

    GLuint fbo0;
    glGenFramebuffers(1, &fbo0);

    GLFramebuffer fbo1;

    GLuint tex0;
    glGenTextures(1, &tex0);
    glBindTexture(GL_TEXTURE_2D, tex0);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8UI, 10, 10);

    PLSTestTexture tex1(GL_R32UI);

    glBindFramebuffer(GL_FRAMEBUFFER, fbo0);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I, GL_NONE);

    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glFramebufferTexturePixelLocalStorageANGLE(0, tex0, 0, 0, GL_NONE);
    glFramebufferTexturePixelLocalStorageANGLE(1, tex1, 0, 0, GL_NONE);
    glFramebufferMemorylessPixelLocalStorageANGLE(3, GL_RGBA8I, GL_NONE);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glLoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET, GL_INNOCENT_CONTEXT_RESET);

    // Delete tex1.
    // Don't delete tex0.
    // Delete fbo1.
    // Don't delete fbo0.

    // The PixelLocalStorage frontend implementation has internal assertions that verify all its GL
    // context objects are properly disposed of.
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageValidationTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageWebGLValidationTest);
ANGLE_INSTANTIATE_TEST(PixelLocalStorageValidationTest,
                       WithRobustness(ES31_NULL()).enable(Feature::EmulatePixelLocalStorage));
ANGLE_INSTANTIATE_TEST(PixelLocalStorageWebGLValidationTest,
                       WithRobustness(ES31_NULL()).enable(Feature::EmulatePixelLocalStorage));

class PixelLocalStorageCompilerTest : public ANGLETest<>
{
  public:
    PixelLocalStorageCompilerTest() { setExtensionsEnabled(false); }

  protected:
    void testSetUp() override
    {
        ASSERT_TRUE(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));

        // INVALID_OPERATION is generated if DITHER is enabled.
        glDisable(GL_DITHER);

        ANGLETest::testSetUp();
    }
    ShaderInfoLog log;
};

// Check that PLS #extension support is properly implemented.
TEST_P(PixelLocalStorageCompilerTest, Extension)
{
    // GL_ANGLE_shader_pixel_local_storage_coherent isn't a shader extension. Shaders must always
    // use GL_ANGLE_shader_pixel_local_storage, regardless of coherency.
    constexpr char kNonexistentPLSCoherentExtension[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage_coherent : require
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kNonexistentPLSCoherentExtension));
    EXPECT_TRUE(log.has(
        "ERROR: 0:2: 'GL_ANGLE_shader_pixel_local_storage_coherent' : extension is not supported"));

    // PLS type names cannot be used as variable names when the extension is enabled.
    constexpr char kPLSEnabledTypesAsNames[] = R"(#version 310 es
    #extension all : warn
    void main()
    {
        int pixelLocalANGLE = 0;
        int ipixelLocalANGLE = 0;
        int upixelLocalANGLE = 0;
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSEnabledTypesAsNames));
    EXPECT_TRUE(log.has("ERROR: 0:5: 'pixelLocalANGLE' : syntax error"));

    // PLS type names are fair game when the extension is disabled.
    constexpr char kPLSDisabledTypesAsNames[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : disable
    void main()
    {
        int pixelLocalANGLE = 0;
        int ipixelLocalANGLE = 0;
        int upixelLocalANGLE = 0;
    })";
    EXPECT_TRUE(log.compileFragmentShader(kPLSDisabledTypesAsNames));

    // PLS is not allowed in a vertex shader.
    constexpr char kPLSInVertexShader[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : enable
    layout(binding=0, rgba8) lowp uniform pixelLocalANGLE pls;
    void main()
    {
        pixelLocalStoreANGLE(pls, vec4(0));
    })";
    EXPECT_FALSE(log.compileShader(kPLSInVertexShader, GL_VERTEX_SHADER));
    EXPECT_TRUE(
        log.has("ERROR: 0:3: 'pixelLocalANGLE' : undefined use of pixel local storage outside a "
                "fragment shader"));

    // Internal synchronization functions used by the compiler shouldn't be visible in ESSL.
    EXPECT_FALSE(log.compileFragmentShader(R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void main()
    {
        beginInvocationInterlockNV();
        endInvocationInterlockNV();
    })"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:5: 'beginInvocationInterlockNV' : no matching overloaded function found"));
    EXPECT_TRUE(
        log.has("ERROR: 0:6: 'endInvocationInterlockNV' : no matching overloaded function found"));

    EXPECT_FALSE(log.compileFragmentShader(R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void main()
    {
        beginFragmentShaderOrderingINTEL();
    })"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:5: 'beginFragmentShaderOrderingINTEL' : no matching overloaded function found"));

    EXPECT_FALSE(log.compileFragmentShader(R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void main()
    {
        beginInvocationInterlockARB();
        endInvocationInterlockARB();
    })"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:5: 'beginInvocationInterlockARB' : no matching overloaded function found"));
    EXPECT_TRUE(
        log.has("ERROR: 0:6: 'endInvocationInterlockARB' : no matching overloaded function found"));

    ASSERT_GL_NO_ERROR();
}

// Check proper validation of PLS handle declarations.
TEST_P(PixelLocalStorageCompilerTest, Declarations)
{
    // PLS handles must be uniform.
    constexpr char kPLSTypesMustBeUniform[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : enable
    layout(binding=0, rgba8) highp pixelLocalANGLE pls1;
    void main()
    {
        highp ipixelLocalANGLE pls2;
        highp upixelLocalANGLE pls3;
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSTypesMustBeUniform));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'pixelLocalANGLE' : pixelLocalANGLEs must be uniform"));
    EXPECT_TRUE(log.has("ERROR: 0:6: 'ipixelLocalANGLE' : ipixelLocalANGLEs must be uniform"));
    EXPECT_TRUE(log.has("ERROR: 0:7: 'upixelLocalANGLE' : upixelLocalANGLEs must be uniform"));

    // Memory qualifiers are not allowed on PLS handles.
    constexpr char kPLSMemoryQualifiers[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0, rgba8) uniform lowp volatile coherent restrict pixelLocalANGLE pls1;
    layout(binding=1, rgba8i) uniform mediump readonly ipixelLocalANGLE pls2;
    void f(uniform highp writeonly upixelLocalANGLE pls);
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSMemoryQualifiers));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'coherent' : "));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'restrict' : "));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'volatile' : "));
    EXPECT_TRUE(log.has("ERROR: 0:4: 'readonly' : "));
    EXPECT_TRUE(log.has("ERROR: 0:5: 'writeonly' : "));

    // PLS handles must specify precision.
    constexpr char kPLSNoPrecision[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : enable
    layout(binding=0, rgba8) uniform pixelLocalANGLE pls1;
    layout(binding=1, rgba8i) uniform ipixelLocalANGLE pls2;
    void f(upixelLocalANGLE pls3)
    {
    }
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSNoPrecision));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'pixelLocalANGLE' : No precision specified"));
    EXPECT_TRUE(log.has("ERROR: 0:4: 'ipixelLocalANGLE' : No precision specified"));
    EXPECT_TRUE(log.has("ERROR: 0:5: 'upixelLocalANGLE' : No precision specified"));

    // PLS handles cannot cannot be aggregated in arrays.
    constexpr char kPLSArrays[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls1[1];
    layout(binding=1, rgba8i) uniform mediump ipixelLocalANGLE pls2[2];
    layout(binding=2, rgba8ui) uniform highp upixelLocalANGLE pls3[3];
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSArrays));
    EXPECT_TRUE(log.has(
        "ERROR: 0:3: 'array' : pixel local storage handles cannot be aggregated in arrays"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:4: 'array' : pixel local storage handles cannot be aggregated in arrays"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:5: 'array' : pixel local storage handles cannot be aggregated in arrays"));

    // If PLS handles could be used before their declaration, then we would need to update the PLS
    // rewriters to make two passes.
    constexpr char kPLSUseBeforeDeclaration[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void f()
    {
        pixelLocalStoreANGLE(pls, vec4(0));
        pixelLocalStoreANGLE(pls2, ivec4(0));
    }
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
    void main()
    {
        pixelLocalStoreANGLE(pls, vec4(0));
        pixelLocalStoreANGLE(pls2, ivec4(0));
    }
    layout(binding=1, rgba8i) uniform lowp ipixelLocalANGLE pls2;)";
    EXPECT_FALSE(log.compileFragmentShader(kPLSUseBeforeDeclaration));
    EXPECT_TRUE(log.has("ERROR: 0:5: 'pls' : undeclared identifier"));
    EXPECT_TRUE(log.has("ERROR: 0:6: 'pls2' : undeclared identifier"));
    EXPECT_TRUE(log.has("ERROR: 0:12: 'pls2' : undeclared identifier"));

    // PLS unimorms must be declared at global scope; they cannot be declared in structs or
    // interface blocks.
    constexpr char kPLSInStruct[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    struct Foo
    {
        lowp pixelLocalANGLE pls;
    };
    uniform Foo foo;
    uniform PLSBlock
    {
        lowp pixelLocalANGLE blockpls;
    };
    void main()
    {
        pixelLocalStoreANGLE(foo.pls, pixelLocalLoadANGLE(blockpls));
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSInStruct));
    EXPECT_TRUE(log.has("ERROR: 0:5: 'pixelLocalANGLE' : disallowed type in struct"));
    EXPECT_TRUE(
        log.has("ERROR: 0:10: 'PLSBlock' : Opaque types are not allowed in interface blocks"));

    ASSERT_GL_NO_ERROR();
}

// Check proper validation of PLS layout qualifiers.
TEST_P(PixelLocalStorageCompilerTest, LayoutQualifiers)
{
    // PLS handles must use a supported format and binding.
    constexpr char kPLSUnsupportedFormatsAndBindings[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0, rgba32f) highp uniform pixelLocalANGLE pls0;
    layout(binding=1, rgba16f) highp uniform pixelLocalANGLE pls1;
    layout(binding=2, rgba8_snorm) highp uniform pixelLocalANGLE pls2;
    layout(binding=3, rgba32ui) highp uniform upixelLocalANGLE pls3;
    layout(binding=4, rgba16ui) highp uniform upixelLocalANGLE pls4;
    layout(binding=5, rgba32i) highp uniform ipixelLocalANGLE pls5;
    layout(binding=6, rgba16i) highp uniform ipixelLocalANGLE pls6;
    layout(binding=999999999, rgba) highp uniform ipixelLocalANGLE pls7;
    highp uniform pixelLocalANGLE pls8;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSUnsupportedFormatsAndBindings));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'rgba32f' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:4: 'rgba16f' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:5: 'rgba8_snorm' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:6: 'rgba32ui' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:7: 'rgba16ui' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:8: 'rgba32i' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:9: 'rgba16i' : illegal pixel local storage format"));
    EXPECT_TRUE(log.has("ERROR: 0:10: 'rgba' : invalid layout qualifier"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:10: 'layout qualifier' : pixel local storage requires a format specifier"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:11: 'layout qualifier' : pixel local storage requires a format specifier"));
    EXPECT_TRUE(
        log.has("ERROR: 0:11: 'layout qualifier' : pixel local storage requires a binding index"));

    // PLS handles must be within MAX_PIXEL_LOCAL_STORAGE_PLANES.
    GLint MAX_PIXEL_LOCAL_STORAGE_PLANES;
    glGetIntegerv(GL_MAX_PIXEL_LOCAL_STORAGE_PLANES_ANGLE, &MAX_PIXEL_LOCAL_STORAGE_PLANES);
    std::ostringstream bindingTooLarge;
    bindingTooLarge << "#version 310 es\n";
    bindingTooLarge << "#extension GL_ANGLE_shader_pixel_local_storage : require\n";
    bindingTooLarge << "layout(binding=" << MAX_PIXEL_LOCAL_STORAGE_PLANES
                    << ", rgba8) highp uniform pixelLocalANGLE pls;\n";
    bindingTooLarge << "void main() {}\n";
    EXPECT_FALSE(log.compileFragmentShader(bindingTooLarge.str().c_str()));
    EXPECT_TRUE(
        log.has("ERROR: 0:3: 'layout qualifier' : pixel local storage binding out of range"));

    // PLS handles must use the correct type for the given format.
    constexpr char kPLSInvalidTypeForFormat[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0) highp uniform pixelLocalANGLE pls0;
    layout(binding=1) highp uniform upixelLocalANGLE pls1;
    layout(binding=2) highp uniform ipixelLocalANGLE pls2;
    layout(binding=3, rgba8) highp uniform ipixelLocalANGLE pls3;
    layout(binding=4, rgba8) highp uniform upixelLocalANGLE pls4;
    layout(binding=5, rgba8ui) highp uniform pixelLocalANGLE pls5;
    layout(binding=6, rgba8ui) highp uniform ipixelLocalANGLE pls6;
    layout(binding=7, rgba8i) highp uniform upixelLocalANGLE pls7;
    layout(binding=8, rgba8i) highp uniform pixelLocalANGLE pls8;
    layout(binding=9, r32f) highp uniform ipixelLocalANGLE pls9;
    layout(binding=10, r32f) highp uniform upixelLocalANGLE pls10;
    layout(binding=11, r32ui) highp uniform pixelLocalANGLE pls11;
    layout(binding=12, r32ui) highp uniform ipixelLocalANGLE pls12;
    layout(binding=13, r32i) highp uniform pixelLocalANGLE pls13;
    layout(binding=14, r32i) highp uniform upixelLocalANGLE pls14;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSInvalidTypeForFormat));
    EXPECT_TRUE(log.has(
        "ERROR: 0:3: 'layout qualifier' : pixel local storage requires a format specifier"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:4: 'layout qualifier' : pixel local storage requires a format specifier"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:5: 'layout qualifier' : pixel local storage requires a format specifier"));
    EXPECT_TRUE(
        log.has("ERROR: 0:6: 'rgba8' : pixel local storage format requires pixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:7: 'rgba8' : pixel local storage format requires pixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:8: 'rgba8ui' : pixel local storage format requires upixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:9: 'rgba8ui' : pixel local storage format requires upixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:10: 'rgba8i' : pixel local storage format requires ipixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:11: 'rgba8i' : pixel local storage format requires ipixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:12: 'r32f' : pixel local storage format requires pixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:13: 'r32f' : pixel local storage format requires pixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:14: 'r32ui' : pixel local storage format requires upixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:15: 'r32ui' : pixel local storage format requires upixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:16: 'r32i' : pixel local storage format requires ipixelLocalANGLE"));
    EXPECT_TRUE(
        log.has("ERROR: 0:17: 'r32i' : pixel local storage format requires ipixelLocalANGLE"));

    // PLS handles cannot have duplicate binding indices.
    constexpr char kPLSDuplicateBindings[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0, rgba) uniform highp pixelLocalANGLE pls0;
    layout(rgba8i, binding=1) uniform highp ipixelLocalANGLE pls1;
    layout(binding=2, rgba8ui) uniform highp upixelLocalANGLE pls2;
    layout(binding=1, rgba) uniform highp ipixelLocalANGLE pls3;
    layout(rgba8i, binding=0) uniform mediump ipixelLocalANGLE pls4;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSDuplicateBindings));
    EXPECT_TRUE(log.has("ERROR: 0:6: '1' : duplicate pixel local storage binding index"));
    EXPECT_TRUE(log.has("ERROR: 0:7: '0' : duplicate pixel local storage binding index"));

    // PLS handles cannot have duplicate binding indices.
    constexpr char kPLSIllegalLayoutQualifiers[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(foo) highp uniform pixelLocalANGLE pls1;
    layout(binding=0, location=0, rgba8ui) highp uniform upixelLocalANGLE pls2;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSIllegalLayoutQualifiers));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'foo' : invalid layout qualifier"));
    EXPECT_TRUE(
        log.has("ERROR: 0:4: 'location' : location must only be specified for a single input or "
                "output variable"));

    // Check that binding is not allowed in ES3, other than pixel local storage. ES3 doesn't have
    // blocks, and only has one opaque type: samplers. So we just need to make sure binding isn't
    // allowed on samplers.
    constexpr char kBindingOnSampler[] = R"(#version 300 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0) uniform mediump sampler2D sampler;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kBindingOnSampler));
    EXPECT_TRUE(
        log.has("ERROR: 0:3: 'binding' : invalid layout qualifier: only valid when used with pixel "
                "local storage"));

    // Binding qualifiers generate different error messages depending on ES3 and ES31.
    constexpr char kBindingOnOutput[] = R"(#version 310 es
    layout(binding=0) out mediump vec4 color;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kBindingOnOutput));
    EXPECT_TRUE(
        log.has("ERROR: 0:2: 'binding' : invalid layout qualifier: only valid when used with "
                "opaque types or blocks"));

    // Check that internalformats are not allowed in ES3 except for PLS.
    constexpr char kFormatOnSamplerES3[] = R"(#version 300 es
    layout(rgba8) uniform mediump sampler2D sampler1;
    layout(rgba8_snorm) uniform mediump sampler2D sampler2;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kFormatOnSamplerES3));
    EXPECT_TRUE(
        log.has("ERROR: 0:2: 'rgba8' : invalid layout qualifier: not supported before GLSL ES "
                "3.10, except pixel local storage"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:3: 'rgba8_snorm' : invalid layout qualifier: not supported before GLSL ES 3.10"));

    // Format qualifiers generate different error messages depending on whether they can be used
    // with PLS.
    constexpr char kFormatOnSamplerES31[] = R"(#version 310 es
    layout(rgba8) uniform mediump sampler2D sampler1;
    layout(rgba8_snorm) uniform mediump sampler2D sampler2;
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kFormatOnSamplerES31));
    EXPECT_TRUE(
        log.has("ERROR: 0:2: 'rgba8' : invalid layout qualifier: only valid when used with images "
                "or pixel local storage"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:3: 'rgba8_snorm' : invalid layout qualifier: only valid when used with images"));

    ASSERT_GL_NO_ERROR();
}

// Check proper validation of the discard statement when pixel local storage is(n't) declared.
TEST_P(PixelLocalStorageCompilerTest, Discard)
{
    // Discard is not allowed when pixel local storage has been declared. When polyfilled with
    // shader images, pixel local storage requires early_fragment_tests, which causes discard to
    // interact differently with the depth and stencil tests.
    //
    // To ensure identical behavior across all backends (some of which may not have access to
    // early_fragment_tests), we disallow discard if pixel local storage has been declared.
    constexpr char kDiscardWithPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;
    void a()
    {
        discard;
    }
    void b();
    void main()
    {
        if (gl_FragDepth == 3.14)
            discard;
        discard;
    }
    void b()
    {
        discard;
    })";
    EXPECT_FALSE(log.compileFragmentShader(kDiscardWithPLS));
    EXPECT_TRUE(
        log.has("ERROR: 0:6: 'discard' : illegal discard when pixel local storage is declared"));
    EXPECT_TRUE(
        log.has("ERROR: 0:12: 'discard' : illegal discard when pixel local storage is declared"));
    EXPECT_TRUE(
        log.has("ERROR: 0:13: 'discard' : illegal discard when pixel local storage is declared"));
    EXPECT_TRUE(
        log.has("ERROR: 0:17: 'discard' : illegal discard when pixel local storage is declared"));

    // Discard is OK when pixel local storage has _not_ been declared.
    constexpr char kDiscardNoPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void f(lowp pixelLocalANGLE pls);  // Function arguments don't trigger PLS restrictions.
    void a()
    {
        discard;
    }
    void b();
    void main()
    {
        if (gl_FragDepth == 3.14)
            discard;
        discard;
    }
    void b()
    {
        discard;
    })";
    EXPECT_TRUE(log.compileFragmentShader(kDiscardNoPLS));

    // Ensure discard is caught even if it happens before PLS is declared.
    constexpr char kDiscardBeforePLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void a()
    {
        discard;
    }
    void main()
    {
    }
    layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;)";
    EXPECT_FALSE(log.compileFragmentShader(kDiscardBeforePLS));
    EXPECT_TRUE(
        log.has("ERROR: 0:5: 'discard' : illegal discard when pixel local storage is declared"));

    ASSERT_GL_NO_ERROR();
}

// Check proper validation of the return statement when pixel local storage is(n't) declared.
TEST_P(PixelLocalStorageCompilerTest, Return)
{
    // Returning from main isn't allowed when pixel local storage has been declared.
    // (ARB_fragment_shader_interlock isn't allowed after return from main.)
    constexpr char kReturnFromMainWithPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;
    void main()
    {
        if (gl_FragDepth == 3.14)
            return;
        return;
    })";
    EXPECT_FALSE(log.compileFragmentShader(kReturnFromMainWithPLS));
    EXPECT_TRUE(log.has(
        "ERROR: 0:7: 'return' : illegal return from main when pixel local storage is declared"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:8: 'return' : illegal return from main when pixel local storage is declared"));

    // Returning from main is OK when pixel local storage has _not_ been declared.
    constexpr char kReturnFromMainNoPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void main()
    {
        if (gl_FragDepth == 3.14)
            return;
        return;
    })";
    EXPECT_TRUE(log.compileFragmentShader(kReturnFromMainNoPLS));

    // Returning from subroutines is OK when pixel local storage has been declared.
    constexpr char kReturnFromSubroutinesWithPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(rgba8ui, binding=0) highp uniform upixelLocalANGLE pls;
    void a()
    {
        return;
    }
    void b();
    void main()
    {
        a();
        b();
    }
    void b()
    {
        return;
    })";
    EXPECT_TRUE(log.compileFragmentShader(kReturnFromSubroutinesWithPLS));

    // Ensure return from main is caught even if it happens before PLS is declared.
    constexpr char kDiscardBeforePLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void main()
    {
        return;
    }
    layout(binding=0, rgba8) highp uniform pixelLocalANGLE pls;)";
    EXPECT_FALSE(log.compileFragmentShader(kDiscardBeforePLS));
    EXPECT_TRUE(log.has(
        "ERROR: 0:5: 'return' : illegal return from main when pixel local storage is declared"));

    ASSERT_GL_NO_ERROR();
}

// Check that gl_FragDepth(EXT) and gl_SampleMask are not assignable when PLS is declared.
TEST_P(PixelLocalStorageCompilerTest, FragmentTestVariables)
{
    // gl_FragDepth is not assignable when pixel local storage has been declared. When polyfilled
    // with shader images, pixel local storage requires early_fragment_tests, which causes
    // assignments to gl_FragDepth(EXT) and gl_SampleMask to be ignored.
    //
    // To ensure identical behavior across all backends, we disallow assignment to these values if
    // pixel local storage has been declared.
    constexpr char kAssignFragDepthWithPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void set(out mediump float x, mediump float val)
    {
        x = val;
    }
    void set2(inout mediump float x, mediump float val)
    {
        x = val;
    }
    void main()
    {
        gl_FragDepth = 0.0;
        gl_FragDepth -= 1.0;
        set(gl_FragDepth, 0.0);
        set2(gl_FragDepth, 0.1);
    }
    layout(binding=0, rgba8i) lowp uniform ipixelLocalANGLE pls;)";
    EXPECT_FALSE(log.compileFragmentShader(kAssignFragDepthWithPLS));
    EXPECT_TRUE(log.has(
        "ERROR: 0:13: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:14: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:15: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));
    EXPECT_TRUE(log.has(
        "ERROR: 0:16: 'gl_FragDepth' : value not assignable when pixel local storage is declared"));

    // Assigning gl_FragDepth is OK if we don't declare any PLS.
    constexpr char kAssignFragDepthNoPLS[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void f(highp ipixelLocalANGLE pls)
    {
        // Function arguments don't trigger PLS restrictions.
        pixelLocalStoreANGLE(pls, ivec4(8));
    }
    void set(out mediump float x, mediump float val)
    {
        x = val;
    }
    void main()
    {
        gl_FragDepth = 0.0;
        gl_FragDepth /= 2.0;
        set(gl_FragDepth, 0.0);
    })";
    EXPECT_TRUE(log.compileFragmentShader(kAssignFragDepthNoPLS));

    // Reading gl_FragDepth is OK.
    constexpr char kReadFragDepth[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(r32f, binding=0) highp uniform pixelLocalANGLE pls;
    highp vec4 get(in mediump float x)
    {
        return vec4(x);
    }
    void set(inout mediump float x, mediump float val)
    {
        x = val;
    }
    void main()
    {
        pixelLocalStoreANGLE(pls, get(gl_FragDepth));
        // Check when gl_FragDepth is involved in an l-value expression, but not assigned to.
        highp float x[2];
        x[int(gl_FragDepth)] = 1.0;
        set(x[1 - int(gl_FragDepth)], 2.0);
    })";
    EXPECT_TRUE(log.compileFragmentShader(kReadFragDepth));

    if (EnsureGLExtensionEnabled("GL_OES_sample_variables"))
    {
        // gl_SampleMask is not assignable when pixel local storage has been declared. The shader
        // image polyfill requires early_fragment_tests, which causes gl_SampleMask to be ignored.
        //
        // To ensure identical behavior across all implementations (some of which may not have
        // access to early_fragment_tests), we disallow assignment to these values if pixel local
        // storage has been declared.
        constexpr char kAssignSampleMaskWithPLS[] = R"(#version 310 es
        #extension GL_ANGLE_shader_pixel_local_storage : require
        #extension GL_OES_sample_variables : require
        void set(out highp int x, highp int val)
        {
            x = val;
        }
        void set2(inout highp int x, highp int val)
        {
            x = val;
        }
        void main()
        {
            gl_SampleMask[0] = 0;
            gl_SampleMask[0] ^= 1;
            set(gl_SampleMask[0], 9);
            set2(gl_SampleMask[0], 10);
        }
        layout(binding=0, rgba8i) highp uniform ipixelLocalANGLE pls;)";
        EXPECT_FALSE(log.compileFragmentShader(kAssignSampleMaskWithPLS));
        EXPECT_TRUE(
            log.has("ERROR: 0:14: 'gl_SampleMask' : value not assignable when pixel local storage "
                    "is declared"));
        EXPECT_TRUE(
            log.has("ERROR: 0:15: 'gl_SampleMask' : value not assignable when pixel local storage "
                    "is declared"));
        EXPECT_TRUE(
            log.has("ERROR: 0:16: 'gl_SampleMask' : value not assignable when pixel local storage "
                    "is declared"));
        EXPECT_TRUE(
            log.has("ERROR: 0:17: 'gl_SampleMask' : value not assignable when pixel local storage "
                    "is declared"));

        // Assigning gl_SampleMask is OK if we don't declare any PLS.
        constexpr char kAssignSampleMaskNoPLS[] = R"(#version 310 es
        #extension GL_ANGLE_shader_pixel_local_storage : require
        #extension GL_OES_sample_variables : require
        void set(out highp int x, highp int val)
        {
            x = val;
        }
        void main()
        {
            gl_SampleMask[0] = 0;
            gl_SampleMask[0] ^= 1;
            set(gl_SampleMask[0], 9);
        })";
        EXPECT_TRUE(log.compileFragmentShader(kAssignSampleMaskNoPLS));

        // Reading gl_SampleMask is OK enough (even though it's technically output only).
        constexpr char kReadSampleMask[] = R"(#version 310 es
        #extension GL_ANGLE_shader_pixel_local_storage : require
        #extension GL_OES_sample_variables : require
        layout(binding=0, rgba8i) highp uniform ipixelLocalANGLE pls;
        highp int get(in highp int x)
        {
            return x;
        }
        void set(out highp int x, highp int val)
        {
            x = val;
        }
        void main()
        {
            pixelLocalStoreANGLE(pls, ivec4(get(gl_SampleMask[0]), gl_SampleMaskIn[0], 0, 1));
            // Check when gl_SampleMask is involved in an l-value expression, but not assigned to.
            highp int x[2];
            x[gl_SampleMask[0]] = 1;
            set(x[gl_SampleMask[0]], 2);
        })";
        EXPECT_TRUE(log.compileFragmentShader(kReadSampleMask));
    }

    ASSERT_GL_NO_ERROR();
}

// Check that the "blend_support" layout qualifiers defined in KHR_blend_equation_advanced are
// illegal when PLS is declared.
TEST_P(PixelLocalStorageCompilerTest, BlendFuncExtended_illegal_with_PLS)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended"));

    // Just declaring the extension is ok.
    constexpr char kRequireBlendFuncExtended[] = R"(#version 300 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    #extension GL_EXT_blend_func_extended : require
    void main()
    {}
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;)";
    EXPECT_TRUE(log.compileFragmentShader(kRequireBlendFuncExtended));

    // The <index> layout qualifier from EXT_blend_func_extended is illegal.
    constexpr char kBlendFuncExtendedIndex[] = R"(#version 300 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    #extension GL_EXT_blend_func_extended : require
    layout(location=0, index=1) out lowp vec4 out1;
    void main()
    {}
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;)";
    EXPECT_FALSE(log.compileFragmentShader(kBlendFuncExtendedIndex));
    EXPECT_TRUE(
        log.has("ERROR: 0:4: 'layout' : illegal nonzero index qualifier when pixel local storage "
                "is declared"));

    // Multiple unassigned fragment output locations are illegal, even if EXT_blend_func_extended is
    // enabled.
    constexpr char kBlendFuncExtendedNoLocation[] = R"(#version 300 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    #extension GL_EXT_blend_func_extended : require
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
    out lowp vec4 out1;
    out lowp vec4 out0;
    void main()
    {})";
    EXPECT_FALSE(log.compileFragmentShader(kBlendFuncExtendedNoLocation));
    EXPECT_TRUE(
        log.has("'out1' : must explicitly specify all locations when using multiple fragment "
                "outputs and pixel local storage, even if EXT_blend_func_extended is enabled"));
    EXPECT_TRUE(
        log.has("'out0' : must explicitly specify all locations when using multiple fragment "
                "outputs and pixel local storage, even if EXT_blend_func_extended is enabled"));

    // index=0 is ok.
    constexpr char kValidFragmentIndex0[] = R"(#version 300 es
    #extension all : warn
    layout(binding=0, rgba8) uniform lowp pixelLocalANGLE plane1;
    layout(location=0, index=0) out lowp vec4 outColor0;
    layout(location=1, index=0) out lowp vec4 outColor1;
    layout(location=2, index=0) out lowp vec4 outColor2;
    void main()
    {})";
    EXPECT_TRUE(log.compileFragmentShader(kValidFragmentIndex0));
}

// Check that the "blend_support" layout qualifiers defined in KHR_blend_equation_advanced are
// illegal when PLS is declared.
TEST_P(PixelLocalStorageCompilerTest, BlendEquationAdvanced_illegal_with_PLS)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_KHR_blend_equation_advanced"));

    // Just declaring the extension is ok.
    constexpr char kRequireBlendAdvanced[] = R"(#version 300 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    #extension GL_KHR_blend_equation_advanced : require
    void main()
    {}
    layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";
    EXPECT_TRUE(log.compileFragmentShader(kRequireBlendAdvanced));

    bool before = true;
    for (const char *layoutQualifier : {
             "blend_support_multiply",
             "blend_support_screen",
             "blend_support_overlay",
             "blend_support_darken",
             "blend_support_lighten",
             "blend_support_colordodge",
             "blend_support_colorburn",
             "blend_support_hardlight",
             "blend_support_softlight",
             "blend_support_difference",
             "blend_support_exclusion",
             "blend_support_hsl_hue",
             "blend_support_hsl_saturation",
             "blend_support_hsl_color",
             "blend_support_hsl_luminosity",
             "blend_support_all_equations",
         })
    {
        constexpr char kRequireBlendAdvancedBeforePLS[] = R"(#version 300 es
        #extension GL_ANGLE_shader_pixel_local_storage : require
        #extension GL_KHR_blend_equation_advanced : require
        layout(%s) out;
        void main()
        {}
        layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;)";

        constexpr char kRequireBlendAdvancedAfterPLS[] = R"(#version 300 es
        #extension GL_ANGLE_shader_pixel_local_storage : require
        #extension GL_KHR_blend_equation_advanced : require
        layout(binding=0, rgba8i) uniform lowp ipixelLocalANGLE pls;
        layout(%s) out;
        void main()
        {})";

        const char *formatStr =
            before ? kRequireBlendAdvancedBeforePLS : kRequireBlendAdvancedAfterPLS;
        size_t buffSize = ANGLE_UNSAFE_TODO(snprintf(nullptr, 0, formatStr, layoutQualifier) +
                                            1);  // Extra space for '\0'
        std::unique_ptr<char[]> shader(new char[buffSize]);
        ANGLE_UNSAFE_TODO(std::snprintf(shader.get(), buffSize, formatStr, layoutQualifier));
        EXPECT_FALSE(log.compileFragmentShader(shader.get()));
        if (before)
        {
            EXPECT_TRUE(
                log.has("ERROR: 0:4: 'layout' : illegal advanced blend equation when pixel local "
                        "storage is declared"));
        }
        else
        {
            EXPECT_TRUE(
                log.has("ERROR: 0:5: 'layout' : illegal advanced blend equation when pixel local "
                        "storage is declared"));
        }

        before = !before;
    }
}

// Check proper validation of PLS function arguments.
TEST_P(PixelLocalStorageCompilerTest, FunctionArguments)
{
    // Ensure PLS handles can't be the result of complex expressions.
    constexpr char kPLSHandleComplexExpression[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    layout(rgba8, binding=0) mediump uniform pixelLocalANGLE pls0;
    layout(rgba8, binding=1) mediump uniform pixelLocalANGLE pls1;
    void clear(mediump pixelLocalANGLE pls)
    {
        pixelLocalStoreANGLE(pls, vec4(0));
    }
    void main()
    {
        highp float x = gl_FragDepth;
        clear(((x += 50.0) < 100.0) ? pls0 : pls1);
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSHandleComplexExpression));
    EXPECT_TRUE(log.has("ERROR: 0:12: '?:' : ternary operator is not allowed for opaque types"));

    // As function arguments, PLS handles cannot have layout qualifiers.
    constexpr char kPLSFnArgWithLayoutQualifiers[] = R"(#version 310 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void f(layout(rgba8, binding=1) mediump pixelLocalANGLE pls)
    {
    }
    void g(layout(rgba8) lowp pixelLocalANGLE pls);
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kPLSFnArgWithLayoutQualifiers));
    EXPECT_TRUE(log.has("ERROR: 0:3: 'layout' : only allowed at global scope"));
    EXPECT_TRUE(log.has("ERROR: 0:6: 'layout' : only allowed at global scope"));

    ASSERT_GL_NO_ERROR();
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageCompilerTest);
ANGLE_INSTANTIATE_TEST(PixelLocalStorageCompilerTest,
                       ES31_NULL().enable(Feature::EmulatePixelLocalStorage));

class PixelLocalStorageTestPreES3 : public ANGLETest<>
{
  public:
    PixelLocalStorageTestPreES3() { setExtensionsEnabled(false); }
};

// Check that GL_ANGLE_shader_pixel_local_storage is not advertised before ES 3.0.
TEST_P(PixelLocalStorageTestPreES3, UnsupportedClientVersion)
{
    EXPECT_FALSE(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage"));
    EXPECT_FALSE(EnsureGLExtensionEnabled("GL_ANGLE_shader_pixel_local_storage_coherent"));

    ShaderInfoLog log;

    constexpr char kRequireUnsupportedPLS[] = R"(#version 300 es
    #extension GL_ANGLE_shader_pixel_local_storage : require
    void main()
    {
    })";
    EXPECT_FALSE(log.compileFragmentShader(kRequireUnsupportedPLS));
    EXPECT_TRUE(
        log.has("ERROR: 0:2: 'GL_ANGLE_shader_pixel_local_storage' : extension is not supported"));

    ASSERT_GL_NO_ERROR();
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PixelLocalStorageTestPreES3);
ANGLE_INSTANTIATE_TEST(PixelLocalStorageTestPreES3,
                       ES2_NULL().enable(Feature::EmulatePixelLocalStorage));
