//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// CompressedTextureFormatsTest:
//   Tests that only the appropriate entry points are affected after
//   enabling compressed texture extensions.
//

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

using namespace angle;

namespace
{

struct FormatDesc
{
    GLenum format;
    GLsizei blockX;
    GLsizei blockY;
    GLsizei blockZ;
    GLsizei size;

    bool isPVRTC1() const
    {
        return ((format & ~3) == GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG) ||
               ((format & ~3) == GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT);
    }
    bool mayBeEmulated() const
    {
        return format == GL_COMPRESSED_R11_EAC || format == GL_COMPRESSED_RG11_EAC ||
               format == GL_COMPRESSED_SIGNED_R11_EAC || format == GL_COMPRESSED_SIGNED_RG11_EAC ||
               format == GL_ETC1_RGB8_OES || format == GL_COMPRESSED_RGB8_ETC2 ||
               format == GL_COMPRESSED_SRGB8_ETC2 ||
               format == GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 ||
               format == GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 ||
               format == GL_COMPRESSED_RGBA8_ETC2_EAC ||
               format == GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
    }
};
using CompressedTextureTestParams = std::tuple<angle::PlatformParameters, FormatDesc>;

class CompressedTextureFormatsTest : public ANGLETest<CompressedTextureTestParams>
{
  public:
    CompressedTextureFormatsTest(const std::string ext1,
                                 const std::string ext2,
                                 const bool supportsUpdates,
                                 const bool supportsPartialUpdates,
                                 const bool supports2DArray,
                                 const bool supports3D,
                                 const bool alwaysOnES3)
        : mExtNames({ext1, ext2}),
          mSupportsUpdates(supportsUpdates),
          mSupportsPartialUpdates(supportsPartialUpdates),
          mSupports2DArray(supports2DArray),
          mSupports3D(supports3D),
          mAlwaysOnES3(alwaysOnES3)
    {
        setExtensionsEnabled(false);
    }

    void checkSubImage2D(FormatDesc desc, int numX)
    {
        GLubyte data[64] = {};

        // The semantic of this call is to take uncompressed data, compress it on-the-fly,
        // and perform a partial update of an existing GPU-compressed texture. This
        // operation is not supported in OpenGL ES.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.blockX, desc.blockY, GL_RGBA, GL_UNSIGNED_BYTE,
                        nullptr);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Compressed texture extensions never extend TexSubImage2D.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.blockX, desc.blockY, desc.format,
                        GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);

