//
// Copyright 2015 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.
//
// ETCTextureTest:
//   Tests for ETC lossy decode formats.
//

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

#include "media/etc2bc_srgb8_alpha8.inc"

using namespace angle;

namespace
{

class ETCTextureTest : public ANGLETest<>
{
  protected:
    ETCTextureTest() : mTexture(0u)
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void testSetUp() override
    {
        glGenTextures(1, &mTexture);
        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override { glDeleteTextures(1, &mTexture); }

    GLuint mTexture;
};

// Tests a cube map array texture with compressed ETC2 RGB8 format
TEST_P(ETCTextureTest, ETC2RGB8_CubeMapValidation)
{
    ANGLE_SKIP_TEST_IF(!(IsGLExtensionEnabled("GL_EXT_texture_cube_map_array") ||
                         (getClientMajorVersion() >= 3 && getClientMinorVersion() > 1)));

    constexpr GLsizei kInvalidTextureWidth  = 8;
    constexpr GLsizei kInvalidTextureHeight = 8;
    constexpr GLsizei kCubemapFaceCount     = 6;
    const std::vector<GLubyte> kInvalidTextureData(
        kInvalidTextureWidth * kInvalidTextureHeight * kCubemapFaceCount, 0);

    glBindTexture(GL_TEXTURE_CUBE_MAP, mTexture);
    EXPECT_GL_NO_ERROR();

    glCompressedTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGB, kInvalidTextureWidth,
                           kInvalidTextureHeight, kCubemapFaceCount, 0, kInvalidTextureData.size(),
                           kInvalidTextureData.data());
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    constexpr GLenum kFormat = GL_COMPRESSED_RGB8_ETC2;

    constexpr GLuint kWidth       = 4u;
    constexpr GLuint kHeight      = 4u;
    constexpr GLuint kDepth       = 6u;
    constexpr GLuint kPixelBytes  = 8u;
    constexpr GLuint kBlockWidth  = 4u;
    constexpr GLuint kBlockHeight = 4u;

    constexpr GLuint kNumBlocksWide = (kWidth + kBlockWidth - 1u) / kBlockWidth;
    constexpr GLuint kNumBlocksHigh = (kHeight + kBlockHeight - 1u) / kBlockHeight;
    constexpr GLuint kBytes         = kNumBlocksWide * kNumBlocksHigh * kPixelBytes * kDepth;

    std::vector<GLubyte> arrayData(kBytes, 0);

    glCompressedTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, kFormat, kWidth, kHeight, kDepth, 0,
                           kBytes, arrayData.data());
    EXPECT_GL_NO_ERROR();

    // Invalid Dimensions
    glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kInvalidTextureWidth,
                              kInvalidTextureHeight, kDepth, kFormat, kBytes, arrayData.data());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    // Invalid Format
    glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth,
                              GL_RGB, kBytes, arrayData.data());
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Invalid Data Size
    glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kWidth, kHeight, kDepth,
                              kFormat, kInvalidTextureData.size(), kInvalidTextureData.data());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Tests that uploading compressed texture from a PBO with a misaligned offset doesn't crash.
TEST_P(ETCTextureTest, PBOWithMisalignedOffset)
{
    // Need ES 3.0 for PBOs.
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);

    constexpr GLsizei kWidth  = 512;
    constexpr GLsizei kHeight = 512;
    constexpr GLsizei kBPB    = 8;  // 8 bytes per block
    constexpr GLsizei kBW     = 4;
    constexpr GLsizei kBH     = 4;

    GLsizei blocksX        = kWidth / kBW;
    GLsizei blocksY        = kHeight / kBH;
    GLsizei compressedSize = blocksX * blocksY * kBPB;

    glBindTexture(GL_TEXTURE_2D, mTexture);
    // Use GL_COMPRESSED_RGB8_ETC2 which is core in ES 3.0
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, kWidth, kHeight, 0,
                           compressedSize, nullptr);

    // Misaligned offset to trigger the fallback path in Metal backend
    constexpr GLsizei kPBOOffset = 1;

    GLBuffer pbo;
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kPBOOffset + compressedSize, nullptr, GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_COMPRESSED_RGB8_ETC2,
                              compressedSize, reinterpret_cast<void *>(kPBOOffset));
    EXPECT_GL_NO_ERROR();
}

