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

// MultiviewMultisampledRenderToTextureTest:
// Tests of OVR_multiview_multisampled_render_to_texture extension

#include <tuple>
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"

using namespace angle;

namespace
{
constexpr int kWindowSize = 6;
class MultiviewMSRTTTest : public ANGLETest<>
{
  protected:
    MultiviewMSRTTTest()
    {
        setWindowWidth(kWindowSize);
        setWindowHeight(kWindowSize);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void testSetUp() override
    {
        if (getClientMajorVersion() >= 3)
        {
            glGetIntegerv(GL_MAX_SAMPLES, &mMaxIntegerSamples);
            glGetIntegerv(GL_MAX_VIEWS_OVR, &mMaxViewsOVR);
            glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &mMaxArrayTextureLayers);
        }
    }

    GLint mMaxIntegerSamples     = 0;
    GLint mMaxViewsOVR           = 0;
    GLint mMaxArrayTextureLayers = 0;
};

class MultiviewMSRTTES3Test : public MultiviewMSRTTTest
{};

// Test that INVALID_FRAMEBUFFER_OPERATION is generated by glReadPixels() command if the number
// of views in the current read framebuffer is greater than 1
TEST_P(MultiviewMSRTTES3Test, ReadPixelsFromMultiviewFramebufferShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 2);
    ASSERT_GL_NO_ERROR();
    std::vector<GLColor> pixels(kWindowSize * kWindowSize * numLayers);
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
    EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
}

// Test that INVALID_FRAMEBUFFER_OPERATION is generated by glCopyTexImage2D() command if the number
// of views in the current read framebuffer is greater than 1
TEST_P(MultiviewMSRTTES3Test, CopyTexImageFromMultiviewFramebufferShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 2);
    ASSERT_GL_NO_ERROR();
    GLTexture copyImageTargetTexture;
    glBindTexture(GL_TEXTURE_2D, copyImageTargetTexture);
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, kWindowSize, kWindowSize, 0);
    EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
}

// Test that INVALID_FRAMEBUFFER_OPERATION is generated by glCopyTexSubImage() command if the number
// of views in the current read framebuffer is greater than 1
TEST_P(MultiviewMSRTTES3Test, CopyTexSubImageFromMultiviewFramebufferShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 2);
    ASSERT_GL_NO_ERROR();
    GLTexture copySubImageTargetTexture;
    glBindTexture(GL_TEXTURE_2D, copySubImageTargetTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWindowSize, kWindowSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 nullptr);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kWindowSize, kWindowSize);
    EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
}

// Test that INVALID_FRAMEBUFFER_OPERATION is generated by glCopyTexSubImage3D() command if the
// number of views in the current read framebuffer is greater than 1
TEST_P(MultiviewMSRTTES3Test, CopyTexSubImage3DFromMultiviewFramebufferShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 2);
    ASSERT_GL_NO_ERROR();
    GLTexture copySubImage3DTargetTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, copySubImage3DTargetTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, kWindowSize, kWindowSize);
    EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
}

