Implement GL_KHR_debug.

BUG=angleproject:520

Change-Id: I78d14cc8c94f5cef58604220f0ca847473b25bf8
Reviewed-on: https://chromium-review.googlesource.com/317820
Tryjob-Request: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Tested-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Buffer.cpp b/src/libANGLE/Buffer.cpp
index 59b2d5e..32c84e0 100644
--- a/src/libANGLE/Buffer.cpp
+++ b/src/libANGLE/Buffer.cpp
@@ -17,6 +17,7 @@
 
 Buffer::Buffer(rx::BufferImpl *impl, GLuint id)
     : RefCountObject(id),
+      mLabel(),
       mBuffer(impl),
       mUsage(GL_STATIC_DRAW),
       mSize(0),
@@ -34,6 +35,16 @@
     SafeDelete(mBuffer);
 }
 
+void Buffer::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &Buffer::getLabel() const
+{
+    return mLabel;
+}
+
 Error Buffer::bufferData(const void *data, GLsizeiptr size, GLenum usage)
 {
     gl::Error error = mBuffer->setData(data, size, usage);
diff --git a/src/libANGLE/Buffer.h b/src/libANGLE/Buffer.h
index be8d7f3..6c951ef 100644
--- a/src/libANGLE/Buffer.h
+++ b/src/libANGLE/Buffer.h
@@ -12,6 +12,7 @@
 #define LIBANGLE_BUFFER_H_
 
 #include "common/angleutils.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/IndexRangeCache.h"
 #include "libANGLE/RefCountObject.h"
@@ -24,13 +25,15 @@
 namespace gl
 {
 
-class Buffer : public RefCountObject
+class Buffer final : public RefCountObject, public LabeledObject
 {
   public:
     Buffer(rx::BufferImpl *impl, GLuint id);
-
     virtual ~Buffer();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     Error bufferData(const void *data, GLsizeiptr size, GLenum usage);
     Error bufferSubData(const void *data, GLsizeiptr size, GLintptr offset);
     Error copyBufferSubData(Buffer* source, GLintptr sourceOffset, GLintptr destOffset, GLsizeiptr size);
@@ -61,6 +64,8 @@
   private:
     rx::BufferImpl *mBuffer;
 
+    std::string mLabel;
+
     GLenum mUsage;
     GLint64 mSize;
     GLbitfield mAccessFlags;
diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp
index 5766b19..a7c7a0b 100644
--- a/src/libANGLE/Caps.cpp
+++ b/src/libANGLE/Caps.cpp
@@ -148,6 +148,10 @@
       packSubimage(false),
       vertexArrayObject(false),
       debug(false),
+      maxDebugMessageLength(0),
+      maxDebugLoggedMessages(0),
+      maxDebugGroupStackDepth(0),
+      maxLabelLength(0),
       colorBufferFloat(false)
 {
 }
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index 9596df8..537f964 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -262,6 +262,10 @@
 
     // GL_KHR_debug
     bool debug;
+    GLuint maxDebugMessageLength;
+    GLuint maxDebugLoggedMessages;
+    GLuint maxDebugGroupStackDepth;
+    GLuint maxLabelLength;
 
     // ES3 Extension support
 
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 1a13433..d652695 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -62,7 +62,8 @@
                  const Context *shareContext,
                  rx::Renderer *renderer,
                  bool notifyResets,
-                 bool robustAccess)
+                 bool robustAccess,
+                 bool debug)
     : ValidationContext(clientVersion,
                         mState,
                         mCaps,
@@ -77,7 +78,7 @@
     ASSERT(robustAccess == false);   // Unimplemented
 
     initCaps(clientVersion);
-    mState.initialize(mCaps, clientVersion);
+    mState.initialize(mCaps, mExtensions, clientVersion, debug);
 
     mClientVersion = clientVersion;
 
@@ -503,7 +504,7 @@
     }
 }
 
-Buffer *Context::getBuffer(GLuint handle)
+Buffer *Context::getBuffer(GLuint handle) const
 {
     return mResourceManager->getBuffer(handle);
 }
@@ -523,7 +524,7 @@
     return mResourceManager->getTexture(handle);
 }
 
-Renderbuffer *Context::getRenderbuffer(GLuint handle)
+Renderbuffer *Context::getRenderbuffer(GLuint handle) const
 {
     return mResourceManager->getRenderbuffer(handle);
 }
@@ -550,6 +551,41 @@
     return (iter != mTransformFeedbackMap.end()) ? iter->second : nullptr;
 }
 
+LabeledObject *Context::getLabeledObject(GLenum identifier, GLuint name) const
+{
+    switch (identifier)
+    {
+        case GL_BUFFER:
+            return getBuffer(name);
+        case GL_SHADER:
+            return getShader(name);
+        case GL_PROGRAM:
+            return getProgram(name);
+        case GL_VERTEX_ARRAY:
+            return getVertexArray(name);
+        case GL_QUERY:
+            return getQuery(name);
+        case GL_TRANSFORM_FEEDBACK:
+            return getTransformFeedback(name);
+        case GL_SAMPLER:
+            return getSampler(name);
+        case GL_TEXTURE:
+            return getTexture(name);
+        case GL_RENDERBUFFER:
+            return getRenderbuffer(name);
+        case GL_FRAMEBUFFER:
+            return getFramebuffer(name);
+        default:
+            UNREACHABLE();
+            return nullptr;
+    }
+}
+
+LabeledObject *Context::getLabeledObjectFromPtr(const void *ptr) const
+{
+    return getFenceSync(reinterpret_cast<GLsync>(const_cast<void *>(ptr)));
+}
+
 bool Context::isSampler(GLuint samplerName) const
 {
     return mResourceManager->isSampler(samplerName);
@@ -776,6 +812,12 @@
     }
 }
 
+Query *Context::getQuery(GLuint handle) const
+{
+    auto iter = mQueryMap.find(handle);
+    return (iter != mQueryMap.end()) ? iter->second : nullptr;
+}
+
 Texture *Context::getTargetTexture(GLenum target) const
 {
     ASSERT(ValidTextureTarget(this, target));
@@ -905,6 +947,21 @@
       case GL_NUM_EXTENSIONS:
         *params = static_cast<GLint>(mExtensionStrings.size());
         break;
+
+      // GL_KHR_debug
+      case GL_MAX_DEBUG_MESSAGE_LENGTH:
+          *params = mExtensions.maxDebugMessageLength;
+          break;
+      case GL_MAX_DEBUG_LOGGED_MESSAGES:
+          *params = mExtensions.maxDebugLoggedMessages;
+          break;
+      case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
+          *params = mExtensions.maxDebugGroupStackDepth;
+          break;
+      case GL_MAX_LABEL_LENGTH:
+          *params = mExtensions.maxLabelLength;
+          break;
+
       default:
         mState.getIntegerv(getData(), pname, params);
         break;
@@ -938,6 +995,11 @@
     }
 }
 
+void Context::getPointerv(GLenum pname, void **params) const
+{
+    mState.getPointerv(pname, params);
+}
+
 bool Context::getIndexedIntegerv(GLenum target, GLuint index, GLint *data)
 {
     // Queries about context capabilities and maximums are answered by Context.
@@ -1149,6 +1211,29 @@
         return true;
     }
 
+    if (mExtensions.debug)
+    {
+        switch (pname)
+        {
+            case GL_DEBUG_LOGGED_MESSAGES:
+            case GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH:
+            case GL_DEBUG_GROUP_STACK_DEPTH:
+            case GL_MAX_DEBUG_MESSAGE_LENGTH:
+            case GL_MAX_DEBUG_LOGGED_MESSAGES:
+            case GL_MAX_DEBUG_GROUP_STACK_DEPTH:
+            case GL_MAX_LABEL_LENGTH:
+                *type      = GL_INT;
+                *numParams = 1;
+                return true;
+
+            case GL_DEBUG_OUTPUT_SYNCHRONOUS:
+            case GL_DEBUG_OUTPUT:
+                *type      = GL_BOOL;
+                *numParams = 1;
+                return true;
+        }
+    }
+
     // Check for ES3.0+ parameter names which are also exposed as ES2 extensions
     switch (pname)
     {
@@ -1393,6 +1478,13 @@
     if (error.isError())
     {
         mErrors.insert(error.getCode());
+
+        if (!error.getMessage().empty())
+        {
+            auto &debug = mState.getDebug();
+            debug.insertMessage(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, error.getID(),
+                                GL_DEBUG_SEVERITY_HIGH, error.getMessage());
+        }
     }
 }
 
@@ -1740,6 +1832,13 @@
         //mExtensions.sRGB = false;
     }
 
