//
// Copyright 2020 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.
//
// VulkanFramebufferTest:
//   Tests to validate our Vulkan framebuffer image allocation.
//

#include "test_utils/ANGLETest.h"
#include "test_utils/angle_test_instantiate.h"
// 'None' is defined as 'struct None {};' in
// third_party/googletest/src/googletest/include/gtest/internal/gtest-type-util.h.
// But 'None' is also defined as a numeric constant 0L in <X11/X.h>.
// So we need to include ANGLETest.h first to avoid this conflict.

#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/ProgramVk.h"
#include "libANGLE/renderer/vulkan/TextureVk.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include "util/shader_utils.h"

using namespace angle;

namespace
{

class VulkanFramebufferTest : public ANGLETest<>
{
  protected:
    gl::Context *hackContext() const
    {
        egl::Display *display   = static_cast<egl::Display *>(getEGLWindow()->getDisplay());
        gl::ContextID contextID = {
            static_cast<GLuint>(reinterpret_cast<uintptr_t>(getEGLWindow()->getContext()))};
        return display->getContext(contextID);
    }

    rx::ContextVk *hackANGLE() const
    {
        // Hack the angle!
        return rx::GetImplAs<rx::ContextVk>(hackContext());
    }

    rx::TextureVk *hackTexture(GLuint handle) const
    {
        // Hack the angle!
        const gl::Context *context = hackContext();
        const gl::Texture *texture = context->getTexture({handle});
        return rx::vk::GetImpl(texture);
    }
};

// Test that framebuffer can be created from a mip-incomplete texture, and that its allocation only
// includes the framebuffer's attached mip.
TEST_P(VulkanFramebufferTest, TextureAttachmentMipIncomplete)
{
    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 100, 100, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 5, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set framebuffer to mip 0.  Framebuffer should be complete, and make the texture allocate
    // an image of only 1 level.
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glClearColor(0, 0, 0, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);

    // http://anglebug.com/4686: The Vulkan backend is allocating three mips of sizes 100x100,
    // 50x50 and 25x25 instead of one mip of size 100x100.
    ANGLE_SKIP_TEST_IF(IsVulkan());

    rx::TextureVk *textureVk = hackTexture(texture);
    EXPECT_EQ(textureVk->getImage().getLevelCount(), 1u);
}

// Test ensure that a R4G4B4A4 format sample only will actually uses R4G4B4A4 format instead of
// R8G8B8A8.
TEST_P(VulkanFramebufferTest, R4G4B4A4TextureSampleOnlyActuallyUses444Format)
{
    rx::ContextVk *contextVk = hackANGLE();
    rx::RendererVk *renderer = contextVk->getRenderer();
    angle::FormatID formatID = angle::FormatID::R4G4B4A4_UNORM;

    // Check if R4G4B4A4_UNORM is supported format.
    bool isTexturable = renderer->hasImageFormatFeatureBits(
        formatID,
        VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT);
    ANGLE_SKIP_TEST_IF(!isTexturable);

    static constexpr GLsizei kTexWidth  = 100;
    static constexpr GLsizei kTexHeight = 100;
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);

    const GLushort u16Color = 0xF00F;  // red
    std::vector<GLushort> pixels(kTexWidth * kTexHeight, u16Color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, kTexWidth, kTexHeight, 0, GL_RGBA,
                 GL_UNSIGNED_SHORT_4_4_4_4, pixels.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Texture2DLod(), essl3_shaders::fs::Texture2DLod());
    glUseProgram(program);
    GLint textureLocation = glGetUniformLocation(program, essl3_shaders::Texture2DUniform());
    ASSERT_NE(-1, textureLocation);
    GLint lodLocation = glGetUniformLocation(program, essl3_shaders::LodUniform());
    ASSERT_NE(-1, lodLocation);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glClearColor(0, 1, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glUniform1f(lodLocation, 0);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 255, 0, 0, 255);
    ASSERT_GL_NO_ERROR();

    rx::TextureVk *textureVk = hackTexture(texture);
    EXPECT_EQ(textureVk->getImage().getActualFormatID(), formatID);
}

ANGLE_INSTANTIATE_TEST(VulkanFramebufferTest, ES3_VULKAN());

}  // anonymous namespace
