//
// 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.
//
// DepthStencilTest:
//   Tests covering depth- or stencil-only rendering to make sure the other non-existing aspect is
//   not affecting the results (since the format may be emulated with one that has both aspects).
//

#include "test_utils/ANGLETest.h"

#include "platform/FeaturesVk.h"
#include "test_utils/gl_raii.h"

using namespace angle;

namespace
{

class DepthStencilTest : public ANGLETest
{
  protected:
    DepthStencilTest()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setConfigDepthBits(24);
        setConfigStencilBits(8);
    }

    void testSetUp() override
    {
        glBindTexture(GL_TEXTURE_2D, mColorTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);

        // Setup Color/Stencil FBO with a stencil format that's emulated with packed depth/stencil.
        glBindFramebuffer(GL_FRAMEBUFFER, mColorStencilFBO);

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mColorTexture,
                               0);
        glBindRenderbuffer(GL_RENDERBUFFER, mStencilTexture);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, getWindowWidth(),
                              getWindowHeight());
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                                  mStencilTexture);

        ASSERT_GL_NO_ERROR();

        // Note: GL_DEPTH_COMPONENT24 is allowed in GLES2 with GL_OES_depth24 extension.
        if (getClientMajorVersion() >= 3 || IsGLExtensionEnabled("GL_OES_depth24"))
        {
            // Setup Color/Depth FBO with a depth format that's emulated with packed depth/stencil.
            glBindFramebuffer(GL_FRAMEBUFFER, mColorDepthFBO);

            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   mColorTexture, 0);
            glBindRenderbuffer(GL_RENDERBUFFER, mDepthTexture);
            glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, getWindowWidth(),
                                  getWindowHeight());
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
                                      mDepthTexture);
        }

        ASSERT_GL_NO_ERROR();
    }

    void bindColorStencilFBO()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, mColorStencilFBO);
        mHasDepth = false;
    }

    void bindColorDepthFBO()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, mColorDepthFBO);
        mHasStencil = false;
    }

    // Override a feature to force emulation of stencil-only and depth-only formats with a packed
    // depth/stencil format
    void overrideFeaturesVk(FeaturesVk *featuresVk) override
    {
        featuresVk->overrideFeatures({"force_fallback_format"}, true);
    }

    void prepareSingleEmulatedWithPacked();
    void ensureColor(GLColor color);
    void ensureDepthUnaffected();
    void ensureStencilUnaffected();

  private:
    GLFramebuffer mColorStencilFBO;
    GLFramebuffer mColorDepthFBO;
    GLTexture mColorTexture;
    GLRenderbuffer mDepthTexture;
    GLRenderbuffer mStencilTexture;

    bool mHasDepth   = true;
    bool mHasStencil = true;
};

class DepthStencilTestES3 : public DepthStencilTest
{
  protected:
    void compareDepth(uint32_t expected);
    void clearAndCompareDepth(GLfloat depth, uint32_t expected);
    void drawAndCompareDepth(GLProgram &program, GLfloat depth, uint32_t expected);
};

void DepthStencilTest::ensureColor(GLColor color)
{
    const int width  = getWindowWidth();
    const int height = getWindowHeight();

    std::vector<GLColor> pixelData(width * height);
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data());

    for (int i = 0; i < width * height; i += 16)
    {
        GLColor actualColor = pixelData[i];
        EXPECT_NEAR(color.R, actualColor.R, 1);
        EXPECT_NEAR(color.G, actualColor.G, 1);
        EXPECT_NEAR(color.B, actualColor.B, 1);
        EXPECT_NEAR(color.A, actualColor.A, 1);

        if (i % width == 0)
            i += 16 * width;
    }
}

void DepthStencilTest::ensureDepthUnaffected()
{
    ANGLE_GL_PROGRAM(depthTestProgram, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::Blue());
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_EQUAL);
    drawQuad(depthTestProgram, essl1_shaders::PositionAttrib(), 0.123f);
    glDisable(GL_DEPTH_TEST);
    ASSERT_GL_NO_ERROR();

    // Since depth shouldn't exist, the drawQuad above should succeed in turning the whole image
    // blue.
    ensureColor(GLColor::blue);
}

