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

// DebugTest.cpp : Tests of the GL_KHR_debug extension

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

namespace angle
{
constexpr char kBufferObjLabel[]          = "buffer";
constexpr char kShaderObjLabel[]          = "shader";
constexpr char kProgramObjLabel[]         = "program";
constexpr char kVertexArrayObjLabel[]     = "vertexarray";
constexpr char kQueryObjLabel[]           = "query";
constexpr char kProgramPipelineObjLabel[] = "programpipeline";
constexpr GLenum kObjectTypes[]           = {GL_BUFFER_OBJECT_EXT,           GL_SHADER_OBJECT_EXT,
                                   GL_PROGRAM_OBJECT_EXT,          GL_QUERY_OBJECT_EXT,
                                   GL_PROGRAM_PIPELINE_OBJECT_EXT, GL_VERTEX_ARRAY_OBJECT_EXT};

class DebugTest : public ANGLETest
{
  protected:
    DebugTest() : mDebugExtensionAvailable(false)
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setConfigDepthBits(24);
        setDebugEnabled(true);
    }

    void testSetUp() override
    {
        mDebugExtensionAvailable = IsGLExtensionEnabled("GL_KHR_debug");
        if (mDebugExtensionAvailable)
        {
            glEnable(GL_DEBUG_OUTPUT);
        }
    }

    bool mDebugExtensionAvailable;
};

void createGLObjectAndLabel(GLenum identifier, GLuint &object, const char **label)
{
    switch (identifier)
    {
        case GL_BUFFER_OBJECT_EXT:
            glGenBuffers(1, &object);
            glBindBuffer(GL_ARRAY_BUFFER, object);
            *label = kBufferObjLabel;
            break;
        case GL_SHADER_OBJECT_EXT:
            object = glCreateShader(GL_VERTEX_SHADER);
            *label = kShaderObjLabel;
            break;
        case GL_PROGRAM_OBJECT_EXT:
            object = glCreateProgram();
            *label = kProgramObjLabel;
            break;
        case GL_VERTEX_ARRAY_OBJECT_EXT:
            glGenVertexArrays(1, &object);
            glBindVertexArray(object);
            *label = kVertexArrayObjLabel;
            break;
        case GL_QUERY_OBJECT_EXT:
            glGenQueries(1, &object);
            glBeginQuery(GL_ANY_SAMPLES_PASSED, object);
            *label = kQueryObjLabel;
            break;
        case GL_PROGRAM_PIPELINE_OBJECT_EXT:
            glGenProgramPipelines(1, &object);
            glBindProgramPipeline(object);
            *label = kProgramPipelineObjLabel;
            break;
        default:
            UNREACHABLE();
            break;
    }
}

void deleteGLObject(GLenum identifier, GLuint &object)
{
    switch (identifier)
    {
        case GL_BUFFER_OBJECT_EXT:
            glDeleteBuffers(1, &object);
            break;
        case GL_SHADER_OBJECT_EXT:
            glDeleteShader(object);
            break;
        case GL_PROGRAM_OBJECT_EXT:
            glDeleteProgram(object);
            break;
        case GL_VERTEX_ARRAY_OBJECT_EXT:
            glDeleteVertexArrays(1, &object);
            break;
        case GL_QUERY_OBJECT_EXT:
            glEndQuery(GL_ANY_SAMPLES_PASSED);
            glDeleteQueries(1, &object);
            break;
        case GL_PROGRAM_PIPELINE_OBJECT_EXT:
            glDeleteProgramPipelines(1, &object);
            break;
        default:
            UNREACHABLE();
            break;
    }
}