        // The semantic of this call is to take pixel data from the current framebuffer, compress it
        // on-the-fly, and perform a partial update of an existing GPU-compressed texture. This
        // operation is not supported in OpenGL ES.
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, desc.blockX, desc.blockY);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Try whole image update. It is always valid when API supports updates.
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.blockX * numX, desc.blockY * 2,
                                  desc.format, desc.size * 4, data);
        if (!mSupportsUpdates)
        {
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            return;
        }
        EXPECT_GL_NO_ERROR();

        if (!mSupportsPartialUpdates)
        {
            glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.blockX, desc.blockY, desc.format,
                                      desc.size, data);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            return;
        }

        // All compressed formats that support partial updates require the offsets to be
        // multiples of block dimensions.
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, desc.blockX, desc.blockY, desc.format,
                                  desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 1, desc.blockX, desc.blockY, desc.format,
                                  desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // All compressed formats that support partial updates require the dimensions to be
        // multiples of block dimensions or reach the image boundaries.
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.blockX - 1, desc.blockY, desc.format,
                                  desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.blockX, desc.blockY - 1, desc.format,
                                  desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Test should pass when replaced region dimensions are multiples the of block
        // dimensions
        // clang-format off
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0,           0,           desc.blockX, desc.blockY, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, desc.blockX, 0,           desc.blockX, desc.blockY, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0,           desc.blockY, desc.blockX, desc.blockY, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, desc.blockX, desc.blockY, desc.blockX, desc.blockY, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();

        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0,           0,           desc.blockX * 2, desc.blockY,     desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0,           0,           desc.blockX,     desc.blockY * 2, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0,           desc.blockY, desc.blockX * 2, desc.blockY,     desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, desc.blockX, 0,           desc.blockX,     desc.blockY * 2, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        // clang-format on

        // Test should pass when replaced region dimensions are not multiples of block dimensions
        // but reach the image boundaries. For example, it is valid to replace right-bottom 2x2
        // region of a 6x6 texture made of 4x4 blocks. To check this on all platforms with the same
        // code, level 1 should be used to avoid hitting a D3D11 limitation.
        if (getClientMajorVersion() >= 3 || EnsureGLExtensionEnabled("GL_OES_texture_npot"))
        {
            GLTexture texture;
            glBindTexture(GL_TEXTURE_2D, texture);
            const int newW = desc.blockX * 3;
            const int newH = desc.blockY * 3;
            glCompressedTexImage2D(GL_TEXTURE_2D, 0, desc.format, newW, newH, 0, desc.size * 9,
                                   nullptr);
            EXPECT_GL_NO_ERROR();

            glCompressedTexImage2D(GL_TEXTURE_2D, 1, desc.format, newW / 2, newH / 2, 0,
                                   desc.size * 4, nullptr);
            EXPECT_GL_NO_ERROR();

            // clang-format off
            glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, desc.blockX, 0,           desc.blockX / 2,     desc.blockY,         desc.format, desc.size * 1, data);
            EXPECT_GL_NO_ERROR();
            glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, desc.blockX, 0,           desc.blockX / 2,     desc.blockY * 3 / 2, desc.format, desc.size * 2, data);
            EXPECT_GL_NO_ERROR();
            glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0,           desc.blockY, desc.blockX,         desc.blockY / 2,     desc.format, desc.size * 1, data);
            EXPECT_GL_NO_ERROR();
            glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0,           desc.blockY, desc.blockX * 3 / 2, desc.blockY / 2,     desc.format, desc.size * 2, data);
            EXPECT_GL_NO_ERROR();
            glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, desc.blockX, desc.blockY, desc.blockX / 2,     desc.blockY / 2,     desc.format, desc.size * 1, data);
            EXPECT_GL_NO_ERROR();
            // clang-format on
        }
    }

    void checkSubImage3D(GLenum target, FormatDesc desc)
    {
        GLubyte data[128] = {};

        // The semantic of this call is to take uncompressed data, compress it on-the-fly,
        // and perform a partial update of an existing GPU-compressed texture. This
        // operation is not supported in OpenGL ES.
        glTexSubImage3D(target, 0, 0, 0, 0, desc.blockX, desc.blockY, desc.blockZ * 2, GL_RGBA,
                        GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Compressed texture extensions never extend TexSubImage3D.
        glTexSubImage3D(target, 0, 0, 0, 0, desc.blockX, desc.blockY, desc.blockZ * 2, desc.format,
                        GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);

        // The semantic of this call is to take pixel data from the current framebuffer, compress it
        // on-the-fly, and perform a partial update of an existing GPU-compressed texture. This
        // operation is not supported in OpenGL ES.
        glCopyTexSubImage3D(target, 0, 0, 0, 0, 0, 0, desc.blockX, desc.blockY);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // All formats that are accepted for 3D entry points support updates.
        ASSERT(mSupportsUpdates);

        // Try whole image update. It is always valid for formats that support updates.
        glCompressedTexSubImage3D(target, 0, 0, 0, 0, desc.blockX * 2, desc.blockY * 2,
                                  desc.blockZ * 2, desc.format, desc.size * 8, data);
        EXPECT_GL_NO_ERROR();

        // Try a whole image update from a pixel unpack buffer.
        // Don't test non-emulated formats on Desktop GL.
        // TODO(anglebug.com/42264819): implement emulation on Desktop GL, then remove this check.
        if (!(IsDesktopOpenGL() && desc.mayBeEmulated()))
        {
            GLBuffer buffer;
            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
            glBufferData(GL_PIXEL_UNPACK_BUFFER, 128, data, GL_STREAM_DRAW);
            EXPECT_GL_NO_ERROR();

            glCompressedTexSubImage3D(target, 0, 0, 0, 0, desc.blockX * 2, desc.blockY * 2,
                                      desc.blockZ * 2, desc.format, desc.size * 8, nullptr);
            EXPECT_GL_NO_ERROR();

            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
        }

        // All formats that are accepted for 3D entry points support partial updates.
        ASSERT(mSupportsPartialUpdates);

        // All compressed formats that support partial updates require the offsets to be
        // multiples of block dimensions.
        glCompressedTexSubImage3D(target, 0, 1, 0, 0, desc.blockX, desc.blockY, desc.blockZ,
                                  desc.format, desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        glCompressedTexSubImage3D(target, 0, 0, 1, 0, desc.blockX, desc.blockY, desc.blockZ,
                                  desc.format, desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // All compressed formats that support partial updates require the dimensions to be
        // multiples of block dimensions or reach the image boundaries.
        glCompressedTexSubImage3D(target, 0, 0, 0, 0, desc.blockX - 1, desc.blockY, desc.blockZ,
                                  desc.format, desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        glCompressedTexSubImage3D(target, 0, 0, 0, 0, desc.blockX, desc.blockY - 1, desc.blockZ,
                                  desc.format, desc.size, data);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        // Valid partial updates
        // clang-format off
        glCompressedTexSubImage3D(target, 0, 0,           0,           0, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, 0,           0, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           desc.blockY, 0, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, desc.blockY, 0, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           0,           desc.blockZ, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, 0,           desc.blockZ, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           desc.blockY, desc.blockZ, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, desc.blockY, desc.blockZ, desc.blockX, desc.blockY, desc.blockZ, desc.format, desc.size, data);
        EXPECT_GL_NO_ERROR();

        glCompressedTexSubImage3D(target, 0, 0,           0,           0, desc.blockX * 2, desc.blockY,     desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           0,           0, desc.blockX,     desc.blockY * 2, desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           desc.blockY, 0, desc.blockX * 2, desc.blockY,     desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, 0,           0, desc.blockX,     desc.blockY * 2, desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           0,           desc.blockZ, desc.blockX * 2, desc.blockY,     desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           0,           desc.blockZ, desc.blockX,     desc.blockY * 2, desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           desc.blockY, desc.blockZ, desc.blockX * 2, desc.blockY,     desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, 0,           desc.blockZ, desc.blockX,     desc.blockY * 2, desc.blockZ, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           0,           0, desc.blockX,     desc.blockY,     desc.blockZ * 2, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, 0,           0, desc.blockX,     desc.blockY,     desc.blockZ * 2, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, 0,           desc.blockY, 0, desc.blockX,     desc.blockY,     desc.blockZ * 2, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        glCompressedTexSubImage3D(target, 0, desc.blockX, desc.blockY, 0, desc.blockX,     desc.blockY,     desc.blockZ * 2, desc.format, desc.size * 2, data);
        EXPECT_GL_NO_ERROR();
        // clang-format on

        // Test should pass when replaced region dimensions are not multiples of block dimensions
        // but reach the image boundaries. For example, it is valid to replace right-bottom 2x2
        // region of a 6x6 texture made of 4x4 blocks. To check this on all platforms with the same
        // code, level 1 should be used to avoid hitting a D3D11 limitation.
        {
            GLTexture texture;
            glBindTexture(target, texture);
            const int newW = desc.blockX * 3;
            const int newH = desc.blockY * 3;
            const int newD = desc.blockZ * 2;
            glCompressedTexImage3D(target, 0, desc.format, newW, newH, newD, 0, desc.size * 18,
                                   nullptr);
            EXPECT_GL_NO_ERROR();

            if (target == GL_TEXTURE_2D_ARRAY)
            {
                glCompressedTexImage3D(target, 1, desc.format, newW / 2, newH / 2, 2, 0,
                                       desc.size * 8, nullptr);
                EXPECT_GL_NO_ERROR();

                // clang-format off
                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           0, desc.blockX / 2,     desc.blockY,         1, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           0, desc.blockX / 2,     desc.blockY * 3 / 2, 1, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 0, desc.blockX,         desc.blockY / 2,     1, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 0, desc.blockX * 3 / 2, desc.blockY / 2,     1, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, desc.blockY, 0, desc.blockX / 2,     desc.blockY / 2,     1, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();

                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           1, desc.blockX / 2,     desc.blockY,         1, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           1, desc.blockX / 2,     desc.blockY * 3 / 2, 1, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 1, desc.blockX,         desc.blockY / 2,     1, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 1, desc.blockX * 3 / 2, desc.blockY / 2,     1, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, desc.blockY, 1, desc.blockX / 2,     desc.blockY / 2,     1, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();

                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           0, desc.blockX / 2,     desc.blockY,         2, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           0, desc.blockX / 2,     desc.blockY * 3 / 2, 2, desc.format, desc.size * 4, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 0, desc.blockX,         desc.blockY / 2,     2, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 0, desc.blockX * 3 / 2, desc.blockY / 2,     2, desc.format, desc.size * 4, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, desc.blockY, 0, desc.blockX / 2,     desc.blockY / 2,     2, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                // clang-format on
            }
            else
            {
                glCompressedTexImage3D(target, 1, desc.format, newW / 2, newH / 2, newD / 2, 0,
                                       desc.size * 4, nullptr);
                EXPECT_GL_NO_ERROR();

                // clang-format off
                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           0, desc.blockX / 2,     desc.blockY,         desc.blockZ, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, 0,           0, desc.blockX / 2,     desc.blockY * 3 / 2, desc.blockZ, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 0, desc.blockX,         desc.blockY / 2,     desc.blockZ, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, 0,           desc.blockY, 0, desc.blockX * 3 / 2, desc.blockY / 2,     desc.blockZ, desc.format, desc.size * 2, data);
                EXPECT_GL_NO_ERROR();
                glCompressedTexSubImage3D(target, 1, desc.blockX, desc.blockY, 0, desc.blockX / 2,     desc.blockY / 2,     desc.blockZ, desc.format, desc.size * 1, data);
                EXPECT_GL_NO_ERROR();
                // clang-format on
            }
        }
    }

    void check2D(const bool compressedFormatEnabled)
    {
        const FormatDesc desc = ::testing::get<1>(GetParam());

        {
            GLTexture texture;
            glBindTexture(GL_TEXTURE_2D, texture);

            // The semantic of this call is to take uncompressed data and compress it on-the-fly.
            // This operation is not supported in OpenGL ES.
            glTexImage2D(GL_TEXTURE_2D, 0, desc.format, desc.blockX, desc.blockY, 0, GL_RGB,
                         GL_UNSIGNED_BYTE, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_VALUE);

            // Try compressed enum as format. Compressed texture extensions never allow this.
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, desc.blockX, desc.blockY, 0, desc.format,
                         GL_UNSIGNED_BYTE, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_ENUM);

            // The semantic of this call is to take pixel data from the current framebuffer
            // and create a compressed texture from it on-the-fly. This operation is not supported
            // in OpenGL ES.
            glCopyTexImage2D(GL_TEXTURE_2D, 0, desc.format, 0, 0, desc.blockX, desc.blockY, 0);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);

            glCompressedTexImage2D(GL_TEXTURE_2D, 0, desc.format, desc.blockX, desc.blockY, 0,
                                   desc.size, nullptr);
            if (compressedFormatEnabled)
            {
                int numX = 2;
                if (desc.isPVRTC1())
                {
                    if (desc.blockX == 8)
                    {
                        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                        numX = 1;
                    }
                    else
                    {
                        // PVRTC1 formats require data size for at least 2x2 blocks.
                        EXPECT_GL_ERROR(GL_INVALID_VALUE);
                    }
                }
                else
                {
                    EXPECT_GL_NO_ERROR();
                }

                // Create a texture with more than one block to test partial updates
                glCompressedTexImage2D(GL_TEXTURE_2D, 0, desc.format, desc.blockX * numX,
                                       desc.blockY * 2, 0, desc.size * 4, nullptr);
                EXPECT_GL_NO_ERROR();

                // Pixel unpack state must not affect compressed textures
                if (getClientMajorVersion() >= 3 ||
                    (EnsureGLExtensionEnabled("GL_EXT_unpack_subimage") &&
                     EnsureGLExtensionEnabled("GL_NV_pixel_buffer_object")))
                {
                    GLBuffer buf;
                    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
                    glBufferData(GL_PIXEL_UNPACK_BUFFER, desc.size * 4, nullptr, GL_STREAM_DRAW);
                    ASSERT_GL_NO_ERROR();

                    glCompressedTexImage2D(GL_TEXTURE_2D, 0, desc.format, desc.blockX * numX,
                                           desc.blockY * 2, 0, desc.size * 4, nullptr);
                    EXPECT_GL_NO_ERROR();

                    for (GLenum param :
                         {GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS})
                    {
                        glPixelStorei(param, 0x7FFFFFFF);
                        ASSERT_GL_NO_ERROR();
                        glCompressedTexImage2D(GL_TEXTURE_2D, 0, desc.format, desc.blockX * numX,
                                               desc.blockY * 2, 0, desc.size * 4, nullptr);
                        EXPECT_GL_NO_ERROR();
                        glPixelStorei(param, 0);
                    }

                    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                    ASSERT_GL_NO_ERROR();
                }

                checkSubImage2D(desc, numX);
            }
            else
            {
                EXPECT_GL_ERROR(GL_INVALID_ENUM);
            }
        }

        if (getClientMajorVersion() >= 3)
        {
            GLTexture texture;
            glBindTexture(GL_TEXTURE_2D, texture);

            glTexStorage2D(GL_TEXTURE_2D, 1, desc.format, desc.blockX * 2, desc.blockY * 2);
            if (compressedFormatEnabled)
            {
                int numX = 2;
                if (desc.isPVRTC1() && desc.blockX == 8)
                {
                    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                    numX = 1;
                    glTexStorage2D(GL_TEXTURE_2D, 1, desc.format, desc.blockX, desc.blockY * 2);
                }
                EXPECT_GL_NO_ERROR();

                checkSubImage2D(desc, numX);
            }
            else
            {
                EXPECT_GL_ERROR(GL_INVALID_ENUM);
            }
        }

        if (EnsureGLExtensionEnabled("GL_EXT_texture_storage"))
        {
            GLTexture texture;
            glBindTexture(GL_TEXTURE_2D, texture);

            glTexStorage2DEXT(GL_TEXTURE_2D, 1, desc.format, desc.blockX * 2, desc.blockY * 2);
            if (compressedFormatEnabled)
            {
                int numX = 2;
                if (desc.isPVRTC1() && desc.blockX == 8)
                {
                    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                    numX = 1;
                    glTexStorage2DEXT(GL_TEXTURE_2D, 1, desc.format, desc.blockX, desc.blockY * 2);
                }
                EXPECT_GL_NO_ERROR();

                checkSubImage2D(desc, numX);
            }
            else
            {
                EXPECT_GL_ERROR(GL_INVALID_ENUM);
            }
        }
    }

    void check3D(GLenum target, const bool compressedFormatEnabled, const bool supportsTarget)
    {
        const FormatDesc desc = ::testing::get<1>(GetParam());

        {
            GLTexture texture;
            glBindTexture(target, texture);

            // Try compressed enum as internalformat. The semantic of this call is to take
            // uncompressed data and compress it on-the-fly. This operation is not supported in
            // OpenGL ES.
            glTexImage3D(target, 0, desc.format, desc.blockX, desc.blockX, desc.blockZ, 0, GL_RGB,
                         GL_UNSIGNED_BYTE, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_VALUE);

            // Try compressed enum as format. Compressed texture extensions never allow this.
            glTexImage3D(target, 0, GL_RGB, desc.blockX, desc.blockX, desc.blockZ, 0, desc.format,
                         GL_UNSIGNED_BYTE, nullptr);
            EXPECT_GL_ERROR(GL_INVALID_ENUM);

            glCompressedTexImage3D(target, 0, desc.format, desc.blockX * 2, desc.blockY * 2,
                                   desc.blockZ * 2, 0, desc.size * 8, nullptr);
            if (compressedFormatEnabled)
            {
                if (supportsTarget)
                {
                    EXPECT_GL_NO_ERROR();

                    // Pixel unpack state must not affect compressed textures
                    {
                        GLBuffer buf;
                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
                        glBufferData(GL_PIXEL_UNPACK_BUFFER, desc.size * 8, nullptr,
                                     GL_STREAM_DRAW);
                        ASSERT_GL_NO_ERROR();

                        glCompressedTexImage3D(target, 0, desc.format, desc.blockX * 2,
                                               desc.blockY * 2, desc.blockZ * 2, 0, desc.size * 8,
                                               nullptr);
                        EXPECT_GL_NO_ERROR();

                        for (GLenum param :
                             {GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS,
                              GL_UNPACK_SKIP_IMAGES, GL_UNPACK_IMAGE_HEIGHT})
                        {
                            glPixelStorei(param, 0x7FFFFFFF);
                            ASSERT_GL_NO_ERROR();
                            glCompressedTexImage3D(target, 0, desc.format, desc.blockX * 2,
                                                   desc.blockY * 2, desc.blockZ * 2, 0,
                                                   desc.size * 8, nullptr);
                            EXPECT_GL_NO_ERROR();
                            glPixelStorei(param, 0);
                        }

                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                        ASSERT_GL_NO_ERROR();
                    }

                    checkSubImage3D(target, desc);
                }
                else
                {
                    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                }
            }
            else
            {
                EXPECT_GL_ERROR(GL_INVALID_ENUM);
            }
        }

        {
            GLTexture texture;
            glBindTexture(target, texture);

            glTexStorage3D(target, 1, desc.format, desc.blockX * 2, desc.blockY * 2,
                           desc.blockZ * 2);
            if (compressedFormatEnabled)
            {
                if (supportsTarget)
                {
                    EXPECT_GL_NO_ERROR();

                    checkSubImage3D(target, desc);
                }
                else
                {
                    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
                }
            }
            else
            {
                EXPECT_GL_ERROR(GL_INVALID_ENUM);
            }
        }
    }

    void testSampler3D(GLenum target)
    {
        ASSERT(target == GL_TEXTURE_2D_ARRAY || target == GL_TEXTURE_3D);

        const FormatDesc desc = ::testing::get<1>(GetParam());
        {
            int width                                    = desc.blockX;
            int height                                   = desc.blockY;
            int depth                                    = desc.blockZ * 9;
            static GLubyte red_RGBA_ASTC_block_data[144] = {
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255,
                252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255};

            GLTexture texID;
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(target, texID);
            EXPECT_GL_NO_ERROR();
            glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

            glCompressedTexImage3D(target, 0, desc.format, width, height, depth, 0, desc.size * 9,
                                   red_RGBA_ASTC_block_data);
            EXPECT_GL_NO_ERROR();

            float layer = 0.0f;
            for (int i = 0; i < depth; i++)
            {
                GLFramebuffer fb;
                glBindFramebuffer(GL_FRAMEBUFFER, fb);
                GLRenderbuffer rb;
                glBindRenderbuffer(GL_RENDERBUFFER, rb);
                glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
                glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                          rb);
                EXPECT_GL_NO_ERROR();

                glViewport(0, 0, width, height);
                glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);

                if (target == GL_TEXTURE_2D_ARRAY)
                {
                    layer = i * 1.0f;
                    draw2DArrayTexturedQuad(0.0f, 1.0f, false, layer);
                }
                else
                {
                    layer = ((float)i + 0.5f) / (float)depth;
                    draw3DTexturedQuad(0.0f, 1.0f, false, layer);
                }

                std::vector<GLColor> pixels(width * height);
                glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        const int curPos = y * width + x;
                        EXPECT_COLOR_NEAR(GLColor::red, pixels[curPos], 2);
                    }
                }
            }
        }
    }

    void test()
    {
        // It's not possible to disable ETC2/EAC support on ES 3.0.
        const bool compressedFormatEnabled = mAlwaysOnES3 && getClientMajorVersion() >= 3;
        check2D(compressedFormatEnabled);
        if (getClientMajorVersion() >= 3)
        {
            check3D(GL_TEXTURE_2D_ARRAY, compressedFormatEnabled, mSupports2DArray);
            check3D(GL_TEXTURE_3D, compressedFormatEnabled, mSupports3D);
        }

        for (const std::string &extName : mExtNames)
        {
            if (!extName.empty())
            {
                if (IsGLExtensionRequestable(extName))
                {
                    glRequestExtensionANGLE(extName.c_str());
                }
                ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(extName) && !compressedFormatEnabled);
            }
        }

        // Repeat all checks after enabling the extensions.
        check2D(true);
        if (getClientMajorVersion() >= 3)
        {
            check3D(GL_TEXTURE_2D_ARRAY, true, mSupports2DArray);
            check3D(GL_TEXTURE_3D, true, mSupports3D);
        }
    }

    void testSamplerASTCSliced3D()
    {
        for (const std::string &extName : mExtNames)
        {
            if (!extName.empty())
            {
                if (IsGLExtensionRequestable(extName))
                {
                    glRequestExtensionANGLE(extName.c_str());
                }
                ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(extName));
            }
        }

        if (getClientMajorVersion() >= 3)
        {
            testSampler3D(GL_TEXTURE_2D_ARRAY);
            testSampler3D(GL_TEXTURE_3D);
        }
    }

    void test3D()
    {
        for (const std::string &extName : mExtNames)
        {
            if (!extName.empty())
            {
                if (IsGLExtensionRequestable(extName))
                {
                    glRequestExtensionANGLE(extName.c_str());
                }
                ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(extName));
            }
        }

        check3D(GL_TEXTURE_2D_ARRAY, true, mSupports2DArray);
        check3D(GL_TEXTURE_3D, true, mSupports3D);

        testSampler3D(GL_TEXTURE_3D);
    }

  private:
    const std::vector<std::string> mExtNames;
    const bool mSupportsUpdates;
    const bool mSupportsPartialUpdates;
    const bool mSupports2DArray;
    const bool mSupports3D;
    const bool mAlwaysOnES3;
};

