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

// BPTCCompressedTextureTest.cpp: Tests of the GL_EXT_texture_compression_bptc extension

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

using namespace angle;

namespace
{

const unsigned int kPixelTolerance = 1u;

// The pixel data represents a 4x4 pixel image with the left side colored red and the right side
// green. It was BC7 encoded using Microsoft's BC6HBC7Encoder.
const std::array<GLubyte, 16> kBC7Data4x4 = {0x50, 0x1f, 0xfc, 0xf, 0x0,  0xf0, 0xe3, 0xe1,
                                             0xe1, 0xe1, 0xc1, 0xf, 0xfc, 0xc0, 0xf,  0xfc};

// The pixel data represents a 4x4 pixel image with the transparent black solid color.
// Sampling from a zero-filled block is undefined, so use a valid one.
const std::array<GLubyte, 16> kBC7BlackData4x4 = {0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
}  // anonymous namespace

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

    void testSetUp() override
    {
        constexpr char kVS[] = R"(precision highp float;
attribute vec4 position;
varying vec2 texcoord;

void main()
{
    gl_Position = position;
    texcoord = (position.xy * 0.5) + 0.5;
    texcoord.y = 1.0 - texcoord.y;
})";

        constexpr char kFS[] = R"(precision highp float;
uniform sampler2D tex;
varying vec2 texcoord;

void main()
{
    gl_FragColor = texture2D(tex, texcoord);
})";

        mTextureProgram = CompileProgram(kVS, kFS);
        if (mTextureProgram == 0)
        {
            FAIL() << "shader compilation failed.";
        }

        mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");

        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override { glDeleteProgram(mTextureProgram); }

    void setupTextureParameters(GLuint texture)
    {
        glBindTexture(GL_TEXTURE_2D, texture);
        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);
    }

    void drawTexture()
    {
        glUseProgram(mTextureProgram);
        glUniform1i(mTextureUniformLocation, 0);
        drawQuad(mTextureProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
    }

    GLuint mTextureProgram;
    GLint mTextureUniformLocation;
};

class BPTCCompressedTextureTestES3 : public BPTCCompressedTextureTest
{
  public:
    BPTCCompressedTextureTestES3() : BPTCCompressedTextureTest() {}
};

// Test sampling from a BC7 non-SRGB image.
TEST_P(BPTCCompressedTextureTest, CompressedTexImageBC7)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), kBC7Data4x4.data());

    EXPECT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);
}

// Test sampling from a BC7 SRGB image.
TEST_P(BPTCCompressedTextureTest, CompressedTexImageBC7SRGB)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), kBC7Data4x4.data());

    EXPECT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);
}

// Test that using the BC6H floating point formats doesn't crash.
TEST_P(BPTCCompressedTextureTest, CompressedTexImageBC6HNoCrash)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    // This fake pixel data represents a 4x4 pixel image.
    // TODO(http://anglebug.com/2869): Add pixel tests for these formats. These need HDR source
    // images.
    std::vector<GLubyte> data;
    data.resize(16u, 0u);

    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, 4, 4, 0,
                           data.size(), data.data());
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, 4, 4, 0,
                           data.size(), data.data());

    EXPECT_GL_NO_ERROR();

    drawTexture();
}

// Test texStorage2D with a BPTC format.
TEST_P(BPTCCompressedTextureTestES3, CompressedTexStorage)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);

    GLTexture texture;
    setupTextureParameters(texture);

    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4);
    EXPECT_GL_NO_ERROR();
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                              kBC7Data4x4.size(), kBC7Data4x4.data());
    EXPECT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);
}

// Test validation of glCompressedTexSubImage2D with BPTC formats
TEST_P(BPTCCompressedTextureTest, CompressedTexSubImageValidation)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);

    std::vector<GLubyte> data(16 * 2 * 2);  // 2x2 blocks, thats 8x8 pixels.

    // Size mip 0 to a large size.
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 8, 8, 0,
                           data.size(), data.data());
    ASSERT_GL_NO_ERROR();

    // Test a sub image with an offset that isn't a multiple of the block size.
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                              kBC7Data4x4.size(), kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 3, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                              kBC7Data4x4.size(), kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);

    // Test a sub image with a negative offset.
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, -1, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                              kBC7Data4x4.size(), kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_VALUE);
    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, -1, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                              kBC7Data4x4.size(), kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_VALUE);
}