+    // Explicitly enable GL_KHR_debug
+    mExtensions.debug                   = true;
+    mExtensions.maxDebugMessageLength   = 1024;
+    mExtensions.maxDebugLoggedMessages  = 1024;
+    mExtensions.maxDebugGroupStackDepth = 1024;
+    mExtensions.maxLabelLength          = 1024;
+
     // Apply implementation limits
     mCaps.maxVertexAttributes = std::min<GLuint>(mCaps.maxVertexAttributes, MAX_VERTEX_ATTRIBS);
     mCaps.maxVertexUniformBlocks = std::min<GLuint>(mCaps.maxVertexUniformBlocks, IMPLEMENTATION_MAX_VERTEX_SHADER_UNIFORM_BUFFERS);
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index 10fdd0c..f144990 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -58,7 +58,13 @@
 class Context final : public ValidationContext
 {
   public:
-    Context(const egl::Config *config, int clientVersion, const Context *shareContext, rx::Renderer *renderer, bool notifyResets, bool robustAccess);
+    Context(const egl::Config *config,
+            int clientVersion,
+            const Context *shareContext,
+            rx::Renderer *renderer,
+            bool notifyResets,
+            bool robustAccess,
+            bool debug);
 
     virtual ~Context();
 
@@ -133,18 +139,21 @@
     GLint getSamplerParameteri(GLuint sampler, GLenum pname);
     GLfloat getSamplerParameterf(GLuint sampler, GLenum pname);
 
-    Buffer *getBuffer(GLuint handle);
+    Buffer *getBuffer(GLuint handle) const;
     FenceNV *getFenceNV(GLuint handle);
     FenceSync *getFenceSync(GLsync handle) const;
     Shader *getShader(GLuint handle) const;
     Program *getProgram(GLuint handle) const;
     Texture *getTexture(GLuint handle) const;
     Framebuffer *getFramebuffer(GLuint handle) const;
-    Renderbuffer *getRenderbuffer(GLuint handle);
+    Renderbuffer *getRenderbuffer(GLuint handle) const;
     VertexArray *getVertexArray(GLuint handle) const;
     Sampler *getSampler(GLuint handle) const;
     Query *getQuery(GLuint handle, bool create, GLenum type);
+    Query *getQuery(GLuint handle) const;
     TransformFeedback *getTransformFeedback(GLuint handle) const;
+    LabeledObject *getLabeledObject(GLenum identifier, GLuint name) const;
+    LabeledObject *getLabeledObjectFromPtr(const void *ptr) const;
 
     Texture *getTargetTexture(GLenum target) const;
     Texture *getSamplerTexture(unsigned int sampler, GLenum type) const;
@@ -160,6 +169,7 @@
     void getFloatv(GLenum pname, GLfloat *params);
     void getIntegerv(GLenum pname, GLint *params);
     void getInteger64v(GLenum pname, GLint64 *params);
+    void getPointerv(GLenum pname, void **params) const;
 
     bool getIndexedIntegerv(GLenum target, GLuint index, GLint *data);
     bool getIndexedInteger64v(GLenum target, GLuint index, GLint64 *data);
diff --git a/src/libANGLE/Debug.cpp b/src/libANGLE/Debug.cpp
new file mode 100644
index 0000000..30321f4
--- /dev/null
+++ b/src/libANGLE/Debug.cpp
@@ -0,0 +1,303 @@
+//
+// Copyright (c) 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.
+//
+
+// Debug.cpp: Defines debug state used for GL_KHR_debug
+
+#include "libANGLE/Debug.h"
+
+#include "common/debug.h"
+
+#include <algorithm>
+#include <tuple>
+
+namespace gl
+{
+
+Debug::Debug()
+    : mOutputEnabled(false),
+      mCallbackFunction(nullptr),
+      mCallbackUserParam(nullptr),
+      mMessages(),
+      mMaxLoggedMessages(0),
+      mOutputSynchronous(false),
+      mGroups()
+{
+    pushDefaultGroup();
+}
+
+void Debug::setMaxLoggedMessages(GLuint maxLoggedMessages)
+{
+    mMaxLoggedMessages = maxLoggedMessages;
+}
+
+void Debug::setOutputEnabled(bool enabled)
+{
+    mOutputEnabled = enabled;
+}
+
+bool Debug::isOutputEnabled() const
+{
+    return mOutputEnabled;
+}
+
+void Debug::setOutputSynchronous(bool synchronous)
+{
+    mOutputSynchronous = synchronous;
+}
+
+bool Debug::isOutputSynchronous() const
+{
+    return mOutputSynchronous;
+}
+
+void Debug::setCallback(GLDEBUGPROCKHR callback, const void *userParam)
+{
+    mCallbackFunction  = callback;
+    mCallbackUserParam = userParam;
+}
+
+GLDEBUGPROCKHR Debug::getCallback() const
+{
+    return mCallbackFunction;
+}
+
+const void *Debug::getUserParam() const
+{
+    return mCallbackUserParam;
+}
+
+void Debug::insertMessage(GLenum source,
+                          GLenum type,
+                          GLuint id,
+                          GLenum severity,
+                          const std::string &message)
+{
+    std::string messageCopy(message);
+    insertMessage(source, type, id, severity, std::move(messageCopy));
+}
+
+void Debug::insertMessage(GLenum source,
+                          GLenum type,
+                          GLuint id,
+                          GLenum severity,
+                          std::string &&message)
+{
+    if (!isMessageEnabled(source, type, id, severity))
+    {
+        return;
+    }
+
+    if (mCallbackFunction != nullptr)
+    {
+        // TODO(geofflang) Check the synchronous flag and potentially flush messages from another
+        // thread.
+        mCallbackFunction(source, type, id, severity, static_cast<GLsizei>(message.length()),
+                          message.c_str(), mCallbackUserParam);
+    }
+    else
+    {
+        if (mMessages.size() >= mMaxLoggedMessages)
+        {
+            // Drop messages over the limit
+            return;
+        }
+
+        Message m;
+        m.source   = source;
+        m.type     = type;
+        m.id       = id;
+        m.severity = severity;
+        m.message  = std::move(message);
+
+        mMessages.push_back(std::move(m));
+    }
+}
+
+size_t Debug::getMessages(GLuint count,
+                          GLsizei bufSize,
+                          GLenum *sources,
+                          GLenum *types,
+                          GLuint *ids,
+                          GLenum *severities,
+                          GLsizei *lengths,
+                          GLchar *messageLog)
+{
+    size_t messageCount       = 0;
+    size_t messageStringIndex = 0;
+    while (messageCount <= count && !mMessages.empty())
+    {
+        const Message &m = mMessages.front();
+
+        if (messageLog != nullptr)
+        {
+            // Check that this message can fit in the message buffer
+            if (messageStringIndex + m.message.length() + 1 > static_cast<size_t>(bufSize))
+            {
+                break;
+            }
+
+            std::copy(m.message.begin(), m.message.end(), messageLog + messageStringIndex);
+            messageStringIndex += m.message.length();
+
+            messageLog[messageStringIndex] = '\0';
+            messageStringIndex += 1;
+        }
+
+        if (sources != nullptr)
+        {
+            sources[messageCount] = m.source;
+        }
+
+        if (types != nullptr)
+        {
+            types[messageCount] = m.type;
+        }
+
+        if (ids != nullptr)
+        {
+            ids[messageCount] = m.id;
+        }
+
+        if (severities != nullptr)
+        {
+            severities[messageCount] = m.severity;
+        }
+
+        if (lengths != nullptr)
+        {
+            lengths[messageCount] = static_cast<GLsizei>(m.message.length());
+        }
+
+        mMessages.pop_front();
+
+        messageCount++;
+    }
+
+    return messageCount;
+}
+
+size_t Debug::getNextMessageLength() const
+{
+    return mMessages.empty() ? 0 : mMessages.front().message.length();
+}
+
+size_t Debug::getMessageCount() const
+{
+    return mMessages.size();
+}
+
+void Debug::setMessageControl(GLenum source,
+                              GLenum type,
+                              GLenum severity,
+                              std::vector<GLuint> &&ids,
+                              bool enabled)
+{
+    Control c;
+    c.source   = source;
+    c.type     = type;
+    c.severity = severity;
+    c.ids      = std::move(ids);
+    c.enabled  = enabled;
+
+    auto &controls = mGroups.back().controls;
+    controls.push_back(std::move(c));
+}
+
+void Debug::pushGroup(GLenum source, GLuint id, std::string &&message)
+{
+    insertMessage(source, GL_DEBUG_TYPE_PUSH_GROUP, id, GL_DEBUG_SEVERITY_NOTIFICATION,
+                  std::string(message));
+
+    Group g;
+    g.source  = source;
+    g.id      = id;
+    g.message = std::move(message);
+    mGroups.push_back(std::move(g));
+}
+
+void Debug::popGroup()
+{
+    // Make sure the default group is not about to be popped
+    ASSERT(mGroups.size() > 1);
+
+    Group g = mGroups.back();
+    mGroups.pop_back();
+
+    insertMessage(g.source, GL_DEBUG_TYPE_POP_GROUP, g.id, GL_DEBUG_SEVERITY_NOTIFICATION,
+                  g.message);
+}
+
+size_t Debug::getGroupStackDepth() const
+{
+    return mGroups.size();
+}
+
+bool Debug::isMessageEnabled(GLenum source, GLenum type, GLuint id, GLenum severity) const
+{
+    if (!mOutputEnabled)
+    {
+        return false;
+    }
+
+    for (auto groupIter = mGroups.rbegin(); groupIter != mGroups.rend(); groupIter++)
+    {
+        const auto &controls = groupIter->controls;
+        for (auto controlIter = controls.rbegin(); controlIter != controls.rend(); controlIter++)
+        {
+            const auto &control = *controlIter;
+
+            if (control.source != GL_DONT_CARE && control.source != source)
+            {
+                continue;
+            }
+
+            if (control.type != GL_DONT_CARE && control.type != type)
+            {
+                continue;
+            }
+
+            if (control.severity != GL_DONT_CARE && control.severity != severity)
+            {
+                continue;
+            }
+
+            if (!control.ids.empty() &&
+                std::find(control.ids.begin(), control.ids.end(), id) == control.ids.end())
+            {
+                continue;
+            }
+
+            return control.enabled;
+        }
+    }
+
+    return true;
+}
+
+void Debug::pushDefaultGroup()
+{
+    Group g;
+    g.source  = GL_NONE;
+    g.id      = 0;
+    g.message = "";
+
+    Control c0;
+    c0.source   = GL_DONT_CARE;
+    c0.type     = GL_DONT_CARE;
+    c0.severity = GL_DONT_CARE;
+    c0.enabled = true;
+    g.controls.push_back(std::move(c0));
+
+    Control c1;
+    c1.source   = GL_DONT_CARE;
+    c1.type     = GL_DONT_CARE;
+    c1.severity = GL_DEBUG_SEVERITY_LOW;
+    c1.enabled = false;
+    g.controls.push_back(std::move(c1));
+
+    mGroups.push_back(std::move(g));
+}
+}  // namespace gl
diff --git a/src/libANGLE/Debug.h b/src/libANGLE/Debug.h
new file mode 100644
index 0000000..cb5ca23
--- /dev/null
+++ b/src/libANGLE/Debug.h
@@ -0,0 +1,119 @@
+//
+// Copyright (c) 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.
+//
+
+// Debug.h: Defines debug state used for GL_KHR_debug
+
+#ifndef LIBANGLE_DEBUG_H_
+#define LIBANGLE_DEBUG_H_
+
+#include "angle_gl.h"
+#include "common/angleutils.h"
+
+#include <deque>
+#include <string>
+#include <vector>
+
+namespace gl
+{
+
+class LabeledObject
+{
+  public:
+    virtual void setLabel(const std::string &label) = 0;
+    virtual const std::string &getLabel() const = 0;
+};
+
+class Debug : angle::NonCopyable
+{
+  public:
+    Debug();
+
+    void setMaxLoggedMessages(GLuint maxLoggedMessages);
+
+    void setOutputEnabled(bool enabled);
+    bool isOutputEnabled() const;
+
+    void setOutputSynchronous(bool synchronous);
+    bool isOutputSynchronous() const;
+
+    void setCallback(GLDEBUGPROCKHR callback, const void *userParam);
+    GLDEBUGPROCKHR getCallback() const;
+    const void *getUserParam() const;
+
+    void insertMessage(GLenum source,
+                       GLenum type,
+                       GLuint id,
+                       GLenum severity,
+                       const std::string &message);
+    void insertMessage(GLenum source,
+                       GLenum type,
+                       GLuint id,
+                       GLenum severity,
+                       std::string &&message);
+
+    void setMessageControl(GLenum source,
+                           GLenum type,
+                           GLenum severity,
+                           std::vector<GLuint> &&ids,
+                           bool enabled);
+    size_t getMessages(GLuint count,
+                       GLsizei bufSize,
+                       GLenum *sources,
+                       GLenum *types,
+                       GLuint *ids,
+                       GLenum *severities,
+                       GLsizei *lengths,
+                       GLchar *messageLog);
+    size_t getNextMessageLength() const;
+    size_t getMessageCount() const;
+
+    void pushGroup(GLenum source, GLuint id, std::string &&message);
+    void popGroup();
+    size_t getGroupStackDepth() const;
+
+  private:
+    bool isMessageEnabled(GLenum source, GLenum type, GLuint id, GLenum severity) const;
+
+    void pushDefaultGroup();
+
+    struct Message
+    {
+        GLenum source;
+        GLenum type;
+        GLuint id;
+        GLenum severity;
+        std::string message;
+    };
+
+    struct Control
+    {
+        GLenum source;
+        GLenum type;
+        GLenum severity;
+        std::vector<GLuint> ids;
+        bool enabled;
+    };
+
+    struct Group
+    {
+        GLenum source;
+        GLuint id;
+        std::string message;
+
+        std::vector<Control> controls;
+    };
+
+    bool mOutputEnabled;
+    GLDEBUGPROCKHR mCallbackFunction;
+    const void *mCallbackUserParam;
+    std::deque<Message> mMessages;
+    GLuint mMaxLoggedMessages;
+    bool mOutputSynchronous;
+    std::vector<Group> mGroups;
+};
+}  // namespace gl
+
+#endif  // LIBANGLE_DEBUG_H_
diff --git a/src/libANGLE/Error.cpp b/src/libANGLE/Error.cpp
index 0024145..ea52ea1 100644
--- a/src/libANGLE/Error.cpp
+++ b/src/libANGLE/Error.cpp
@@ -17,8 +17,17 @@
 {
 
 Error::Error(GLenum errorCode, const char *msg, ...)
-    : mCode(errorCode),
-      mMessage(nullptr)
+    : mCode(errorCode), mID(errorCode), mMessage(nullptr)
+{
+    va_list vararg;
+    va_start(vararg, msg);
+    createMessageString();
+    *mMessage = FormatString(msg, vararg);
+    va_end(vararg);
+}
+
+Error::Error(GLenum errorCode, GLuint id, const char *msg, ...)
+    : mCode(errorCode), mID(id), mMessage(nullptr)
 {
     va_list vararg;
     va_start(vararg, msg);
diff --git a/src/libANGLE/Error.h b/src/libANGLE/Error.h
index 81c9330..bdb6e0e 100644
--- a/src/libANGLE/Error.h
+++ b/src/libANGLE/Error.h
@@ -22,6 +22,7 @@
   public:
     explicit inline Error(GLenum errorCode);
     Error(GLenum errorCode, const char *msg, ...);
+    Error(GLenum errorCode, GLuint id, const char *msg, ...);
     inline Error(const Error &other);
     inline Error(Error &&other);
 
@@ -31,6 +32,7 @@
     inline Error &operator=(Error &&other);
 
     inline GLenum getCode() const;
+    inline GLuint getID() const;
     inline bool isError() const;
 
     const std::string &getMessage() const;
@@ -43,6 +45,7 @@
     void createMessageString() const;
 
     GLenum mCode;
+    GLuint mID;
     mutable std::string *mMessage;
 };
 
diff --git a/src/libANGLE/Error.inl b/src/libANGLE/Error.inl
index 32e8f05..d16d11d 100644
--- a/src/libANGLE/Error.inl
+++ b/src/libANGLE/Error.inl
@@ -15,12 +15,14 @@
 
 Error::Error(GLenum errorCode)
     : mCode(errorCode),
+      mID(errorCode),
       mMessage(nullptr)
 {
 }
 
 Error::Error(const Error &other)
     : mCode(other.mCode),
+      mID(other.mID),
       mMessage(nullptr)
 {
     if (other.mMessage != nullptr)
@@ -32,6 +34,7 @@
 
 Error::Error(Error &&other)
     : mCode(other.mCode),
+      mID(other.mID),
       mMessage(other.mMessage)
 {
     other.mMessage = nullptr;
@@ -45,6 +48,7 @@
 Error &Error::operator=(const Error &other)
 {
     mCode = other.mCode;
+    mID = other.mID;
 
     if (other.mMessage != nullptr)
     {
@@ -62,6 +66,7 @@
 Error &Error::operator=(Error &&other)
 {
     mCode = other.mCode;
+    mID = other.mID;
     mMessage = other.mMessage;
 
     other.mMessage = nullptr;
@@ -74,6 +79,11 @@
     return mCode;
 }
 
+GLuint Error::getID() const
+{
+    return mID;
+}
+
 bool Error::isError() const
 {
     return (mCode != GL_NO_ERROR);
diff --git a/src/libANGLE/Fence.cpp b/src/libANGLE/Fence.cpp
index aa76791..ff32f4b 100644
--- a/src/libANGLE/Fence.cpp
+++ b/src/libANGLE/Fence.cpp
@@ -76,10 +76,7 @@
 }
 
 FenceSync::FenceSync(rx::FenceSyncImpl *impl, GLuint id)
-    : RefCountObject(id),
-      mFence(impl),
-      mCondition(GL_NONE),
-      mFlags(0)
+    : RefCountObject(id), mFence(impl), mLabel(), mCondition(GL_NONE), mFlags(0)
 {
 }
 
@@ -88,6 +85,16 @@
     SafeDelete(mFence);
 }
 
+void FenceSync::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &FenceSync::getLabel() const
+{
+    return mLabel;
+}
+
 Error FenceSync::set(GLenum condition, GLbitfield flags)
 {
     Error error = mFence->set(condition, flags);
diff --git a/src/libANGLE/Fence.h b/src/libANGLE/Fence.h
index 74dad05..b2daed6 100644
--- a/src/libANGLE/Fence.h
+++ b/src/libANGLE/Fence.h
@@ -10,6 +10,7 @@
 #ifndef LIBANGLE_FENCE_H_
 #define LIBANGLE_FENCE_H_
 
+#include "libANGLE/Debug.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/RefCountObject.h"
 
@@ -47,12 +48,15 @@
     GLenum mCondition;
 };
 
-class FenceSync final : public RefCountObject
+class FenceSync final : public RefCountObject, public LabeledObject
 {
   public:
-    explicit FenceSync(rx::FenceSyncImpl *impl, GLuint id);
+    FenceSync(rx::FenceSyncImpl *impl, GLuint id);
     virtual ~FenceSync();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     Error set(GLenum condition, GLbitfield flags);
     Error clientWait(GLbitfield flags, GLuint64 timeout, GLenum *outResult);
     Error serverWait(GLbitfield flags, GLuint64 timeout);
@@ -64,6 +68,8 @@
   private:
     rx::FenceSyncImpl *mFence;
 
+    std::string mLabel;
+
     GLenum mCondition;
     GLbitfield mFlags;
 };
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index c388d1e..1c2646d 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -40,7 +40,8 @@
 }
 
 Framebuffer::Data::Data()
-    : mColorAttachments(1),
+    : mLabel(),
+      mColorAttachments(1),
       mDrawBufferStates(1, GL_NONE),
       mReadBufferState(GL_COLOR_ATTACHMENT0_EXT)
 {
@@ -48,7 +49,8 @@
 }
 
 Framebuffer::Data::Data(const Caps &caps)
-    : mColorAttachments(caps.maxColorAttachments),
+    : mLabel(),
+      mColorAttachments(caps.maxColorAttachments),
       mDrawBufferStates(caps.maxDrawBuffers, GL_NONE),
       mReadBufferState(GL_COLOR_ATTACHMENT0_EXT)
 {
@@ -59,6 +61,11 @@
 {
 }
 
+const std::string &Framebuffer::Data::getLabel()
+{
+    return mLabel;
+}
+
 const FramebufferAttachment *Framebuffer::Data::getReadAttachment() const
 {
     ASSERT(mReadBufferState == GL_BACK || (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15));
@@ -179,6 +186,16 @@
     SafeDelete(mImpl);
 }
 
+void Framebuffer::setLabel(const std::string &label)
+{
+    mData.mLabel = label;
+}
+
+const std::string &Framebuffer::getLabel() const
+{
+    return mData.mLabel;
+}
+
 void Framebuffer::detachTexture(GLuint textureId)
 {
     detachResourceById(GL_TEXTURE, textureId);
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index fb9060d..57e8de5 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -14,6 +14,7 @@
 
 #include "common/angleutils.h"
 #include "libANGLE/Constants.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/FramebufferAttachment.h"
 #include "libANGLE/RefCountObject.h"
@@ -44,7 +45,7 @@
 struct ImageIndex;
 struct Rectangle;
 
-class Framebuffer
+class Framebuffer final : public LabeledObject
 {
   public:
 
@@ -55,6 +56,8 @@
         explicit Data(const Caps &caps);
         ~Data();
 
+        const std::string &getLabel();
+
         const FramebufferAttachment *getReadAttachment() const;
         const FramebufferAttachment *getFirstColorAttachment() const;
         const FramebufferAttachment *getDepthOrStencilAttachment() const;
@@ -71,6 +74,8 @@
       private:
         friend class Framebuffer;
 
+        std::string mLabel;
+
         std::vector<FramebufferAttachment> mColorAttachments;
         FramebufferAttachment mDepthAttachment;
         FramebufferAttachment mStencilAttachment;
@@ -83,6 +88,9 @@
     Framebuffer(rx::SurfaceImpl *surface);
     virtual ~Framebuffer();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     const rx::FramebufferImpl *getImplementation() const { return mImpl; }
     rx::FramebufferImpl *getImplementation() { return mImpl; }
 
diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp
index 4a99c88..e21c8f4 100644
--- a/src/libANGLE/Program.cpp
+++ b/src/libANGLE/Program.cpp
@@ -223,7 +223,8 @@
 }
 
 Program::Data::Data()
-    : mAttachedFragmentShader(nullptr),
+    : mLabel(),
+      mAttachedFragmentShader(nullptr),
       mAttachedVertexShader(nullptr),
       mTransformFeedbackBufferMode(GL_INTERLEAVED_ATTRIBS),
       mBinaryRetrieveableHint(false)
@@ -243,6 +244,11 @@
     }
 }
 
+const std::string &Program::Data::getLabel()
+{
+    return mLabel;
+}
+
 const LinkedUniform *Program::Data::getUniformByName(const std::string &name) const
 {
     for (const LinkedUniform &linkedUniform : mUniforms)
@@ -328,6 +334,16 @@
     SafeDelete(mProgram);
 }
 
+void Program::setLabel(const std::string &label)
+{
+    mData.mLabel = label;
+}
+
+const std::string &Program::getLabel() const
+{
+    return mData.mLabel;
+}
+
 bool Program::attachShader(Shader *shader)
 {
     if (shader->getType() == GL_VERTEX_SHADER)
diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h
index f538ce4..f885ad1 100644
--- a/src/libANGLE/Program.h
+++ b/src/libANGLE/Program.h
@@ -24,6 +24,7 @@
 
 #include "libANGLE/angletypes.h"
 #include "libANGLE/Constants.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/RefCountObject.h"
 
@@ -143,7 +144,7 @@
     unsigned int index;
 };
 
-class Program : angle::NonCopyable
+class Program final : angle::NonCopyable, public LabeledObject
 {
   public:
     class Data final : angle::NonCopyable
@@ -152,6 +153,8 @@
         Data();
         ~Data();
 
+        const std::string &getLabel();
+
         const Shader *getAttachedVertexShader() const { return mAttachedVertexShader; }
         const Shader *getAttachedFragmentShader() const { return mAttachedFragmentShader; }
         const std::vector<std::string> &getTransformFeedbackVaryingNames() const
@@ -191,6 +194,8 @@
       private:
         friend class Program;
 
+        std::string mLabel;
+
         Shader *mAttachedFragmentShader;
         Shader *mAttachedVertexShader;
 
@@ -224,6 +229,9 @@
 
     GLuint id() const { return mHandle; }
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     rx::ProgramImpl *getImplementation() { return mProgram; }
     const rx::ProgramImpl *getImplementation() const { return mProgram; }
 
diff --git a/src/libANGLE/Query.cpp b/src/libANGLE/Query.cpp
index e88ea2b..09bf53d 100644
--- a/src/libANGLE/Query.cpp
+++ b/src/libANGLE/Query.cpp
@@ -11,9 +11,7 @@
 
 namespace gl
 {
-Query::Query(rx::QueryImpl *impl, GLuint id)
-    : RefCountObject(id),
-      mQuery(impl)
+Query::Query(rx::QueryImpl *impl, GLuint id) : RefCountObject(id), mQuery(impl), mLabel()
 {
 }
 
@@ -22,6 +20,16 @@
     SafeDelete(mQuery);
 }
 
+void Query::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &Query::getLabel() const
+{
+    return mLabel;
+}
+
 Error Query::begin()
 {
     return mQuery->begin();
diff --git a/src/libANGLE/Query.h b/src/libANGLE/Query.h
index cf54199..61c41ec 100644
--- a/src/libANGLE/Query.h
+++ b/src/libANGLE/Query.h
@@ -9,6 +9,7 @@
 #ifndef LIBANGLE_QUERY_H_
 #define LIBANGLE_QUERY_H_
 
+#include "libANGLE/Debug.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/RefCountObject.h"
 
@@ -24,12 +25,15 @@
 namespace gl
 {
 
-class Query : public RefCountObject
+class Query final : public RefCountObject, public LabeledObject
 {
   public:
     Query(rx::QueryImpl *impl, GLuint id);
     virtual ~Query();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     Error begin();
     Error end();
 
@@ -43,6 +47,8 @@
 
   private:
     rx::QueryImpl *mQuery;
+
+    std::string mLabel;
 };
 
 }
diff --git a/src/libANGLE/Renderbuffer.cpp b/src/libANGLE/Renderbuffer.cpp
index 3c1a2cc..161fbea 100644
--- a/src/libANGLE/Renderbuffer.cpp
+++ b/src/libANGLE/Renderbuffer.cpp
@@ -22,6 +22,7 @@
 Renderbuffer::Renderbuffer(rx::RenderbufferImpl *impl, GLuint id)
     : egl::ImageSibling(id),
       mRenderbuffer(impl),
+      mLabel(),
       mWidth(0),
       mHeight(0),
       mInternalFormat(GL_RGBA4),
@@ -34,6 +35,16 @@
     SafeDelete(mRenderbuffer);
 }
 
+void Renderbuffer::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &Renderbuffer::getLabel() const
+{
+    return mLabel;
+}
+
 Error Renderbuffer::setStorage(GLenum internalformat, size_t width, size_t height)
 {
     orphanImages();
diff --git a/src/libANGLE/Renderbuffer.h b/src/libANGLE/Renderbuffer.h
index 1e0d650..04af03e 100644
--- a/src/libANGLE/Renderbuffer.h
+++ b/src/libANGLE/Renderbuffer.h
@@ -13,6 +13,7 @@
 
 #include "angle_gl.h"
 #include "common/angleutils.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/FramebufferAttachment.h"
 #include "libANGLE/Image.h"
@@ -25,12 +26,17 @@
 // FramebufferAttachment and Framebuffer for how they are applied to an FBO via an
 // attachment point.
 
-class Renderbuffer : public egl::ImageSibling, public gl::FramebufferAttachmentObject
+class Renderbuffer final : public egl::ImageSibling,
+                           public gl::FramebufferAttachmentObject,
+                           public LabeledObject
 {
   public:
     Renderbuffer(rx::RenderbufferImpl *impl, GLuint id);
     virtual ~Renderbuffer();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     Error setStorage(GLenum internalformat, size_t width, size_t height);
     Error setStorageMultisample(size_t samples, GLenum internalformat, size_t width, size_t height);
     Error setStorageEGLImageTarget(egl::Image *imageTarget);
@@ -63,6 +69,8 @@
 
     rx::RenderbufferImpl *mRenderbuffer;
 
+    std::string mLabel;
+
     GLsizei mWidth;
     GLsizei mHeight;
     GLenum mInternalFormat;
diff --git a/src/libANGLE/Sampler.cpp b/src/libANGLE/Sampler.cpp
index eff8702..d8d606a 100644
--- a/src/libANGLE/Sampler.cpp
+++ b/src/libANGLE/Sampler.cpp
@@ -16,7 +16,7 @@
 {
 
 Sampler::Sampler(rx::ImplFactory *factory, GLuint id)
-    : RefCountObject(id), mImpl(factory->createSampler()), mSamplerState()
+    : RefCountObject(id), mImpl(factory->createSampler()), mLabel(), mSamplerState()
 {
 }
 
@@ -25,6 +25,16 @@
     SafeDelete(mImpl);
 }
 
+void Sampler::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &Sampler::getLabel() const
+{
+    return mLabel;
+}
+
 void Sampler::setMinFilter(GLenum minFilter)
 {
     mSamplerState.minFilter = minFilter;
diff --git a/src/libANGLE/Sampler.h b/src/libANGLE/Sampler.h
index 2e78c58..a40b165 100644
--- a/src/libANGLE/Sampler.h
+++ b/src/libANGLE/Sampler.h
@@ -11,6 +11,7 @@
 #define LIBANGLE_SAMPLER_H_
 
 #include "libANGLE/angletypes.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/RefCountObject.h"
 
 namespace rx
@@ -22,12 +23,15 @@
 namespace gl
 {
 
-class Sampler final : public RefCountObject
+class Sampler final : public RefCountObject, public LabeledObject
 {
   public:
     Sampler(rx::ImplFactory *factory, GLuint id);
     ~Sampler() override;
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     void setMinFilter(GLenum minFilter);
     GLenum getMinFilter() const;
 
@@ -66,6 +70,8 @@
   private:
     rx::SamplerImpl *mImpl;
 
+    std::string mLabel;
+
     SamplerState mSamplerState;
 };
 
diff --git a/src/libANGLE/Shader.cpp b/src/libANGLE/Shader.cpp
index 6cfa4b9..bbe9077 100644
--- a/src/libANGLE/Shader.cpp
+++ b/src/libANGLE/Shader.cpp
@@ -72,7 +72,7 @@
     return gl::VariableSortOrder(x.type) < gl::VariableSortOrder(y.type);
 }
 
-Shader::Data::Data(GLenum shaderType) : mShaderType(shaderType), mShaderVersion(100)
+Shader::Data::Data(GLenum shaderType) : mLabel(), mShaderType(shaderType), mShaderVersion(100)
 {
 }
 
@@ -103,6 +103,16 @@
     SafeDelete(mImplementation);
 }
 
+void Shader::setLabel(const std::string &label)
+{
+    mData.mLabel = label;
+}
+
+const std::string &Shader::getLabel() const
+{
+    return mData.mLabel;
+}
+
 GLuint Shader::getHandle() const
 {
     return mHandle;
diff --git a/src/libANGLE/Shader.h b/src/libANGLE/Shader.h
index 290e1cd..997977c 100644
--- a/src/libANGLE/Shader.h
+++ b/src/libANGLE/Shader.h
@@ -21,6 +21,7 @@
 
 #include "common/angleutils.h"
 #include "libANGLE/angletypes.h"
+#include "libANGLE/Debug.h"
 
 namespace rx
 {
@@ -36,7 +37,7 @@
 class ResourceManager;
 struct Data;
 
-class Shader : angle::NonCopyable
+class Shader final : angle::NonCopyable, public LabeledObject
 {
   public:
     class Data final : angle::NonCopyable
@@ -45,6 +46,8 @@
         Data(GLenum shaderType);
         ~Data();
 
+        const std::string &getLabel() const { return mLabel; }
+
         const std::string &getSource() const { return mSource; }
         const std::string &getTranslatedSource() const { return mTranslatedSource; }
 
@@ -66,6 +69,8 @@
       private:
         friend class Shader;
 
+        std::string mLabel;
+
         GLenum mShaderType;
         int mShaderVersion;
         std::string mTranslatedSource;
@@ -86,6 +91,9 @@
 
     virtual ~Shader();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     GLenum getType() const { return mType; }
     GLuint getHandle() const;
 
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index e5feef4..aaf4b68 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -10,6 +10,7 @@
 
 #include "libANGLE/Context.h"
 #include "libANGLE/Caps.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Framebuffer.h"
 #include "libANGLE/FramebufferAttachment.h"
 #include "libANGLE/Query.h"
@@ -78,7 +79,10 @@
     reset();
 }
 
-void State::initialize(const Caps &caps, GLuint clientVersion)
+void State::initialize(const Caps &caps,
+                       const Extensions &extensions,
+                       GLuint clientVersion,
+                       bool debug)
 {
     mMaxDrawBuffers = caps.maxDrawBuffers;
     mMaxCombinedTextureImageUnits = caps.maxCombinedTextureImageUnits;
@@ -185,6 +189,9 @@
     mDrawFramebuffer = NULL;
 
     mPrimitiveRestart = false;
+
+    mDebug.setOutputEnabled(debug);
+    mDebug.setMaxLoggedMessages(extensions.maxDebugLoggedMessages);
 }
 
 void State::reset()
@@ -581,6 +588,12 @@
       case GL_DITHER:                        setDither(enabled);                break;
       case GL_PRIMITIVE_RESTART_FIXED_INDEX: setPrimitiveRestart(enabled);      break;
       case GL_RASTERIZER_DISCARD:            setRasterizerDiscard(enabled);     break;
+      case GL_DEBUG_OUTPUT_SYNCHRONOUS:
+          mDebug.setOutputSynchronous(enabled);
+          break;
+      case GL_DEBUG_OUTPUT:
+          mDebug.setOutputEnabled(enabled);
+          break;
       default:                               UNREACHABLE();
     }
 }
@@ -600,6 +613,10 @@
       case GL_DITHER:                        return isDitherEnabled();
       case GL_PRIMITIVE_RESTART_FIXED_INDEX: return isPrimitiveRestartEnabled();
       case GL_RASTERIZER_DISCARD:            return isRasterizerDiscardEnabled();
+      case GL_DEBUG_OUTPUT_SYNCHRONOUS:
+          return mDebug.isOutputSynchronous();
+      case GL_DEBUG_OUTPUT:
+          return mDebug.isOutputEnabled();
       default:                               UNREACHABLE(); return false;
     }
 }
@@ -1271,6 +1288,16 @@
     return mUnpack;
 }
 
+const Debug &State::getDebug() const
+{
+    return mDebug;
+}
+
+Debug &State::getDebug()
+{
+    return mDebug;
+}
+
 void State::getBooleanv(GLenum pname, GLboolean *params)
 {
     switch (pname)
@@ -1300,6 +1327,12 @@
       case GL_RASTERIZER_DISCARD:
           *params = isRasterizerDiscardEnabled() ? GL_TRUE : GL_FALSE;
           break;
+      case GL_DEBUG_OUTPUT_SYNCHRONOUS:
+          *params = mDebug.isOutputSynchronous() ? GL_TRUE : GL_FALSE;
+          break;
+      case GL_DEBUG_OUTPUT:
+          *params = mDebug.isOutputEnabled() ? GL_TRUE : GL_FALSE;
+          break;
       default:
         UNREACHABLE();
         break;
@@ -1557,12 +1590,37 @@
       case GL_PIXEL_UNPACK_BUFFER_BINDING:
         *params = mUnpack.pixelBuffer.id();
         break;
+      case GL_DEBUG_LOGGED_MESSAGES:
+          *params = static_cast<GLint>(mDebug.getMessageCount());
+          break;
+      case GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH:
+          *params = static_cast<GLint>(mDebug.getNextMessageLength());
+          break;
+      case GL_DEBUG_GROUP_STACK_DEPTH:
+          *params = static_cast<GLint>(mDebug.getGroupStackDepth());
+          break;
       default:
         UNREACHABLE();
         break;
     }
 }
 
+void State::getPointerv(GLenum pname, void **params) const
+{
+    switch (pname)
+    {
+        case GL_DEBUG_CALLBACK_FUNCTION:
+            *params = reinterpret_cast<void *>(mDebug.getCallback());
+            break;
+        case GL_DEBUG_CALLBACK_USER_PARAM:
+            *params = const_cast<void *>(mDebug.getUserParam());
+            break;
+        default:
+            UNREACHABLE();
+            break;
+    }
+}
+
 bool State::getIndexedIntegerv(GLenum target, GLuint index, GLint *data)
 {
     switch (target)
diff --git a/src/libANGLE/State.h b/src/libANGLE/State.h
index 41f391f..f6bec0f 100644
--- a/src/libANGLE/State.h
+++ b/src/libANGLE/State.h
@@ -10,8 +10,10 @@
 #define LIBANGLE_STATE_H_
 
 #include <bitset>
+#include <memory>
 
 #include "common/angleutils.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Program.h"
 #include "libANGLE/RefCountObject.h"
 #include "libANGLE/Renderbuffer.h"
@@ -37,7 +39,10 @@
     State();
     ~State();
 
-    void initialize(const Caps& caps, GLuint clientVersion);
+    void initialize(const Caps &caps,
+                    const Extensions &extensions,
+                    GLuint clientVersion,
+                    bool debug);
     void reset();
 
     // State chunk getters
@@ -258,10 +263,15 @@
     const PixelUnpackState &getUnpackState() const;
     PixelUnpackState &getUnpackState();
 
+    // Debug state
+    const Debug &getDebug() const;
+    Debug &getDebug();
+
     // State query functions
     void getBooleanv(GLenum pname, GLboolean *params);
     void getFloatv(GLenum pname, GLfloat *params);
     void getIntegerv(const gl::Data &data, GLenum pname, GLint *params);
+    void getPointerv(GLenum pname, void **params) const;
     bool getIndexedIntegerv(GLenum target, GLuint index, GLint *data);
     bool getIndexedInteger64v(GLenum target, GLuint index, GLint64 *data);
 
@@ -412,6 +422,8 @@
 
     bool mPrimitiveRestart;
 
+    Debug mDebug;
+
     DirtyBits mDirtyBits;
     DirtyBits mUnpackStateBitMask;
     DirtyBits mPackStateBitMask;
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index b22011d..8d7a5f8 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -50,6 +50,7 @@
 Texture::Texture(rx::TextureImpl *impl, GLuint id, GLenum target)
     : egl::ImageSibling(id),
       mTexture(impl),
+      mLabel(),
       mTextureState(),
       mTarget(target),
       mImageDescs(IMPLEMENTATION_MAX_TEXTURE_LEVELS * (target == GL_TEXTURE_CUBE_MAP ? 6 : 1)),
@@ -68,6 +69,16 @@
     SafeDelete(mTexture);
 }
 
+void Texture::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &Texture::getLabel() const
+{
+    return mLabel;
+}
+
 GLenum Texture::getTarget() const
 {
     return mTarget;
diff --git a/src/libANGLE/Texture.h b/src/libANGLE/Texture.h
index 721e775..7ca8a45 100644
--- a/src/libANGLE/Texture.h
+++ b/src/libANGLE/Texture.h
@@ -15,6 +15,7 @@
 #include "angle_gl.h"
 #include "common/debug.h"
 #include "libANGLE/Caps.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/Constants.h"
 #include "libANGLE/Error.h"
 #include "libANGLE/FramebufferAttachment.h"
@@ -33,14 +34,19 @@
 class Framebuffer;
 struct Data;
 
-bool IsMipmapFiltered(const gl::SamplerState &samplerState);
+bool IsMipmapFiltered(const SamplerState &samplerState);
 
-class Texture final : public egl::ImageSibling, public gl::FramebufferAttachmentObject
+class Texture final : public egl::ImageSibling,
+                      public FramebufferAttachmentObject,
+                      public LabeledObject
 {
   public:
     Texture(rx::TextureImpl *impl, GLuint id, GLenum target);
     ~Texture() override;
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     GLenum getTarget() const;
 
     void setSwizzleRed(GLenum swizzleRed);
@@ -184,6 +190,8 @@
 
     rx::TextureImpl *mTexture;
 
+    std::string mLabel;
+
     TextureState mTextureState;
 
     GLenum mTarget;
diff --git a/src/libANGLE/TransformFeedback.cpp b/src/libANGLE/TransformFeedback.cpp
index ff4cd3d..b796197 100644
--- a/src/libANGLE/TransformFeedback.cpp
+++ b/src/libANGLE/TransformFeedback.cpp
@@ -13,9 +13,10 @@
 namespace gl
 {
 
-TransformFeedback::TransformFeedback(rx::TransformFeedbackImpl* impl, GLuint id, const Caps &caps)
+TransformFeedback::TransformFeedback(rx::TransformFeedbackImpl *impl, GLuint id, const Caps &caps)
     : RefCountObject(id),
       mImplementation(impl),
+      mLabel(),
       mActive(false),
       mPrimitiveMode(GL_NONE),
       mPaused(false),
@@ -36,6 +37,16 @@
     SafeDelete(mImplementation);
 }
 
+void TransformFeedback::setLabel(const std::string &label)
+{
+    mLabel = label;
+}
+
+const std::string &TransformFeedback::getLabel() const
+{
+    return mLabel;
+}
+
 void TransformFeedback::begin(GLenum primitiveMode)
 {
     mActive = true;
diff --git a/src/libANGLE/TransformFeedback.h b/src/libANGLE/TransformFeedback.h
index a3aecb8..098e4ea 100644
--- a/src/libANGLE/TransformFeedback.h
+++ b/src/libANGLE/TransformFeedback.h
@@ -10,6 +10,7 @@
 #include "libANGLE/RefCountObject.h"
 
 #include "common/angleutils.h"
+#include "libANGLE/Debug.h"
 
 #include "angle_gl.h"
 
@@ -23,12 +24,15 @@
 class Buffer;
 struct Caps;
 
-class TransformFeedback : public RefCountObject
+class TransformFeedback final : public RefCountObject, public LabeledObject
 {
   public:
     TransformFeedback(rx::TransformFeedbackImpl* impl, GLuint id, const Caps &caps);
     virtual ~TransformFeedback();
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     void begin(GLenum primitiveMode);
     void end();
     void pause();
@@ -53,6 +57,8 @@
   private:
     rx::TransformFeedbackImpl* mImplementation;
 
+    std::string mLabel;
+
     bool mActive;
     GLenum mPrimitiveMode;
     bool mPaused;
diff --git a/src/libANGLE/VertexArray.cpp b/src/libANGLE/VertexArray.cpp
index afa4332..8d51e9b 100644
--- a/src/libANGLE/VertexArray.cpp
+++ b/src/libANGLE/VertexArray.cpp
@@ -15,8 +15,7 @@
 {
 
 VertexArray::Data::Data(size_t maxAttribs)
-    : mVertexAttributes(maxAttribs),
-      mMaxEnabledAttribute(0)
+    : mLabel(), mVertexAttributes(maxAttribs), mMaxEnabledAttribute(0)
 {
 }
 
@@ -46,6 +45,16 @@
     return mId;
 }
 
+void VertexArray::setLabel(const std::string &label)
+{
+    mData.mLabel = label;
+}
+
+const std::string &VertexArray::getLabel() const
+{
+    return mData.mLabel;
+}
+
 void VertexArray::detachBuffer(GLuint bufferName)
 {
     for (size_t attribute = 0; attribute < getMaxAttribs(); attribute++)
diff --git a/src/libANGLE/VertexArray.h b/src/libANGLE/VertexArray.h
index 9ac7f7b..27b020b 100644
--- a/src/libANGLE/VertexArray.h
+++ b/src/libANGLE/VertexArray.h
@@ -15,6 +15,7 @@
 
 #include "libANGLE/RefCountObject.h"
 #include "libANGLE/Constants.h"
+#include "libANGLE/Debug.h"
 #include "libANGLE/State.h"
 #include "libANGLE/VertexAttribute.h"
 
@@ -30,7 +31,7 @@
 {
 class Buffer;
 
-class VertexArray
+class VertexArray final : public LabeledObject
 {
   public:
     VertexArray(rx::ImplFactory *factory, GLuint id, size_t maxAttribs);
@@ -38,6 +39,9 @@
 
     GLuint id() const;
 
+    void setLabel(const std::string &label) override;
+    const std::string &getLabel() const override;
+
     const VertexAttribute &getVertexAttribute(size_t attributeIndex) const;
 
     void detachBuffer(GLuint bufferName);
@@ -63,6 +67,8 @@
         explicit Data(size_t maxAttribs);
         ~Data();
 
+        const std::string &getLabel() const { return mLabel; }
+
         const BindingPointer<Buffer> &getElementArrayBuffer() const { return mElementArrayBuffer; }
         size_t getMaxAttribs() const { return mVertexAttributes.size(); }
         size_t getMaxEnabledAttribute() const { return mMaxEnabledAttribute; }
@@ -74,6 +80,7 @@
 
       private:
         friend class VertexArray;
+        std::string mLabel;
         std::vector<VertexAttribute> mVertexAttributes;
         BindingPointer<Buffer> mElementArrayBuffer;
         size_t mMaxEnabledAttribute;
diff --git a/src/libANGLE/renderer/d3d/DisplayD3D.cpp b/src/libANGLE/renderer/d3d/DisplayD3D.cpp
index ceea6dd..3aced4f 100644
--- a/src/libANGLE/renderer/d3d/DisplayD3D.cpp
+++ b/src/libANGLE/renderer/d3d/DisplayD3D.cpp
@@ -235,8 +235,10 @@
     EGLint clientVersion = attribs.get(EGL_CONTEXT_CLIENT_VERSION, 1);
     bool notifyResets = (attribs.get(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_NO_RESET_NOTIFICATION_EXT) == EGL_LOSE_CONTEXT_ON_RESET_EXT);
     bool robustAccess = (attribs.get(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_FALSE) == EGL_TRUE);
+    bool debug           = (attribs.get(EGL_CONTEXT_OPENGL_DEBUG, EGL_FALSE) == EGL_TRUE);
 
-    *outContext = new gl::Context(config, clientVersion, shareContext, mRenderer, notifyResets, robustAccess);
+    *outContext = new gl::Context(config, clientVersion, shareContext, mRenderer, notifyResets,
+                                  robustAccess, debug);
     return egl::Error(EGL_SUCCESS);
 }
 
diff --git a/src/libANGLE/renderer/gl/DisplayGL.cpp b/src/libANGLE/renderer/gl/DisplayGL.cpp
index 8c809d6..16cbc3c 100644
--- a/src/libANGLE/renderer/gl/DisplayGL.cpp
+++ b/src/libANGLE/renderer/gl/DisplayGL.cpp
@@ -62,8 +62,10 @@
     EGLint clientVersion = attribs.get(EGL_CONTEXT_CLIENT_VERSION, 1);
     bool notifyResets = (attribs.get(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_NO_RESET_NOTIFICATION_EXT) == EGL_LOSE_CONTEXT_ON_RESET_EXT);
     bool robustAccess = (attribs.get(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_FALSE) == EGL_TRUE);
+    bool debug           = (attribs.get(EGL_CONTEXT_OPENGL_DEBUG, EGL_FALSE) == EGL_TRUE);
 
-    *outContext = new gl::Context(config, clientVersion, shareContext, mRenderer, notifyResets, robustAccess);
+    *outContext = new gl::Context(config, clientVersion, shareContext, mRenderer, notifyResets,
+                                  robustAccess, debug);
     return egl::Error(EGL_SUCCESS);
 }
 
diff --git a/src/libANGLE/validationEGL.cpp b/src/libANGLE/validationEGL.cpp
index 14f83ba..5cbb6d8 100644
--- a/src/libANGLE/validationEGL.cpp
+++ b/src/libANGLE/validationEGL.cpp
@@ -210,6 +210,9 @@
             contextFlags = value;
             break;
 
+          case EGL_CONTEXT_OPENGL_DEBUG:
+              break;
+
           case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
             // Only valid for OpenGL (non-ES) contexts
             return Error(EGL_BAD_ATTRIBUTE);
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index e9cd516..1784bba 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -109,9 +109,15 @@
       case GL_BLEND:
       case GL_DITHER:
         return true;
+
       case GL_PRIMITIVE_RESTART_FIXED_INDEX:
       case GL_RASTERIZER_DISCARD:
         return (context->getClientVersion() >= 3);
+
+      case GL_DEBUG_OUTPUT_SYNCHRONOUS:
+      case GL_DEBUG_OUTPUT:
+          return context->getExtensions().debug;
+
       default:
         return false;
     }
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index 75d9096..b929078 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -1069,6 +1069,61 @@
     return ValidateGetProgramBinaryBase(context, program, bufSize, length, binaryFormat, binary);
 }
 
+static bool ValidDebugSource(GLenum source, bool mustBeThirdPartyOrApplication)
+{
+    switch (source)
+    {
+        case GL_DEBUG_SOURCE_API:
+        case GL_DEBUG_SOURCE_SHADER_COMPILER:
+        case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
+        case GL_DEBUG_SOURCE_OTHER:
+            // Only THIRD_PARTY and APPLICATION sources are allowed to be manually inserted
+            return !mustBeThirdPartyOrApplication;
+
+        case GL_DEBUG_SOURCE_THIRD_PARTY:
+        case GL_DEBUG_SOURCE_APPLICATION:
+            return true;
+
+        default:
+            return false;
+    }
+}
+
+static bool ValidDebugType(GLenum type)
+{
+    switch (type)
+    {
+        case GL_DEBUG_TYPE_ERROR:
+        case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
+        case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
+        case GL_DEBUG_TYPE_PERFORMANCE:
+        case GL_DEBUG_TYPE_PORTABILITY:
+        case GL_DEBUG_TYPE_OTHER:
+        case GL_DEBUG_TYPE_MARKER:
+        case GL_DEBUG_TYPE_PUSH_GROUP:
+        case GL_DEBUG_TYPE_POP_GROUP:
+            return true;
+
+        default:
+            return false;
+    }
+}
+
+static bool ValidDebugSeverity(GLenum severity)
+{
+    switch (severity)
+    {
+        case GL_DEBUG_SEVERITY_HIGH:
+        case GL_DEBUG_SEVERITY_MEDIUM:
+        case GL_DEBUG_SEVERITY_LOW:
+        case GL_DEBUG_SEVERITY_NOTIFICATION:
+            return true;
+
+        default:
+            return false;
+    }
+}
+
 bool ValidateDebugMessageControlKHR(Context *context,
                                     GLenum source,
                                     GLenum type,
@@ -1083,7 +1138,43 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (!ValidDebugSource(source, false) && source != GL_DONT_CARE)
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug source."));
+        return false;
+    }
+
+    if (!ValidDebugType(type) && type != GL_DONT_CARE)
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug type."));
+        return false;
+    }
+
+    if (!ValidDebugSeverity(severity) && severity != GL_DONT_CARE)
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug severity."));
+        return false;
+    }
+
+    if (count > 0)
+    {
+        if (source == GL_DONT_CARE || type == GL_DONT_CARE)
+        {
+            context->recordError(Error(
+                GL_INVALID_OPERATION,
+                "If count is greater than zero, source and severity cannot be GL_DONT_CARE."));
+            return false;
+        }
+
+        if (severity != GL_DONT_CARE)
+        {
+            context->recordError(
+                Error(GL_INVALID_OPERATION,
+                      "If count is greater than zero, severity must be GL_DONT_CARE."));
+            return false;
+        }
+    }
+
     return true;
 }
 
@@ -1101,7 +1192,39 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (!context->getState().getDebug().isOutputEnabled())
+    {
+        // If the DEBUG_OUTPUT state is disabled calls to DebugMessageInsert are discarded and do
+        // not generate an error.
+        return false;
+    }
+
+    if (!ValidDebugSeverity(severity))
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug severity."));
+        return false;
+    }
+
+    if (!ValidDebugType(type))
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug type."));
+        return false;
+    }
+
+    if (!ValidDebugSource(source, true))
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug source."));
+        return false;
+    }
+
+    size_t messageLength = (length < 0) ? strlen(buf) : length;
+    if (messageLength > context->getExtensions().maxDebugMessageLength)
+    {
+        context->recordError(
+            Error(GL_INVALID_VALUE, "Message length is larger than GL_MAX_DEBUG_MESSAGE_LENGTH."));
+        return false;
+    }
+
     return true;
 }
 
