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

// MultisampleTest: Tests of multisampled default framebuffer

#include "test_utils/ANGLETest.h"

#include "test_utils/gl_raii.h"
#include "util/OSWindow.h"
#include "util/shader_utils.h"
#include "util/test_utils.h"

using namespace angle;

namespace
{

using MultisampleTestParams = std::tuple<angle::PlatformParameters, bool>;

std::string PrintToStringParamName(const ::testing::TestParamInfo<MultisampleTestParams> &info)
{
    ::std::stringstream ss;
    ss << std::get<0>(info.param);
    if (std::get<1>(info.param))
    {
        ss << "__NoStoreAndResolve";
    }
    return ss.str();
}

class MultisampleTest : public ANGLETestWithParam<MultisampleTestParams>
{
  protected:
    void testSetUp() override
    {
        const angle::PlatformParameters platform = ::testing::get<0>(GetParam());
        std::vector<const char *> disabledFeatures;
        if (::testing::get<1>(GetParam()))
        {
            disabledFeatures.push_back("allow_msaa_store_and_resolve");
        }
        disabledFeatures.push_back(nullptr);

        // Get display.
        EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, platform.getRenderer(),
                                 EGL_FEATURE_OVERRIDES_DISABLED_ANGLE,
                                 reinterpret_cast<EGLAttrib>(disabledFeatures.data()), EGL_NONE};
        mDisplay              = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE,
                                         reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
        ASSERT_TRUE(mDisplay != EGL_NO_DISPLAY);

        ASSERT_TRUE(eglInitialize(mDisplay, nullptr, nullptr) == EGL_TRUE);

        // Nexus 5X and 6P fail to eglMakeCurrent with a config they advertise they support.
        // http://anglebug.com/3464
        ANGLE_SKIP_TEST_IF(IsNexus5X());

        // Find a config that uses RGBA8 and allows 4x multisampling.
        const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                           EGL_WINDOW_BIT,
                                           EGL_RED_SIZE,
                                           8,
                                           EGL_GREEN_SIZE,
                                           8,
                                           EGL_BLUE_SIZE,
                                           8,
                                           EGL_ALPHA_SIZE,
                                           8,
                                           EGL_DEPTH_SIZE,
                                           24,
                                           EGL_STENCIL_SIZE,
                                           8,
                                           EGL_SAMPLE_BUFFERS,
                                           1,
                                           EGL_SAMPLES,
                                           4,
                                           EGL_NONE};

        EGLint configCount;
        EGLConfig multisampledConfig;
        EGLint ret =
            eglChooseConfig(mDisplay, configAttributes, &multisampledConfig, 1, &configCount);
        mMultisampledConfigExists = ret && configCount > 0;

        if (!mMultisampledConfigExists)
        {
            return;
        }

        // Create a window, context and surface if multisampling is possible.
        mOSWindow = OSWindow::New();
        mOSWindow->initialize("MultisampleTest", kWindowWidth, kWindowHeight);
        setWindowVisible(mOSWindow, true);

        EGLint contextAttributes[] = {
            EGL_CONTEXT_MAJOR_VERSION_KHR,
            platform.majorVersion,
            EGL_CONTEXT_MINOR_VERSION_KHR,
            platform.minorVersion,
            EGL_NONE,
        };

        mContext =
            eglCreateContext(mDisplay, multisampledConfig, EGL_NO_CONTEXT, contextAttributes);
        ASSERT_TRUE(mContext != EGL_NO_CONTEXT);

        mSurface = eglCreateWindowSurface(mDisplay, multisampledConfig,
                                          mOSWindow->getNativeWindow(), nullptr);
        ASSERT_EGL_SUCCESS();

        eglMakeCurrent(mDisplay, mSurface, mSurface, mContext);
        ASSERT_EGL_SUCCESS();
    }

    void testTearDown() override
    {
        if (mSurface)
        {
            eglSwapBuffers(mDisplay, mSurface);
        }

        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

        if (mSurface)
        {
            eglDestroySurface(mDisplay, mSurface);
            ASSERT_EGL_SUCCESS();
        }

        if (mContext != EGL_NO_CONTEXT)
        {
            eglDestroyContext(mDisplay, mContext);
            ASSERT_EGL_SUCCESS();
        }

        if (mOSWindow)
        {
            OSWindow::Delete(&mOSWindow);
        }

        eglTerminate(mDisplay);
    }

    void prepareVertexBuffer(GLBuffer &vertexBuffer,
                             const Vector3 *vertices,
                             size_t vertexCount,
                             GLint positionLocation)
    {
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(*vertices) * vertexCount, vertices, GL_STATIC_DRAW);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnableVertexAttribArray(positionLocation);
    }

  protected:
    static constexpr int kWindowWidth  = 16;
    static constexpr int kWindowHeight = 8;

    OSWindow *mOSWindow            = nullptr;
    EGLDisplay mDisplay            = EGL_NO_DISPLAY;
    EGLContext mContext            = EGL_NO_CONTEXT;
    EGLSurface mSurface            = EGL_NO_SURFACE;
    bool mMultisampledConfigExists = false;
};