// Test basic usage of setting and getting labels using GL_EXT_debug_label
TEST_P(DebugTest, ObjectLabelsEXT)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_debug_label"));

    for (const GLenum identifier : kObjectTypes)
    {
        bool skip = false;
        switch (identifier)
        {
            case GL_PROGRAM_PIPELINE_OBJECT_EXT:
                if (!(getClientMajorVersion() >= 3 && getClientMinorVersion() >= 1) ||
                    !IsGLExtensionEnabled("GL_EXT_separate_shader_objects"))
                {
                    skip = true;
                }
                break;
            case GL_QUERY_OBJECT_EXT:
                // GLES3 context is required for glGenQueries()
                if (getClientMajorVersion() < 3 ||
                    !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean"))
                {
                    skip = true;
                }
                break;
            case GL_VERTEX_ARRAY_OBJECT_EXT:
                if (getClientMajorVersion() < 3)
                {
                    skip = true;
                }
                break;
            default:
                break;
        }

        // if object enum is not supported, move on to the next object type
        if (skip)
        {
            continue;
        }

        GLuint object;
        const char *label;
        createGLObjectAndLabel(identifier, object, &label);

        glLabelObjectEXT(identifier, object, 0, label);
        ASSERT_GL_NO_ERROR();

        std::vector<char> labelBuf(strlen(label) + 1);
        GLsizei labelLengthBuf = 0;
        glGetObjectLabelEXT(identifier, object, static_cast<GLsizei>(labelBuf.size()),
                            &labelLengthBuf, labelBuf.data());
        ASSERT_GL_NO_ERROR();

        EXPECT_EQ(static_cast<GLsizei>(strlen(label)), labelLengthBuf);
        EXPECT_STREQ(label, labelBuf.data());

        ASSERT_GL_NO_ERROR();

        deleteGLObject(identifier, object);

        glLabelObjectEXT(identifier, object, 0, label);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);

        glGetObjectLabelEXT(identifier, object, static_cast<GLsizei>(labelBuf.size()),
                            &labelLengthBuf, labelBuf.data());
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    }
}

class DebugTestES3 : public DebugTest
{};

struct Message
{
    GLenum source;
    GLenum type;
    GLuint id;
    GLenum severity;
    std::string message;
    const void *userParam;
};

static void GL_APIENTRY Callback(GLenum source,
                                 GLenum type,
                                 GLuint id,
                                 GLenum severity,
                                 GLsizei length,
                                 const GLchar *message,
                                 const void *userParam)
{
    Message m{source, type, id, severity, std::string(message, length), userParam};
    std::vector<Message> *messages =
        static_cast<std::vector<Message> *>(const_cast<void *>(userParam));
    messages->push_back(m);
}

// Test that all ANGLE back-ends have GL_KHR_debug enabled
TEST_P(DebugTestES3, Enabled)
{
    ASSERT_TRUE(mDebugExtensionAvailable);
}

// Test that when debug output is disabled, no message are outputted
TEST_P(DebugTestES3, DisabledOutput)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    glDisable(GL_DEBUG_OUTPUT);

    glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 1,
                            GL_DEBUG_SEVERITY_NOTIFICATION, -1, "discarded");

    GLint numMessages = 0;
    glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
    ASSERT_EQ(0, numMessages);

    std::vector<Message> messages;
    glDebugMessageCallbackKHR(Callback, &messages);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

    ASSERT_EQ(0u, messages.size());
}

// Test a basic flow of inserting a message and reading it back
TEST_P(DebugTestES3, InsertMessage)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    const GLenum source       = GL_DEBUG_SOURCE_APPLICATION;
    const GLenum type         = GL_DEBUG_TYPE_OTHER;
    const GLuint id           = 1;
    const GLenum severity     = GL_DEBUG_SEVERITY_NOTIFICATION;
    const std::string message = "Message";

    glDebugMessageInsertKHR(source, type, id, severity, -1, message.c_str());

    GLint numMessages = 0;
    glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
    ASSERT_EQ(1, numMessages);

    GLint messageLength = 0;
    glGetIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &messageLength);
    EXPECT_EQ(static_cast<GLint>(message.length()) + 1, messageLength);

    GLenum sourceBuf   = 0;
    GLenum typeBuf     = 0;
    GLenum idBuf       = 0;
    GLenum severityBuf = 0;
    GLsizei lengthBuf  = 0;
    std::vector<char> messageBuf(messageLength);
    GLuint ret =
        glGetDebugMessageLogKHR(1, static_cast<GLsizei>(messageBuf.size()), &sourceBuf, &typeBuf,
                                &idBuf, &severityBuf, &lengthBuf, messageBuf.data());
    EXPECT_EQ(1u, ret);
    EXPECT_EQ(source, sourceBuf);
    EXPECT_EQ(type, typeBuf);
    EXPECT_EQ(id, idBuf);
    EXPECT_EQ(severity, severityBuf);
    EXPECT_EQ(lengthBuf, messageLength);
    EXPECT_STREQ(message.c_str(), messageBuf.data());

    glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
    EXPECT_EQ(0, numMessages);

    ASSERT_GL_NO_ERROR();
}