template <char const *ext1,
          char const *ext2,
          bool supports_updates,
          bool supports_partial_updates,
          bool supports_2d_array,
          bool supports_3d,
          bool always_on_es3>
class _Test : public CompressedTextureFormatsTest
{
  public:
    _Test()
        : CompressedTextureFormatsTest(ext1,
                                       ext2,
                                       supports_updates,
                                       supports_partial_updates,
                                       supports_2d_array,
                                       supports_3d,
                                       always_on_es3)
    {}
};

const char kDXT1[]     = "GL_EXT_texture_compression_dxt1";
const char kDXT3[]     = "GL_ANGLE_texture_compression_dxt3";
const char kDXT5[]     = "GL_ANGLE_texture_compression_dxt5";
const char kS3TCSRGB[] = "GL_EXT_texture_compression_s3tc_srgb";
const char kRGTC[]     = "GL_EXT_texture_compression_rgtc";
const char kBPTC[]     = "GL_EXT_texture_compression_bptc";

const char kETC1[]    = "GL_OES_compressed_ETC1_RGB8_texture";
const char kETC1Sub[] = "GL_EXT_compressed_ETC1_RGB8_sub_texture";

const char kETC2[] = "GL_ANGLE_compressed_texture_etc";

const char kASTC[]         = "GL_KHR_texture_compression_astc_ldr";
const char kASTC3D[]       = "GL_OES_texture_compression_astc";
const char kASTCSliced3D[] = "GL_KHR_texture_compression_astc_sliced_3d";

