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

// CopyCompressedTextureTest.cpp: Tests of the GL_CHROMIUM_copy_compressed_texture extension

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

namespace angle
{

class CopyCompressedTextureTest : public ANGLETest<>
{
  protected:
    CopyCompressedTextureTest()
    {
        setWindowWidth(256);
        setWindowHeight(256);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void testSetUp() override
    {
        glGenTextures(2, mTextures);

        constexpr char kVS[] =
            "attribute vec2 a_position;\n"
            "varying vec2 v_texcoord;\n"
            "void main()\n"
            "{\n"
            "   gl_Position = vec4(a_position, 0.0, 1.0);\n"
            "   v_texcoord = (a_position + 1.0) * 0.5;\n"
            "}\n";

        constexpr char kFS[] =
            "precision mediump float;\n"
            "uniform sampler2D u_texture;\n"
            "varying vec2 v_texcoord;\n"
            "void main()\n"
            "{\n"
            "    gl_FragColor = texture2D(u_texture, v_texcoord);\n"
            "}\n";

        mProgram = CompileProgram(kVS, kFS);
        ASSERT_NE(0u, mProgram);
    }

    void testTearDown() override
    {
        glDeleteTextures(2, mTextures);
        glDeleteProgram(mProgram);
    }

    bool checkExtensions() const
    {
        if (!IsGLExtensionEnabled("GL_CHROMIUM_copy_compressed_texture"))
        {
            std::cout
                << "Test skipped because GL_CHROMIUM_copy_compressed_texture is not available."
                << std::endl;
            return false;
        }

        EXPECT_NE(nullptr, glCompressedCopyTextureCHROMIUM);
        if (glCompressedCopyTextureCHROMIUM == nullptr)
        {
            return false;
        }

        return true;
    }

    GLuint mProgram     = 0;
    GLuint mTextures[2] = {0, 0};
};

namespace
{

const GLColor &CompressedImageColor = GLColor::red;

// Single compressed ATC block of source pixels all set to:
// CompressedImageColor.
const uint8_t CompressedImageATC[8] = {0x0, 0x7c, 0x0, 0xf8, 0x55, 0x55, 0x55, 0x55};

// Single compressed ATCIA block of source pixels all set to:
// CompressedImageColor.
const uint8_t CompressedImageATCIA[16] = {0xff, 0xff, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,
                                          0x0,  0x7c, 0x0, 0xf8, 0x55, 0x55, 0x55, 0x55};

// Single compressed DXT1 block of source pixels all set to:
// CompressedImageColor.
const uint8_t CompressedImageDXT1[8] = {0x00, 0xf8, 0x00, 0xf8, 0xaa, 0xaa, 0xaa, 0xaa};

// Single compressed DXT5 block of source pixels all set to:
// CompressedImageColor.
const uint8_t CompressedImageDXT5[16] = {0xff, 0xff, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0,
                                         0x0,  0xf8, 0x0, 0xf8, 0xaa, 0xaa, 0xaa, 0xaa};

// Single compressed DXT1 block of source pixels all set to:
// CompressedImageColor.
const uint8_t CompressedImageETC1[8] = {0x0, 0x0, 0xf8, 0x2, 0xff, 0xff, 0x0, 0x0};

}  // anonymous namespace

// Test to ensure that the basic functionality of the extension works.
TEST_P(CopyCompressedTextureTest, Basic)
{
    ANGLE_SKIP_TEST_IF(!checkExtensions());

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));

    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
                           sizeof(CompressedImageDXT1), CompressedImageDXT1);
    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
    ASSERT_GL_NO_ERROR();

    // Load texture.
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
    GLint textureLoc = glGetUniformLocation(mProgram, "u_texture");
    glUseProgram(mProgram);
    glUniform1i(textureLoc, 0);

    // Draw.
    drawQuad(mProgram, "a_position", 0.5f);

    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, CompressedImageColor);
    ASSERT_GL_NO_ERROR();
}

