/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL Module
 * ---------------------------------------
 *
 * Copyright 2017 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 the EGL_ANDROID_get_frame_timestamps extension.
 *//*--------------------------------------------------------------------*/

#include "teglGetFrameTimestampsTests.hpp"

#include "teglSimpleConfigCase.hpp"

#include "egluNativeWindow.hpp"
#include "egluUtil.hpp"
#include "egluUnique.hpp"
#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"

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

#include "tcuResultCollector.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuVectorUtil.hpp"

#include "deClock.h"
#include "deMath.h"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deThread.hpp"

#include <algorithm>
#include <string>
#include <vector>
#include <sstream>

// Tentative EGL header definitions for EGL_ANDROID_get_Frame_timestamps.
// \todo [2017-01-25 brianderson] Remove once defined in the official headers.
#define EGL_TIMESTAMPS_ANDROID 0x3430
#define EGL_COMPOSITE_DEADLINE_ANDROID 0x3431
#define EGL_COMPOSITE_INTERVAL_ANDROID 0x3432
#define EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID 0x3433
#define EGL_REQUESTED_PRESENT_TIME_ANDROID 0x3434
#define EGL_RENDERING_COMPLETE_TIME_ANDROID 0x3435
#define EGL_COMPOSITION_LATCH_TIME_ANDROID 0x3436
#define EGL_FIRST_COMPOSITION_START_TIME_ANDROID 0x3437
#define EGL_LAST_COMPOSITION_START_TIME_ANDROID 0x3438
#define EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID 0x3439
#define EGL_DISPLAY_PRESENT_TIME_ANDROID 0x343A
#define EGL_DEQUEUE_READY_TIME_ANDROID 0x343B
#define EGL_READS_DONE_TIME_ANDROID 0x343C
typedef int64_t EGLnsecsANDROID;
typedef uint64_t EGLuint64KHR;
#define EGL_TIMESTAMP_PENDING_ANDROID (-2)
#define EGL_TIMESTAMP_INVALID_ANDROID (-1)
typedef EGLW_APICALL eglw::EGLBoolean(EGLW_APIENTRY *eglGetNextFrameIdANDROIDFunc)(eglw::EGLDisplay dpy,
                                                                                   eglw::EGLSurface surface,
                                                                                   EGLuint64KHR *frameId);
typedef EGLW_APICALL eglw::EGLBoolean(EGLW_APIENTRY *eglGetCompositorTimingANDROIDFunc)(eglw::EGLDisplay dpy,
                                                                                        eglw::EGLSurface surface,
                                                                                        eglw::EGLint numTimestamps,
                                                                                        const eglw::EGLint *names,
                                                                                        EGLnsecsANDROID *values);
typedef EGLW_APICALL eglw::EGLBoolean(EGLW_APIENTRY *eglGetCompositorTimingSupportedANDROIDFunc)(
    eglw::EGLDisplay dpy, eglw::EGLSurface surface, eglw::EGLint name);
typedef EGLW_APICALL eglw::EGLBoolean(EGLW_APIENTRY *eglGetFrameTimestampsANDROIDFunc)(
    eglw::EGLDisplay dpy, eglw::EGLSurface surface, EGLuint64KHR frameId, eglw::EGLint numTimestamps,
    const eglw::EGLint *timestamps, EGLnsecsANDROID *values);
typedef EGLW_APICALL eglw::EGLBoolean(EGLW_APIENTRY *eglGetFrameTimestampSupportedANDROIDFunc)(eglw::EGLDisplay dpy,
                                                                                               eglw::EGLSurface surface,
                                                                                               eglw::EGLint timestamp);