class ETCToBCTextureTest : public ANGLETest<>
{
  protected:
    static constexpr int kWidth    = 4;
    static constexpr int kHeight   = 4;
    static constexpr int kTexSize  = 4;
    static constexpr int kAbsError = 6;
    // the transcoded BC1 data
    // {0x0000a534, 0x05055555}
    // then decoded RGBA data are
    // 0xff000000 0xff000000 0xff000000 0xff000000
    // 0xff000000 0xff000000 0xff000000 0xff000000
    // 0xff000000 0xff000000 0xffa5a6a5 0xffa5a6a5
    // 0xff000000 0xff000000 0xffa5a6a5 0xffa5a6a5
    static constexpr uint32_t kEtcAllZero[2]        = {0x0, 0x0};
    static constexpr uint32_t kEtcRGBData[2]        = {0x14050505, 0x00ffff33};
    static constexpr uint32_t kExpectedRGBColor[16] = {
        0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000,
        0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffa5a6a5, 0xffa5a6a5,
        0xff000000, 0xff000000, 0xffa5a6a5, 0xffa5a6a5,
    };
    // the transcoded RGB data (BC1)
    // {0x0000a534 0x58585c5f}
    // the 16 ETC2 decoded alpha value are
    // 20,  20, 182, 182
    // 128, 128, 110, 110
    // 128, 47,   47, 47
    // 128, 65,   65, 65
    // max alpha value = 0xb6
    // min alpha value = 0x14
    // Result BC4 data are 0xb00914b6, 0xdb3ffb91
    static constexpr uint32_t kEtcRGBAData[4] = {0xd556975c, 0x088ff048, 0x9e6c6c6c, 0x3f11f1ff};
    static constexpr uint32_t kExpectedRGBAColor[16] = {
        0x14373737, 0x14373737, 0xb6000000, 0xb6000000, 0x88a5a6a5, 0x88373737,
        0x70000000, 0x70000000, 0x88a5a6a5, 0x2b6e6f6e, 0x2b000000, 0x2b000000,
        0x88a5a6a5, 0x426e6f6e, 0x42000000, 0x42000000,
    };
    // Result BC4 data as {0xf6f1836f, 0xc41c5e7c}
    static constexpr uint32_t kEacR11Signed[2]            = {0xb068efff, 0x00b989e7};
    static constexpr uint32_t kExpectedR11SignedColor[16] = {
        0xff000003, 0xff000046, 0xff0000ac, 0xff0000ac, 0xff000025, 0xff000003,
        0xff000025, 0xff0000ac, 0xff000046, 0xff0000ac, 0xff000003, 0xff000046,
        0xff000003, 0xff0000ef, 0xff000003, 0xff000046,
    };

    // Result BC1 data as {0xa65a7b55, 0xcc3c4f43}
    static constexpr uint32_t kRgb8a1[2]          = {0x95938c6a, 0x0030e384};
    static constexpr uint32_t kExpectedRgb8a1[16] = {
        0x00000000, 0xffab697b, 0xffab697b, 0xffd6cba5, 0x00000000, 0x00000000,
        0xffab697b, 0xffd6cba5, 0xffab697b, 0x00000000, 0x00000000, 0xffab697b,
        0xffab697b, 0x00000000, 0xffab697b, 0x00000000,
    };

