// // 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. // // Debug.cpp: Defines debug state used for GL_KHR_debug #include "libANGLE/Debug.h" #include "common/debug.h" #include #include namespace { const char *GLSeverityToString(GLenum severity) { switch (severity) { case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_NOTIFICATION: default: return "NOTIFICATION"; } } const char *EGLMessageTypeToString(egl::MessageType messageType) { switch (messageType) { case egl::MessageType::Critical: return "CRITICAL"; case egl::MessageType::Error: return "ERROR"; case egl::MessageType::Warn: return "WARNING"; case egl::MessageType::Info: default: return "INFO"; } } const char *GLMessageTypeToString(GLenum type) { switch (type) { case GL_DEBUG_TYPE_ERROR: return "error"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "deprecated behavior"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "undefined behavior"; case GL_DEBUG_TYPE_PORTABILITY: return "portability"; case GL_DEBUG_TYPE_PERFORMANCE: return "performance"; case GL_DEBUG_TYPE_MARKER: return "marker"; case GL_DEBUG_TYPE_PUSH_GROUP: return "start of group"; case GL_DEBUG_TYPE_POP_GROUP: return "end of group"; case GL_DEBUG_TYPE_OTHER: default: return "other message"; } } } // namespace namespace gl { Debug::Control::Control() {} Debug::Control::~Control() {} Debug::Control::Control(const Control &other) = default; Debug::Group::Group() {} Debug::Group::~Group() {} Debug::Group::Group(const Group &other) = default; Debug::Debug(bool initialDebugState) : mOutputEnabled(initialDebugState), mCallbackFunction(nullptr), mCallbackUserParam(nullptr), mMessages(), mMaxLoggedMessages(0), mOutputSynchronous(false), mGroups() { pushDefaultGroup(); } Debug::~Debug() {} 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, gl::LogSeverity logSeverity, angle::EntryPoint entryPoint) const { std::string messageCopy(message); insertMessage(source, type, id, severity, std::move(messageCopy), logSeverity, entryPoint); } void Debug::insertMessage(GLenum source, GLenum type, GLuint id, GLenum severity, std::string &&message, gl::LogSeverity logSeverity, angle::EntryPoint entryPoint) const { { // output all messages to the debug log const char *messageTypeString = GLMessageTypeToString(type); const char *severityString = GLSeverityToString(severity); std::ostringstream messageStream; if (entryPoint != angle::EntryPoint::GLInvalid) { messageStream << GetEntryPointName(entryPoint) << ": "; } messageStream << "GL " << messageTypeString << ": " << severityString << ": " << message; switch (logSeverity) { case gl::LOG_FATAL: FATAL() << messageStream.str(); break; case gl::LOG_ERR: ERR() << messageStream.str(); break; case gl::LOG_WARN: WARN() << messageStream.str(); break; case gl::LOG_INFO: INFO() << messageStream.str(); break; case gl::LOG_EVENT: ANGLE_LOG(EVENT) << messageStream.str(); break; } } 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(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(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(m.message.length()) + 1; } mMessages.pop_front(); messageCount++; } return messageCount; } size_t Debug::getNextMessageLength() const { return mMessages.empty() ? 0 : mMessages.front().message.length() + 1; } size_t Debug::getMessageCount() const { return mMessages.size(); } void Debug::setMessageControl(GLenum source, GLenum type, GLenum severity, std::vector &&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), gl::LOG_INFO, angle::EntryPoint::GLPushDebugGroup); 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, gl::LOG_INFO, angle::EntryPoint::GLPopDebugGroup); } size_t Debug::getGroupStackDepth() const { return mGroups.size(); } void Debug::insertPerfWarning(GLenum severity, const char *message, uint32_t *repeatCount) const { bool repeatLast; { constexpr uint32_t kMaxRepeat = 4; std::lock_guard lock(GetDebugMutex()); if (*repeatCount >= kMaxRepeat) { return; } ++*repeatCount; repeatLast = (*repeatCount == kMaxRepeat); } std::string msg = message; if (repeatLast) { msg += " (this message will no longer repeat)"; } // Release the lock before we call insertMessage. It will re-acquire the lock. insertMessage(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_PERFORMANCE, 0, severity, std::move(msg), gl::LOG_INFO, angle::EntryPoint::GLInvalid); } 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 namespace egl { namespace { angle::PackedEnumBitSet GetDefaultMessageTypeBits() { angle::PackedEnumBitSet result; result.set(MessageType::Critical); result.set(MessageType::Error); return result; } } // anonymous namespace Debug::Debug() : mCallback(nullptr), mEnabledMessageTypes(GetDefaultMessageTypeBits()) {} void Debug::setCallback(EGLDEBUGPROCKHR callback, const AttributeMap &attribs) { mCallback = callback; const angle::PackedEnumBitSet defaultMessageTypes = GetDefaultMessageTypeBits(); if (mCallback != nullptr) { for (MessageType messageType : angle::AllEnums()) { mEnabledMessageTypes[messageType] = (attribs.getAsInt(egl::ToEGLenum(messageType), defaultMessageTypes[messageType]) == EGL_TRUE); } } } EGLDEBUGPROCKHR Debug::getCallback() const { return mCallback; } bool Debug::isMessageTypeEnabled(MessageType type) const { return mEnabledMessageTypes[type]; } void Debug::insertMessage(EGLenum error, const char *command, MessageType messageType, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const std::string &message) const { { // output all messages to the debug log const char *messageTypeString = EGLMessageTypeToString(messageType); std::ostringstream messageStream; messageStream << "EGL " << messageTypeString << ": " << command << ": " << message; INFO() << messageStream.str(); } // TODO(geofflang): Lock before checking the callback. http://anglebug.com/2464 if (mCallback && isMessageTypeEnabled(messageType)) { mCallback(error, command, egl::ToEGLenum(messageType), threadLabel, objectLabel, message.c_str()); } } } // namespace egl