void DepthStencilTest::ensureStencilUnaffected()
{
    ANGLE_GL_PROGRAM(stencilTestProgram, essl1_shaders::vs::Passthrough(),
                     essl1_shaders::fs::Green());
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x1B, 0xFF);
    drawQuad(stencilTestProgram, essl1_shaders::PositionAttrib(), 0.0f);
    glDisable(GL_STENCIL_TEST);
    ASSERT_GL_NO_ERROR();

    // Since stencil shouldn't exist, the drawQuad above should succeed in turning the whole image
    // green.
    ensureColor(GLColor::green);
}

void DepthStencilTest::prepareSingleEmulatedWithPacked()
{
    const int w     = getWindowWidth();
    const int h     = getWindowHeight();
    const int whalf = w >> 1;
    const int hhalf = h >> 1;

    // Clear to a random color, 0.75 depth and 0x36 stencil
    Vector4 color1(0.1f, 0.2f, 0.3f, 0.4f);
    GLColor color1RGB(color1);

    glClearColor(color1[0], color1[1], color1[2], color1[3]);
    glClearDepthf(0.75f);
    glClearStencil(0x36);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Verify color was cleared correctly.
    EXPECT_PIXEL_COLOR_NEAR(0, 0, color1RGB, 1);

    // Use masked color to clear two channels of the image to a second color, 0.25 depth and 0x59
    // stencil.
    Vector4 color2(0.2f, 0.4f, 0.6f, 0.8f);
    glClearColor(color2[0], color2[1], color2[2], color2[3]);
    glClearDepthf(0.25f);
    glClearStencil(0x59);
    glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    ASSERT_GL_NO_ERROR();

    GLColor color2RGB(Vector4(color2[0], color1[1], color2[2], color1[3]));

    EXPECT_PIXEL_COLOR_NEAR(whalf, hhalf, color2RGB, 1);

    EXPECT_PIXEL_COLOR_NEAR(0, 0, color2RGB, 1);
    EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, color2RGB, 1);
    EXPECT_PIXEL_COLOR_NEAR(0, h - 1, color2RGB, 1);
    EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, color2RGB, 1);

    // Use scissor to clear the center to a third color, 0.5 depth and 0xA9 stencil.
    glEnable(GL_SCISSOR_TEST);
    glScissor(whalf / 2, hhalf / 2, whalf, hhalf);

    Vector4 color3(0.3f, 0.5f, 0.7f, 0.9f);
    GLColor color3RGB(color3);
    glClearColor(color3[0], color3[1], color3[2], color3[3]);
    glClearDepthf(0.5f);
    glClearStencil(0xA9);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glDisable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(whalf, hhalf, color3RGB, 1);

    EXPECT_PIXEL_COLOR_NEAR(0, 0, color2RGB, 1);
    EXPECT_PIXEL_COLOR_NEAR(w - 1, 0, color2RGB, 1);
    EXPECT_PIXEL_COLOR_NEAR(0, h - 1, color2RGB, 1);
    EXPECT_PIXEL_COLOR_NEAR(w - 1, h - 1, color2RGB, 1);

    // Use scissor to draw to the right half of the image with a fourth color, 0.6 depth and 0x84
    // stencil.
    glEnable(GL_SCISSOR_TEST);
    glScissor(whalf, 0, whalf, h);

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x84, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glStencilMask(0xFF);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.2f);

    glDisable(GL_STENCIL_TEST);
    glDisable(GL_SCISSOR_TEST);
}

// Tests that clearing or rendering into a depth-only format doesn't affect stencil.
TEST_P(DepthStencilTest, DepthOnlyEmulatedWithPacked)
{
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && !IsGLExtensionEnabled("GL_OES_depth24"));

    bindColorDepthFBO();
    prepareSingleEmulatedWithPacked();
    ensureStencilUnaffected();
}