@@ -1115,7 +1238,6 @@
         return false;
     }
 
-    UNIMPLEMENTED();
     return true;
 }
 
@@ -1135,7 +1257,13 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (bufSize < 0 && messageLog != nullptr)
+    {
+        context->recordError(
+            Error(GL_INVALID_VALUE, "bufSize must be positive if messageLog is not null."));
+        return false;
+    }
+
     return true;
 }
 
@@ -1151,7 +1279,29 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (!ValidDebugSource(source, true))
+    {
+        context->recordError(Error(GL_INVALID_ENUM, "Invalid debug source."));
+        return false;
+    }
+
+    size_t messageLength = (length < 0) ? strlen(message) : length;
+    if (messageLength > context->getExtensions().maxDebugMessageLength)
+    {
+        context->recordError(
+            Error(GL_INVALID_VALUE, "Message length is larger than GL_MAX_DEBUG_MESSAGE_LENGTH."));
+        return false;
+    }
+
+    size_t currentStackSize = context->getState().getDebug().getGroupStackDepth();
+    if (currentStackSize >= context->getExtensions().maxDebugGroupStackDepth)
+    {
+        context->recordError(
+            Error(GL_STACK_OVERFLOW,
+                  "Cannot push more than GL_MAX_DEBUG_GROUP_STACK_DEPTH debug groups."));
+        return false;
+    }
+
     return true;
 }
 