// Test that INVALID_VALUE is generated by FramebufferTextureMultisampleMultiviewOVR if numViews is
// less than 1
TEST_P(MultiviewMSRTTES3Test, NumViewsLessThanOneShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 0);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that INVALID_VALUE is generated by FramebufferTextureMultisampleMultiviewOVR if numViews is
// more than MAX_VIEWS_OVR
TEST_P(MultiviewMSRTTES3Test, NumViewsGreaterThanMaxShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0,
                                                mMaxViewsOVR + 1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that INVALID_VALUE is generated by FramebufferTextureMultisampleMultiviewOVR if
// (baseViewIndex + numViews) exceeds GL_MAX_ARRAY_TEXTURE_LAYERS
TEST_P(MultiviewMSRTTES3Test, BaseViewIndexPlusNumViewsExceedsMaxArrayTextureLayersShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize,
                                                mMaxArrayTextureLayers, 1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that INVALID_VALUE is generated by FramebufferTextureMultisampleMultiviewOVR if samples is
// greater than MAX_SAMPLES
TEST_P(MultiviewMSRTTES3Test, SamplesGreaterThanMaxSamplesShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers = 2;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                mMaxIntegerSamples + 1, 0, 2);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that INVALID_OPERATION is generated if a rendering command is issued and the number
// of views in the current draw framebuffer is not equal to the number of views declared in
// the currently bound program.
TEST_P(MultiviewMSRTTES3Test, ProgramViewMismatchFramebufferViewShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    // Create a program with 2 views
    const std::string vsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 2) in;
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
        }
    )";
    const std::string fsSource = R"(#version 300 es
        precision mediump float;
        out vec4 FragColor;
        void main()
        {
            FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    )";

    ANGLE_GL_PROGRAM(program, vsSource.c_str(), fsSource.c_str());
    glUseProgram(program);

    // Create a framebuffer with 1 view
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 1;  // 1 view in framebuffer
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 1);
    ASSERT_GL_NO_ERROR();

    // Draw with program (2 views) and framebuffer (1 view) mismatch
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that INVALID_OPERATION is generated if the target type of <texture> specified in
// FramebufferTextureMultisampleMultiviewOVR is not TEXTURE_2D_ARRAY
TEST_P(MultiviewMSRTTES3Test, InvalidTextureTargetShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);  // Bind to GL_TEXTURE_2D instead of GL_TEXTURE_2D_ARRAY
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWindowSize, kWindowSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that calling glFramebufferTextureMultisampleMultiviewOVR() on both GL_COLOR_ATTACHMENT0
// and GL_DEPTH_ATTACHMENT works, and that drawing to it works.
TEST_P(MultiviewMSRTTES3Test, ColorAndDepthAttachment)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    const std::string vsSource = R"(#version 300 es
    #extension GL_OVR_multiview : require
    layout(num_views = 2) in;
    in vec4 a_position;
    void main()
    {
        gl_Position = a_position;
    }
)";
    const std::string fsSource = R"(#version 300 es
    #extension GL_OVR_multiview : require
    precision mediump float;
    out vec4 FragColor;
    void main()
    {
        if (gl_ViewID_OVR == 0u)
        {
            FragColor = vec4(0.0, 1.0, 0.0, 1.0);
            gl_FragDepth = 0.25;
        }
        else
        {
            FragColor = vec4(1.0, 0.0, 0.0, 1.0);
            gl_FragDepth = 0.75;
        }
    }
)";
    ANGLE_GL_PROGRAM(program, vsSource.c_str(), fsSource.c_str());
    glUseProgram(program);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numViews                                 = 2;
    constexpr int baseViewIndex                            = 0;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    // Color attachment
    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture,
                                                0, multisampleRenderToTextureSampleSize,
                                                baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();

    // Depth attachment
    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, depthTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, kWindowSize, kWindowSize, numViews,
                 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture,
                                                0, multisampleRenderToTextureSampleSize,
                                                baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glEnable(GL_DEPTH_TEST);
    glClearDepthf(1.0f);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    ASSERT_GL_NO_ERROR();

    // Verification
    GLFramebuffer verifyFbo;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);

    // Verify view 0
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture, 0,
                              baseViewIndex + 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::green);

    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0,
                              baseViewIndex + 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    float depth0 = 0.0f;
    glReadPixels(0, 0, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth0);
    EXPECT_NEAR(1.0f, depth0, 1e-6);
    glReadPixels(kWindowSize / 2, kWindowSize / 2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth0);
    EXPECT_NEAR(0.25, depth0, 1e-6);

    // Verify view 1
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::red);

    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    float depth1 = 0.0f;
    glReadPixels(0, 0, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth1);
    EXPECT_NEAR(1.0f, depth1, 1e-6);
    glReadPixels(kWindowSize / 2, kWindowSize / 2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth1);
    EXPECT_NEAR(0.75, depth1, 1e-6);

    ASSERT_GL_NO_ERROR();
}

