//
// Copyright 2016 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.
//
// FramebufferMixedSamplesTest:
//   Test CHROMIUM subset of NV_framebuffer_mixed_samples.
//   This extension allows rendering to a framebuffer that has different
//   sample counts for different render buffers (stencil, depth, color)

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

using namespace angle;

namespace
{

const GLuint kWidth  = 100;
const GLuint kHeight = 100;

class CHROMIUMFramebufferMixedSamplesTest : public ANGLETest<>
{
  protected:
    enum SetupFBOType
    {
        MixedSampleFBO,   // 1 color sample, N stencil samples.
        SingleSampleFBO,  // 1 color sample, 1 stencil sample.
    };

    bool isApplicable() const
    {
        return IsGLExtensionEnabled("GL_CHROMIUM_framebuffer_mixed_samples") &&
               IsGLExtensionEnabled("GL_OES_rgb8_rgba8");
    }

    void testSetUp() override
    {
        mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());

        GLuint position_loc = glGetAttribLocation(mProgram, essl1_shaders::PositionAttrib());
        mColorLoc           = glGetUniformLocation(mProgram, essl1_shaders::ColorUniform());

        glGenBuffers(1, &mVBO);
        glBindBuffer(GL_ARRAY_BUFFER, mVBO);

        static float vertices[] = {
            1.0f,  1.0f, -1.0f, 1.0f,  -1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
            -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f,  -1.0f, 1.0f, 1.0f,
        };
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(position_loc);
        glVertexAttribPointer(position_loc, 2, GL_FLOAT, GL_FALSE, 0, 0);

        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override
    {
        glDeleteBuffers(1, &mVBO);
        glDeleteProgram(mProgram);

        ASSERT_GL_NO_ERROR();
    }

    void prepareForDraw(SetupFBOType fbo_type)
    {
        glActiveTexture(GL_TEXTURE0);
        glGenTextures(1, &mTexture);
        glBindTexture(GL_TEXTURE_2D, mTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        glBindTexture(GL_TEXTURE_2D, 0);

        glGenRenderbuffers(1, &mStencilRB);
        glBindRenderbuffer(GL_RENDERBUFFER, mStencilRB);

        if (fbo_type == MixedSampleFBO)
        {
            // Create a sample buffer.
            GLsizei num_samples = 8, max_samples = 0;
            glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
            num_samples = std::min(num_samples, max_samples);
            glRenderbufferStorageMultisampleANGLE(GL_RENDERBUFFER, num_samples, GL_STENCIL_INDEX8,
                                                  kWidth, kHeight);
            GLint param = 0;
            glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &param);
            EXPECT_GT(param, 1);
        }
        else
        {
            glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, kWidth, kHeight);
        }
        glBindRenderbuffer(GL_RENDERBUFFER, 0);

        glGenFramebuffers(1, &mSampleFBO);
        glBindFramebuffer(GL_FRAMEBUFFER, mSampleFBO);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                                  mStencilRB);
        EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
                  glCheckFramebufferStatus(GL_FRAMEBUFFER));

        glUseProgram(mProgram);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glViewport(0, 0, kWidth, kHeight);
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        glClearStencil(1);
        glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glEnable(GL_STENCIL_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glStencilMask(0xffffffff);
        glStencilFunc(GL_EQUAL, 1, 0xffffffff);
        glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);

        ASSERT_GL_NO_ERROR();
    }

    void cleanup()
    {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glDeleteFramebuffers(1, &mSampleFBO);
        glDeleteTextures(1, &mTexture);
        glDeleteRenderbuffers(1, &mStencilRB);

        ASSERT_GL_NO_ERROR();
    }

    GLuint mSampleFBO;
    GLuint mStencilRB;
    GLuint mTexture;

    GLuint mProgram;
    GLuint mVBO;
    GLint mColorLoc;
};

}  // namespace

TEST_P(CHROMIUMFramebufferMixedSamplesTest, StateSettingTest)
{
    if (!isApplicable())
    {
        return;
    }

    GLint value = -1;
    glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value);
    EXPECT_EQ(GL_NONE, value);
    GLenum kValues[] = {GL_NONE, GL_RGB, GL_RGBA, GL_ALPHA};
    for (auto expect : kValues)
    {
        glCoverageModulationCHROMIUM(expect);
        value = -1;
        glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value);
        EXPECT_EQ(expect, static_cast<GLenum>(value));
        EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
    }

    glCoverageModulationCHROMIUM(GL_BYTE);
    EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), glGetError());
    value = -1;
    glGetIntegerv(GL_COVERAGE_MODULATION_CHROMIUM, &value);
    EXPECT_EQ(static_cast<GLenum>(GL_ALPHA), static_cast<GLenum>(value));
    EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
}