#define CHECK_NAKED_EGL_CALL(EGLW, CALL)                                \
    do                                                                  \
    {                                                                   \
        CALL;                                                           \
        eglu::checkError((EGLW).getError(), #CALL, __FILE__, __LINE__); \
    } while (false)

namespace deqp
{
namespace egl
{

using std::string;
using std::vector;
using tcu::TestLog;
using namespace eglw;

namespace
{

// Careful: This has microsecond precision, which can cause timestamps to
// appear non monotonic when compared to the nanosecond precision timestamps
// we get from the eglGetFrameTimestamps extension.
// Current test expectations only make sure microsecond precision timestamps
// are less than the nanosecond precision timestamps, so this is okay.
EGLnsecsANDROID getNanoseconds(void)
{
    return deGetMicroseconds() * 1000;
}

struct FrameTimes
{
    FrameTimes(void)
        : frameId(-1)
        , swapBufferBeginNs(-1)
        , compositeDeadline(-1)
        , compositeInterval(-1)
        , compositeToPresentLatency(-1)
        , requestedPresent(-1)
        , latch(-1)
        , firstCompositionStart(-1)
        , lastCompositionStart(-1)
        , dequeueReady(-1)
        , renderingComplete(-1)
        , firstCompositionGpuFinished(-1)
        , displayPresent(-1)
        , readsDone(-1)
    {
    }

    EGLuint64KHR frameId;

    // Timestamps sampled by the test.
    EGLnsecsANDROID swapBufferBeginNs;

    // Compositor info.
    EGLnsecsANDROID compositeDeadline;
    EGLnsecsANDROID compositeInterval;
    EGLnsecsANDROID compositeToPresentLatency;

    // CPU Timeline.
    EGLnsecsANDROID requestedPresent;
    EGLnsecsANDROID latch;
    EGLnsecsANDROID firstCompositionStart;
    EGLnsecsANDROID lastCompositionStart;
    EGLnsecsANDROID dequeueReady;

    // GPU Timeline.
    EGLnsecsANDROID renderingComplete;
    EGLnsecsANDROID firstCompositionGpuFinished;
    EGLnsecsANDROID displayPresent;
    EGLnsecsANDROID readsDone;
};

struct TimestampInfo
{
    TimestampInfo() : required(false), supported(false), supportedIndex(0)
    {
    }

    TimestampInfo(bool required_, bool supported_, size_t supportedIndex_)
        : required(required_)
        , supported(supported_)
        , supportedIndex(supportedIndex_)
    {
    }

    bool required;
    bool supported;
    size_t supportedIndex;
};

typedef std::map<eglw::EGLint, TimestampInfo> TimestampInfoMap;

EGLnsecsANDROID getTimestamp(eglw::EGLint name, TimestampInfoMap &map,
                             const std::vector<EGLnsecsANDROID> &supportedValues)
{
    TimestampInfo &info = map[name];
    return info.supported ? supportedValues[info.supportedIndex] : EGL_TIMESTAMP_INVALID_ANDROID;
}

void populateFrameTimes(FrameTimes *frameTimes, TimestampInfoMap &map,
                        const std::vector<EGLnsecsANDROID> &supportedValues)
{
    frameTimes->requestedPresent      = getTimestamp(EGL_REQUESTED_PRESENT_TIME_ANDROID, map, supportedValues);
    frameTimes->renderingComplete     = getTimestamp(EGL_RENDERING_COMPLETE_TIME_ANDROID, map, supportedValues);
    frameTimes->latch                 = getTimestamp(EGL_COMPOSITION_LATCH_TIME_ANDROID, map, supportedValues);
    frameTimes->firstCompositionStart = getTimestamp(EGL_FIRST_COMPOSITION_START_TIME_ANDROID, map, supportedValues);
    frameTimes->lastCompositionStart  = getTimestamp(EGL_LAST_COMPOSITION_START_TIME_ANDROID, map, supportedValues);
    frameTimes->firstCompositionGpuFinished =
        getTimestamp(EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID, map, supportedValues);
    frameTimes->displayPresent = getTimestamp(EGL_DISPLAY_PRESENT_TIME_ANDROID, map, supportedValues);
    frameTimes->dequeueReady   = getTimestamp(EGL_DEQUEUE_READY_TIME_ANDROID, map, supportedValues);
    frameTimes->readsDone      = getTimestamp(EGL_READS_DONE_TIME_ANDROID, map, supportedValues);
}

bool timestampValid(EGLnsecsANDROID timestamp)
{
    // \todo [2017-10-19 brianderson] Don't consider 0 invalid once kernel fix is in.
    return (timestamp > 0) || (timestamp == EGL_TIMESTAMP_PENDING_ANDROID);
}

bool timestampPending(EGLnsecsANDROID timestamp)
{
    return timestamp == EGL_TIMESTAMP_PENDING_ANDROID;
}

template <typename T>
void check_lt(tcu::ResultCollector &result, const T &a, const T &b, const std::string &msg)
{
    if (a < b)
        return;
    std::string m = msg + "!(" + de::toString(a) + " < " + de::toString(b) + ")";
    result.fail(m);
}

template <typename T>
void check_le(tcu::ResultCollector &result, const T &a, const T &b, const std::string &msg)
{
    if (a <= b)
        return;
    std::string m = msg + "!(" + de::toString(a) + " <= " + de::toString(b) + ")";
    result.fail(m);
}

void verifySingleFrame(const FrameTimes &frameTimes, tcu::ResultCollector &result, bool verifyReadsDone)
{
    // Verify CPU timeline is monotonic.
    check_lt(result, frameTimes.swapBufferBeginNs, frameTimes.latch, "Buffer latched before it was swapped.");
    check_lt(result, frameTimes.latch, frameTimes.firstCompositionStart, "Buffer composited before it was latched.");
    check_le(result, frameTimes.firstCompositionStart, frameTimes.lastCompositionStart,
             "First composition start after last composition start.");
    check_lt(result, frameTimes.lastCompositionStart, frameTimes.dequeueReady,
             "Buffer composited after it was ready to be dequeued.");

    // Verify GPU timeline is monotonic.
    if (timestampValid(frameTimes.firstCompositionGpuFinished))
        check_lt(result, frameTimes.renderingComplete, frameTimes.firstCompositionGpuFinished,
                 "Buffer rendering completed after compositor GPU work finished.");

    if (timestampValid(frameTimes.displayPresent))
        check_lt(result, frameTimes.renderingComplete, frameTimes.displayPresent,
                 "Buffer displayed before rendering completed.");

    if (timestampValid(frameTimes.firstCompositionGpuFinished) && timestampValid(frameTimes.displayPresent))
        check_lt(result, frameTimes.firstCompositionGpuFinished, frameTimes.displayPresent,
                 "Buffer displayed before compositor GPU work completed");

    // Drivers may maintain shadow copies of the buffer, so the readsDone time
    // of the real buffer may be earlier than apparent dependencies. We can only
    // be sure that the readsDone time must be after the renderingComplete time.
    // It may also be equal to the renderingComplete time if no reads were
    // peformed.
    if (verifyReadsDone && timestampValid(frameTimes.readsDone))
        check_le(result, frameTimes.renderingComplete, frameTimes.readsDone,
                 "Buffer rendering completed after reads completed.");

    // Verify CPU/GPU dependencies
    if (timestampValid(frameTimes.firstCompositionGpuFinished))
        check_lt(result, frameTimes.firstCompositionStart, frameTimes.firstCompositionGpuFinished,
                 "Composition CPU work started after GPU work finished.");

    if (timestampValid(frameTimes.displayPresent))
        check_lt(result, frameTimes.firstCompositionStart, frameTimes.displayPresent,
                 "Buffer displayed before it was composited.");
}

void verifyNeighboringFrames(const FrameTimes &frame1, const FrameTimes &frame2, tcu::ResultCollector &result)
{
    // CPU timeline.
    check_lt(result, frame1.swapBufferBeginNs, frame2.swapBufferBeginNs, "Swap begin times not monotonic.");
    check_lt(result, frame1.latch, frame2.latch, "Latch times not monotonic.");
    check_lt(result, frame1.lastCompositionStart, frame2.latch, "Old buffer composited after new buffer latched.");
    check_lt(result, frame1.lastCompositionStart, frame2.firstCompositionStart, "Composition times overlap.");
    check_lt(result, frame1.dequeueReady, frame2.dequeueReady, "Dequeue ready times not monotonic.");

    // GPU timeline.
    if (timestampValid(frame1.firstCompositionGpuFinished) && timestampValid(frame2.firstCompositionGpuFinished))
        check_lt(result, frame1.firstCompositionGpuFinished, frame2.firstCompositionGpuFinished,
                 "Composition GPU work complete times not monotonic.");

    if (timestampValid(frame1.displayPresent) && timestampValid(frame2.displayPresent))
        check_lt(result, frame1.displayPresent, frame2.displayPresent, "Display present times not monotonic.");
}

EGLContext createGLES2Context(const Library &egl, EGLDisplay display, EGLConfig config)
{
    EGLContext context        = EGL_NO_CONTEXT;
    const EGLint attribList[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};

    EGLU_CHECK_CALL(egl, bindAPI(EGL_OPENGL_ES_API));

    context = egl.createContext(display, config, EGL_NO_CONTEXT, attribList);
    EGLU_CHECK_MSG(egl, "eglCreateContext() failed");
    TCU_CHECK(context);

    return context;
}

class GetFrameTimestampTest : public SimpleConfigCase
{
public:
    GetFrameTimestampTest(EglTestContext &eglTestCtx, const NamedFilterList &filters);
    ~GetFrameTimestampTest(void);

private:
    void executeForConfig(EGLDisplay display, EGLConfig config);
    void initializeExtension(const Library &egl);

    // Not allowed
    GetFrameTimestampTest(const GetFrameTimestampTest &);
    GetFrameTimestampTest &operator=(const GetFrameTimestampTest &);

    // TODO: Move these to eglw::Library.
    eglGetNextFrameIdANDROIDFunc m_eglGetNextFrameIdANDROID;
    eglGetCompositorTimingANDROIDFunc m_eglGetCompositorTimingANDROID;
    eglGetCompositorTimingSupportedANDROIDFunc m_eglGetCompositorTimingSupportedANDROID;
    eglGetFrameTimestampsANDROIDFunc m_eglGetFrameTimestampsANDROID;
    eglGetFrameTimestampSupportedANDROIDFunc m_eglGetFrameTimestampSupportedANDROID;

    tcu::ResultCollector m_result;
};

GetFrameTimestampTest::GetFrameTimestampTest(EglTestContext &eglTestCtx, const NamedFilterList &filters)
    : SimpleConfigCase(eglTestCtx, filters.getName(), filters.getDescription(), filters)
    , m_eglGetNextFrameIdANDROID(DE_NULL)
    , m_eglGetCompositorTimingANDROID(DE_NULL)
    , m_eglGetCompositorTimingSupportedANDROID(DE_NULL)
    , m_eglGetFrameTimestampsANDROID(DE_NULL)
    , m_eglGetFrameTimestampSupportedANDROID(DE_NULL)
    , m_result(m_testCtx.getLog())
{
}

GetFrameTimestampTest::~GetFrameTimestampTest(void)
{
}

void GetFrameTimestampTest::initializeExtension(const Library &egl)
{
    m_eglGetNextFrameIdANDROID =
        reinterpret_cast<eglGetNextFrameIdANDROIDFunc>(egl.getProcAddress("eglGetNextFrameIdANDROID"));
    EGLU_CHECK_MSG(egl, "getProcAddress of eglGetNextFrameIdANDROID failed.");
    m_eglGetCompositorTimingANDROID =
        reinterpret_cast<eglGetCompositorTimingANDROIDFunc>(egl.getProcAddress("eglGetCompositorTimingANDROID"));
    EGLU_CHECK_MSG(egl, "getProcAddress of eglGetCompositorTimingANDROID failed.");
    m_eglGetCompositorTimingSupportedANDROID = reinterpret_cast<eglGetCompositorTimingSupportedANDROIDFunc>(
        egl.getProcAddress("eglGetCompositorTimingSupportedANDROID"));
    EGLU_CHECK_MSG(egl, "getProcAddress of eglGetCompositorTimingSupportedANDROID failed.");
    m_eglGetFrameTimestampsANDROID =
        reinterpret_cast<eglGetFrameTimestampsANDROIDFunc>(egl.getProcAddress("eglGetFrameTimestampsANDROID"));
    EGLU_CHECK_MSG(egl, "getProcAddress of eglGetFrameTimestampsANDROID failed.");
    m_eglGetFrameTimestampSupportedANDROID = reinterpret_cast<eglGetFrameTimestampSupportedANDROIDFunc>(
        egl.getProcAddress("eglGetFrameTimestampSupportedANDROID"));
    EGLU_CHECK_MSG(egl, "getProcAddress of eglGetFrameTimestampSupportedANDROID failed.");
}

string getConfigIdString(const Library &egl, EGLDisplay display, EGLConfig config)
{
    std::ostringstream stream;
    EGLint id;

    EGLU_CHECK_CALL(egl, getConfigAttrib(display, config, EGL_CONFIG_ID, &id));

    stream << id;

    return stream.str();
}

uint32_t createGLES2Program(const glw::Functions &gl, TestLog &log)
{
    const char *const vertexShaderSource = "attribute highp vec2 a_pos;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
                                           "}";

    const char *const fragmentShaderSource = "void main (void)\n"
                                             "{\n"
                                             "\tgl_FragColor = vec4(0.9, 0.1, 0.4, 1.0);\n"
                                             "}";

    uint32_t program        = 0;
    uint32_t vertexShader   = 0;
    uint32_t fragmentShader = 0;

    int32_t vertexCompileStatus;
    string vertexInfoLog;
    int32_t fragmentCompileStatus;
    string fragmentInfoLog;
    int32_t linkStatus;
    string programInfoLog;

    try
    {
        program        = gl.createProgram();
        vertexShader   = gl.createShader(GL_VERTEX_SHADER);
        fragmentShader = gl.createShader(GL_FRAGMENT_SHADER);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create shaders and program");

        gl.shaderSource(vertexShader, 1, &vertexShaderSource, DE_NULL);
        gl.compileShader(vertexShader);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup vertex shader");

        gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, DE_NULL);
        gl.compileShader(fragmentShader);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup fragment shader");

        {
            int32_t infoLogLength = 0;

            gl.getShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexCompileStatus);
            gl.getShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLogLength);

            vertexInfoLog.resize(infoLogLength, '\0');

            gl.getShaderInfoLog(vertexShader, (glw::GLsizei)vertexInfoLog.length(), &infoLogLength,
                                &(vertexInfoLog[0]));
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get vertex shader compile info");

            vertexInfoLog.resize(infoLogLength);
        }

        {
            int32_t infoLogLength = 0;

            gl.getShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentCompileStatus);
            gl.getShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &infoLogLength);

            fragmentInfoLog.resize(infoLogLength, '\0');

            gl.getShaderInfoLog(fragmentShader, (glw::GLsizei)fragmentInfoLog.length(), &infoLogLength,
                                &(fragmentInfoLog[0]));
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get fragment shader compile info");

            fragmentInfoLog.resize(infoLogLength);
        }

        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup program");

        {
            int32_t infoLogLength = 0;

            gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
            gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

            programInfoLog.resize(infoLogLength, '\0');

            gl.getProgramInfoLog(program, (glw::GLsizei)programInfoLog.length(), &infoLogLength, &(programInfoLog[0]));
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get program link info");

            programInfoLog.resize(infoLogLength);
        }

        if (linkStatus == 0 || vertexCompileStatus == 0 || fragmentCompileStatus == 0)
        {

            log.startShaderProgram(linkStatus != 0, programInfoLog.c_str());

            log << TestLog::Shader(QP_SHADER_TYPE_VERTEX, vertexShaderSource, vertexCompileStatus != 0, vertexInfoLog);
            log << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, fragmentShaderSource, fragmentCompileStatus != 0,
                                   fragmentInfoLog);

            log.endShaderProgram();
        }

        gl.deleteShader(vertexShader);
        gl.deleteShader(fragmentShader);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to delete shaders");

        TCU_CHECK(linkStatus != 0 && vertexCompileStatus != 0 && fragmentCompileStatus != 0);
    }
    catch (...)
    {
        if (program)
            gl.deleteProgram(program);

        if (vertexShader)
            gl.deleteShader(vertexShader);

        if (fragmentShader)
            gl.deleteShader(fragmentShader);

        throw;
    }

    return program;
}