// Test that an INVALID_OPERATION error is generated if samples is greater than the maximum number
// of samples supported for target and its internalformat.
TEST_P(MultiviewMSRTTES3Test, SamplesGreaterThanMaxSamplesForTextureFormatShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    GLint maxSamplesForFormat = 0;
    glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, GL_RGBA8, GL_SAMPLES, 1,
                          &maxSamplesForFormat);
    INFO() << "maxSamplesForFormat: " << maxSamplesForFormat;
    ASSERT_GL_NO_ERROR();

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers = 2;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                maxSamplesForFormat + 1, 0, 2);

    if (maxSamplesForFormat < mMaxIntegerSamples)
    {
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    }
    else
    {
        // GL_INVALID_VALUE error code is generated if samples > mMaxIntegerSamples
        EXPECT_GL_ERROR(GL_INVALID_VALUE);
    }
}

// Test that passing samples 0 to glFramebufferTextureMultisampleMultiviewOVR() works and
// produces the same result as glFramebufferTextureMultiviewOVR.
TEST_P(MultiviewMSRTTES3Test, SamplesZeroWorks)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    const std::string vsSource = R"(#version 300 es
    #extension GL_OVR_multiview : require
    layout(num_views = 2) in;
    in vec4 a_position;
    void main()
    {
        gl_Position = a_position;
    }
)";
    const std::string fsSource = R"(#version 300 es
    #extension GL_OVR_multiview : require
    precision mediump float;
    out vec4 FragColor;
    void main()
    {
        if (gl_ViewID_OVR == 0u)
        {
            FragColor = vec4(0.0, 1.0, 0.0, 1.0);
        }
        else
        {
            FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    }
)";
    ANGLE_GL_PROGRAM(program, vsSource.c_str(), fsSource.c_str());
    glUseProgram(program);

    constexpr int numViews      = 2;
    constexpr int baseViewIndex = 0;

    // FBO 0: Use glFramebufferTextureMultisampleMultiviewOVR with 0 samples
    GLFramebuffer fbo0;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo0);
    GLTexture colorTexture0;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture0);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0,
                                                0, 0, baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    ASSERT_GL_NO_ERROR();

    // FBO 1: Use glFramebufferTextureMultiviewOVR
    GLFramebuffer fbo1;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    GLTexture colorTexture1;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture1);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture1, 0,
                                     baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    ASSERT_GL_NO_ERROR();

    // Verification: Compare colorTexture0 and colorTexture1
    GLFramebuffer verifyFbo;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);

    std::vector<GLColor> pixels0(kWindowSize * kWindowSize);
    std::vector<GLColor> pixels1(kWindowSize * kWindowSize);

    for (int i = 0; i < numViews; ++i)
    {
        // Read from colorTexture0
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0, 0,
                                  baseViewIndex + i);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        glReadPixels(0, 0, kWindowSize, kWindowSize, GL_RGBA, GL_UNSIGNED_BYTE, pixels0.data());
        ASSERT_GL_NO_ERROR();

        // Read from colorTexture1
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture1, 0,
                                  baseViewIndex + i);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        glReadPixels(0, 0, kWindowSize, kWindowSize, GL_RGBA, GL_UNSIGNED_BYTE, pixels1.data());
        ASSERT_GL_NO_ERROR();

        // Compare
        EXPECT_EQ(pixels0, pixels1);
    }

    ASSERT_GL_NO_ERROR();
}