const char kPVRTC1[]    = "GL_IMG_texture_compression_pvrtc";
const char kPVRTCSRGB[] = "GL_EXT_pvrtc_sRGB";

const char kEmpty[] = "";

// clang-format off
using CompressedTextureDXT1Test     = _Test<kDXT1,     kEmpty, true, true, true, false, false>;
using CompressedTextureDXT3Test     = _Test<kDXT3,     kEmpty, true, true, true, false, false>;
using CompressedTextureDXT5Test     = _Test<kDXT5,     kEmpty, true, true, true, false, false>;
using CompressedTextureS3TCSRGBTest = _Test<kS3TCSRGB, kEmpty, true, true, true, false, false>;
using CompressedTextureRGTCTest     = _Test<kRGTC,     kEmpty, true, true, true, false, false>;
using CompressedTextureBPTCTest     = _Test<kBPTC,     kEmpty, true, true, true, true,  false>;

using CompressedTextureETC1Test    = _Test<kETC1, kEmpty,   false, false, false, false, false>;
using CompressedTextureETC1SubTest = _Test<kETC1, kETC1Sub, true,  true,  true,  false, false>;

using CompressedTextureETC2Test  = _Test<kETC2, kEmpty, true, true, true, false, true>;

using CompressedTextureASTCTest                = _Test<kASTC, kEmpty,        true, true,  true, false, false>;
using CompressedTextureASTC3DTest              = _Test<kASTC3D, kEmpty,      true, true, false,  true, false>;
using CompressedTextureASTCSliced3DTest        = _Test<kASTC, kASTCSliced3D, true, true,  true,  true, false>;
using CompressedTextureSamplerASTCSliced3DTest = _Test<kASTC, kASTCSliced3D, true, true,  true,  true, false>;

