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

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

using namespace angle;

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

TEST_P(DiscardFramebufferEXTTest, DefaultFramebuffer)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_discard_framebuffer"));

    // TODO: fix crash issue. http://anglebug.com/4141
    ANGLE_SKIP_TEST_IF(IsD3D11());

    // These should succeed on the default framebuffer
    const GLenum discards1[] = {GL_COLOR_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards1);
    EXPECT_GL_NO_ERROR();

    const GLenum discards2[] = {GL_DEPTH_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards2);
    EXPECT_GL_NO_ERROR();

    const GLenum discards3[] = {GL_STENCIL_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards3);
    EXPECT_GL_NO_ERROR();

    const GLenum discards4[] = {GL_STENCIL_EXT, GL_COLOR_EXT, GL_DEPTH_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 3, discards4);
    EXPECT_GL_NO_ERROR();

    // These should fail on the default framebuffer
    const GLenum discards5[] = {GL_COLOR_ATTACHMENT0};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards5);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    const GLenum discards6[] = {GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards6);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    const GLenum discards7[] = {GL_STENCIL_ATTACHMENT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards7);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);
}

TEST_P(DiscardFramebufferEXTTest, NonDefaultFramebuffer)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_discard_framebuffer"));

    GLuint tex2D;
    GLuint framebuffer;

    // Create a basic off-screen framebuffer
    // Don't create a depth/stencil texture, to ensure that also works correctly
    glGenTextures(1, &tex2D);
    glGenFramebuffers(1, &framebuffer);
    glBindTexture(GL_TEXTURE_2D, tex2D);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex2D, 0);
    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

    // These should fail on the non-default framebuffer
    const GLenum discards1[] = {GL_COLOR_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards1);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    const GLenum discards2[] = {GL_DEPTH_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards2);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    const GLenum discards3[] = {GL_STENCIL_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards3);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    const GLenum discards4[] = {GL_STENCIL_EXT, GL_COLOR_EXT, GL_DEPTH_EXT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 3, discards4);
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    // These should succeed on the non-default framebuffer
    const GLenum discards5[] = {GL_COLOR_ATTACHMENT0};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards5);
    EXPECT_GL_NO_ERROR();

    const GLenum discards6[] = {GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards6);
    EXPECT_GL_NO_ERROR();

    const GLenum discards7[] = {GL_STENCIL_ATTACHMENT};
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards7);
    EXPECT_GL_NO_ERROR();
}

// ANGLE implements an optimization that if depth stencil buffer has not been used and not stored in
// the renderpass, the depth buffer clear will be dropped.
TEST_P(DiscardFramebufferEXTTest, ClearDepthThenDrawWithoutDepthTestThenDiscard)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_discard_framebuffer"));
    // TODO: fix crash issue. http://anglebug.com/4141
    ANGLE_SKIP_TEST_IF(IsD3D11());

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorUniformLocation =
        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    constexpr GLfloat kDepthClearValue = 0.5f;
    // This depth value equals to kDepthClearValue after viewport transform
    constexpr GLfloat depthDrawValue = kDepthClearValue * 2.0f - 1.0f;

    // This depth clear should be optimized out. We do not have a good way to verify that it
    // actually gets dropped, but at least we will ensure rendering is still correct.
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glEnable(GL_DEPTH_TEST);
    glClearDepthf(kDepthClearValue);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST);
    glUniform4fv(colorUniformLocation, 1, GLColor::cyan.toNormalizedVector().data());
    glViewport(0, 0, getWindowWidth(), getWindowHeight());
    drawQuad(program, essl1_shaders::PositionAttrib(), depthDrawValue + 0.05f);
    GLenum discards = GL_DEPTH_EXT;
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, &discards);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::cyan);
}

// This test is try to ensure that if depth test has been used, depth clear does not get optimized
// out. It also tests that if the depth buffer has not been used, the rendering is still correct.
TEST_P(DiscardFramebufferEXTTest, ClearDepthThenDrawWithDepthTestThenDiscard)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_discard_framebuffer"));
    // http://anglebug.com/4914
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel() && IsWindows());

    GLTexture texture;
    GLRenderbuffer renderbuffer;
    GLFramebuffer framebuffer;
    GLint colorUniformLocation;
    constexpr GLsizei kTexWidth  = 256;
    constexpr GLsizei kTexHeight = 256;

    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kTexWidth, kTexHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
                 nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    ASSERT_GL_NO_ERROR();
    glBindTexture(GL_TEXTURE_2D, 0);

    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kTexWidth, kTexWidth);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);
    ASSERT_GL_NO_ERROR();

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    ASSERT_GL_NO_ERROR();

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    colorUniformLocation = glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    // Draw into FBO0
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);  // clear to green
    constexpr GLfloat kDepthClearValue = 0.5f;
    // This depth value equals to kDepthClearValue after viewport transform
    constexpr GLfloat depthDrawValue = kDepthClearValue * 2.0f - 1.0f;
    glClearDepthf(kDepthClearValue);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // Draw bottom left with depth test disabled. DepthValue should remain 0.5f with blue color.
    glDepthFunc(GL_LESS);
    glDisable(GL_DEPTH_TEST);
    glViewport(0, 0, kTexWidth / 2, kTexHeight / 2);
    glUniform4fv(colorUniformLocation, 1, GLColor::blue.toNormalizedVector().data());
    drawQuad(program, essl1_shaders::PositionAttrib(), depthDrawValue - 0.1f);
    // Draw bottom right with depth test enabled. DepthValue should be 0.45f with blue color.
    glEnable(GL_DEPTH_TEST);
    glViewport(kTexWidth / 2, 0, kTexWidth / 2, kTexHeight / 2);
    drawQuad(program, essl1_shaders::PositionAttrib(), depthDrawValue - 0.1f);
    // Draw to top left with depth test disabled. DepthValue should remain 0.5f with blue color
    glDisable(GL_DEPTH_TEST);
    glViewport(0, kTexHeight / 2, kTexWidth / 2, kTexHeight / 2);
    drawQuad(program, essl1_shaders::PositionAttrib(), depthDrawValue + 0.1f);
    // Draw to top right with depth test enabled. DepthValue should remain 0.5f with green color
    glEnable(GL_DEPTH_TEST);
    glViewport(kTexWidth / 2, kTexHeight / 2, kTexWidth / 2, kTexHeight / 2);
    drawQuad(program, essl1_shaders::PositionAttrib(), depthDrawValue + 0.1f);

    // Now draw the full quad with depth test enabled to verify the depth value is expected.
    // It should fail depth test in bottom right which will keep it with blue color. All other
    // quarters will pass depth test and draw a red quad.
    glEnable(GL_DEPTH_TEST);
    glUniform4fv(colorUniformLocation, 1, GLColor::red.toNormalizedVector().data());
    glViewport(0, 0, kTexWidth, kTexHeight);
    drawQuad(program, essl1_shaders::PositionAttrib(), depthDrawValue - 0.05f);

    // Invalidate depth buffer. This will trigger depth value not been written to buffer but the
    // depth load/clear should not optimize out
    GLenum discards = GL_DEPTH_ATTACHMENT;
    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, &discards);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kTexWidth / 2 + 1, 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(1, kTexHeight / 2 + 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kTexWidth / 2 + 1, kTexHeight / 2 + 1, GLColor::red);
}

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