class MultisampleTestES3 : public MultisampleTest
{};

// Test point rendering on a multisampled surface.  GLES2 section 3.3.1.
TEST_P(MultisampleTest, Point)
{
    ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
    // http://anglebug.com/3470
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES());
    // http://anglebug.com/5727
    ANGLE_SKIP_TEST_IF(IsOzone());

    constexpr char kPointsVS[] = R"(precision highp float;
attribute vec4 a_position;

void main()
{
    gl_PointSize = 3.0;
    gl_Position = a_position;
})";

    ANGLE_GL_PROGRAM(program, kPointsVS, essl1_shaders::fs::Red());
    glUseProgram(program);
    const GLint positionLocation = glGetAttribLocation(program, "a_position");

    GLBuffer vertexBuffer;
    const Vector3 vertices[1] = {{0.0f, 0.0f, 0.0f}};
    prepareVertexBuffer(vertexBuffer, vertices, 1, positionLocation);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_POINTS, 0, 1);

    ASSERT_GL_NO_ERROR();

    // The center pixels should be all red.
    EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2, kWindowHeight / 2, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2 - 1, kWindowHeight / 2, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2, kWindowHeight / 2 - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 2 - 1, kWindowHeight / 2 - 1, GLColor::red);

    // Border pixels should be between red and black, and not exactly either; corners are darker and
    // sides are brighter.
    const GLColor kSideColor   = {128, 0, 0, 128};
    const GLColor kCornerColor = {64, 0, 0, 64};
    constexpr int kErrorMargin = 16;
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2 - 2, kCornerColor,
                            kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2 + 1, kCornerColor,
                            kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2 - 2, kCornerColor,
                            kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2 + 1, kCornerColor,
                            kErrorMargin);

    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2 - 1, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 2, kWindowHeight / 2, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 1, kWindowHeight / 2 - 2, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 - 1, kWindowHeight / 2 + 1, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2, kWindowHeight / 2 - 2, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2, kWindowHeight / 2 + 1, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2 - 1, kSideColor, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2 + 1, kWindowHeight / 2, kSideColor, kErrorMargin);
}

// Test line rendering on a multisampled surface.  GLES2 section 3.4.4.
TEST_P(MultisampleTest, Line)
{
    ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
    ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    // http://anglebug.com/5727
    ANGLE_SKIP_TEST_IF(IsOzone());

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glUseProgram(program);
    const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());

    GLBuffer vertexBuffer;
    const Vector3 vertices[2] = {{-1.0f, -0.3f, 0.0f}, {1.0f, 0.3f, 0.0f}};
    prepareVertexBuffer(vertexBuffer, vertices, 2, positionLocation);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_LINES, 0, 2);

    ASSERT_GL_NO_ERROR();

    // The line goes from left to right at about -17 degrees slope.  It renders as such (captured
    // with renderdoc):
    //
    // D                    D = Dark Red (0.25) or (0.5)
    //  BRA                 R = Red (1.0)
    //     ARB              M = Middle Red (0.75)
    //        D             B = Bright Red (1.0 or 0.75)
    //                      A = Any red (0.5, 0.75 or 1.0)
    //
    // Verify that rendering is done as above.

    const GLColor kDarkRed     = {128, 0, 0, 128};
    const GLColor kMidRed      = {192, 0, 0, 192};
    constexpr int kErrorMargin = 16;
    constexpr int kLargeMargin = 80;

    static_assert(kWindowWidth == 16, "Verification code written for 16x8 window");
    EXPECT_PIXEL_COLOR_NEAR(0, 2, kDarkRed, kLargeMargin);
    EXPECT_PIXEL_COLOR_NEAR(3, 3, GLColor::red, kLargeMargin);
    EXPECT_PIXEL_COLOR_NEAR(4, 3, GLColor::red, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(6, 3, kMidRed, kLargeMargin);
    EXPECT_PIXEL_COLOR_NEAR(8, 4, kMidRed, kLargeMargin);
    EXPECT_PIXEL_COLOR_NEAR(11, 4, GLColor::red, kErrorMargin);
    EXPECT_PIXEL_COLOR_NEAR(12, 4, GLColor::red, kLargeMargin);
    EXPECT_PIXEL_COLOR_NEAR(15, 5, kDarkRed, kLargeMargin);
}