// Tests that clearing or rendering into a stencil-only format doesn't affect depth.
TEST_P(DepthStencilTest, StencilOnlyEmulatedWithPacked)
{
    // http://anglebug.com/4092
    ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D9());
    bindColorStencilFBO();
    prepareSingleEmulatedWithPacked();
    ensureDepthUnaffected();
}

// Tests that clearing depth/stencil followed by draw works when the depth/stencil attachment is a
// texture.
TEST_P(DepthStencilTestES3, ClearThenDraw)
{
    GLFramebuffer FBO;
    glBindFramebuffer(GL_FRAMEBUFFER, FBO);

    constexpr GLsizei kSize = 6;

    // Create framebuffer to draw into, with both color and depth attachments.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLTexture depth;
    glBindTexture(GL_TEXTURE_2D, depth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, kSize, kSize, 0, GL_DEPTH_STENCIL,
                 GL_UNSIGNED_INT_24_8_OES, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depth, 0);
    ASSERT_GL_NO_ERROR();

    // Set viewport and clear depth/stencil
    glViewport(0, 0, kSize, kSize);
    glClearDepthf(1);
    glClearStencil(0x55);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // If depth is not cleared to 1, rendering would fail.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    // If stencil is not clear to 0x55, rendering would fail.
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);

    // Set up program
    ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());

    // Draw red
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Verify.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::red);
}

void DepthStencilTestES3::compareDepth(uint32_t expected)
{
    uint32_t pixel;
    glReadPixels(0, 0, 1, 1, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, &pixel);
    ASSERT_GL_NO_ERROR();

    // Right shift by 8 bits to only compare 24 depth bits
    // and ignore 8 undefined bits.
    pixel = pixel >> 8;

    EXPECT_NEAR(pixel, expected, 1);
}

void DepthStencilTestES3::clearAndCompareDepth(GLfloat depth, uint32_t expected)
{
    glClearDepthf(depth);
    glClear(GL_DEPTH_BUFFER_BIT);
    compareDepth(expected);
}

void DepthStencilTestES3::drawAndCompareDepth(GLProgram &program,
                                              GLfloat positionZ,
                                              uint32_t expected)
{
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    drawQuad(program, essl3_shaders::PositionAttrib(), positionZ, 1.0f);
    glDisable(GL_DEPTH_TEST);
    compareDepth(expected);
}

TEST_P(DepthStencilTestES3, ReadPixelsDepth24)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_depth24") ||
                       !IsGLExtensionEnabled("GL_NV_read_depth"));

    // The test fails on native GLES on Android in glReadPixels
    // with GL_INVALID_OPERATION due to the format/type combination
    // not being supported.
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    // Create GL_DEPTH_COMPONENT24 texture
    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, getWindowWidth(), getWindowHeight(), 0,
                 GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);

    // Set up framebuffer
    GLFramebuffer depthFBO;
    GLRenderbuffer depthRenderbuffer;

    glBindFramebuffer(GL_FRAMEBUFFER, depthFBO);

    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, getWindowWidth(),
                          getWindowHeight());
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
                              depthRenderbuffer);

    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Test clear
    clearAndCompareDepth(0.0f, 0x0);
    clearAndCompareDepth(0.125f, 0x200000);
    clearAndCompareDepth(0.5f, 0x800000);
    clearAndCompareDepth(1.0f, 0xffffff);

    // Test draw
    ANGLE_GL_PROGRAM(depthTestProgram, essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());
    drawAndCompareDepth(depthTestProgram, 0.0f, 0x800000);
    drawAndCompareDepth(depthTestProgram, 0.125f, 0x8fffff);
    drawAndCompareDepth(depthTestProgram, 0.5f, 0xbfffff);
    drawAndCompareDepth(depthTestProgram, 1.0f, 0xffffff);

    ASSERT_GL_NO_ERROR();
}

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(DepthStencilTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DepthStencilTestES3);
ANGLE_INSTANTIATE_TEST_ES3(DepthStencilTestES3);

}  // anonymous namespace