    static constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 position;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
        "}\n";
    ETCToBCTextureTest() : mEtcTexture(0u)
    {
        setWindowWidth(256);
        setWindowHeight(256);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void testSetUp() override
    {
        glGenTextures(1, &mEtcTexture);
        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override { glDeleteTextures(1, &mEtcTexture); }
    GLuint mEtcTexture;
};

// Tests GPU compute transcode ETC2_RGB8 to BC1
TEST_P(ETCToBCTextureTest, ETC2Rgb8UnormToBC1_2D)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));
    glViewport(0, 0, kWidth, kHeight);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_ETC2, kTexSize, kTexSize);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize, GL_COMPRESSED_RGB8_ETC2,
                              sizeof(kEtcRGBData), kEtcRGBData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    draw2DTexturedQuad(0.5f, 1.0f, false);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(kExpectedRGBColor[0]), kAbsError);
    EXPECT_PIXEL_COLOR_NEAR(1, 1, GLColor(kExpectedRGBColor[5]), kAbsError);
    EXPECT_PIXEL_COLOR_NEAR(2, 2, GLColor(kExpectedRGBColor[10]), kAbsError);
    EXPECT_PIXEL_COLOR_NEAR(3, 3, GLColor(kExpectedRGBColor[15]), kAbsError);
}

// Tests GPU compute transcode ETC2_RGB8 to BC1 with cube texture type
TEST_P(ETCToBCTextureTest, ETC2Rgb8UnormToBC1_Cube)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));
    glViewport(0, 0, kWidth, kHeight);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_COMPRESSED_RGB8_ETC2, kTexSize, kTexSize);
    glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcAllZero), kEtcAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcAllZero), kEtcAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcRGBData), kEtcRGBData);
    glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcAllZero), kEtcAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcAllZero), kEtcAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcAllZero), kEtcAllZero);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    constexpr char kTextureCubeFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "uniform highp samplerCube texCube;\n"
        "uniform vec3 faceCoord;\n"
        "out vec4 fragColor;\n"
        "void main()\n"
        "{\n"
        "    fragColor = texture(texCube, faceCoord);\n"
        "}\n";
    GLuint program = CompileProgram(kVS, kTextureCubeFS);
    glUseProgram(program);
    GLint texCubePos = glGetUniformLocation(program, "texCube");
    glUniform1i(texCubePos, 0);
    GLint faceCoordpos = glGetUniformLocation(program, "faceCoord");
    // sample at (2, 2) on y plane.
    glUniform3f(faceCoordpos, 0.25f, 1.0f, 0.25f);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(kExpectedRGBColor[10]), kAbsError);
}

// Tests GPU compute transcode ETC2_RGB8 to BC1 with 2DArray texture type
TEST_P(ETCToBCTextureTest, ETC2Rgb8UnormToBC1_2DArray)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));
    glViewport(0, 0, kWidth, kHeight);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D_ARRAY, mEtcTexture);
    static constexpr int kArraySize = 6;
    uint32_t data[2 * kArraySize]   = {
        kEtcAllZero[0], kEtcAllZero[1],  // array 0
        kEtcRGBData[0], kEtcRGBData[1],  // array 1
        kEtcRGBData[0], kEtcRGBData[1],  // array 2
        kEtcAllZero[0], kEtcAllZero[1],  // array 3
        kEtcAllZero[0], kEtcAllZero[1],  // array 4
        kEtcAllZero[0], kEtcAllZero[1],  // array 5
    };
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_COMPRESSED_RGB8_ETC2, kTexSize, kTexSize, kArraySize);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kTexSize, kTexSize, kArraySize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(data), data);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    constexpr char kTexture2DArrayFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "uniform highp sampler2DArray tex2DArray;\n"
        "uniform vec3 coord;\n"
        "out vec4 fragColor;\n"
        "void main()\n"
        "{\n"
        "    fragColor = texture(tex2DArray, coord);\n"
        "}\n";
    GLuint program = CompileProgram(kVS, kTexture2DArrayFS);
    glUseProgram(program);
    GLint texCubePos = glGetUniformLocation(program, "tex2DArray");
    glUniform1i(texCubePos, 0);
    GLint faceCoordpos = glGetUniformLocation(program, "coord");
    // sample at (2, 2) on y plane.
    glUniform3f(faceCoordpos, 0.625f, 0.625f, 2.0f);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(kExpectedRGBColor[10]), kAbsError);
}