@@ -1163,7 +1313,106 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    size_t currentStackSize = context->getState().getDebug().getGroupStackDepth();
+    if (currentStackSize <= 1)
+    {
+        context->recordError(Error(GL_STACK_UNDERFLOW, "Cannot pop the default debug group."));
+        return false;
+    }
+
+    return true;
+}
+
+static bool ValidateObjectIdentifierAndName(Context *context, GLenum identifier, GLuint name)
+{
+    switch (identifier)
+    {
+        case GL_BUFFER:
+            if (context->getBuffer(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid buffer."));
+                return false;
+            }
+            return true;
+
+        case GL_SHADER:
+            if (context->getShader(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid shader."));
+                return false;
+            }
+            return true;
+
+        case GL_PROGRAM:
+            if (context->getProgram(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid program."));
+                return false;
+            }
+            return true;
+
+        case GL_VERTEX_ARRAY:
+            if (context->getVertexArray(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid vertex array."));
+                return false;
+            }
+            return true;
+
+        case GL_QUERY:
+            if (context->getQuery(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid query."));
+                return false;
+            }
+            return true;
+
+        case GL_TRANSFORM_FEEDBACK:
+            if (context->getTransformFeedback(name) == nullptr)
+            {
+                context->recordError(
+                    Error(GL_INVALID_VALUE, "name is not a valid transform feedback."));
+                return false;
+            }
+            return true;
+
+        case GL_SAMPLER:
+            if (context->getSampler(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid sampler."));
+                return false;
+            }
+            return true;
+
+        case GL_TEXTURE:
+            if (context->getTexture(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid texture."));
+                return false;
+            }
+            return true;
+
+        case GL_RENDERBUFFER:
+            if (context->getRenderbuffer(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid renderbuffer."));
+                return false;
+            }
+            return true;
+
+        case GL_FRAMEBUFFER:
+            if (context->getFramebuffer(name) == nullptr)
+            {
+                context->recordError(Error(GL_INVALID_VALUE, "name is not a valid framebuffer."));
+                return false;
+            }
+            return true;
+
+        default:
+            context->recordError(Error(GL_INVALID_ENUM, "Invalid identifier."));
+            return false;
+    }
+
     return true;
 }
 
@@ -1179,7 +1428,19 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (!ValidateObjectIdentifierAndName(context, identifier, name))
+    {
+        return false;
+    }
+
+    size_t labelLength = (length < 0) ? strlen(label) : length;
+    if (labelLength > context->getExtensions().maxLabelLength)
+    {
+        context->recordError(
+            Error(GL_INVALID_VALUE, "Label length is larger than GL_MAX_LABEL_LENGTH."));
+        return false;
+    }
+
     return true;
 }
 
@@ -1196,7 +1457,29 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (bufSize < 0)
+    {
+        context->recordError(Error(GL_INVALID_VALUE, "bufSize cannot be negative."));
+        return false;
+    }
+
+    if (!ValidateObjectIdentifierAndName(context, identifier, name))
+    {
+        return false;
+    }
+
+    // Can no-op if bufSize is zero.
+    return bufSize > 0;
+}
+
+static bool ValidateObjectPtrName(Context *context, const void *ptr)
+{
+    if (context->getFenceSync(reinterpret_cast<GLsync>(const_cast<void *>(ptr))) == nullptr)
+    {
+        context->recordError(Error(GL_INVALID_VALUE, "name is not a valid sync."));
+        return false;
+    }
+
     return true;
 }
 
@@ -1211,7 +1494,19 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    if (!ValidateObjectPtrName(context, ptr))
+    {
+        return false;
+    }
+
+    size_t labelLength = (length < 0) ? strlen(label) : length;
+    if (labelLength > context->getExtensions().maxLabelLength)
+    {
+        context->recordError(
+            Error(GL_INVALID_VALUE, "Label length is larger than GL_MAX_LABEL_LENGTH."));
+        return false;
+    }
+
     return true;
 }
 
@@ -1227,8 +1522,19 @@
         return false;
     }
 
-    UNIMPLEMENTED();
-    return true;
+    if (bufSize < 0)
+    {
+        context->recordError(Error(GL_INVALID_VALUE, "bufSize cannot be negative."));
+        return false;
+    }
+
+    if (!ValidateObjectPtrName(context, ptr))
+    {
+        return false;
+    }
+
+    // Can no-op if bufSize is zero.
+    return bufSize > 0;
 }
 
 bool ValidateGetPointervKHR(Context *context, GLenum pname, void **params)
@@ -1239,7 +1545,18 @@
         return false;
     }
 
-    UNIMPLEMENTED();
+    // TODO: represent this in Context::getQueryParameterInfo.
+    switch (pname)
+    {
+        case GL_DEBUG_CALLBACK_FUNCTION:
+        case GL_DEBUG_CALLBACK_USER_PARAM:
+            break;
+
+        default:
+            context->recordError(Error(GL_INVALID_ENUM, "Invalid pname."));
+            return false;
+    }
+
     return true;
 }
 }