// Test that after calling glFramebufferTextureMultisampleMultiviewOVR, the resulting value for
// TEXTURE_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more than the
// next larger sample count supported by the implementation.
TEST_P(MultiviewMSRTTES3Test, FrameBufferTextureSamplesExtValue)
{

    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numViews             = 2;
    constexpr int baseViewIndex        = 0;
    constexpr GLsizei requestedSamples = 2;  // Request 2 samples
    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture,
                                                0, requestedSamples, baseViewIndex, numViews);

    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Query TEXTURE_SAMPLES_EXT from the framebuffer attachment
    GLint actualSamples = 0;
    glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                          GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT,
                                          &actualSamples);
    ASSERT_GL_NO_ERROR();

    // Get supported sample counts for GL_RGBA8
    GLint numSampleCounts = 0;
    glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1,
                          &numSampleCounts);
    ASSERT_GL_NO_ERROR();
    std::vector<GLint> supportedSampleCounts(numSampleCounts);
    glGetInternalformativ(GL_RENDERBUFFER, GL_RGBA8, GL_SAMPLES, numSampleCounts,
                          supportedSampleCounts.data());
    ASSERT_GL_NO_ERROR();

    // Sort supported sample counts in ascending order
    std::sort(supportedSampleCounts.begin(), supportedSampleCounts.end());
    // Find the smallest supported sample count that is >= requestedSamples
    GLint expectedMaxSamples = 0;
    if (requestedSamples == 0)
    {
        expectedMaxSamples = 0;
    }
    else
    {
        auto it = std::lower_bound(supportedSampleCounts.begin(), supportedSampleCounts.end(),
                                   requestedSamples);
        ASSERT(it != supportedSampleCounts.end());
        expectedMaxSamples = *it;
    }
    // Verify the conditions
    EXPECT_GE(actualSamples, requestedSamples);
    EXPECT_LE(actualSamples, expectedMaxSamples);
}

// Test that glReadPixels() does not return an error if the GL_SAMPLE_BUFFERS of read framebuffer is
// 1, when the read framebuffer attachment is attached with
// glFramebufferTextureMultisampleMultiviewOVR()
TEST_P(MultiviewMSRTTES3Test, ReadPixelsFromSingleViewFramebufferShouldSucceed)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numLayers                                = 1;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;
    std::vector<GLubyte> textureData;
    textureData.resize(kWindowSize * kWindowSize * numLayers * 4, 0u);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0,
                                                multisampleRenderToTextureSampleSize, 0, 1);
    ASSERT_GL_NO_ERROR();

    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
    GLint sampleBuffers = 0;
    glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
    if (sampleBuffers == 1)
    {
        std::vector<GLColor> pixels(kWindowSize * kWindowSize * numLayers);
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
        EXPECT_GL_NO_ERROR();
    }
}

