/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL Module
 * ---------------------------------------
 *
 * Copyright 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Test KHR_mutable_render_buffer
 *//*--------------------------------------------------------------------*/

#include "teglMutableRenderBufferTests.hpp"

#include "egluUtil.hpp"

#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"

#include "gluDefs.hpp"
#include "gluRenderContext.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

using namespace eglw;

using std::vector;

namespace deqp
{
namespace egl
{
namespace
{

class MutableRenderBufferTest : public TestCase
{
public:
    MutableRenderBufferTest(EglTestContext &eglTestCtx, const char *name, const char *description,
                            bool enableConfigBit);
    ~MutableRenderBufferTest(void);
    void init(void);
    void deinit(void);
    IterateResult iterate(void);

protected:
    uint32_t drawAndSwap(const Library &egl, uint32_t color, bool flush);
    bool m_enableConfigBit;
    EGLDisplay m_eglDisplay;
    EGLSurface m_eglSurface;
    EGLConfig m_eglConfig;
    eglu::NativeWindow *m_window;
    EGLContext m_eglContext;
    glw::Functions m_gl;
};

MutableRenderBufferTest::MutableRenderBufferTest(EglTestContext &eglTestCtx, const char *name, const char *description,
                                                 bool enableConfigBit)
    : TestCase(eglTestCtx, name, description)
    , m_enableConfigBit(enableConfigBit)
    , m_eglDisplay(EGL_NO_DISPLAY)
    , m_eglSurface(EGL_NO_SURFACE)
    , m_eglConfig(DE_NULL)
    , m_window(DE_NULL)
    , m_eglContext(EGL_NO_CONTEXT)
{
}

MutableRenderBufferTest::~MutableRenderBufferTest(void)
{
    deinit();
}

void MutableRenderBufferTest::init(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    // create display
    m_eglDisplay = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());

    if (!eglu::hasExtension(egl, m_eglDisplay, "EGL_KHR_mutable_render_buffer"))
    {
        TCU_THROW(NotSupportedError, "EGL_KHR_mutable_render_buffer is not supported");
    }

    // get mutable render buffer config
    const EGLint attribs[]      = {EGL_RED_SIZE,
                                   8,
                                   EGL_GREEN_SIZE,
                                   8,
                                   EGL_BLUE_SIZE,
                                   8,
                                   EGL_ALPHA_SIZE,
                                   8,
                                   EGL_SURFACE_TYPE,
                                   EGL_WINDOW_BIT | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
                                   EGL_RENDERABLE_TYPE,
                                   EGL_OPENGL_ES2_BIT,
                                   EGL_NONE};
    const EGLint attribsNoBit[] = {EGL_RED_SIZE,
                                   8,
                                   EGL_GREEN_SIZE,
                                   8,
                                   EGL_BLUE_SIZE,
                                   8,
                                   EGL_ALPHA_SIZE,
                                   8,
                                   EGL_SURFACE_TYPE,
                                   EGL_WINDOW_BIT,
                                   EGL_RENDERABLE_TYPE,
                                   EGL_OPENGL_ES2_BIT,
                                   EGL_NONE};

    if (m_enableConfigBit)
    {
        m_eglConfig = eglu::chooseSingleConfig(egl, m_eglDisplay, attribs);
    }
    else
    {
        const vector<EGLConfig> configs = eglu::chooseConfigs(egl, m_eglDisplay, attribsNoBit);

        for (vector<EGLConfig>::const_iterator config = configs.begin(); config != configs.end(); ++config)
        {
            EGLint surfaceType = -1;
            EGLU_CHECK_CALL(egl, getConfigAttrib(m_eglDisplay, *config, EGL_SURFACE_TYPE, &surfaceType));

            if (!(surfaceType & EGL_MUTABLE_RENDER_BUFFER_BIT_KHR))
            {
                m_eglConfig = *config;
                break;
            }
        }

        if (m_eglConfig == DE_NULL)
            TCU_THROW(NotSupportedError, "No config without support for mutable_render_buffer found");
    }

    // create surface
    const eglu::NativeWindowFactory &factory =
        eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());
    m_window =
        factory.createWindow(&m_eglTestCtx.getNativeDisplay(), m_eglDisplay, m_eglConfig, DE_NULL,
                             eglu::WindowParams(480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
    m_eglSurface =
        eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_eglDisplay, m_eglConfig, DE_NULL);

    // create context and make current
    const EGLint contextAttribList[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};

    egl.bindAPI(EGL_OPENGL_ES_API);
    m_eglContext = egl.createContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, contextAttribList);
    EGLU_CHECK_MSG(egl, "eglCreateContext");
    TCU_CHECK(m_eglSurface != EGL_NO_SURFACE);
    egl.makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext);
    EGLU_CHECK_MSG(egl, "eglMakeCurrent");

    m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2, 0));
}

void MutableRenderBufferTest::deinit(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    if (m_eglContext != EGL_NO_CONTEXT)
    {
        egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        egl.destroyContext(m_eglDisplay, m_eglContext);
        m_eglContext = EGL_NO_CONTEXT;
    }

    if (m_eglSurface != EGL_NO_SURFACE)
    {
        egl.destroySurface(m_eglDisplay, m_eglSurface);
        m_eglSurface = EGL_NO_SURFACE;
    }

    if (m_eglDisplay != EGL_NO_DISPLAY)
    {
        egl.terminate(m_eglDisplay);
        m_eglDisplay = EGL_NO_DISPLAY;
    }

    if (m_window != DE_NULL)
    {
        delete m_window;
        m_window = DE_NULL;
    }
}