// Test inserting multiple messages
TEST_P(DebugTestES3, InsertMessageMultiple)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    const GLenum source          = GL_DEBUG_SOURCE_APPLICATION;
    const GLenum type            = GL_DEBUG_TYPE_OTHER;
    const GLuint startID         = 1;
    const GLenum severity        = GL_DEBUG_SEVERITY_NOTIFICATION;
    const char messageRepeatChar = 'm';
    const size_t messageCount    = 32;

    for (size_t i = 0; i < messageCount; i++)
    {
        std::string message(i + 1, messageRepeatChar);
        glDebugMessageInsertKHR(source, type, startID + static_cast<GLuint>(i), severity, -1,
                                message.c_str());
    }

    GLint numMessages = 0;
    glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
    ASSERT_EQ(static_cast<GLint>(messageCount), numMessages);

    for (size_t i = 0; i < messageCount; i++)
    {
        glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
        EXPECT_EQ(static_cast<GLint>(messageCount - i), numMessages);

        std::string expectedMessage(i + 1, messageRepeatChar);

        GLint messageLength = 0;
        glGetIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &messageLength);
        EXPECT_EQ(static_cast<GLint>(expectedMessage.length()) + 1, messageLength);

        GLenum sourceBuf   = 0;
        GLenum typeBuf     = 0;
        GLenum idBuf       = 0;
        GLenum severityBuf = 0;
        GLsizei lengthBuf  = 0;
        std::vector<char> messageBuf(messageLength);
        GLuint ret =
            glGetDebugMessageLogKHR(1, static_cast<GLsizei>(messageBuf.size()), &sourceBuf,
                                    &typeBuf, &idBuf, &severityBuf, &lengthBuf, messageBuf.data());
        EXPECT_EQ(1u, ret);
        EXPECT_EQ(source, sourceBuf);
        EXPECT_EQ(type, typeBuf);
        EXPECT_EQ(startID + i, idBuf);
        EXPECT_EQ(severity, severityBuf);
        EXPECT_EQ(lengthBuf, messageLength);
        EXPECT_STREQ(expectedMessage.c_str(), messageBuf.data());
    }

    glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
    EXPECT_EQ(0, numMessages);

    ASSERT_GL_NO_ERROR();
}

// Test using a debug callback
TEST_P(DebugTestES3, DebugCallback)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    std::vector<Message> messages;

    glDebugMessageCallbackKHR(Callback, &messages);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

    const GLenum source       = GL_DEBUG_SOURCE_APPLICATION;
    const GLenum type         = GL_DEBUG_TYPE_OTHER;
    const GLuint id           = 1;
    const GLenum severity     = GL_DEBUG_SEVERITY_NOTIFICATION;
    const std::string message = "Message";

    glDebugMessageInsertKHR(source, type, id, severity, -1, message.c_str());

    GLint numMessages = 0;
    glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages);
    EXPECT_EQ(0, numMessages);

    ASSERT_EQ(1u, messages.size());

    const Message &m = messages.front();
    EXPECT_EQ(source, m.source);
    EXPECT_EQ(type, m.type);
    EXPECT_EQ(id, m.id);
    EXPECT_EQ(severity, m.severity);
    EXPECT_EQ(message, m.message);

    ASSERT_GL_NO_ERROR();
}

// Test the glGetPointervKHR entry point
TEST_P(DebugTestES3, GetPointer)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    std::vector<Message> messages;

    glDebugMessageCallbackKHR(Callback, &messages);

    void *callback = nullptr;
    glGetPointervKHR(GL_DEBUG_CALLBACK_FUNCTION, &callback);
    EXPECT_EQ(reinterpret_cast<void *>(Callback), callback);

    void *userData = nullptr;
    glGetPointervKHR(GL_DEBUG_CALLBACK_USER_PARAM, &userData);
    EXPECT_EQ(static_cast<void *>(&messages), userData);
}

// Test usage of message control.  Example taken from GL_KHR_debug spec.
TEST_P(DebugTestES3, MessageControl1)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    std::vector<Message> messages;

    glDebugMessageCallbackKHR(Callback, &messages);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

    // Setup of the default active debug group: Filter everything in
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);

    // Generate a debug marker debug output message
    glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 100,
                            GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Message 1");

    // Push debug group 1
    glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Message 2");

    // Setup of the debug group 1: Filter everything out
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE);

    // This message won't appear in the debug output log of
    glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 100,
                            GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Message 3");

    // Pop debug group 1, restore the volume control of the default debug group.
    glPopDebugGroupKHR();

    // Generate a debug marker debug output message
    glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 100,
                            GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Message 5");

    // Expected debug output from the GL implementation
    // Message 1
    // Message 2
    // Message 2
    // Message 5
    EXPECT_EQ(4u, messages.size());
    EXPECT_STREQ(messages[0].message.c_str(), "Message 1");
    EXPECT_STREQ(messages[1].message.c_str(), "Message 2");
    EXPECT_STREQ(messages[2].message.c_str(), "Message 2");
    EXPECT_STREQ(messages[3].message.c_str(), "Message 5");

    ASSERT_GL_NO_ERROR();
}