void GetFrameTimestampTest::executeForConfig(EGLDisplay display, EGLConfig config)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    if (!eglu::hasExtension(egl, display, "EGL_ANDROID_get_frame_timestamps"))
        TCU_THROW(NotSupportedError, "EGL_ANDROID_get_frame_timestamps is not supported");

    initializeExtension(egl);

    const string configIdStr(getConfigIdString(egl, display, config));
    tcu::ScopedLogSection logSection(m_testCtx.getLog(), ("Config ID " + configIdStr).c_str(),
                                     ("Config ID " + configIdStr).c_str());
    const eglu::NativeWindowFactory &factory =
        eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());

    {
        TestLog &log = m_testCtx.getLog();

        log << TestLog::Message << "EGL_RED_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_RED_SIZE)
            << TestLog::EndMessage;
        log << TestLog::Message << "EGL_GREEN_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_GREEN_SIZE)
            << TestLog::EndMessage;
        log << TestLog::Message << "EGL_BLUE_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_BLUE_SIZE)
            << TestLog::EndMessage;
        log << TestLog::Message << "EGL_ALPHA_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_ALPHA_SIZE)
            << TestLog::EndMessage;
        log << TestLog::Message << "EGL_DEPTH_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_DEPTH_SIZE)
            << TestLog::EndMessage;
        log << TestLog::Message
            << "EGL_STENCIL_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_STENCIL_SIZE)
            << TestLog::EndMessage;
        log << TestLog::Message << "EGL_SAMPLES: " << eglu::getConfigAttribInt(egl, display, config, EGL_SAMPLES)
            << TestLog::EndMessage;
    }

    de::UniquePtr<eglu::NativeWindow> window(
        factory.createWindow(&m_eglTestCtx.getNativeDisplay(), display, config, DE_NULL,
                             eglu::WindowParams(128, 128, eglu::WindowParams::VISIBILITY_VISIBLE)));

    eglu::UniqueSurface surface(
        egl, display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display, config, DE_NULL));
    eglu::UniqueContext context(egl, display, createGLES2Context(egl, display, config));
    glw::Functions gl;
    uint32_t program = 0;

    EGLU_CHECK_CALL(egl, surfaceAttrib(display, *surface, EGL_TIMESTAMPS_ANDROID, EGL_TRUE));

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

    EGLU_CHECK_CALL(egl, makeCurrent(display, *surface, *surface, *context));

    try
    {
        // EGL_DISPLAY_PRESENT_TIME_ANDROID support is currently optional
        // but should be required once HWC1 is no longer supported.
        // All HWC2 devices should support EGL_DISPLAY_PRESENT_TIME_ANDROID.
        TimestampInfoMap timestamps;
        timestamps[EGL_REQUESTED_PRESENT_TIME_ANDROID]              = TimestampInfo(true, false, 0);
        timestamps[EGL_RENDERING_COMPLETE_TIME_ANDROID]             = TimestampInfo(true, false, 0);
        timestamps[EGL_COMPOSITION_LATCH_TIME_ANDROID]              = TimestampInfo(true, false, 0);
        timestamps[EGL_FIRST_COMPOSITION_START_TIME_ANDROID]        = TimestampInfo(true, false, 0);
        timestamps[EGL_LAST_COMPOSITION_START_TIME_ANDROID]         = TimestampInfo(true, false, 0);
        timestamps[EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID] = TimestampInfo(true, false, 0);
        timestamps[EGL_DISPLAY_PRESENT_TIME_ANDROID]                = TimestampInfo(false, false, 0);
        timestamps[EGL_DEQUEUE_READY_TIME_ANDROID]                  = TimestampInfo(true, false, 0);
        timestamps[EGL_READS_DONE_TIME_ANDROID]                     = TimestampInfo(true, false, 0);

        const eglw::EGLint invalidTimestampName = EGL_READS_DONE_TIME_ANDROID + 1;

        // Verify required timestamps are supported and populate supportedNames.
        std::vector<eglw::EGLint> supportedNames;
        for (TimestampInfoMap::iterator i = timestamps.begin(); i != timestamps.end(); i++)
        {
            TimestampInfo &info = i->second;
            info.supported      = m_eglGetFrameTimestampSupportedANDROID(display, *surface, i->first) != EGL_FALSE;
            EGLU_CHECK_MSG(egl, "eglGetFrameTimestampSupportedANDROID failed.");

            if (info.supported)
            {
                info.supportedIndex = supportedNames.size();
                supportedNames.push_back(i->first);
            }
            else
                TCU_CHECK_MSG(!info.required, "Required timestamp not supported.");
        }

        // Verify unsupported timestamps are reported properly.
        const bool invalidSupported =
            m_eglGetFrameTimestampSupportedANDROID(display, *surface, invalidTimestampName) != EGL_FALSE;
        EGLU_CHECK_MSG(egl, "eglGetFrameTimestampSupportedANDROID failed.");
        TCU_CHECK_MSG(!invalidSupported, "Non existant timestamp reports that it is supported.");

        // Verify compositor timings are supported.
        const bool deadlineSupported =
            m_eglGetCompositorTimingSupportedANDROID(display, *surface, EGL_COMPOSITE_DEADLINE_ANDROID) != EGL_FALSE;
        EGLU_CHECK_MSG(egl, "eglGetCompositorTimingSupportedANDROID failed.");
        TCU_CHECK_MSG(deadlineSupported, "EGL_COMPOSITE_DEADLINE_ANDROID not supported.");
        const bool intervalSupported =
            m_eglGetCompositorTimingSupportedANDROID(display, *surface, EGL_COMPOSITE_INTERVAL_ANDROID) != EGL_FALSE;
        EGLU_CHECK_MSG(egl, "eglGetCompositorTimingSupportedANDROID failed.");
        TCU_CHECK_MSG(intervalSupported, "EGL_COMPOSITE_INTERVAL_ANDROID not supported.");
        const bool latencySupported = m_eglGetCompositorTimingSupportedANDROID(
                                          display, *surface, EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID) != EGL_FALSE;
        EGLU_CHECK_MSG(egl, "eglGetCompositorTimingSupportedANDROID failed.");
        TCU_CHECK_MSG(latencySupported, "EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID not supported.");

        const float positions1[] = {0.00f, 0.00f, 0.75f, 0.00f, 0.75f, 0.75f,

                                    0.75f, 0.75f, 0.00f, 0.75f, 0.00f, 0.00f};

        const float positions2[] = {-0.75f, -0.75f, 0.00f,  -0.75f, 0.00f,  0.00f,

                                    0.00f,  0.00f,  -0.75f, 0.00f,  -0.75f, -0.75f};

        uint32_t posLocation;

        program = createGLES2Program(gl, m_testCtx.getLog());

        gl.useProgram(program);
        posLocation = gl.getAttribLocation(program, "a_pos");
        gl.enableVertexAttribArray(posLocation);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup shader program for rendering");

        // Do extra rendering to allow frame pacing to stabilize.
        // The frame timestamp validation below assumes there is no frame janking,
        // however, this is not guaranteed.
        // e.g. on some hardware, the shader compilation causes first few frames
        // to jank. This will cause frame timestamps read back not matching with
        // expectations: compositeToPresentLatency < 4 * compositeInterval.
        // Do extra frames rendering to allow frames to stablize before measuring
        // and verifying frame timestamps.
        const size_t stablizeFramesCount = 120;
        for (size_t i = 0; i < stablizeFramesCount; ++i)
        {
            gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
            gl.clear(GL_COLOR_BUFFER_BIT);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");

            const bool posSelect = ((i % 2) == 0);
            gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, posSelect ? positions1 : positions2);

            gl.drawArrays(GL_TRIANGLES, 0, 6);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");

            EGLU_CHECK_CALL(egl, swapBuffers(display, *surface));
        }

        const size_t frameCount = 120;
        std::vector<FrameTimes> frameTimes(frameCount);
        for (size_t i = 0; i < frameCount; i++)
        {
            FrameTimes &frame = frameTimes[i];

            const eglw::EGLint compositorTimingNames[] = {
                EGL_COMPOSITE_DEADLINE_ANDROID,
                EGL_COMPOSITE_INTERVAL_ANDROID,
                EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID,
            };
            const EGLint compositorTimingCount                            = DE_LENGTH_OF_ARRAY(compositorTimingNames);
            EGLnsecsANDROID compositorTimingValues[compositorTimingCount] = {-2};

            // Get the current time before making any API calls in case "now"
            // just happens to get sampled near one of the composite deadlines.
            EGLnsecsANDROID now = getNanoseconds();

            // Get the frame id.
            EGLuint64KHR nextFrameId = 0;
            CHECK_NAKED_EGL_CALL(egl, m_eglGetNextFrameIdANDROID(display, *surface, &nextFrameId));
            frame.frameId = nextFrameId;

            // Get the compositor timing.
            CHECK_NAKED_EGL_CALL(egl, m_eglGetCompositorTimingANDROID(display, *surface, compositorTimingCount,
                                                                      compositorTimingNames, compositorTimingValues));
            frame.compositeDeadline         = compositorTimingValues[0];
            frame.compositeInterval         = compositorTimingValues[1];
            frame.compositeToPresentLatency = compositorTimingValues[2];

            // Verify compositor timing is sane.
            check_lt<EGLnsecsANDROID>(m_result, 1000000, frame.compositeInterval,
                                      "Reported refresh rate greater than 1kHz.");
            check_lt<EGLnsecsANDROID>(m_result, frame.compositeInterval, 1000000000,
                                      "Reported refresh rate less than 1Hz.");
            check_lt<EGLnsecsANDROID>(m_result, 0, frame.compositeToPresentLatency,
                                      "Composite to present latency must be greater than 0.");
            check_lt(m_result, frame.compositeToPresentLatency, frame.compositeInterval * 4,
                     "Composite to present latency is more than 4 vsyncs.");
            const EGLnsecsANDROID minDeadline = now;
            check_lt(m_result, minDeadline, frame.compositeDeadline, "Next composite deadline is in the past.");
            const EGLnsecsANDROID maxDeadline = now + frame.compositeInterval * 2;
            check_lt(m_result, frame.compositeDeadline, maxDeadline,
                     "Next composite deadline over two intervals away.");

            const float colorAngle = (static_cast<float>(i) / static_cast<float>(frameCount)) * 6.28318f;
            gl.clearColor((1.0f + deFloatSin(colorAngle)) / 2.0f, 0.7f, (1.0f + deFloatCos(colorAngle)) / 2.0f, 1.0f);
            gl.clear(GL_COLOR_BUFFER_BIT);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");

            const bool posSelect = ((i % 2) == 0);
            gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, posSelect ? positions1 : positions2);
            gl.drawArrays(GL_TRIANGLES, 0, 6);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");

            frame.swapBufferBeginNs = getNanoseconds();
            EGLU_CHECK_CALL(egl, swapBuffers(display, *surface));

            // All timestamps from 6 frames ago should definitely be available.
            const size_t frameDelay = 6;
            if (i >= frameDelay)
            {
                // \todo [2017-01-25 brianderson] Remove this work around once reads done is fixed.
                const bool verifyReadsDone = i > (frameDelay + 3);
                FrameTimes &frame6ago      = frameTimes[i - frameDelay];
                std::vector<EGLnsecsANDROID> supportedValues(supportedNames.size(), 0);

                CHECK_NAKED_EGL_CALL(egl,
                                     m_eglGetFrameTimestampsANDROID(display, *surface, frame6ago.frameId,
                                                                    static_cast<eglw::EGLint>(supportedNames.size()),
                                                                    &supportedNames[0], &supportedValues[0]));
                populateFrameTimes(&frame6ago, timestamps, supportedValues);

                verifySingleFrame(frame6ago, m_result, verifyReadsDone);
                if (i >= frameDelay + 1)
                {
                    FrameTimes &frame7ago = frameTimes[i - frameDelay - 1];
                    verifyNeighboringFrames(frame7ago, frame6ago, m_result);
                }
            }
            deSleep(static_cast<uint32_t>(frame.compositeInterval / 1000000));
        }

        // All timestamps for the most recently swapped frame should
        // become available by only polling eglGetFrametimestamps.
        // No additional swaps should be necessary.
        FrameTimes &lastFrame                 = frameTimes.back();
        const EGLnsecsANDROID pollingDeadline = lastFrame.swapBufferBeginNs + 1000000000;
        bool finalTimestampAvailable          = false;

        do
        {
            std::vector<EGLnsecsANDROID> supportedValues(supportedNames.size(), 0);
            CHECK_NAKED_EGL_CALL(egl, m_eglGetFrameTimestampsANDROID(display, *surface, lastFrame.frameId,
                                                                     static_cast<eglw::EGLint>(supportedNames.size()),
                                                                     &supportedNames[0], &supportedValues[0]));
            populateFrameTimes(&lastFrame, timestamps, supportedValues);

            // Poll for present if it's supported.
            // Otherwise, poll for firstCompositionStart.
            if (timestamps[EGL_DISPLAY_PRESENT_TIME_ANDROID].supported)
                finalTimestampAvailable = !timestampPending(lastFrame.displayPresent);
            else
                finalTimestampAvailable = !timestampPending(lastFrame.firstCompositionStart);
        } while (!finalTimestampAvailable && (getNanoseconds() < pollingDeadline));

        m_result.check(finalTimestampAvailable, "Timed out polling for timestamps of last swap.");
        m_result.check((lastFrame.requestedPresent >= 0), "Requested present of last swap not avaiable.");
        m_result.check((lastFrame.renderingComplete >= 0), "Rendering complete of last swap not avaiable.");
        m_result.check((lastFrame.latch >= 0), "Latch of last swap not avaiable.");
        m_result.check((lastFrame.firstCompositionStart >= 0), "First composite time of last swap not avaiable.");
        m_result.check((lastFrame.lastCompositionStart >= 0), "Last composite time of last swap not avaiable.");

        window->processEvents();
        gl.disableVertexAttribArray(posLocation);
        gl.useProgram(0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to release program state");

        gl.deleteProgram(program);
        program = 0;
        GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteProgram()");

        m_result.setTestContextResult(m_testCtx);
    }
    catch (...)
    {
        if (program != 0)
            gl.deleteProgram(program);

        EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
        throw;
    }

    EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
}