// Test polygon rendering on a multisampled surface.  GLES2 section 3.5.3.
TEST_P(MultisampleTest, Triangle)
{
    ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
    // http://anglebug.com/3470
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES());
    // http://anglebug.com/5727
    ANGLE_SKIP_TEST_IF(IsOzone());

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glUseProgram(program);
    const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());

    GLBuffer vertexBuffer;
    const Vector3 vertices[3] = {{-1.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}};
    prepareVertexBuffer(vertexBuffer, vertices, 3, positionLocation);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    ASSERT_GL_NO_ERROR();

    // Top-left pixels should be all red.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 4, kWindowHeight / 4, GLColor::red);

    // Diagonal pixels from bottom-left to top-right are between red and black.  Pixels above the
    // diagonal are red and pixels below it are black.
    const GLColor kMidRed = {128, 0, 0, 128};
    // D3D11 is off by 63 for red (191 instead of 128), where other back-ends get 128
    constexpr int kErrorMargin = 64;

    for (int i = 2; i + 2 < kWindowWidth; i += 2)
    {
        int j = kWindowHeight - 1 - (i / 2);
        EXPECT_PIXEL_COLOR_NEAR(i, j, kMidRed, kErrorMargin);
        EXPECT_PIXEL_COLOR_EQ(i, j - 1, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(i, j + 1, GLColor::transparentBlack);
    }
}

// Test polygon rendering on a multisampled surface. And rendering is interrupted by a compute pass
// that converts the index buffer. Make sure the rendering's multisample result is preserved after
// interruption.
TEST_P(MultisampleTest, ContentPresevedAfterInterruption)
{
    ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_rgb8_rgba8"));
    // http://anglebug.com/3470
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsNVIDIAShield() && IsOpenGLES());
    // http://anglebug.com/4609
    ANGLE_SKIP_TEST_IF(IsD3D11());
    // http://anglebug.com/5727
    ANGLE_SKIP_TEST_IF(IsOzone());

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glUseProgram(program);
    const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());

    if (IsGLExtensionEnabled("GL_EXT_discard_framebuffer"))
    {
        GLenum attachments[] = {GL_COLOR_EXT, GL_DEPTH_EXT, GL_STENCIL_EXT};
        glDiscardFramebufferEXT(GL_FRAMEBUFFER, 3, attachments);
    }
    // Draw triangle
    GLBuffer vertexBuffer;
    const Vector3 vertices[3] = {{-1.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}};
    prepareVertexBuffer(vertexBuffer, vertices, 3, positionLocation);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    ASSERT_GL_NO_ERROR();

    // Draw a line
    GLBuffer vertexBuffer2;
    GLBuffer indexBuffer2;
    const Vector3 vertices2[2] = {{-1.0f, -0.3f, 0.0f}, {1.0f, 0.3f, 0.0f}};
    const GLubyte indices2[]   = {0, 1};
    prepareVertexBuffer(vertexBuffer2, vertices2, 2, positionLocation);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer2);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);

    glDrawElements(GL_LINES, 2, GL_UNSIGNED_BYTE, 0);

    ASSERT_GL_NO_ERROR();

    // Top-left pixels should be all red.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kWindowWidth / 4, kWindowHeight / 4, GLColor::red);

    // Triangle edge:
    // Diagonal pixels from bottom-left to top-right are between red and black.  Pixels above the
    // diagonal are red and pixels below it are black.
    {
        const GLColor kMidRed      = {128, 0, 0, 128};
        constexpr int kErrorMargin = 16;

        for (int i = 2; i + 2 < kWindowWidth; i += 2)
        {
            // Exclude the middle pixel where the triangle and line cross each other.
            if (abs(kWindowHeight / 2 - (i / 2)) <= 1)
            {
                continue;
            }
            int j = kWindowHeight - 1 - (i / 2);
            EXPECT_PIXEL_COLOR_NEAR(i, j, kMidRed, kErrorMargin);
            EXPECT_PIXEL_COLOR_EQ(i, j - 1, GLColor::red);
            EXPECT_PIXEL_COLOR_EQ(i, j + 1, GLColor::transparentBlack);
        }
    }

    // Line edge:
    {
        const GLColor kDarkRed     = {128, 0, 0, 128};
        constexpr int kErrorMargin = 16;
        constexpr int kLargeMargin = 80;

        static_assert(kWindowWidth == 16, "Verification code written for 16x8 window");
        // Exclude the triangle region.
        EXPECT_PIXEL_COLOR_NEAR(11, 4, GLColor::red, kErrorMargin);
        EXPECT_PIXEL_COLOR_NEAR(12, 4, GLColor::red, kLargeMargin);
        EXPECT_PIXEL_COLOR_NEAR(15, 5, kDarkRed, kLargeMargin);
    }
}