// Test that framebuffer succeed with draw operations if the attachments are created with
// glFramebufferTextureMultisampleMultiviewOVR() and have the same render-to-texture sample counts
TEST_P(MultiviewMSRTTES3Test, FBOAttachmentsWithSameSampleCountShouldSucceed)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));
    constexpr GLsizei multisampleRenderToTextureSampleSize4 = 4;
    ANGLE_SKIP_TEST_IF(mMaxIntegerSamples <= multisampleRenderToTextureSampleSize4);

    constexpr int numViews      = 2;
    constexpr int baseViewIndex = 0;

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Attach msrtt-multiview (4 samples) texture to COLOR_ATTACHMENT0
    GLTexture colorTexture0;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture0);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0,
                                                0, multisampleRenderToTextureSampleSize4,
                                                baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();

    // Attach msrtt-multiview (4 samples) texture to COLOR_ATTACHMENT1
    GLTexture colorTexture1;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture1);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, colorTexture1,
                                                0, multisampleRenderToTextureSampleSize4,
                                                baseViewIndex, numViews);

    const std::string vsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 2) in;
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
        }
    )";
    const std::string fsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        precision mediump float;
        layout(location = 0)out vec4 FragColor0;
        layout(location = 1)out vec4 FragColor1;
        void main()
        {
            if (gl_ViewID_OVR == 0u) {
                FragColor0 = vec4(1.0, 0.0, 0.0, 1.0); // Red for view 0 GL_COLOR_ATTACHMENT0
                FragColor1 = vec4(1.0, 1.0, 0.0, 1.0); // Yellow for view 0 GL_COLOR_ATTACHMENT1
            } else {
                FragColor0 = vec4(0.0, 1.0, 0.0, 1.0); // Green for view 1 GL_COLOR_ATTACHMENT0
                FragColor1 = vec4(0.0, 0.0, 1.0, 1.0); // Blue for view 1 GL_COLOR_ATTACHMENT1
            }
        }
    )";

    ANGLE_GL_PROGRAM(program, vsSource.c_str(), fsSource.c_str());
    glUseProgram(program);

    GLenum buffers[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
    glDrawBuffers(2, buffers);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw a quad
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    ASSERT_GL_NO_ERROR();

    // Verify draw result
    GLFramebuffer verifyFbo;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0, 0,
                              baseViewIndex);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::red);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::green);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture1, 0,
                              baseViewIndex);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::yellow);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture1, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::blue);

    // Now attach with a different render-to-texture sample count
    // Attach msrtt-multiview (mMaxIntegerSamples samples) texture to COLOR_ATTACHMENT0
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    GLTexture colorTexture3;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture3);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture3,
                                                0, mMaxIntegerSamples, baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();

    // Attach msrtt-multiview (mMaxIntegerSamples samples) texture to COLOR_ATTACHMENT1
    GLTexture colorTexture4;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture4);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, colorTexture4,
                                                0, mMaxIntegerSamples, baseViewIndex, numViews);
    // Draw a quad
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    ASSERT_GL_NO_ERROR();

    // Verify draw result
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture3, 0,
                              baseViewIndex);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::red);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture3, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::green);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture4, 0,
                              baseViewIndex);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::yellow);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture4, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::blue);
}

// Test that framebuffer fails completeness check if the attachments created with
// glFramebufferTextureMultisampleMultiviewOVR() have different render-to-texture sample counts
TEST_P(MultiviewMSRTTES3Test, FBOAttachmentsWithDifferentSampleCountShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    constexpr GLsizei multisampleRenderToTextureSampleSize4 = 4;
    ANGLE_SKIP_TEST_IF(mMaxIntegerSamples <= multisampleRenderToTextureSampleSize4);

    constexpr int numViews      = 2;
    constexpr int baseViewIndex = 0;

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Attach msrtt-multiview (4 samples) texture to COLOR_ATTACHMENT0
    GLTexture colorTexture0;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture0);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0,
                                                0, multisampleRenderToTextureSampleSize4,
                                                baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();

    // Attach msrtt-multiview (2 samples) texture to COLOR_ATTACHMENT1
    GLTexture colorTexture1;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture1);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, colorTexture1,
                                                0, mMaxIntegerSamples, baseViewIndex, numViews);
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
                     glCheckFramebufferStatus(GL_FRAMEBUFFER));
}

// Test that framebuffer fails completeness check if the attachments created with
// glFramebufferTextureMultisampleMultiviewOVR() have different numViews
TEST_P(MultiviewMSRTTES3Test, FBOAttachmentsWithDifferentNumViewsShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    constexpr int multisampleRenderToTextureNumViews1 = 1;
    ANGLE_SKIP_TEST_IF(mMaxViewsOVR <= multisampleRenderToTextureNumViews1);

    constexpr GLsizei samples   = 2;
    constexpr int baseViewIndex = 0;

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    // Attach a texture with numViews as 1 to COLOR_ATTACHMENT0
    GLTexture colorTexture0;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture0);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize,
                 multisampleRenderToTextureNumViews1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture0,
                                                0, samples, baseViewIndex,
                                                multisampleRenderToTextureNumViews1);
    ASSERT_GL_NO_ERROR();

    // Attach a texture with numViews as max to COLOR_ATTACHMENT1
    GLTexture colorTexture1;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture1);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, mMaxViewsOVR, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, colorTexture1,
                                                0, samples, baseViewIndex, mMaxViewsOVR);
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR,
                     glCheckFramebufferStatus(GL_FRAMEBUFFER));
}