// Test that copying BPTC textures is not allowed. This restriction exists only in
// EXT_texture_compression_bptc, and not in the ARB variant.
TEST_P(BPTCCompressedTextureTest, CopyTexImage2DDisallowed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 0, 0, 4, 4, 0);
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that copying BPTC textures is not allowed. This restriction exists only in
// EXT_texture_compression_bptc, and not in the ARB variant.
TEST_P(BPTCCompressedTextureTest, CopyTexSubImage2DDisallowed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), kBC7Data4x4.data());
    ASSERT_GL_NO_ERROR();

    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 4, 4);
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that copying BPTC textures is not allowed. This restriction exists only in
// EXT_texture_compression_bptc, and not in the ARB variant.
TEST_P(BPTCCompressedTextureTestES3, CopyTexSubImage3DDisallowed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);

    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 1);
    ASSERT_GL_NO_ERROR();

    glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 0, 0, 4, 4);
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test uploading texture data from a PBO to a texture.
TEST_P(BPTCCompressedTextureTestES3, PBOCompressedTexImage)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    GLBuffer buffer;
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
    ASSERT_GL_NO_ERROR();

    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), nullptr);
    ASSERT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);

    // Destroy the data
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7BlackData4x4.size(), kBC7BlackData4x4.data(),
                 GL_STREAM_DRAW);
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7BlackData4x4.size(), nullptr);
    ASSERT_GL_NO_ERROR();

    drawTexture();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    // Initialize again.  This time, the texture's image is already allocated, so the PBO data
    // upload could be directly done.
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), nullptr);
    ASSERT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);
}

// Test uploading texture data from a PBO to a non-zero base texture.
TEST_P(BPTCCompressedTextureTestES3, PBOCompressedTexImageNonZeroBase)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    GLBuffer buffer;
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
    ASSERT_GL_NO_ERROR();

    glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
    ASSERT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);

    // Destroy the data
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7BlackData4x4.size(), kBC7BlackData4x4.data(),
                 GL_STREAM_DRAW);
    glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7BlackData4x4.size(), nullptr);
    ASSERT_GL_NO_ERROR();

    drawTexture();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    // Initialize again.  This time, the texture's image is already allocated, so the PBO data
    // upload could be directly done.
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
    glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4, 0,
                           kBC7Data4x4.size(), nullptr);
    ASSERT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);
}

// Test uploading texture data from a PBO to a texture allocated with texStorage2D.
TEST_P(BPTCCompressedTextureTestES3, PBOCompressedTexStorage)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    setupTextureParameters(texture);

    glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 4, 4);
    ASSERT_GL_NO_ERROR();

    GLBuffer buffer;
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, kBC7Data4x4.size(), kBC7Data4x4.data(), GL_STREAM_DRAW);
    ASSERT_GL_NO_ERROR();

    glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT,
                              kBC7Data4x4.size(), nullptr);

    ASSERT_GL_NO_ERROR();

    drawTexture();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(0, getWindowHeight() - 1, GLColor::red, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, 0, GLColor::green, kPixelTolerance);
    EXPECT_PIXEL_COLOR_NEAR(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green,
                            kPixelTolerance);
}

// Test validation of glCompressedTexSubImage3D with BPTC formats
TEST_P(BPTCCompressedTextureTestES3, CompressedTexSubImage3DValidation)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture.get());

    std::vector<GLubyte> data(16 * 2 * 2);  // 2x2x1 blocks, thats 8x8x1 pixels.

    // Size mip 0 to a large size.
    glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, 8, 8, 1, 0,
                           data.size(), data.data());
    ASSERT_GL_NO_ERROR();

    // Test a sub image with an offset that isn't a multiple of the block size.
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 2, 0, 0, 4, 4, 1,
                              GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
                              kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 2, 0, 4, 4, 1,
                              GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
                              kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_OPERATION);

    // Test a sub image with a negative offset.
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, -1, 0, 0, 4, 4, 1,
                              GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
                              kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_VALUE);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, -1, 0, 4, 4, 1,
                              GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
                              kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_VALUE);
    glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, -1, 4, 4, 1,
                              GL_COMPRESSED_RGBA_BPTC_UNORM_EXT, kBC7Data4x4.size(),
                              kBC7Data4x4.data());
    ASSERT_GL_ERROR(GL_INVALID_VALUE);
}

// 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(BPTCCompressedTextureTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BPTCCompressedTextureTestES3);
ANGLE_INSTANTIATE_TEST_ES3(BPTCCompressedTextureTestES3);