diff --git a/src/libANGLE/validationES_unittest.cpp b/src/libANGLE/validationES_unittest.cpp
index 9071ae9..e81b846 100644
--- a/src/libANGLE/validationES_unittest.cpp
+++ b/src/libANGLE/validationES_unittest.cpp
@@ -96,7 +96,7 @@
     caps.maxElementIndex     = 100;
     caps.maxDrawBuffers      = 1;
     caps.maxColorAttachments = 1;
-    state.initialize(caps, 3);
+    state.initialize(caps, extensions, 3, false);
 
     MockTextureImpl *textureImpl = new MockTextureImpl();
     EXPECT_CALL(*textureImpl, setStorage(_, _, _, _)).WillOnce(Return(Error(GL_NO_ERROR)));
diff --git a/src/libGLESv2.gypi b/src/libGLESv2.gypi
index 6a9975a..aebcab5 100644
--- a/src/libGLESv2.gypi
+++ b/src/libGLESv2.gypi
@@ -68,6 +68,8 @@
             'libANGLE/Context.h',
             'libANGLE/Data.cpp',
             'libANGLE/Data.h',
+            'libANGLE/Debug.cpp',
+            'libANGLE/Debug.h',
             'libANGLE/Device.cpp',
             'libANGLE/Device.h',
             'libANGLE/Display.cpp',