// Test that if the layout qualifier num_views is declared more than once in the same shader,
// all those declarations must set num_views to the same value; otherwise a compile-time error
// results.
TEST_P(MultiviewMSRTTES3Test, ConflictingNumViewsDeclarationsShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    const std::string vsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 2) in;
        layout(num_views = 3) in; // Conflicting declaration
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
        }
    )";
    const std::string fsSource = R"(#version 300 es
        precision mediump float;
        out vec4 FragColor;
        void main()
        {
            FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    )";

    // Expect program compilation/linking to fail due to conflicting num_views
    GLuint program = CompileProgram(vsSource.c_str(), fsSource.c_str());
    EXPECT_EQ(0u, program);
}

// Test that it is a compile-time error to declare num_views to be less than or equal to zero.
TEST_P(MultiviewMSRTTES3Test, NumViewsLessThanOrEqualToZeroShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    // Test with num_views = 0
    const std::string vsSourceZero = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 0) in;
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
        }
    )";

    GLuint vs1 = CompileShader(GL_VERTEX_SHADER, vsSourceZero.c_str());
    ASSERT(vs1 == 0);

    // Test with num_views = -1
    const std::string vsSourceNegative = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = -1) in;
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
        }
    )";
    GLuint vs2                         = CompileShader(GL_VERTEX_SHADER, vsSourceNegative.c_str());
    ASSERT(vs2 == 0);
    glDeleteShader(vs1);
    glDeleteShader(vs2);
}

// Test that it is a compile-time error to declare num_views to be greater than MAX_VIEWS_OVR.
TEST_P(MultiviewMSRTTES3Test, NumViewsGreaterThanMaxViewsOVRShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    // Use mMaxViewsOVR + 1 for the num_views declaration
    std::string vsSource = "#version 300 es\n";
    vsSource += "    #extension GL_OVR_multiview : require\n";
    vsSource += "    layout(num_views = " + std::to_string(mMaxViewsOVR + 1) + ") in;\n";
    vsSource += "    in vec4 a_position;\n";
    vsSource += "    void main()\n";
    vsSource += "    {\n";
    vsSource += "        gl_Position = a_position;\n";
    vsSource += "    }\n";

    GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource.c_str());
    ASSERT(vs == 0);
    glDeleteShader(vs);
}

// Test that when attaching 2D array texture to FBO with
// glFramebufferTextureMultisampleMultiviewOVR(), glClear() will apply clear color to all views.
TEST_P(MultiviewMSRTTES3Test, ClearAppliesToAllViewsInMultiviewFramebuffer)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    constexpr int numViews                                 = 2;
    constexpr int baseViewIndex                            = 0;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture,
                                                0, multisampleRenderToTextureSampleSize,
                                                baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Set a clear color (e.g., red)
    GLColor clearColor(255, 0, 0, 255);
    glClearColor(clearColor.R / 255.0f, clearColor.G / 255.0f, clearColor.B / 255.0f,
                 clearColor.A / 255.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Verify that all views are cleared to the specified color
    GLFramebuffer verifyFbo;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);

    for (int i = 0; i < numViews; ++i)
    {
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture, 0,
                                  baseViewIndex + i);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, clearColor);
    }
    ASSERT_GL_NO_ERROR();
}