// Tests GPU compute transcode ETC2_RGB8 to BC1 with partial updates
TEST_P(ETCToBCTextureTest, ETC2Rgb8UnormToBC1_partial)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan() || !IsNVIDIA() ||
                       getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
                       !IsGLExtensionEnabled("GL_EXT_texture_compression_dxt1"));
    glViewport(0, 0, kWidth, kHeight);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_ETC2, kTexSize * 3, kTexSize * 3);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize, GL_COMPRESSED_RGB8_ETC2,
                              sizeof(kEtcAllZero), kEtcAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 2 * kTexSize, 2 * kTexSize, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_ETC2, sizeof(kEtcRGBData), kEtcRGBData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "uniform highp sampler2D tex2D;\n"
        "uniform vec2 coord;\n"
        "out vec4 fragColor;\n"
        "void main()\n"
        "{\n"
        "    fragColor = texture(tex2D, coord);\n"
        "}\n";
    GLuint program = CompileProgram(kVS, kFS);
    glUseProgram(program);
    GLint texCubePos = glGetUniformLocation(program, "tex2D");
    glUniform1i(texCubePos, 0);
    GLint faceCoordpos = glGetUniformLocation(program, "coord");
    // sample at (2, 2) on y plane.
    glUniform2f(faceCoordpos, 21.0f / 24.0f, 21.0f / 24.0f);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(kExpectedRGBColor[10]), kAbsError);
}

// Tests GPU compute transcode ETC2_RGBA8 to BC3
TEST_P(ETCToBCTextureTest, ETC2Rgba8UnormToBC3)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc"));

    glViewport(0, 0, kWidth, kHeight);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA8_ETC2_EAC, kTexSize, kTexSize);

    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGBA8_ETC2_EAC, sizeof(kEtcRGBAData), kEtcRGBAData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    draw2DTexturedQuad(0.5f, 1.0f, false);
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            ANGLE_UNSAFE_TODO(
                EXPECT_PIXEL_COLOR_NEAR(j, i, GLColor(kExpectedRGBAColor[i * 4 + j]), kAbsError));
        }
    }
}

// Tests GPU compute transcode ETC2_RGBA8 to BC3 texture with lod.
TEST_P(ETCToBCTextureTest, ETC2Rgba8UnormToBC3_Lod)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc"));

    glViewport(0, 0, kWidth, kHeight);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_COMPRESSED_RGBA8_ETC2_EAC, kTexSize, kTexSize);

    static constexpr uint32_t kAllZero[] = {0, 0, 0, 0};
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGBA8_ETC2_EAC, sizeof(kAllZero), kAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, kTexSize / 2, kTexSize / 2,
                              GL_COMPRESSED_RGBA8_ETC2_EAC, sizeof(kAllZero), kAllZero);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 2, 0, 0, kTexSize / 4, kTexSize / 4,
                              GL_COMPRESSED_RGBA8_ETC2_EAC, sizeof(kEtcRGBAData), kEtcRGBAData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec2 position;\n"
        "out vec2 texCoord;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(position, 0, 1);\n"
        "    texCoord = position * 0.5 + vec2(0.5);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "uniform highp sampler2D tex2D;\n"
        "in vec2 texCoord;\n"
        "out vec4 fragColor;\n"
        "void main()\n"
        "{\n"
        "    fragColor = textureLod(tex2D, texCoord, 2.0);\n"
        "}\n";
    GLuint program = CompileProgram(kVS, kFS);
    glUseProgram(program);
    GLint texPos = glGetUniformLocation(program, "tex2D");
    glUniform1i(texPos, 0);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(kExpectedRGBAColor[0]), kAbsError);
}