// The test pattern is as follows:
// A green triangle (bottom left, top right, top left).
// A blue triangle (top left, bottom right, bottom left).
// The triangles will overlap but overlap only contains green pixels,
// due to each draw erasing its area from stencil.
// The blue triangle will fill only the area (bottom left, center,
// bottom right).

// The test tests that CoverageModulation call works.
// The fractional pixels of both triangles end up being modulated
// by the coverage of the fragment. Test that drawing with and without
// CoverageModulation causes the result to be different.
TEST_P(CHROMIUMFramebufferMixedSamplesTest, CoverageModulation)
{
    if (!isApplicable())
    {
        return;
    }
    static const float kBlue[]  = {0.0f, 0.0f, 1.0f, 1.0f};
    static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
    std::unique_ptr<uint8_t[]> results[3];
    const GLint kResultSize = kWidth * kHeight * 4;

    for (int pass = 0; pass < 3; ++pass)
    {
        prepareForDraw(MixedSampleFBO);
        if (pass == 1)
        {
            glCoverageModulationCHROMIUM(GL_RGBA);
        }
        glUniform4fv(mColorLoc, 1, kGreen);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glUniform4fv(mColorLoc, 1, kBlue);
        glDrawArrays(GL_TRIANGLES, 3, 3);
        if (pass == 1)
        {
            glCoverageModulationCHROMIUM(GL_NONE);
        }
        ANGLE_UNSAFE_TODO(results[pass]).reset(new uint8_t[kResultSize]);
        ANGLE_UNSAFE_TODO(memset(results[pass].get(), 123u, kResultSize));
        glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                     ANGLE_UNSAFE_TODO(results[pass]).get());

        cleanup();
    }

    ANGLE_UNSAFE_TODO(EXPECT_NE(0, memcmp(results[0].get(), results[1].get(), kResultSize)));
    // Verify that rendering is deterministic, so that the pass above does not
    // come from non-deterministic rendering.
    ANGLE_UNSAFE_TODO(EXPECT_EQ(0, memcmp(results[0].get(), results[2].get(), kResultSize)));
}

// The test tests that the stencil buffer can be multisampled, even though the
// color buffer is single-sampled. Draws the same pattern with single-sample
// stencil buffer and with multisample stencil buffer. The images should differ.
TEST_P(CHROMIUMFramebufferMixedSamplesTest, MultisampleStencilEffective)
{
    if (!isApplicable())
    {
        return;
    }

    static const float kBlue[]  = {0.0f, 0.0f, 1.0f, 1.0f};
    static const float kGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};

    std::unique_ptr<uint8_t[]> results[3];
    const GLint kResultSize = kWidth * kHeight * 4;

    for (int pass = 0; pass < 3; ++pass)
    {
        if (pass == 1)
        {
            prepareForDraw(MixedSampleFBO);
        }
        else
        {
            prepareForDraw(SingleSampleFBO);
        }
        glUniform4fv(mColorLoc, 1, kGreen);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glUniform4fv(mColorLoc, 1, kBlue);
        glDrawArrays(GL_TRIANGLES, 3, 3);

        ANGLE_UNSAFE_TODO(results[pass]).reset(new uint8_t[kResultSize]);
        ANGLE_UNSAFE_TODO(memset(results[pass].get(), 12u, kResultSize));
        glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                     ANGLE_UNSAFE_TODO(results[pass]).get());

        cleanup();
    }

    ANGLE_UNSAFE_TODO(EXPECT_NE(0, memcmp(results[0].get(), results[1].get(), kResultSize)));
    // Verify that rendering is deterministic, so that the pass above does not
    // come from non-deterministic rendering.
    ANGLE_UNSAFE_TODO(EXPECT_EQ(0, memcmp(results[0].get(), results[2].get(), kResultSize)));
}

ANGLE_INSTANTIATE_TEST_ES2(CHROMIUMFramebufferMixedSamplesTest);