// Test that alpha to coverage is enabled works properly along with early fragment test.
TEST_P(MultisampleTest, AlphaToSampleCoverage)
{
    ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
    // http://anglebug.com/5727
    ANGLE_SKIP_TEST_IF(IsOzone());

    constexpr char kFS[] =
        "precision highp float;\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(transparentRedProgram, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(transparentRedProgram);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glClearDepthf(1.0f);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);  // clear to green
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // This should pass depth test, but because of the alpha to coverage enabled, and alpha is 0,
    // the fragment should be discarded. If early fragment test is disabled, no depth will be
    // written. depth buffer should be 1.0.
    glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    // There was a bug in ANGLE that we are checking sampler coverage enabled or not instead of
    // alpha to sample coverage enabled or not. This is specically try to trick ANGLE so that it
    // will enable early fragment test. When early fragment test is accidentally enabled, then the
    // depth test will occur before fragment shader, and depth buffer maybe written with value
    // (0.0+1.0)/2.0=0.5.
    glEnable(GL_SAMPLE_COVERAGE);
    drawQuad(transparentRedProgram, essl1_shaders::PositionAttrib(), 0.0f);

    // Now draw with blue color but to test against 0.0f. This should fail depth test
    glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    glDisable(GL_SAMPLE_COVERAGE);
    glDepthFunc(GL_GREATER);
    ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    // Zd = 0.5f means (0.5+1.0)/2.0=0.75. Depends on early fragment on or off this will pass or
    // fail depth test.
    drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::green);

    ASSERT_GL_NO_ERROR();
}

// Test that resolve from multisample default framebuffer works.
TEST_P(MultisampleTestES3, ResolveToFBO)
{
    ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);

    GLTexture resolveTexture;
    glBindTexture(GL_TEXTURE_2D, resolveTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWindowWidth, kWindowHeight, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLFramebuffer resolveFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);

    // Clear the default framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClearColor(0.25, 0.5, 0.75, 0.25);
    glClear(GL_COLOR_BUFFER_BIT);

    // Resolve into FBO
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glBlitFramebuffer(0, 0, kWindowWidth, kWindowHeight, 0, 0, kWindowWidth, kWindowHeight,
                      GL_COLOR_BUFFER_BIT, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    const GLColor kResult = GLColor(63, 127, 191, 63);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, kResult, 1);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth - 1, 0, kResult, 1);
    EXPECT_PIXEL_COLOR_NEAR(0, kWindowHeight - 1, kResult, 1);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth - 1, kWindowHeight - 1, kResult, 1);
    EXPECT_PIXEL_COLOR_NEAR(kWindowWidth / 2, kWindowHeight / 2, kResult, 1);
}

class MultisampleResolveTest : public ANGLETest
{
  protected:
    static const GLColor kEXPECTED_R8;
    static const GLColor kEXPECTED_RG8;
    static const GLColor kEXPECTED_RGB8;
    static const GLColor kEXPECTED_RGBA8;
    static const GLColor32F kEXPECTED_RF;
    static const GLColor32F kEXPECTED_RGF;
    static const GLColor32F kEXPECTED_RGBF;
    static const GLColor32F kEXPECTED_RGBAF;
    static constexpr GLint kWidth  = 13;
    static constexpr GLint kHeight = 11;

    MultisampleResolveTest() {}

    struct GLResources
    {
        GLFramebuffer fb;
        GLRenderbuffer rb;
    };

    void resolveToFBO(GLenum format,
                      GLint samples,
                      GLint width,
                      GLint height,
                      GLResources &resources)
    {
        constexpr char kVS[] = R"(#version 300 es
        layout(location = 0) in vec4 position;
        void main() {
           gl_Position = position;
        }
        )";