// Tests GPU compute transcode ETC2_SRGB8_ALPHA8 to BC3
TEST_P(ETCToBCTextureTest, ETC2SrgbAlpha8UnormToBC3)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc_srgb"));

    glViewport(0, 0, pixel_width, pixel_height);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    EXPECT_GL_NO_ERROR();
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, pixel_width,
                           pixel_height, 0, sizeof(etc2bc_srgb8_alpha8), etc2bc_srgb8_alpha8);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    draw2DTexturedQuad(0.5f, 1.0f, false);
    EXPECT_PIXEL_ALPHA_NEAR(96, 160, 255, kAbsError);
    EXPECT_PIXEL_ALPHA_NEAR(88, 148, 0, kAbsError);
}

// Tests GPU compute transcode R11 Signed to BC4
TEST_P(ETCToBCTextureTest, ETC2R11SignedToBC4)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_rgtc"));

    glViewport(0, 0, kWidth, kHeight);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_SIGNED_R11_EAC, kTexSize, kTexSize);

    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_SIGNED_R11_EAC, sizeof(kEacR11Signed), kEacR11Signed);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    constexpr char kVS[] =
        "attribute vec2 position;\n"
        "varying mediump vec2 texCoord;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(position, 0, 1);\n"
        "    texCoord = position * 0.5 + vec2(0.5);\n"
        "}\n";

    constexpr char kFS[] =
        "varying mediump vec2 texCoord;\n"
        "uniform sampler2D tex;\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = texture2D(tex, texCoord)/2.0 + vec4(0.5, 0.0, 0.0, 0.5);\n"
        "}\n";
    GLuint program = CompileProgram(kVS, kFS);
    glUseProgram(program);
    GLint pos = glGetUniformLocation(program, "tex");
    glUniform1i(pos, 0);
    drawQuad(program, "position", 0.5f);
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            ANGLE_UNSAFE_TODO(EXPECT_PIXEL_COLOR_NEAR(
                j, i, GLColor(kExpectedR11SignedColor[i * 4 + j]), kAbsError));
        }
    }
}

// Tests GPU compute transcode RG11 to BC5
TEST_P(ETCToBCTextureTest, ETC2RG11ToBC5)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_rgtc"));
    glViewport(0, 0, kWidth, kHeight);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    uint32_t data[4] = {
        kEtcRGBAData[0],
        kEtcRGBAData[1],
        kEtcRGBAData[0],
        kEtcRGBAData[1],
    };
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RG11_EAC, kTexSize, kTexSize);

    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize, GL_COMPRESSED_RG11_EAC,
                              sizeof(data), data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    draw2DTexturedQuad(0.5f, 1.0f, false);
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            uint32_t color = (ANGLE_UNSAFE_TODO(kExpectedRGBAColor[i * 4 + j]) & 0xff000000) >> 24;
            color |= color << 8;
            color |= 0xff000000;
            EXPECT_PIXEL_COLOR_NEAR(j, i, GLColor(color), kAbsError);
        }
    }
}

// Tests GPU compute transcode RGB8A1 to BC1_RGBA
TEST_P(ETCToBCTextureTest, ETC2Rgb8a1UnormToBC1)
{
    ANGLE_SKIP_TEST_IF(
        !IsVulkan() || !IsNVIDIA() ||
        !getEGLWindow()->isFeatureEnabled(Feature::SupportsComputeTranscodeEtcToBc) ||
        !IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc"));
    glViewport(0, 0, kWidth, kHeight);
    glBindTexture(GL_TEXTURE_2D, mEtcTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, kTexSize,
                   kTexSize);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize,
                              GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, sizeof(kRgb8a1),
                              kRgb8a1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    draw2DTexturedQuad(0.5f, 1.0f, false);
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            ANGLE_UNSAFE_TODO(
                EXPECT_PIXEL_COLOR_NEAR(j, i, GLColor(kExpectedRgb8a1[i * 4 + j]), kAbsError));
        }
    }
}

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31_AND_ES32(ETCTextureTest);
ANGLE_INSTANTIATE_TEST_ES3_AND(ETCToBCTextureTest,
                               ES3_VULKAN().enable(Feature::SupportsComputeTranscodeEtcToBc));
}  // anonymous namespace