// Test usage of message control.  Example taken from GL_KHR_debug spec.
TEST_P(DebugTestES3, MessageControl2)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    std::vector<Message> messages;

    glDebugMessageCallbackKHR(Callback, &messages);
    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

    // Setup the control of de debug output for the default debug group
    glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE);
    glDebugMessageControlKHR(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr,
                             GL_FALSE);
    std::vector<GLuint> ids0 = {1234, 2345, 3456, 4567};
    glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE,
                             static_cast<GLuint>(ids0.size()), ids0.data(), GL_FALSE);
    glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, GL_DONT_CARE,
                             static_cast<GLuint>(ids0.size()), ids0.data(), GL_FALSE);

    // Push debug group 1
    // Inherit of the default debug group debug output volume control
    // Filtered out by glDebugMessageControl
    glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Message 1");

    // In this section of the code, we are interested in performances.
    glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PERFORMANCE, GL_DONT_CARE,
                             0, nullptr, GL_TRUE);
    // But we already identify that some messages are not really useful for us.
    std::vector<GLuint> ids1 = {5678, 6789};
    glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE,
                             static_cast<GLuint>(ids1.size()), ids1.data(), GL_FALSE);

    glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PERFORMANCE, 1357,
                            GL_DEBUG_SEVERITY_MEDIUM, -1, "Message 2");
    glDebugMessageInsertKHR(GL_DEBUG_SOURCE_THIRD_PARTY,  // We still filter out these messages.
                            GL_DEBUG_TYPE_OTHER, 3579, GL_DEBUG_SEVERITY_MEDIUM, -1, "Message 3");

    glPopDebugGroupKHR();

    // Expected debug output from the GL implementation
    // Message 2
    EXPECT_EQ(1u, messages.size());
    EXPECT_STREQ(messages[0].message.c_str(), "Message 2");

    ASSERT_GL_NO_ERROR();
}

// Test basic usage of setting and getting labels
TEST_P(DebugTestES3, ObjectLabels)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    GLuint renderbuffer = 0;
    glGenRenderbuffers(1, &renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);

    const std::string &label = "renderbuffer";
    glObjectLabelKHR(GL_RENDERBUFFER, renderbuffer, -1, label.c_str());

    std::vector<char> labelBuf(label.length() + 1);
    GLsizei labelLengthBuf = 0;
    glGetObjectLabelKHR(GL_RENDERBUFFER, renderbuffer, static_cast<GLsizei>(labelBuf.size()),
                        &labelLengthBuf, labelBuf.data());

    EXPECT_EQ(static_cast<GLsizei>(label.length()), labelLengthBuf);
    EXPECT_STREQ(label.c_str(), labelBuf.data());

    ASSERT_GL_NO_ERROR();

    glDeleteRenderbuffers(1, &renderbuffer);

    glObjectLabelKHR(GL_RENDERBUFFER, renderbuffer, -1, label.c_str());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glGetObjectLabelKHR(GL_RENDERBUFFER, renderbuffer, static_cast<GLsizei>(labelBuf.size()),
                        &labelLengthBuf, labelBuf.data());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Test basic usage of setting and getting labels
TEST_P(DebugTestES3, ObjectPtrLabels)
{
    ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable);

    GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

    const std::string &label = "sync";
    glObjectPtrLabelKHR(sync, -1, label.c_str());

    std::vector<char> labelBuf(label.length() + 1);
    GLsizei labelLengthBuf = 0;
    glGetObjectPtrLabelKHR(sync, static_cast<GLsizei>(labelBuf.size()), &labelLengthBuf,
                           labelBuf.data());

    EXPECT_EQ(static_cast<GLsizei>(label.length()), labelLengthBuf);
    EXPECT_STREQ(label.c_str(), labelBuf.data());

    ASSERT_GL_NO_ERROR();

    glDeleteSync(sync);

    glObjectPtrLabelKHR(sync, -1, label.c_str());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    glGetObjectPtrLabelKHR(sync, static_cast<GLsizei>(labelBuf.size()), &labelLengthBuf,
                           labelBuf.data());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES3(DebugTestES3);
ANGLE_INSTANTIATE_TEST(DebugTest,
                       ANGLE_ALL_TEST_PLATFORMS_ES1,
                       ANGLE_ALL_TEST_PLATFORMS_ES2,
                       ANGLE_ALL_TEST_PLATFORMS_ES3,
                       ANGLE_ALL_TEST_PLATFORMS_ES31);
}  // namespace angle