using CompressedTexturePVRTC1Test     = _Test<kPVRTC1, kEmpty,     true, false, false, false, false>;
using CompressedTexturePVRTC1SRGBTest = _Test<kPVRTC1, kPVRTCSRGB, true, false, false, false, false>;
// clang-format on

std::string PrintToStringParamName(
    const ::testing::TestParamInfo<CompressedTextureTestParams> &info)
{
    std::string name = gl::GLinternalFormatToString(std::get<1>(info.param).format);
    if (name.find("GL_") == 0)
        name.erase(0, 3);
    if (name.find("COMPRESSED_") == 0)
        name.erase(0, 11);
    for (std::string str : {"_EXT", "_IMG", "_KHR", "_OES"})
    {
        if (name.find(str) != std::string::npos)
        {
            name.erase(name.length() - 4, 4);
            break;
        }
    }
    std::stringstream nameStr;
    nameStr << std::get<0>(info.param) << "__" << name;
    return nameStr.str();
}

static const FormatDesc kDXT1Formats[] = {{GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 4, 4, 1, 8},
                                          {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 4, 4, 1, 8}};

static const FormatDesc kDXT3Formats[] = {{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 4, 4, 1, 16}};

static const FormatDesc kDXT5Formats[] = {{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 4, 4, 1, 16}};