        constexpr char kFS[] = R"(#version 300 es
        precision highp float;
        out vec4 color;
        void main() {
           color = vec4(0.5, 0.6, 0.7, 0.8);
        }
        )";

        ANGLE_GL_PROGRAM(program, kVS, kFS);

        // Make samples = 4 multi-sample framebuffer.
        GLFramebuffer fb0;
        glBindFramebuffer(GL_FRAMEBUFFER, fb0);

        GLRenderbuffer rb0;
        glBindRenderbuffer(GL_RENDERBUFFER, rb0);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Make samples = 0 multi-sample framebuffer.
        glBindFramebuffer(GL_FRAMEBUFFER, resources.fb);

        glBindRenderbuffer(GL_RENDERBUFFER, resources.rb);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 0, format, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                  resources.rb);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Draw quad to fb0.
        glBindFramebuffer(GL_FRAMEBUFFER, fb0);
        glViewport(0, 0, width, height);
        glUseProgram(program);
        GLBuffer buf;
        glBindBuffer(GL_ARRAY_BUFFER, buf);

        constexpr float vertices[] = {
            -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
        };
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Blit fb0 to fb1.
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fb0);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resources.fb);
        glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT,
                          GL_NEAREST);
        ASSERT_GL_NO_ERROR();

        // Prep for read pixels.
        glBindFramebuffer(GL_READ_FRAMEBUFFER, resources.fb);
    }

    void testResolveToUNormFBO(GLenum format,
                               const GLColor &expected_color,
                               GLint samples,
                               GLint width,
                               GLint height)
    {
        GLResources resources;
        resolveToFBO(format, samples, width, height, resources);

        // Check the results
        for (GLint y = 0; y < kHeight; ++y)
        {
            for (GLint x = 0; x < kWidth; ++x)
            {
                EXPECT_PIXEL_COLOR_NEAR(x, y, expected_color, 2);
            }
        }
        ASSERT_GL_NO_ERROR();
    }

    void testResolveToHalfFBO(GLenum format,
                              const GLColor32F &expected_color,
                              GLint samples,
                              GLint width,
                              GLint height)
    {
        if (!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"))
        {
            return;
        }

        GLResources resources;
        resolveToFBO(format, samples, width, height, resources);

        // Check the results
        for (GLint y = 0; y < kHeight; ++y)
        {
            for (GLint x = 0; x < kWidth; ++x)
            {
                EXPECT_PIXEL_COLOR32F_NEAR(x, y, expected_color, 2.0f / 255.0f);
            }
        }
        ASSERT_GL_NO_ERROR();
    }

    void testResolveToFloatFBO(GLenum format,
                               const GLColor32F &expected_color,
                               GLint samples,
                               GLint width,
                               GLint height)
    {
        if (!IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgba"))
        {
            return;
        }

        GLResources resources;
        resolveToFBO(format, samples, width, height, resources);

        // Check the results
        for (GLint y = 0; y < kHeight; ++y)
        {
            for (GLint x = 0; x < kWidth; ++x)
            {
                EXPECT_PIXEL_COLOR32F_NEAR(x, y, expected_color, 2.0f / 255.0f);
            }
        }
        ASSERT_GL_NO_ERROR();
    }

    void testResolveToRGBFloatFBO(GLenum format,
                                  const GLColor32F &expected_color,
                                  GLint samples,
                                  GLint width,
                                  GLint height)
    {
        if (!IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgb"))
        {
            return;
        }

        GLResources resources;
        resolveToFBO(format, samples, width, height, resources);

        // Check the results
        for (GLint y = 0; y < kHeight; ++y)
        {
            for (GLint x = 0; x < kWidth; ++x)
            {
                EXPECT_PIXEL_COLOR32F_NEAR(x, y, expected_color, 2.0f / 255.0f);
            }
        }
        ASSERT_GL_NO_ERROR();
    }

    void peelDepth(GLint colorLoc)
    {
        // Draw full quads from front to back and increasing depths
        // with depth test = less.
        glDepthMask(GL_FALSE);
        constexpr int steps = 64;
        for (int i = 0; i < steps; ++i)
        {
            float l = float(i) / float(steps);
            float c = l;
            float z = c * 2.0f - 1.0f;
            glVertexAttrib4f(1, 0, 0, z, 0);
            glUniform4f(colorLoc, c, c, c, c);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }
        glDepthMask(GL_TRUE);
    }

    void testResolveDepthToFBO(GLenum format,
                               GLenum attachment,
                               GLint samples,
                               GLint width,
                               GLint height)
    {
        constexpr char kVS[] = R"(#version 300 es
        layout(location = 0) in vec4 position;
        void main() {
           gl_Position = position;
        }
        )";

        constexpr char kFS[] = R"(#version 300 es
        precision highp float;
        out vec4 color;
        void main() {
           color = vec4(0.5, 0.6, 0.7, 0.8);
        }
        )";

        constexpr char kDepthVS[] = R"(#version 300 es
        layout(location = 0) in vec4 position;
        layout(location = 1) in vec4 offset;
        void main() {
           gl_Position = position + offset;
        }
        )";

        constexpr char kDepthFS[] = R"(#version 300 es
        precision highp float;
        uniform vec4 color;
        out vec4 outColor;
        void main() {
           outColor = color;
        }
        )";

        ANGLE_GL_PROGRAM(program, kVS, kFS);
        ANGLE_GL_PROGRAM(depthProgram, kDepthVS, kDepthFS);

        // Make samples = 4 multi-sample framebuffer.
        GLFramebuffer fb0;
        glBindFramebuffer(GL_FRAMEBUFFER, fb0);

        GLRenderbuffer rb0;
        glBindRenderbuffer(GL_RENDERBUFFER, rb0);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb0);

        GLRenderbuffer db0;
        glBindRenderbuffer(GL_RENDERBUFFER, db0);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, db0);

        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Make samples = 0 multi-sample framebuffer.
        GLFramebuffer fb1;
        glBindFramebuffer(GL_FRAMEBUFFER, fb1);

        GLRenderbuffer rb1;
        glBindRenderbuffer(GL_RENDERBUFFER, rb1);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb1);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        GLRenderbuffer db1;
        glBindRenderbuffer(GL_RENDERBUFFER, db1);
        glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, db1);

        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Draw quad to fb0.
        glBindFramebuffer(GL_FRAMEBUFFER, fb0);
        glViewport(0, 0, width, height);
        glClearColor(1, 1, 1, 1);
        glUseProgram(program);

        GLVertexArray va0;
        glBindVertexArray(va0);

        GLBuffer buf0;
        glBindBuffer(GL_ARRAY_BUFFER, buf0);

        // clang-format off
        constexpr float vertices[] = {
            -1.0f, -1.0f, -1.0,
             1.0f, -1.0f,  0.0,
            -1.0f,  1.0f,  0.0,
            -1.0f,  1.0f,  0.0,
             1.0f, -1.0f,  0.0,
             1.0f,  1.0f,  1.0,
        };
        // clang-format on

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Blit fb0 to fb1.
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fb0);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb1);
        glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_DEPTH_BUFFER_BIT,
                          GL_NEAREST);
        ASSERT_GL_NO_ERROR();

        GLVertexArray va1;
        glBindVertexArray(va1);

        // clang-format off
        constexpr float depthVertices[] = {
            -1.0f, -1.0f,
             1.0f, -1.0f,
            -1.0f,  1.0f,
            -1.0f,  1.0f,
             1.0f, -1.0f,
             1.0f,  1.0f,
        };
        // clang-format on

        GLBuffer buf1;
        glBindBuffer(GL_ARRAY_BUFFER, buf1);
        glBufferData(GL_ARRAY_BUFFER, sizeof(depthVertices), depthVertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);

        glUseProgram(depthProgram);
        GLint colorLoc = glGetUniformLocation(depthProgram, "color");

        // Extract the depth results.
        glBindFramebuffer(GL_FRAMEBUFFER, fb1);
        glClear(GL_COLOR_BUFFER_BIT);
        glDepthFunc(GL_LESS);
        peelDepth(colorLoc);
        std::vector<GLColor> actual(width * height);
        glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, actual.data());

        // Render what should be a similar result to the non-multi-sampled fb
        glBindVertexArray(va0);
        glDepthFunc(GL_ALWAYS);
        glUseProgram(program);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Extract the expected depth results.
        glBindVertexArray(va1);
        glUseProgram(depthProgram);
        glClear(GL_COLOR_BUFFER_BIT);
        glDepthFunc(GL_LESS);
        peelDepth(colorLoc);
        std::vector<GLColor> expected(width * height);
        glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, expected.data());

        for (size_t i = 0; i < expected.size(); ++i)
        {
            EXPECT_NEAR(expected[i].R, actual[i].R, 8)
                << "at " << (i % width) << "," << (i / width);
        }

        // Verify we read the depth buffer.
        const GLint minDimension = std::min(width, height);
        for (GLint i = 1; i < minDimension; ++i)
        {
            const GLColor &c1 = expected[i - 1];
            const GLColor &c2 = expected[i * width + i];
            EXPECT_LT(c1.R, c2.R);
        }
        ASSERT_GL_NO_ERROR();
    }
};