// Test validation of compressed formats
TEST_P(CopyCompressedTextureTest, InternalFormat)
{
    if (!checkExtensions())
    {
        return;
    }

    struct Data
    {
        GLint format;
        const uint8_t *data;
        GLsizei dataSize;

        Data() : Data(GL_NONE, nullptr, 0) {}
        Data(GLint format, const uint8_t *data, GLsizei dataSize)
            : format(format), data(data), dataSize(dataSize)
        {}
    };
    std::vector<Data> supportedFormats;

    if (IsGLExtensionEnabled("GL_AMD_compressed_ATC_texture"))
    {
        supportedFormats.push_back(
            Data(GL_ATC_RGB_AMD, CompressedImageATC, sizeof(CompressedImageATC)));
        supportedFormats.push_back(Data(GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD, CompressedImageATCIA,
                                        sizeof(CompressedImageATCIA)));
    }
    if (IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"))
    {
        supportedFormats.push_back(Data(GL_COMPRESSED_RGB_S3TC_DXT1_EXT, CompressedImageDXT1,
                                        sizeof(CompressedImageDXT1)));
    }
    if (IsGLExtensionEnabled("GL_ANGLE_texture_compression_dxt5"))
    {
        supportedFormats.push_back(Data(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, CompressedImageDXT5,
                                        sizeof(CompressedImageDXT5)));
    }
    if (IsGLExtensionEnabled("GL_OES_compressed_ETC1_RGB8_texture"))
    {
        supportedFormats.push_back(
            Data(GL_ETC1_RGB8_OES, CompressedImageETC1, sizeof(CompressedImageETC1)));
    }

    for (const auto &supportedFormat : supportedFormats)
    {
        glBindTexture(GL_TEXTURE_2D, mTextures[0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, supportedFormat.format, 4, 4, 0,
                               supportedFormat.dataSize, supportedFormat.data);
        ASSERT_GL_NO_ERROR();

        glBindTexture(GL_TEXTURE_2D, mTextures[1]);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
        ASSERT_GL_NO_ERROR();
    }
}

// Test that uncompressed textures generate errors when copying
TEST_P(CopyCompressedTextureTest, InternalFormatNotSupported)
{
    if (!checkExtensions())
    {
        return;
    }

    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Check that the GL_RGBA format reports an error.
    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that uncompressed to compressed textures generate errors when copying
TEST_P(CopyCompressedTextureTest, UncompressedToCompressed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                       !IsGLExtensionEnabled("GL_ANGLE_compressed_texture_etc"));

    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA8_ETC2_EAC, 1, 1, 0, 16, NULL);
    ASSERT_GL_NO_ERROR();

    // return GL_INVALID_OPERATION because the two formats are not compatible
    glCopyImageSubDataEXT(mTextures[0], GL_TEXTURE_2D, 0, 0, 0, 0, mTextures[1], GL_TEXTURE_2D, 0,
                          0, 0, 0, 1, 1, 1);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test validation of texture IDs
TEST_P(CopyCompressedTextureTest, InvalidTextureIds)
{
    if (!checkExtensions())
    {
        return;
    }

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));

    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
                           sizeof(CompressedImageDXT1), CompressedImageDXT1);
    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    glCompressedCopyTextureCHROMIUM(mTextures[0], 99993);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glCompressedCopyTextureCHROMIUM(99994, mTextures[1]);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glCompressedCopyTextureCHROMIUM(99995, 99996);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
    EXPECT_GL_NO_ERROR();
}

// Test that only 2D textures are valid
TEST_P(CopyCompressedTextureTest, BindingPoints)
{
    if (!checkExtensions())
    {
        return;
    }

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));

    glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[0]);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    for (GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X; face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
         face++)
    {
        glCompressedTexImage2D(face, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
                               sizeof(CompressedImageDXT1), CompressedImageDXT1);
    }
    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[1]);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test the destination texture cannot be immutable