diff --git a/src/libGLESv2/entry_points_gles_2_0_ext.cpp b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
index 20a7683..50f3dec 100644
--- a/src/libGLESv2/entry_points_gles_2_0_ext.cpp
+++ b/src/libGLESv2/entry_points_gles_2_0_ext.cpp
@@ -1298,7 +1298,9 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        std::vector<GLuint> idVector(ids, ids + count);
+        context->getState().getDebug().setMessageControl(
+            source, type, severity, std::move(idVector), (enabled != GL_FALSE));
     }
 }
 
@@ -1322,7 +1324,8 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        std::string msg(buf, (length > 0) ? static_cast<size_t>(length) : strlen(buf));
+        context->getState().getDebug().insertMessage(source, type, id, severity, std::move(msg));
     }
 }
 
@@ -1339,7 +1342,7 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        context->getState().getDebug().setCallback(callback, userParam);
     }
 }
 
@@ -1367,7 +1370,8 @@
             return 0;
         }
 
-        UNIMPLEMENTED();
+        return static_cast<GLuint>(context->getState().getDebug().getMessages(
+            count, bufSize, sources, types, ids, severities, lengths, messageLog));
     }
 
     return 0;
@@ -1388,7 +1392,8 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        std::string msg(message, (length > 0) ? static_cast<size_t>(length) : strlen(message));
+        context->getState().getDebug().pushGroup(source, id, std::move(msg));
     }
 }
 