uint32_t MutableRenderBufferTest::drawAndSwap(const Library &egl, uint32_t color, bool flush)
{
    DE_ASSERT(color < 256);
    m_gl.clearColor((float)color / 255.f, (float)color / 255.f, (float)color / 255.f, (float)color / 255.f);
    m_gl.clear(GL_COLOR_BUFFER_BIT);
    if (flush)
    {
        m_gl.flush();
    }
    else
    {
        EGLU_CHECK_CALL(egl, swapBuffers(m_eglDisplay, m_eglSurface));
    }
    return (color | color << 8 | color << 16 | color << 24);
}

TestCase::IterateResult MutableRenderBufferTest::iterate(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    int frameNumber = 1;

    // run a few back-buffered frames even if we can't verify their contents
    for (; frameNumber < 5; frameNumber++)
    {
        drawAndSwap(egl, frameNumber, false);
    }

    // switch to single-buffer rendering
    EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Use eglSwapBuffers for the first frame
    drawAndSwap(egl, frameNumber, false);
    frameNumber++;

    // test a few single-buffered frames
    for (; frameNumber < 10; frameNumber++)
    {
        uint32_t backBufferPixel  = 0xFFFFFFFF;
        uint32_t frontBufferPixel = drawAndSwap(egl, frameNumber, true);
        m_gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &backBufferPixel);

        // when single buffered, front-buffer == back-buffer
        if (backBufferPixel != frontBufferPixel)
        {
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface isn't single-buffered");
            return STOP;
        }
    }

    // switch back to back-buffer rendering
    EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER));

    // run a few back-buffered frames even if we can't verify their contents
    for (; frameNumber < 14; frameNumber++)
    {
        drawAndSwap(egl, frameNumber, false);
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

class MutableRenderBufferQueryTest : public MutableRenderBufferTest
{
public:
    MutableRenderBufferQueryTest(EglTestContext &eglTestCtx, const char *name, const char *description);
    ~MutableRenderBufferQueryTest(void);
    IterateResult iterate(void);
};

MutableRenderBufferQueryTest::MutableRenderBufferQueryTest(EglTestContext &eglTestCtx, const char *name,
                                                           const char *description)
    : MutableRenderBufferTest(eglTestCtx, name, description, true)
{
}

MutableRenderBufferQueryTest::~MutableRenderBufferQueryTest(void)
{
    deinit();
}

TestCase::IterateResult MutableRenderBufferQueryTest::iterate(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    // check that by default the query returns back buffered
    EGLint curRenderBuffer = -1;
    EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
    if (curRenderBuffer != EGL_BACK_BUFFER)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't default to back-buffered rendering");
        return STOP;
    }

    // switch to single-buffer rendering and check that the query output changed
    EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
    if (curRenderBuffer != EGL_SINGLE_BUFFER)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't switch to single-buffer rendering");
        return STOP;
    }

    // switch back to back-buffer rendering and check the query again
    EGLU_CHECK_CALL(egl, surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER));
    EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
    if (curRenderBuffer != EGL_BACK_BUFFER)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't switch back to back-buffer rendering");
        return STOP;
    }
    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

class MutableRenderBufferQueryNegativeTest : public MutableRenderBufferTest
{
public:
    MutableRenderBufferQueryNegativeTest(EglTestContext &eglTestCtx, const char *name, const char *description);
    ~MutableRenderBufferQueryNegativeTest(void);
    IterateResult iterate(void);
};

MutableRenderBufferQueryNegativeTest::MutableRenderBufferQueryNegativeTest(EglTestContext &eglTestCtx, const char *name,
                                                                           const char *description)
    : MutableRenderBufferTest(eglTestCtx, name, description, false)
{
}

MutableRenderBufferQueryNegativeTest::~MutableRenderBufferQueryNegativeTest(void)
{
    deinit();
}

TestCase::IterateResult MutableRenderBufferQueryNegativeTest::iterate(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    // check that by default the query returns back buffered
    EGLint curRenderBuffer = -1;
    EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
    if (curRenderBuffer != EGL_BACK_BUFFER)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't default to back-buffered rendering");
        return STOP;
    }

    // check that trying to switch to single-buffer rendering fails when the config bit is not set
    EGLBoolean ret = egl.surfaceAttrib(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER);
    EGLint err     = egl.getError();
    if (ret != EGL_FALSE)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
                                "eglSurfaceAttrib didn't return false when trying to enable single-buffering on a "
                                "context without the mutable render buffer bit set");
        return STOP;
    }
    if (err != EGL_BAD_MATCH)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
                                "eglSurfaceAttrib didn't set the EGL_BAD_MATCH error when trying to enable "
                                "single-buffering on a context without the mutable render buffer bit set");
        return STOP;
    }

    EGLU_CHECK_CALL(egl, querySurface(m_eglDisplay, m_eglSurface, EGL_RENDER_BUFFER, &curRenderBuffer));
    if (curRenderBuffer != EGL_BACK_BUFFER)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Surface didn't stay in back-buffered rendering after error");
        return STOP;
    }

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

} // namespace

MutableRenderBufferTests::MutableRenderBufferTests(EglTestContext &eglTestCtx)
    : TestCaseGroup(eglTestCtx, "mutable_render_buffer", "Mutable render buffer tests")
{
}

void MutableRenderBufferTests::init(void)
{
    addChild(new MutableRenderBufferQueryTest(
        m_eglTestCtx, "querySurface", "Tests if querySurface returns the correct value after surfaceAttrib is called"));
    addChild(new MutableRenderBufferQueryNegativeTest(
        m_eglTestCtx, "negativeConfigBit",
        "Tests trying to enable single-buffering on a context without the mutable render buffer bit set"));
    addChild(new MutableRenderBufferTest(
        m_eglTestCtx, "basic", "Tests enabling/disabling single-buffer rendering and checks the buffering behavior",
        true));
}

} // namespace egl
} // namespace deqp