class GetFrameTimestampsTests : public TestCaseGroup
{
public:
    GetFrameTimestampsTests(EglTestContext &eglTestCtx);
    void init(void);

private:
    GetFrameTimestampsTests(const GetFrameTimestampsTests &);
    GetFrameTimestampsTests &operator=(const GetFrameTimestampsTests &);
};

GetFrameTimestampsTests::GetFrameTimestampsTests(EglTestContext &eglTestCtx)
    : TestCaseGroup(eglTestCtx, "get_frame_timestamps", "Get frame timestamp tests")
{
}

bool isWindow(const eglu::CandidateConfig &c)
{
    return (c.surfaceType() & EGL_WINDOW_BIT) != 0;
}

void GetFrameTimestampsTests::init(void)
{
    eglu::FilterList baseFilters;
    baseFilters << isWindow;

    vector<NamedFilterList> filterLists;
    getDefaultFilterLists(filterLists, baseFilters);

    for (vector<NamedFilterList>::iterator i = filterLists.begin(); i != filterLists.end(); i++)
        addChild(new GetFrameTimestampTest(m_eglTestCtx, *i));
}

} // namespace

TestCaseGroup *createGetFrameTimestampsTests(EglTestContext &eglTestCtx)
{
    return new GetFrameTimestampsTests(eglTestCtx);
}

} // namespace egl
} // namespace deqp