TEST_P(CopyCompressedTextureTest, Immutable)
{
    if (!checkExtensions() || getClientMajorVersion() < 3)
    {
        return;
    }

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));

    glBindTexture(GL_TEXTURE_2D, mTextures[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 0,
                           sizeof(CompressedImageDXT1), CompressedImageDXT1);
    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_2D, mTextures[1]);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    glCompressedCopyTextureCHROMIUM(mTextures[0], mTextures[1]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

class CopyCompressedTextureTestES31 : public ANGLETest<>
{
  protected:
    CopyCompressedTextureTestES31()
    {
        setWindowWidth(256);
        setWindowHeight(256);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }
};

// Test that if the copy subregion depth is bigger than the depth range of either source texture
// image or destination texture image, glCopyImageSubData() fails with GL_INVALID_VALUE
TEST_P(CopyCompressedTextureTestES31, CopyRegionDepthOverflow)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    // Initialize texture data
    std::vector<uint8_t> compressedSrcImgDataLevel0;
    for (uint8_t i = 1; i < 32 + 1; ++i)
    {
        compressedSrcImgDataLevel0.push_back(i);
    }

    std::vector<uint8_t> compressedSrcImgDataLevel1;
    for (uint8_t i = 1; i < 16 + 1; ++i)
    {
        compressedSrcImgDataLevel1.push_back(i);
    }

    // Allocate storage for compressedTexture2D, and fills each of 2 levels with the texture data
    GLTexture compressedTexture2D;
    glBindTexture(GL_TEXTURE_2D, compressedTexture2D);
    glTexStorage2D(GL_TEXTURE_2D, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 8, 4);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 4, GL_COMPRESSED_RGBA_ASTC_6x6, 32,
                              compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 4, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 16,
                              compressedSrcImgDataLevel1.data());

    // Allocate storage for compressedTexture2DArray, and fills each of 2 levels with the texture
    // data
    GLTexture compressedTexture2DArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, compressedTexture2DArray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 8, 4, 2);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 4, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              32, compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 4, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              32, compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 2, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              16, compressedSrcImgDataLevel1.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 1, 4, 2, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              16, compressedSrcImgDataLevel1.data());

    // Perform a copy from compressedTexture2D mipmap 0 to compressedTexture2DArray mipmap 0, where
    // the copy region depth is bigger than the depth of source texture mTexture2D mipmap 0. This
    // should fail with GL_INVALID_VALUE.
    glCopyImageSubDataEXT(compressedTexture2D, GL_TEXTURE_2D, 0, 0, 0, 0, compressedTexture2DArray,
                          GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
    // Perform a copy from compressedTexture2DArray mipmap 0 to compressedTexture2D mipmap 0, where
    // the copy region depth is bigger than the depth of destination texture mTexture2D mipmap 0.
    // This should fail with GL_INVALID_VALUE.
    glCopyImageSubDataEXT(compressedTexture2DArray, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0,
                          compressedTexture2D, GL_TEXTURE_2D, 0, 0, 0, 0, 8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that if the copy subregion width and height equals to the texture level width and height,
// even if width and height are not aligned with the compressed texture block size, the
// glCopyImageSubData() should be allowed.
TEST_P(CopyCompressedTextureTestES31, CopyRegionOccupiesEntireMipDoNotNeedAlignment)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    // Initialize texture data
    std::vector<uint8_t> compressedSrcImgDataLevel0;
    for (uint8_t i = 1; i < 32 + 1; ++i)
    {
        compressedSrcImgDataLevel0.push_back(i);
    }

    std::vector<uint8_t> compressedSrcImgDataLevel1;
    for (uint8_t i = 1; i < 16 + 1; ++i)
    {
        compressedSrcImgDataLevel1.push_back(i);
    }

    // Allocate storage for compressedTexture2D, and fills each of 2 levels with the texture data
    GLTexture compressedTexture2D;
    glBindTexture(GL_TEXTURE_2D, compressedTexture2D);
    glTexStorage2D(GL_TEXTURE_2D, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 8, 4);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 4, GL_COMPRESSED_RGBA_ASTC_6x6, 32,
                              compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 4, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 16,
                              compressedSrcImgDataLevel1.data());

    // Allocate storage for compressedTexture2DArray, and fills each of 2 levels with the texture
    // data
    GLTexture compressedTexture2DArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, compressedTexture2DArray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 8, 4, 2);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 4, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              32, compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 4, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              32, compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 2, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              16, compressedSrcImgDataLevel1.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 1, 4, 2, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              16, compressedSrcImgDataLevel1.data());

    // Perform a copy from compressedTexture2D mipmap 0 to compressedTexture2DArray mipmap 0.
    // This should succeed. Even if the width and height are not multiple of 6, the region covers
    // the entire mipmap 0 of source texture mTexture2D, and the region covers the entire slice 0 of
    // mipmap 0 of destination texture mTexture2DArray
    glCopyImageSubDataEXT(compressedTexture2D, GL_TEXTURE_2D, 0, 0, 0, 0, compressedTexture2DArray,
                          GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 4, 1);
    EXPECT_GL_NO_ERROR();
}

