1 //
2 // Copyright 2015 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 // Debug.cpp: Defines debug state used for GL_KHR_debug
8
9 #include "libANGLE/Debug.h"
10
11 #include "common/debug.h"
12
13 #include <algorithm>
14 #include <tuple>
15
16 namespace
17 {
GLSeverityToString(GLenum severity)18 const char *GLSeverityToString(GLenum severity)
19 {
20 switch (severity)
21 {
22 case GL_DEBUG_SEVERITY_HIGH:
23 return "HIGH";
24 case GL_DEBUG_SEVERITY_MEDIUM:
25 return "MEDIUM";
26 case GL_DEBUG_SEVERITY_LOW:
27 return "LOW";
28 case GL_DEBUG_SEVERITY_NOTIFICATION:
29 default:
30 return "NOTIFICATION";
31 }
32 }
33
EGLMessageTypeToString(egl::MessageType messageType)34 const char *EGLMessageTypeToString(egl::MessageType messageType)
35 {
36 switch (messageType)
37 {
38 case egl::MessageType::Critical:
39 return "CRITICAL";
40 case egl::MessageType::Error:
41 return "ERROR";
42 case egl::MessageType::Warn:
43 return "WARNING";
44 case egl::MessageType::Info:
45 default:
46 return "INFO";
47 }
48 }
49
GLMessageTypeToString(GLenum type)50 const char *GLMessageTypeToString(GLenum type)
51 {
52 switch (type)
53 {
54 case GL_DEBUG_TYPE_ERROR:
55 return "error";
56 case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
57 return "deprecated behavior";
58 case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
59 return "undefined behavior";
60 case GL_DEBUG_TYPE_PORTABILITY:
61 return "portability";
62 case GL_DEBUG_TYPE_PERFORMANCE:
63 return "performance";
64 case GL_DEBUG_TYPE_MARKER:
65 return "marker";
66 case GL_DEBUG_TYPE_PUSH_GROUP:
67 return "start of group";
68 case GL_DEBUG_TYPE_POP_GROUP:
69 return "end of group";
70 case GL_DEBUG_TYPE_OTHER:
71 default:
72 return "other message";
73 }
74 }
75 } // namespace
76
77 namespace gl
78 {
79
Control()80 Debug::Control::Control() {}
81
~Control()82 Debug::Control::~Control() {}
83
84 Debug::Control::Control(const Control &other) = default;
85
Group()86 Debug::Group::Group() {}
87
~Group()88 Debug::Group::~Group() {}
89
90 Debug::Group::Group(const Group &other) = default;
91
Debug(bool initialDebugState)92 Debug::Debug(bool initialDebugState)
93 : mOutputEnabled(initialDebugState),
94 mCallbackFunction(nullptr),
95 mCallbackUserParam(nullptr),
96 mMessages(),
97 mMaxLoggedMessages(0),
98 mOutputSynchronous(false),
99 mGroups()
100 {
101 pushDefaultGroup();
102 }
103
~Debug()104 Debug::~Debug() {}
105
setMaxLoggedMessages(GLuint maxLoggedMessages)106 void Debug::setMaxLoggedMessages(GLuint maxLoggedMessages)
107 {
108 mMaxLoggedMessages = maxLoggedMessages;
109 }
110
setOutputEnabled(bool enabled)111 void Debug::setOutputEnabled(bool enabled)
112 {
113 mOutputEnabled = enabled;
114 }
115
isOutputEnabled() const116 bool Debug::isOutputEnabled() const
117 {
118 return mOutputEnabled;
119 }
120
setOutputSynchronous(bool synchronous)121 void Debug::setOutputSynchronous(bool synchronous)
122 {
123 mOutputSynchronous = synchronous;
124 }
125
isOutputSynchronous() const126 bool Debug::isOutputSynchronous() const
127 {
128 return mOutputSynchronous;
129 }
130
setCallback(GLDEBUGPROCKHR callback,const void * userParam)131 void Debug::setCallback(GLDEBUGPROCKHR callback, const void *userParam)
132 {
133 mCallbackFunction = callback;
134 mCallbackUserParam = userParam;
135 }
136
getCallback() const137 GLDEBUGPROCKHR Debug::getCallback() const
138 {
139 return mCallbackFunction;
140 }
141
getUserParam() const142 const void *Debug::getUserParam() const
143 {
144 return mCallbackUserParam;
145 }
146
insertMessage(GLenum source,GLenum type,GLuint id,GLenum severity,const std::string & message,gl::LogSeverity logSeverity,angle::EntryPoint entryPoint) const147 void Debug::insertMessage(GLenum source,
148 GLenum type,
149 GLuint id,
150 GLenum severity,
151 const std::string &message,
152 gl::LogSeverity logSeverity,
153 angle::EntryPoint entryPoint) const
154 {
155 std::string messageCopy(message);
156 insertMessage(source, type, id, severity, std::move(messageCopy), logSeverity, entryPoint);
157 }
158
insertMessage(GLenum source,GLenum type,GLuint id,GLenum severity,std::string && message,gl::LogSeverity logSeverity,angle::EntryPoint entryPoint) const159 void Debug::insertMessage(GLenum source,
160 GLenum type,
161 GLuint id,
162 GLenum severity,
163 std::string &&message,
164 gl::LogSeverity logSeverity,
165 angle::EntryPoint entryPoint) const
166 {
167 {
168 // output all messages to the debug log
169 const char *messageTypeString = GLMessageTypeToString(type);
170 const char *severityString = GLSeverityToString(severity);
171 std::ostringstream messageStream;
172 if (entryPoint != angle::EntryPoint::Invalid)
173 {
174 messageStream << GetEntryPointName(entryPoint) << ": ";
175 }
176 messageStream << "GL " << messageTypeString << ": " << severityString << ": " << message;
177 switch (logSeverity)
178 {
179 case gl::LOG_FATAL:
180 FATAL() << messageStream.str();
181 break;
182 case gl::LOG_ERR:
183 ERR() << messageStream.str();
184 break;
185 case gl::LOG_WARN:
186 WARN() << messageStream.str();
187 break;
188 case gl::LOG_INFO:
189 INFO() << messageStream.str();
190 break;
191 case gl::LOG_EVENT:
192 ANGLE_LOG(EVENT) << messageStream.str();
193 break;
194 }
195 }
196
197 if (!isMessageEnabled(source, type, id, severity))
198 {
199 return;
200 }
201
202 // TODO(geofflang) Check the synchronous flag and potentially flush messages from another
203 // thread.
204 // If isOutputSynchronous(), mMutex does not need to be held, but instead the message should be
205 // dropped/queued if it doesn't originate from the current context. If !isOutputSynchronous(),
206 // the callback is expected to be thread-safe per spec, so there is no need for locking.
207 if (mCallbackFunction != nullptr)
208 {
209 mCallbackFunction(source, type, id, severity, static_cast<GLsizei>(message.length()),
210 message.c_str(), mCallbackUserParam);
211 }
212 else
213 {
214 std::lock_guard<std::mutex> lock(mMutex);
215
216 if (mMessages.size() >= mMaxLoggedMessages)
217 {
218 // Drop messages over the limit
219 return;
220 }
221
222 Message m;
223 m.source = source;
224 m.type = type;
225 m.id = id;
226 m.severity = severity;
227 m.message = std::move(message);
228
229 mMessages.push_back(std::move(m));
230 }
231 }
232
getMessages(GLuint count,GLsizei bufSize,GLenum * sources,GLenum * types,GLuint * ids,GLenum * severities,GLsizei * lengths,GLchar * messageLog)233 size_t Debug::getMessages(GLuint count,
234 GLsizei bufSize,
235 GLenum *sources,
236 GLenum *types,
237 GLuint *ids,
238 GLenum *severities,
239 GLsizei *lengths,
240 GLchar *messageLog)
241 {
242 std::lock_guard<std::mutex> lock(mMutex);
243
244 size_t messageCount = 0;
245 size_t messageStringIndex = 0;
246 while (messageCount <= count && !mMessages.empty())
247 {
248 const Message &m = mMessages.front();
249
250 if (messageLog != nullptr)
251 {
252 // Check that this message can fit in the message buffer
253 if (messageStringIndex + m.message.length() + 1 > static_cast<size_t>(bufSize))
254 {
255 break;
256 }
257
258 std::copy(m.message.begin(), m.message.end(), messageLog + messageStringIndex);
259 messageStringIndex += m.message.length();
260
261 messageLog[messageStringIndex] = '\0';
262 messageStringIndex += 1;
263 }
264
265 if (sources != nullptr)
266 {
267 sources[messageCount] = m.source;
268 }
269
270 if (types != nullptr)
271 {
272 types[messageCount] = m.type;
273 }
274
275 if (ids != nullptr)
276 {
277 ids[messageCount] = m.id;
278 }
279
280 if (severities != nullptr)
281 {
282 severities[messageCount] = m.severity;
283 }
284
285 if (lengths != nullptr)
286 {
287 lengths[messageCount] = static_cast<GLsizei>(m.message.length()) + 1;
288 }
289
290 mMessages.pop_front();
291
292 messageCount++;
293 }
294
295 return messageCount;
296 }
297
getNextMessageLength() const298 size_t Debug::getNextMessageLength() const
299 {
300 std::lock_guard<std::mutex> lock(mMutex);
301 return mMessages.empty() ? 0 : mMessages.front().message.length() + 1;
302 }
303
getMessageCount() const304 size_t Debug::getMessageCount() const
305 {
306 std::lock_guard<std::mutex> lock(mMutex);
307 return mMessages.size();
308 }
309
setMessageControl(GLenum source,GLenum type,GLenum severity,std::vector<GLuint> && ids,bool enabled)310 void Debug::setMessageControl(GLenum source,
311 GLenum type,
312 GLenum severity,
313 std::vector<GLuint> &&ids,
314 bool enabled)
315 {
316 Control c;
317 c.source = source;
318 c.type = type;
319 c.severity = severity;
320 c.ids = std::move(ids);
321 c.enabled = enabled;
322
323 auto &controls = mGroups.back().controls;
324 controls.push_back(std::move(c));
325 }
326
pushGroup(GLenum source,GLuint id,std::string && message)327 void Debug::pushGroup(GLenum source, GLuint id, std::string &&message)
328 {
329 insertMessage(source, GL_DEBUG_TYPE_PUSH_GROUP, id, GL_DEBUG_SEVERITY_NOTIFICATION,
330 std::string(message), gl::LOG_INFO, angle::EntryPoint::GLPushDebugGroup);
331
332 Group g;
333 g.source = source;
334 g.id = id;
335 g.message = std::move(message);
336 mGroups.push_back(std::move(g));
337 }
338
popGroup()339 void Debug::popGroup()
340 {
341 // Make sure the default group is not about to be popped
342 ASSERT(mGroups.size() > 1);
343
344 Group g = mGroups.back();
345 mGroups.pop_back();
346
347 insertMessage(g.source, GL_DEBUG_TYPE_POP_GROUP, g.id, GL_DEBUG_SEVERITY_NOTIFICATION,
348 g.message, gl::LOG_INFO, angle::EntryPoint::GLPopDebugGroup);
349 }
350
getGroupStackDepth() const351 size_t Debug::getGroupStackDepth() const
352 {
353 return mGroups.size();
354 }
355
insertPerfWarning(GLenum severity,const char * message,uint32_t * repeatCount) const356 void Debug::insertPerfWarning(GLenum severity, const char *message, uint32_t *repeatCount) const
357 {
358 bool repeatLast;
359
360 {
361 constexpr uint32_t kMaxRepeat = 4;
362 std::lock_guard<std::mutex> lock(GetDebugMutex());
363
364 if (*repeatCount >= kMaxRepeat)
365 {
366 return;
367 }
368
369 ++*repeatCount;
370 repeatLast = (*repeatCount == kMaxRepeat);
371 }
372
373 std::string msg = message;
374 if (repeatLast)
375 {
376 msg += " (this message will no longer repeat)";
377 }
378
379 // Note: insertMessage will acquire GetDebugMutex(), so it must be released before this call.
380 insertMessage(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_PERFORMANCE, 0, severity, std::move(msg),
381 gl::LOG_INFO, angle::EntryPoint::Invalid);
382 }
383
isMessageEnabled(GLenum source,GLenum type,GLuint id,GLenum severity) const384 bool Debug::isMessageEnabled(GLenum source, GLenum type, GLuint id, GLenum severity) const
385 {
386 if (!mOutputEnabled)
387 {
388 return false;
389 }
390
391 for (auto groupIter = mGroups.rbegin(); groupIter != mGroups.rend(); groupIter++)
392 {
393 const auto &controls = groupIter->controls;
394 for (auto controlIter = controls.rbegin(); controlIter != controls.rend(); controlIter++)
395 {
396 const auto &control = *controlIter;
397
398 if (control.source != GL_DONT_CARE && control.source != source)
399 {
400 continue;
401 }
402
403 if (control.type != GL_DONT_CARE && control.type != type)
404 {
405 continue;
406 }
407
408 if (control.severity != GL_DONT_CARE && control.severity != severity)
409 {
410 continue;
411 }
412
413 if (!control.ids.empty() &&
414 std::find(control.ids.begin(), control.ids.end(), id) == control.ids.end())
415 {
416 continue;
417 }
418
419 return control.enabled;
420 }
421 }
422
423 return true;
424 }
425
pushDefaultGroup()426 void Debug::pushDefaultGroup()
427 {
428 Group g;
429 g.source = GL_NONE;
430 g.id = 0;
431 g.message = "";
432
433 Control c0;
434 c0.source = GL_DONT_CARE;
435 c0.type = GL_DONT_CARE;
436 c0.severity = GL_DONT_CARE;
437 c0.enabled = true;
438 g.controls.push_back(std::move(c0));
439
440 Control c1;
441 c1.source = GL_DONT_CARE;
442 c1.type = GL_DONT_CARE;
443 c1.severity = GL_DEBUG_SEVERITY_LOW;
444 c1.enabled = false;
445 g.controls.push_back(std::move(c1));
446
447 mGroups.push_back(std::move(g));
448 }
449 } // namespace gl
450
451 namespace egl
452 {
453
454 namespace
455 {
GetDefaultMessageTypeBits()456 angle::PackedEnumBitSet<MessageType> GetDefaultMessageTypeBits()
457 {
458 angle::PackedEnumBitSet<MessageType> result;
459 result.set(MessageType::Critical);
460 result.set(MessageType::Error);
461 return result;
462 }
463 } // anonymous namespace
464
Debug()465 Debug::Debug() : mCallback(nullptr), mEnabledMessageTypes(GetDefaultMessageTypeBits()) {}
466
setCallback(EGLDEBUGPROCKHR callback,const AttributeMap & attribs)467 void Debug::setCallback(EGLDEBUGPROCKHR callback, const AttributeMap &attribs)
468 {
469 mCallback = callback;
470
471 const angle::PackedEnumBitSet<MessageType> defaultMessageTypes = GetDefaultMessageTypeBits();
472 if (mCallback != nullptr)
473 {
474 for (MessageType messageType : angle::AllEnums<MessageType>())
475 {
476 mEnabledMessageTypes[messageType] =
477 (attribs.getAsInt(egl::ToEGLenum(messageType), defaultMessageTypes[messageType]) ==
478 EGL_TRUE);
479 }
480 }
481 }
482
getCallback() const483 EGLDEBUGPROCKHR Debug::getCallback() const
484 {
485 return mCallback;
486 }
487
isMessageTypeEnabled(MessageType type) const488 bool Debug::isMessageTypeEnabled(MessageType type) const
489 {
490 return mEnabledMessageTypes[type];
491 }
492
insertMessage(EGLenum error,const char * command,MessageType messageType,EGLLabelKHR threadLabel,EGLLabelKHR objectLabel,const std::string & message) const493 void Debug::insertMessage(EGLenum error,
494 const char *command,
495 MessageType messageType,
496 EGLLabelKHR threadLabel,
497 EGLLabelKHR objectLabel,
498 const std::string &message) const
499 {
500 {
501 // output all messages to the debug log
502 const char *messageTypeString = EGLMessageTypeToString(messageType);
503 std::ostringstream messageStream;
504 messageStream << "EGL " << messageTypeString << ": " << command << ": " << message;
505 INFO() << messageStream.str();
506 }
507
508 // TODO(geofflang): Lock before checking the callback. http://anglebug.com/2464
509 if (mCallback && isMessageTypeEnabled(messageType))
510 {
511 mCallback(error, command, egl::ToEGLenum(messageType), threadLabel, objectLabel,
512 message.c_str());
513 }
514 }
515
516 } // namespace egl
517