// Test that INVALID_OPERATION is generated for a draw operation when the draw framebuffer is
// multiview and transform feedback is active.
TEST_P(MultiviewMSRTTES3Test, MultiviewDrawFramebufferWithTransformFeedbackShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    // Create a multiview draw framebuffer
    GLFramebuffer drawFbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFbo);

    constexpr int numViews                                 = 2;
    constexpr int baseViewIndex                            = 0;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;

    GLTexture drawColorTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, drawColorTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(
        GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, drawColorTexture, 0,
        multisampleRenderToTextureSampleSize, baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);

    // Create a program with transform feedback varyings
    const std::string vsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 2) in;
        in vec4 a_position;
        out vec4 xfb_position;
        void main()
        {
            gl_Position = a_position;
            xfb_position = a_position;
        }
    )";
    const std::string fsSource = R"(#version 300 es
        precision mediump float;
        out vec4 FragColor;
        void main()
        {
            FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    )";

    std::vector<std::string> varyings = {"xfb_position"};
    ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(program, vsSource.c_str(), fsSource.c_str(), varyings,
                                        GL_INTERLEAVED_ATTRIBS);
    glUseProgram(program);

    // Create transform feedback buffer
    GLBuffer transformFeedbackBuffer;
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, transformFeedbackBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(GLfloat) * 4 * 3, nullptr, GL_STREAM_READ);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, transformFeedbackBuffer);

    // Begin transform feedback
    glBeginTransformFeedback(GL_TRIANGLES);
    ASSERT_GL_NO_ERROR();

    // Attempt a draw operation with transform feedback active and multiview framebuffer
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glEndTransformFeedback();
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
}

// Test that instanced drawing commands work with multiview.
TEST_P(MultiviewMSRTTES3Test, InstancedDrawingWithMultiview)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    const std::string vsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 2) in;
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
            if (gl_InstanceID == 1) {
                gl_Position.y = gl_Position.y * 0.5 + 0.5;
            } else {
                gl_Position.y = gl_Position.y * 0.5 - 0.5;
            }
        }
    )";
    const std::string fsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        precision mediump float;
        out vec4 FragColor;
        void main()
        {
            if (gl_ViewID_OVR == 0u)
            {
                FragColor = vec4(0.0, 1.0, 0.0, 1.0); // Green for view 0
            }
            else
            {
                FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Red for view 1
            }
        }

    )";
    ANGLE_GL_PROGRAM(program, vsSource.c_str(), fsSource.c_str());
    glUseProgram(program);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    constexpr int numViews                                 = 2;
    constexpr int baseViewIndex                            = 0;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numViews, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture,
                                                0, multisampleRenderToTextureSampleSize,
                                                baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw 2 instance
    drawQuadInstanced(program, "a_position", 0.5f, 0.5f, true, 2);

    ASSERT_GL_NO_ERROR();

    // Verification
    GLFramebuffer verifyFbo;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);

    // Verify view 0 (should be green)
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture, 0,
                              baseViewIndex + 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    // verify first instance
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 4, GLColor::green);
    // verify second instance
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize * 3 / 4, GLColor::green);

    // Verify view 1 (should be red)
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture, 0,
                              baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    // verify first instance
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 4, GLColor::red);
    // verify second instance
    EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize * 3 / 4, GLColor::red);

    ASSERT_GL_NO_ERROR();
}