// Test that if the copy region offsets are not aligned with the compressed texture block size,
// glCopyImageSubData() should fail with a validation error.
TEST_P(CopyCompressedTextureTestES31, CopyRegionoOffSetNotAlignedShouldGenerateGLError)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    // Initialize texture data
    std::vector<uint8_t> compressedSrcImgDataLevel0;
    for (uint8_t i = 1; i < 32 + 1; ++i)
    {
        compressedSrcImgDataLevel0.push_back(i);
    }

    std::vector<uint8_t> compressedSrcImgDataLevel1;
    for (uint8_t i = 1; i < 16 + 1; ++i)
    {
        compressedSrcImgDataLevel1.push_back(i);
    }

    // Allocate storage for compressedTexture2D, and fills each of 2 levels with the texture data
    GLTexture compressedTexture2D;
    glBindTexture(GL_TEXTURE_2D, compressedTexture2D);
    glTexStorage2D(GL_TEXTURE_2D, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 8, 4);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 4, GL_COMPRESSED_RGBA_ASTC_6x6, 32,
                              compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 4, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 16,
                              compressedSrcImgDataLevel1.data());

    // Allocate storage for compressedTexture2DArray, and fills each of 2 levels with the texture
    // data
    GLTexture compressedTexture2DArray;
    glBindTexture(GL_TEXTURE_2D_ARRAY, compressedTexture2DArray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_COMPRESSED_RGBA_ASTC_6x6, 8, 4, 2);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 4, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              32, compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 4, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              32, compressedSrcImgDataLevel0.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 2, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              16, compressedSrcImgDataLevel1.data());
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 1, 4, 2, 1, GL_COMPRESSED_RGBA_ASTC_6x6,
                              16, compressedSrcImgDataLevel1.data());

    // Perform a copy from compressedTexture2D mipmap 0 to compressedTexture2DArray mipmap 0.
    // The copy region offset srcX does not aligned at multiple of 6.
    // This should fail with GL_INVALID_VALUE
    glCopyImageSubDataEXT(compressedTexture2D, GL_TEXTURE_2D, 0, 2, 0, 0, compressedTexture2DArray,
                          GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 6, 4, 1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that copying from uncompressed texture to compressed texture works, if their internal
// formats are compatible, and the transformed compressed texture copy subregion aligns with the
// compressed texture block.
TEST_P(CopyCompressedTextureTestES31, CopyFromUncompressedToCompressed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));

    GLTexture uncompressedTexture2D;
    glBindTexture(GL_TEXTURE_2D, uncompressedTexture2D);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32UI, 2, 1);
    constexpr GLuint kTex2DData[] = {0, 0, 0, 0, 0, 0, 0, 0};
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, kTex2DData);
    ASSERT_GL_NO_ERROR();

    // Initialize compressed texture data
    std::vector<uint8_t> compressedSrcImgDataLevel0;
    for (uint8_t i = 1; i < 32 + 1; ++i)
    {
        compressedSrcImgDataLevel0.push_back(i);
    }

    // Allocate storage for compressedTexture2D, and fills with texture data
    GLTexture compressedTexture2D;
    glBindTexture(GL_TEXTURE_2D, compressedTexture2D);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_ASTC_6x6, 12, 6);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 12, 6, GL_COMPRESSED_RGBA_ASTC_6x6, 32,
                              compressedSrcImgDataLevel0.data());

    glCopyImageSubDataEXT(uncompressedTexture2D, GL_TEXTURE_2D, 0, 0, 0, 0, compressedTexture2D,
                          GL_TEXTURE_2D, 0, 0, 0, 0, 2, 1, 1);
    EXPECT_GL_NO_ERROR();
}

// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(CopyCompressedTextureTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CopyCompressedTextureTestES31);
ANGLE_INSTANTIATE_TEST_ES31(CopyCompressedTextureTestES31);

}  // namespace angle