// Test the multisampled optimized resolve subpass
TEST_P(MultisampleResolveTest, DISABLED_ResolveSubpassMSImage)
{
    ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    // Draw green.
    drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
    swapBuffers();

    // Wait for visual verification.
    angle::Sleep(2000);
}

// These colors match the shader in resolveToFBO which returns (0.5, 0.6, 0.7, 0.8).
const GLColor MultisampleResolveTest::kEXPECTED_R8(128, 0, 0, 255);
const GLColor MultisampleResolveTest::kEXPECTED_RG8(128, 153, 0, 255);
const GLColor MultisampleResolveTest::kEXPECTED_RGB8(128, 153, 178, 255);
const GLColor MultisampleResolveTest::kEXPECTED_RGBA8(128, 153, 178, 204);
const GLColor32F MultisampleResolveTest::kEXPECTED_RF(0.5f, 0.0f, 0.0f, 1.0f);
const GLColor32F MultisampleResolveTest::kEXPECTED_RGF(0.5f, 0.6f, 0.0f, 1.0f);
const GLColor32F MultisampleResolveTest::kEXPECTED_RGBF(0.5f, 0.6f, 0.7f, 1.0f);
const GLColor32F MultisampleResolveTest::kEXPECTED_RGBAF(0.5f, 0.6f, 0.7f, 0.8f);