@@ -1404,7 +1409,7 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        context->getState().getDebug().popGroup();
     }
 }
 
@@ -1423,7 +1428,11 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        LabeledObject *object = context->getLabeledObject(identifier, name);
+        ASSERT(object != nullptr);
+
+        std::string lbl(label, (length > 0) ? static_cast<size_t>(length) : strlen(label));
+        object->setLabel(lbl);
     }
 }
 
@@ -1443,7 +1452,14 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        LabeledObject *object = context->getLabeledObject(identifier, name);
+        ASSERT(object != nullptr);
+
+        const std::string &objectLabel = object->getLabel();
+        size_t writeLength = std::min(static_cast<size_t>(bufSize) - 1, objectLabel.length());
+        std::copy(objectLabel.begin(), objectLabel.begin() + writeLength, label);
+        label[writeLength] = '\0';
+        *length            = static_cast<GLsizei>(writeLength);
     }
 }
 
@@ -1460,7 +1476,11 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        LabeledObject *object = context->getLabeledObjectFromPtr(ptr);
+        ASSERT(object != nullptr);
+
+        std::string lbl(label, (length > 0) ? static_cast<size_t>(length) : strlen(label));
+        object->setLabel(lbl);
     }
 }
 
@@ -1482,7 +1502,14 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        LabeledObject *object = context->getLabeledObjectFromPtr(ptr);
+        ASSERT(object != nullptr);
+
+        const std::string &objectLabel = object->getLabel();
+        size_t writeLength = std::min(static_cast<size_t>(bufSize) - 1, objectLabel.length());
+        std::copy(objectLabel.begin(), objectLabel.begin() + writeLength, label);
+        label[writeLength] = '\0';
+        *length            = static_cast<GLsizei>(writeLength);
     }
 }
 