static const FormatDesc kS3TCSRGBFormats[] = {
    {GL_COMPRESSED_SRGB_S3TC_DXT1_EXT, 4, 4, 1, 8},
    {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 4, 4, 1, 8},
    {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 4, 4, 1, 16},
    {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 4, 4, 1, 16}};

static const FormatDesc kRGTCFormats[] = {{GL_COMPRESSED_RED_RGTC1_EXT, 4, 4, 1, 8},
                                          {GL_COMPRESSED_SIGNED_RED_RGTC1_EXT, 4, 4, 1, 8},
                                          {GL_COMPRESSED_RED_GREEN_RGTC2_EXT, 4, 4, 1, 16},
                                          {GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT, 4, 4, 1, 16}};

static const FormatDesc kBPTCFormats[] = {{GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 1, 16},
                                          {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, 4, 4, 1, 16},
                                          {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, 4, 4, 1, 16},
                                          {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, 4, 4, 1, 16}};

static const FormatDesc kETC1Formats[] = {{GL_ETC1_RGB8_OES, 4, 4, 1, 8}};

static const FormatDesc kETC2Formats[] = {
    {GL_COMPRESSED_R11_EAC, 4, 4, 1, 8},
    {GL_COMPRESSED_SIGNED_R11_EAC, 4, 4, 1, 8},
    {GL_COMPRESSED_RG11_EAC, 4, 4, 1, 16},
    {GL_COMPRESSED_SIGNED_RG11_EAC, 4, 4, 1, 16},
    {GL_COMPRESSED_RGB8_ETC2, 4, 4, 1, 8},
    {GL_COMPRESSED_SRGB8_ETC2, 4, 4, 1, 8},
    {GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, 4, 4, 1, 8},
    {GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, 4, 4, 1, 8},
    {GL_COMPRESSED_RGBA8_ETC2_EAC, 4, 4, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, 4, 4, 1, 16}};