// Test that rendering to a mipmap level of a 2D array texture works.
TEST_P(MultiviewMSRTTES3Test, RenderToMipmapLevel1)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    const std::string vsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        layout(num_views = 2) in;
        in vec4 a_position;
        void main()
        {
            gl_Position = a_position;
        }
    )";
    const std::string fsSource = R"(#version 300 es
        #extension GL_OVR_multiview : require
        precision mediump float;
        out vec4 FragColor;
        void main()
        {
            if (gl_ViewID_OVR == 0u) {
                FragColor = vec4(0.0, 1.0, 0.0, 1.0); // Green for view 0
            } else {
                FragColor = vec4(1.0, 1.0, 0.0, 1.0); // Yellow for view 1
            }
        }
    )";

    ANGLE_GL_PROGRAM(program, vsSource.c_str(), fsSource.c_str());
    glUseProgram(program);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    constexpr int numViews      = 2;
    constexpr int baseViewIndex = 0;
    constexpr GLsizei samples   = 4;
    constexpr int mipmapLevels  = 2;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipmapLevels, GL_RGBA8, kWindowSize, kWindowSize, 2);

    // Initialize mipmap level 0 with red
    std::vector<GLColor> level0Data(kWindowSize * kWindowSize * numViews, GLColor::red);
    glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kWindowSize, kWindowSize, numViews, GL_RGBA,
                    GL_UNSIGNED_BYTE, level0Data.data());

    // Initialize mipmap level 1 with blue
    constexpr int mipLevel1Size = kWindowSize >> 1;
    std::vector<GLColor> level1Data(mipLevel1Size * mipLevel1Size * numViews, GLColor::blue);
    glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, mipLevel1Size, mipLevel1Size, numViews,
                    GL_RGBA, GL_UNSIGNED_BYTE, level1Data.data());

    // Attachm mipLevel 1 of the texture array to the FBO
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 1,
                                                samples, baseViewIndex, numViews);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Draw a quad
    glViewport(0, 0, mipLevel1Size, mipLevel1Size);
    drawQuad(program, "a_position", 0.5f, 0.5f, true);
    ASSERT_GL_NO_ERROR();

    // Verification
    GLFramebuffer verifyFbo;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFbo);

    // Verify mipmap level 0 is unchanged (should be red)
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, baseViewIndex);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(mipLevel1Size / 2, mipLevel1Size / 2, GLColor::red);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(mipLevel1Size / 2, mipLevel1Size / 2, GLColor::red);

    // Verify mipmap level 1 is changed (outer edge is blue, inner quad on layer 0 is green, inner
    // quad on layer 1 is yellow)
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 1, baseViewIndex);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(mipLevel1Size / 2, mipLevel1Size / 2, GLColor::green);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 1, baseViewIndex + 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(mipLevel1Size / 2, mipLevel1Size / 2, GLColor::yellow);
    ASSERT_GL_NO_ERROR();
}

class MultiviewMSRTTES31Test : public MultiviewMSRTTTest
{};

// Test that it is invalid to attach a 2d array texture to the framebuffer with
// glFramebufferTextureMultisampleMultiviewOVR and then attach a multisampled texture that is
// created with glTexStorage2DMultisample() to the same framebuffer.
TEST_P(MultiviewMSRTTES31Test,
       FramebufferTextureMultisampleMultiviewOVRInvalidAttachmentsShouldFail)
{
    ANGLE_SKIP_TEST_IF(
        !EnsureGLExtensionEnabled("GL_OVR_multiview_multisampled_render_to_texture"));

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    constexpr int numLayers                                = 2;
    constexpr GLsizei multisampleRenderToTextureSampleSize = 4;

    // Attach a 2D array texture with glFramebufferTextureMultisampleMultiviewOVR
    GLTexture textureArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, textureArray);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kWindowSize, kWindowSize, numLayers, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureArray,
                                                0, multisampleRenderToTextureSampleSize, 0,
                                                numLayers);
    ASSERT_GL_NO_ERROR();

    // Attempt to attach a multisample texture created with glTexStorage2DMultisample to the same
    // Framebuffer should fail
    GLTexture textureMultisample;
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureMultisample);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, multisampleRenderToTextureSampleSize,
                              GL_RGBA8, kWindowSize, kWindowSize, GL_TRUE);
    glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
                                                textureMultisample, 0,
                                                multisampleRenderToTextureSampleSize, 0, numLayers);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultiviewMSRTTES3Test);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultiviewMSRTTES31Test);

ANGLE_INSTANTIATE_TEST_ES3(MultiviewMSRTTES3Test);
ANGLE_INSTANTIATE_TEST_ES31(MultiviewMSRTTES31Test);

}  // namespace