@@ -1498,7 +1525,7 @@
             return;
         }
 
-        UNIMPLEMENTED();
+        context->getPointerv(pname, params);
     }
 }
 }
diff --git a/src/tests/angle_end2end_tests.gypi b/src/tests/angle_end2end_tests.gypi
index e67cda3..b245be9 100644
--- a/src/tests/angle_end2end_tests.gypi
+++ b/src/tests/angle_end2end_tests.gypi
@@ -23,6 +23,7 @@
             '<(angle_path)/src/tests/gl_tests/CopyTexImageTest.cpp',
             '<(angle_path)/src/tests/gl_tests/CubeMapTextureTest.cpp',
             '<(angle_path)/src/tests/gl_tests/DebugMarkerTest.cpp',
+            '<(angle_path)/src/tests/gl_tests/DebugTest.cpp',
             '<(angle_path)/src/tests/gl_tests/DepthStencilFormatsTest.cpp',
             '<(angle_path)/src/tests/gl_tests/DiscardFramebufferEXTTest.cpp',
             '<(angle_path)/src/tests/gl_tests/DrawBuffersTest.cpp',
diff --git a/src/tests/gl_tests/DebugTest.cpp b/src/tests/gl_tests/DebugTest.cpp
new file mode 100644
index 0000000..9a0965f
--- /dev/null
+++ b/src/tests/gl_tests/DebugTest.cpp
@@ -0,0 +1,453 @@
+//
+// 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 "test_utils/ANGLETest.h"
+
+namespace angle
+{
+
+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 SetUp() override
+    {
+        ANGLETest::SetUp();
+
+        mDebugExtensionAvailable = extensionEnabled("GL_KHR_debug");
+        if (mDebugExtensionAvailable)
+        {
+            glEnable(GL_DEBUG_OUTPUT);
+        }
+    }
+
+    bool mDebugExtensionAvailable;
+};
+
+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(DebugTest, Enabled)
+{
+    ASSERT_TRUE(mDebugExtensionAvailable);
+}
+
+// Test that when debug output is disabled, no message are outputted
+TEST_P(DebugTest, DisabledOutput)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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(DebugTest, InsertMessage)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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()), messageLength);
+
+    GLenum sourceBuf   = 0;
+    GLenum typeBuf     = 0;
+    GLenum idBuf       = 0;
+    GLenum severityBuf = 0;
+    GLsizei lengthBuf = 0;
+    std::vector<char> messageBuf(messageLength + 1);
+    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(DebugTest, InsertMessageMultiple)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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()), messageLength);
+
+        GLenum sourceBuf   = 0;
+        GLenum typeBuf     = 0;
+        GLenum idBuf       = 0;
+        GLenum severityBuf = 0;
+        GLsizei lengthBuf = 0;
+        std::vector<char> messageBuf(messageLength + 1);
+        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(DebugTest, DebugCallback)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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(DebugTest, GetPointer)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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(DebugTest, MessageControl1)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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, NULL, 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, NULL, 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(DebugTest, MessageControl2)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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, NULL, GL_FALSE);
+    glDebugMessageControlKHR(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, NULL,
+                             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, NULL, 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(DebugTest, ObjectLabels)
+{
+    if (!mDebugExtensionAvailable)
+    {
+        std::cout << "Test skipped because GL_KHR_debug is not available." << std::endl;
+        return;
+    }
+
+    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(DebugTest, ObjectPtrLabels)
+{
+    if (!mDebugExtensionAvailable || getClientVersion() < 3)
+    {
+        std::cout << "Test skipped because GL_KHR_debug or ES3 is not available." << std::endl;
+        return;
+    }
+
+    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(DebugTest,
+                       ES2_D3D9(),
+                       ES2_D3D11(),
+                       ES3_D3D11(),
+                       ES2_OPENGL(),
+                       ES3_OPENGL(),
+                       ES2_OPENGLES(),
+                       ES3_OPENGLES());
+
+}  // namespace angle
diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp
index ffc1ec1..7ead125 100644
--- a/src/tests/test_utils/ANGLETest.cpp
+++ b/src/tests/test_utils/ANGLETest.cpp
@@ -223,6 +223,11 @@
     mEGLWindow->setMultisample(enabled);
 }
 
+void ANGLETest::setDebugEnabled(bool enabled)
+{
+    mEGLWindow->setDebugEnabled(enabled);
+}
+
 int ANGLETest::getClientVersion() const
 {
     return mEGLWindow->getClientMajorVersion();
diff --git a/src/tests/test_utils/ANGLETest.h b/src/tests/test_utils/ANGLETest.h
index 7ebf248..64f5e9d 100644
--- a/src/tests/test_utils/ANGLETest.h
+++ b/src/tests/test_utils/ANGLETest.h
@@ -96,6 +96,7 @@
     void setConfigDepthBits(int bits);
     void setConfigStencilBits(int bits);
     void setMultisampleEnabled(bool enabled);
+    void setDebugEnabled(bool enabled);
 
     int getClientVersion() const;