static const FormatDesc kASTCFormats[] = {
    {GL_COMPRESSED_RGBA_ASTC_4x4_KHR, 4, 4, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_5x4_KHR, 5, 4, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_5x5_KHR, 5, 5, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_6x5_KHR, 6, 5, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_6x6_KHR, 6, 6, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_8x5_KHR, 8, 5, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_8x6_KHR, 8, 6, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_8x8_KHR, 8, 8, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_10x5_KHR, 10, 5, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_10x6_KHR, 10, 6, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_10x8_KHR, 10, 8, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_10x10_KHR, 10, 10, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_12x10_KHR, 12, 10, 1, 16},
    {GL_COMPRESSED_RGBA_ASTC_12x12_KHR, 12, 12, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, 4, 4, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, 5, 4, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, 5, 5, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, 6, 5, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, 6, 6, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, 8, 5, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, 8, 6, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, 8, 8, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, 10, 5, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, 10, 6, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, 10, 8, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, 10, 10, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, 12, 10, 1, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR, 12, 12, 1, 16}};

static const FormatDesc kASTC3DFormats[] = {
    {GL_COMPRESSED_RGBA_ASTC_3x3x3_OES, 3, 3, 3, 16},
    {GL_COMPRESSED_RGBA_ASTC_4x3x3_OES, 4, 3, 3, 16},
    {GL_COMPRESSED_RGBA_ASTC_4x4x3_OES, 4, 4, 3, 16},
    {GL_COMPRESSED_RGBA_ASTC_4x4x4_OES, 4, 4, 4, 16},
    {GL_COMPRESSED_RGBA_ASTC_5x4x4_OES, 5, 4, 4, 16},
    {GL_COMPRESSED_RGBA_ASTC_5x5x4_OES, 5, 5, 4, 16},
    {GL_COMPRESSED_RGBA_ASTC_5x5x5_OES, 5, 5, 5, 16},
    {GL_COMPRESSED_RGBA_ASTC_6x5x5_OES, 6, 5, 5, 16},
    {GL_COMPRESSED_RGBA_ASTC_6x6x5_OES, 6, 6, 5, 16},
    {GL_COMPRESSED_RGBA_ASTC_6x6x6_OES, 6, 6, 6, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES, 3, 3, 3, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES, 4, 3, 3, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES, 4, 4, 3, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES, 4, 4, 4, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES, 5, 4, 4, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES, 5, 5, 4, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES, 5, 5, 5, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES, 6, 5, 5, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES, 6, 6, 5, 16},
    {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES, 6, 6, 6, 16}};