// Test we can render to and resolve an RGBA8 renderbuffer
TEST_P(MultisampleResolveTest, ResolveRGBA8ToFBO4Samples)
{
    testResolveToUNormFBO(GL_RGBA8, kEXPECTED_RGBA8, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RGB8 renderbuffer
TEST_P(MultisampleResolveTest, ResolveRGB8ToFBO4Samples)
{
    testResolveToUNormFBO(GL_RGB8, kEXPECTED_RGB8, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RG8 renderbuffer
TEST_P(MultisampleResolveTest, ResolveRG8ToFBO4Samples)
{
    testResolveToUNormFBO(GL_RG8, kEXPECTED_RG8, 4, kWidth, kHeight);
}

// Test we can render to and resolve an R8 renderbuffer
TEST_P(MultisampleResolveTest, ResolveR8ToFBO4Samples)
{
    testResolveToUNormFBO(GL_R8, kEXPECTED_R8, 4, kWidth, kHeight);
}

// Test we can render to and resolve an R16F renderbuffer
TEST_P(MultisampleResolveTest, ResolveR16FToFBO4Samples)
{
    testResolveToHalfFBO(GL_R16F, kEXPECTED_RF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RG16F renderbuffer
TEST_P(MultisampleResolveTest, ResolveRG16FToFBO4Samples)
{
    testResolveToHalfFBO(GL_RG16F, kEXPECTED_RGF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RGB16F renderbuffer
TEST_P(MultisampleResolveTest, ResolveRGB16FToFBO4Samples)
{
    testResolveToHalfFBO(GL_RGB16F, kEXPECTED_RGBF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RGBA16F renderbuffer
TEST_P(MultisampleResolveTest, ResolveRGBA16FToFBO4Samples)
{
    testResolveToHalfFBO(GL_RGBA16F, kEXPECTED_RGBAF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an R32F renderbuffer
TEST_P(MultisampleResolveTest, ResolveR32FToFBO4Samples)
{
    testResolveToFloatFBO(GL_R32F, kEXPECTED_RF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RG32F renderbuffer
TEST_P(MultisampleResolveTest, ResolveRG32FToFBO4Samples)
{
    testResolveToFloatFBO(GL_RG32F, kEXPECTED_RGF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RGB32F renderbuffer
TEST_P(MultisampleResolveTest, ResolveRGB32FToFBO4Samples)
{
    testResolveToRGBFloatFBO(GL_RGB32F, kEXPECTED_RGBF, 4, kWidth, kHeight);
}

// Test we can render to and resolve an RGBA32F renderbuffer
TEST_P(MultisampleResolveTest, ResolveRGBA32FToFBO4Samples)
{
    testResolveToFloatFBO(GL_RGBA32F, kEXPECTED_RGBAF, 4, kWidth, kHeight);
}

TEST_P(MultisampleResolveTest, ResolveD32FS8F4Samples)
{
    testResolveDepthToFBO(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, 4, kWidth, kHeight);
}

TEST_P(MultisampleResolveTest, ResolveD24S8Samples)
{
    testResolveDepthToFBO(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT, 4, kWidth, kHeight);
}

TEST_P(MultisampleResolveTest, ResolveD32FSamples)
{
    testResolveDepthToFBO(GL_DEPTH_COMPONENT32F, GL_DEPTH_ATTACHMENT, 4, kWidth, kHeight);
}

TEST_P(MultisampleResolveTest, ResolveD24Samples)
{
    testResolveDepthToFBO(GL_DEPTH_COMPONENT24, GL_DEPTH_ATTACHMENT, 4, kWidth, kHeight);
}

TEST_P(MultisampleResolveTest, ResolveD16Samples)
{
    testResolveDepthToFBO(GL_DEPTH_COMPONENT16, GL_DEPTH_ATTACHMENT, 4, kWidth, kHeight);
}

void drawRectAndBlit(GLuint msFramebuffer,
                     GLuint resolveFramebuffer,
                     GLint width,
                     GLint height,
                     GLint matLoc,
                     GLint colorLoc,
                     float x,
                     float y,
                     float w,
                     float h,
                     const GLColor &color)
{
    glBindFramebuffer(GL_FRAMEBUFFER, msFramebuffer);
    float matrix[16] = {
        w, 0, 0, 0, 0, h, 0, 0, 0, 0, 1, 0, x, y, 0, 1,
    };
    glUniformMatrix4fv(matLoc, 1, false, matrix);
    angle::Vector4 c(color.toNormalizedVector());
    glUniform4f(colorLoc, c[0], c[1], c[2], c[3]);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFramebuffer);
    glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}

// Tests if we resolve(blit) a multisample renderbuffer that it
// does not lose its contents.
TEST_P(MultisampleResolveTest, DrawAndResolveMultipleTimes)
{
    constexpr GLint samples = 4;
    constexpr GLenum format = GL_RGBA8;
    constexpr GLint width   = 16;
    constexpr GLint height  = 16;

    constexpr char kVS[] = R"(#version 300 es
    layout(location = 0) in vec4 position;
    uniform mat4 mat;
    void main() {
       gl_Position = mat * position;
    }
    )";

    constexpr char kFS[] = R"(#version 300 es
    precision highp float;
    uniform vec4 color;
    out vec4 outColor;
    void main() {
       outColor = color;
    }
    )";

    glViewport(0, 0, width, height);

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    GLint matLoc   = glGetUniformLocation(program, "mat");
    GLint colorLoc = glGetUniformLocation(program, "color");
    glUseProgram(program);

    GLBuffer buf;
    glBindBuffer(GL_ARRAY_BUFFER, buf);

    constexpr float vertices[] = {
        0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);

    // Make samples = 4 multi-sample framebuffer.
    GLFramebuffer msFB;
    glBindFramebuffer(GL_FRAMEBUFFER, msFB);

    GLRenderbuffer msRB;
    glBindRenderbuffer(GL_RENDERBUFFER, msRB);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msRB);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Make non-multi-sample framebuffer.
    GLFramebuffer drawFB;
    glBindFramebuffer(GL_FRAMEBUFFER, drawFB);

    GLRenderbuffer drawRB;
    glBindRenderbuffer(GL_RENDERBUFFER, drawRB);
    glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, drawRB);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, -1, -1, 2, 2, GLColor::red);
    drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, 0, -1, 1, 1, GLColor::green);
    drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, -1, 0, 1, 1, GLColor::blue);
    drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, 0, 0, 1, 1, GLColor::yellow);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawRectAndBlit(msFB, drawFB, width, height, matLoc, colorLoc, -0.5, -0.5, 1, 1,
                    GLColor(0x80, 0x80, 0x80, 0x80));
    glDisable(GL_BLEND);
    ASSERT_GL_NO_ERROR();

    /*
       expected
       +-------------+--------------+
       | blue        |       yellow |
       |   +---------+----------+   |
       |   |.5,.5,1,1| 1,1,.5,1 |   |
       +---+---------+----------+---+
       |   |1,.5,.5,1| .5,1,.5,1|   |
       |   +---------+----------+   |
       | red         |        green |
       +-------------+--------------+
      0,0
    */

    glBindFramebuffer(GL_FRAMEBUFFER, drawFB);
    EXPECT_PIXEL_RECT_EQ(0, 0, width / 2, height / 4, GLColor::red);
    EXPECT_PIXEL_RECT_EQ(width / 2, 0, width / 2, height / 4, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(0, height * 3 / 4, width / 2, height / 4, GLColor::blue);
    EXPECT_PIXEL_RECT_EQ(width / 2, height * 3 / 4, width / 2, height / 4, GLColor::yellow);

    EXPECT_PIXEL_RECT_EQ(width / 4, height / 4, width / 4, height / 4, GLColor(255, 128, 128, 255));
    EXPECT_PIXEL_RECT_EQ(width / 2, height / 4, width / 4, height / 4, GLColor(128, 255, 128, 255));
    EXPECT_PIXEL_RECT_EQ(width / 4, height / 2, width / 4, height / 4, GLColor(128, 128, 255, 255));
    EXPECT_PIXEL_RECT_EQ(width / 2, height / 2, width / 4, height / 4, GLColor(255, 255, 128, 255));
}

ANGLE_INSTANTIATE_TEST_COMBINE_1(MultisampleTest,
                                 PrintToStringParamName,
                                 testing::Values(false),
                                 WithNoFixture(ES2_D3D11()),
                                 WithNoFixture(ES3_D3D11()),
                                 WithNoFixture(ES31_D3D11()),
                                 WithNoFixture(ES2_METAL()),
                                 WithNoFixture(ES2_OPENGL()),
                                 WithNoFixture(ES3_OPENGL()),
                                 WithNoFixture(ES31_OPENGL()),
                                 WithNoFixture(ES2_OPENGLES()),
                                 WithNoFixture(ES3_OPENGLES()),
                                 WithNoFixture(ES31_OPENGLES()),
                                 WithNoFixture(ES2_VULKAN()),
                                 WithNoFixture(ES3_VULKAN()),
                                 WithNoFixture(ES31_VULKAN()));

namespace store_and_resolve_feature_off
{
// Simulate missing msaa auto resolve feature in Metal.
ANGLE_INSTANTIATE_TEST_COMBINE_1(MultisampleTest,
                                 PrintToStringParamName,
                                 testing::Values(true),
                                 WithNoFixture(ES2_METAL()));
}  // namespace store_and_resolve_feature_off

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultisampleTestES3);
ANGLE_INSTANTIATE_TEST_COMBINE_1(MultisampleTestES3,
                                 PrintToStringParamName,
                                 testing::Values(false),
                                 WithNoFixture(ES3_D3D11()),
                                 WithNoFixture(ES31_D3D11()),
                                 WithNoFixture(ES3_OPENGL()),
                                 WithNoFixture(ES31_OPENGL()),
                                 WithNoFixture(ES3_OPENGLES()),
                                 WithNoFixture(ES31_OPENGLES()),
                                 WithNoFixture(ES3_VULKAN()),
                                 WithNoFixture(ES31_VULKAN()),
                                 WithNoFixture(ES3_METAL()));

ANGLE_INSTANTIATE_TEST_ES3(MultisampleResolveTest);

}  // anonymous namespace