static const FormatDesc kPVRTC1Formats[] = {{GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 4, 4, 1, 8},
                                            {GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 8, 4, 1, 8},
                                            {GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 4, 4, 1, 8},
                                            {GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, 8, 4, 1, 8}};

static const FormatDesc kPVRTC1SRGBFormats[] = {
    {GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT, 8, 4, 1, 8},
    {GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT, 4, 4, 1, 8},
    {GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT, 8, 4, 1, 8},
    {GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT, 4, 4, 1, 8}};

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureDXT1Test,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kDXT1Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureDXT3Test,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kDXT3Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureDXT5Test,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kDXT5Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureS3TCSRGBTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kS3TCSRGBFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureRGTCTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kRGTCFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureBPTCTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kBPTCFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureETC1Test,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kETC1Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureETC1SubTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kETC1Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureETC2Test,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kETC2Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureASTCTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kASTCFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureASTC3DTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kASTC3DFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureASTCSliced3DTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kASTCFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTextureSamplerASTCSliced3DTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kASTCFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTexturePVRTC1Test,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kPVRTC1Formats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

ANGLE_INSTANTIATE_TEST_COMBINE_1(CompressedTexturePVRTC1SRGBTest,
                                 PrintToStringParamName,
                                 testing::ValuesIn(kPVRTC1SRGBFormats),
                                 ANGLE_ALL_TEST_PLATFORMS_ES2,
                                 ANGLE_ALL_TEST_PLATFORMS_ES3);

// clang-format off
TEST_P(CompressedTextureDXT1Test,     Test) { test(); }
TEST_P(CompressedTextureDXT3Test,     Test) { test(); }
TEST_P(CompressedTextureDXT5Test,     Test) { test(); }
TEST_P(CompressedTextureS3TCSRGBTest, Test) { test(); }
TEST_P(CompressedTextureRGTCTest,     Test) { test(); }
TEST_P(CompressedTextureBPTCTest,     Test) { test(); }

TEST_P(CompressedTextureETC1Test,    Test) { test(); }
TEST_P(CompressedTextureETC1SubTest, Test) { test(); }

TEST_P(CompressedTextureETC2Test,  Test) { test(); }

TEST_P(CompressedTextureASTCTest,         Test) { test(); }
TEST_P(CompressedTextureASTC3DTest,       Test) { test3D(); }
TEST_P(CompressedTextureASTCSliced3DTest, Test) { test(); }

// Check that texture sampling works correctly
TEST_P(CompressedTextureSamplerASTCSliced3DTest, Test) { testSamplerASTCSliced3D(); }

TEST_P(CompressedTexturePVRTC1Test,     Test) { test(); }
TEST_P(CompressedTexturePVRTC1SRGBTest, Test) { test(); }
// clang-format on
}  // namespace
