// // Copyright 2015 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // DebugTest.cpp : Tests of the GL_KHR_debug extension #include "common/debug.h" #include "test_utils/ANGLETest.h" namespace angle { constexpr char kBufferObjLabel[] = "buffer"; constexpr char kShaderObjLabel[] = "shader"; constexpr char kProgramObjLabel[] = "program"; constexpr char kVertexArrayObjLabel[] = "vertexarray"; constexpr char kQueryObjLabel[] = "query"; constexpr char kProgramPipelineObjLabel[] = "programpipeline"; constexpr GLenum kObjectTypes[] = {GL_BUFFER_OBJECT_EXT, GL_SHADER_OBJECT_EXT, GL_PROGRAM_OBJECT_EXT, GL_QUERY_OBJECT_EXT, GL_PROGRAM_PIPELINE_OBJECT_EXT, GL_VERTEX_ARRAY_OBJECT_EXT}; class DebugTest : public ANGLETest { protected: DebugTest() : mDebugExtensionAvailable(false) { setWindowWidth(128); setWindowHeight(128); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); setConfigDepthBits(24); setDebugEnabled(true); } void testSetUp() override { mDebugExtensionAvailable = IsGLExtensionEnabled("GL_KHR_debug"); if (mDebugExtensionAvailable) { glEnable(GL_DEBUG_OUTPUT); } } bool mDebugExtensionAvailable; }; void createGLObjectAndLabel(GLenum identifier, GLuint &object, const char **label) { switch (identifier) { case GL_BUFFER_OBJECT_EXT: glGenBuffers(1, &object); glBindBuffer(GL_ARRAY_BUFFER, object); *label = kBufferObjLabel; break; case GL_SHADER_OBJECT_EXT: object = glCreateShader(GL_VERTEX_SHADER); *label = kShaderObjLabel; break; case GL_PROGRAM_OBJECT_EXT: object = glCreateProgram(); *label = kProgramObjLabel; break; case GL_VERTEX_ARRAY_OBJECT_EXT: glGenVertexArrays(1, &object); glBindVertexArray(object); *label = kVertexArrayObjLabel; break; case GL_QUERY_OBJECT_EXT: glGenQueries(1, &object); glBeginQuery(GL_ANY_SAMPLES_PASSED, object); *label = kQueryObjLabel; break; case GL_PROGRAM_PIPELINE_OBJECT_EXT: glGenProgramPipelines(1, &object); glBindProgramPipeline(object); *label = kProgramPipelineObjLabel; break; default: UNREACHABLE(); break; } } void deleteGLObject(GLenum identifier, GLuint &object) { switch (identifier) { case GL_BUFFER_OBJECT_EXT: glDeleteBuffers(1, &object); break; case GL_SHADER_OBJECT_EXT: glDeleteShader(object); break; case GL_PROGRAM_OBJECT_EXT: glDeleteProgram(object); break; case GL_VERTEX_ARRAY_OBJECT_EXT: glDeleteVertexArrays(1, &object); break; case GL_QUERY_OBJECT_EXT: glEndQuery(GL_ANY_SAMPLES_PASSED); glDeleteQueries(1, &object); break; case GL_PROGRAM_PIPELINE_OBJECT_EXT: glDeleteProgramPipelines(1, &object); break; default: UNREACHABLE(); break; } } // Test basic usage of setting and getting labels using GL_EXT_debug_label TEST_P(DebugTest, ObjectLabelsEXT) { ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_debug_label")); for (const GLenum identifier : kObjectTypes) { bool skip = false; switch (identifier) { case GL_PROGRAM_PIPELINE_OBJECT_EXT: if (!(getClientMajorVersion() >= 3 && getClientMinorVersion() >= 1) || !IsGLExtensionEnabled("GL_EXT_separate_shader_objects")) { skip = true; } break; case GL_QUERY_OBJECT_EXT: // GLES3 context is required for glGenQueries() if (getClientMajorVersion() < 3 || !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")) { skip = true; } break; case GL_VERTEX_ARRAY_OBJECT_EXT: if (getClientMajorVersion() < 3) { skip = true; } break; default: break; } // if object enum is not supported, move on to the next object type if (skip) { continue; } GLuint object; const char *label; createGLObjectAndLabel(identifier, object, &label); glLabelObjectEXT(identifier, object, 0, label); ASSERT_GL_NO_ERROR(); std::vector labelBuf(strlen(label) + 1); GLsizei labelLengthBuf = 0; glGetObjectLabelEXT(identifier, object, static_cast(labelBuf.size()), &labelLengthBuf, labelBuf.data()); ASSERT_GL_NO_ERROR(); EXPECT_EQ(static_cast(strlen(label)), labelLengthBuf); EXPECT_STREQ(label, labelBuf.data()); ASSERT_GL_NO_ERROR(); deleteGLObject(identifier, object); glLabelObjectEXT(identifier, object, 0, label); EXPECT_GL_ERROR(GL_INVALID_OPERATION); glGetObjectLabelEXT(identifier, object, static_cast(labelBuf.size()), &labelLengthBuf, labelBuf.data()); EXPECT_GL_ERROR(GL_INVALID_OPERATION); } } class DebugTestES3 : public DebugTest {}; struct Message { GLenum source; GLenum type; GLuint id; GLenum severity; std::string message; const void *userParam; }; static void GL_APIENTRY Callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { Message m{source, type, id, severity, std::string(message, length), userParam}; std::vector *messages = static_cast *>(const_cast(userParam)); messages->push_back(m); } // Test that all ANGLE back-ends have GL_KHR_debug enabled TEST_P(DebugTestES3, Enabled) { ASSERT_TRUE(mDebugExtensionAvailable); } // Test that when debug output is disabled, no message are outputted TEST_P(DebugTestES3, DisabledOutput) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); glDisable(GL_DEBUG_OUTPUT); glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 1, GL_DEBUG_SEVERITY_NOTIFICATION, -1, "discarded"); GLint numMessages = 0; glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); ASSERT_EQ(0, numMessages); std::vector messages; glDebugMessageCallbackKHR(Callback, &messages); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); ASSERT_EQ(0u, messages.size()); } // Test a basic flow of inserting a message and reading it back TEST_P(DebugTestES3, InsertMessage) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); const GLenum source = GL_DEBUG_SOURCE_APPLICATION; const GLenum type = GL_DEBUG_TYPE_OTHER; const GLuint id = 1; const GLenum severity = GL_DEBUG_SEVERITY_NOTIFICATION; const std::string message = "Message"; glDebugMessageInsertKHR(source, type, id, severity, -1, message.c_str()); GLint numMessages = 0; glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); ASSERT_EQ(1, numMessages); GLint messageLength = 0; glGetIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &messageLength); EXPECT_EQ(static_cast(message.length()) + 1, messageLength); GLenum sourceBuf = 0; GLenum typeBuf = 0; GLenum idBuf = 0; GLenum severityBuf = 0; GLsizei lengthBuf = 0; std::vector messageBuf(messageLength); GLuint ret = glGetDebugMessageLogKHR(1, static_cast(messageBuf.size()), &sourceBuf, &typeBuf, &idBuf, &severityBuf, &lengthBuf, messageBuf.data()); EXPECT_EQ(1u, ret); EXPECT_EQ(source, sourceBuf); EXPECT_EQ(type, typeBuf); EXPECT_EQ(id, idBuf); EXPECT_EQ(severity, severityBuf); EXPECT_EQ(lengthBuf, messageLength); EXPECT_STREQ(message.c_str(), messageBuf.data()); glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); EXPECT_EQ(0, numMessages); ASSERT_GL_NO_ERROR(); } // Test inserting multiple messages TEST_P(DebugTestES3, InsertMessageMultiple) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); const GLenum source = GL_DEBUG_SOURCE_APPLICATION; const GLenum type = GL_DEBUG_TYPE_OTHER; const GLuint startID = 1; const GLenum severity = GL_DEBUG_SEVERITY_NOTIFICATION; const char messageRepeatChar = 'm'; const size_t messageCount = 32; for (size_t i = 0; i < messageCount; i++) { std::string message(i + 1, messageRepeatChar); glDebugMessageInsertKHR(source, type, startID + static_cast(i), severity, -1, message.c_str()); } GLint numMessages = 0; glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); ASSERT_EQ(static_cast(messageCount), numMessages); for (size_t i = 0; i < messageCount; i++) { glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); EXPECT_EQ(static_cast(messageCount - i), numMessages); std::string expectedMessage(i + 1, messageRepeatChar); GLint messageLength = 0; glGetIntegerv(GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH, &messageLength); EXPECT_EQ(static_cast(expectedMessage.length()) + 1, messageLength); GLenum sourceBuf = 0; GLenum typeBuf = 0; GLenum idBuf = 0; GLenum severityBuf = 0; GLsizei lengthBuf = 0; std::vector messageBuf(messageLength); GLuint ret = glGetDebugMessageLogKHR(1, static_cast(messageBuf.size()), &sourceBuf, &typeBuf, &idBuf, &severityBuf, &lengthBuf, messageBuf.data()); EXPECT_EQ(1u, ret); EXPECT_EQ(source, sourceBuf); EXPECT_EQ(type, typeBuf); EXPECT_EQ(startID + i, idBuf); EXPECT_EQ(severity, severityBuf); EXPECT_EQ(lengthBuf, messageLength); EXPECT_STREQ(expectedMessage.c_str(), messageBuf.data()); } glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); EXPECT_EQ(0, numMessages); ASSERT_GL_NO_ERROR(); } // Test using a debug callback TEST_P(DebugTestES3, DebugCallback) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); std::vector messages; glDebugMessageCallbackKHR(Callback, &messages); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); const GLenum source = GL_DEBUG_SOURCE_APPLICATION; const GLenum type = GL_DEBUG_TYPE_OTHER; const GLuint id = 1; const GLenum severity = GL_DEBUG_SEVERITY_NOTIFICATION; const std::string message = "Message"; glDebugMessageInsertKHR(source, type, id, severity, -1, message.c_str()); GLint numMessages = 0; glGetIntegerv(GL_DEBUG_LOGGED_MESSAGES, &numMessages); EXPECT_EQ(0, numMessages); ASSERT_EQ(1u, messages.size()); const Message &m = messages.front(); EXPECT_EQ(source, m.source); EXPECT_EQ(type, m.type); EXPECT_EQ(id, m.id); EXPECT_EQ(severity, m.severity); EXPECT_EQ(message, m.message); ASSERT_GL_NO_ERROR(); } // Test the glGetPointervKHR entry point TEST_P(DebugTestES3, GetPointer) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); std::vector messages; glDebugMessageCallbackKHR(Callback, &messages); void *callback = nullptr; glGetPointervKHR(GL_DEBUG_CALLBACK_FUNCTION, &callback); EXPECT_EQ(reinterpret_cast(Callback), callback); void *userData = nullptr; glGetPointervKHR(GL_DEBUG_CALLBACK_USER_PARAM, &userData); EXPECT_EQ(static_cast(&messages), userData); } // Test usage of message control. Example taken from GL_KHR_debug spec. TEST_P(DebugTestES3, MessageControl1) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); std::vector messages; glDebugMessageCallbackKHR(Callback, &messages); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); // Setup of the default active debug group: Filter everything in glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); // Generate a debug marker debug output message glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 100, GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Message 1"); // Push debug group 1 glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Message 2"); // Setup of the debug group 1: Filter everything out glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); // This message won't appear in the debug output log of glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 100, GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Message 3"); // Pop debug group 1, restore the volume control of the default debug group. glPopDebugGroupKHR(); // Generate a debug marker debug output message glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_MARKER, 100, GL_DEBUG_SEVERITY_NOTIFICATION, -1, "Message 5"); // Expected debug output from the GL implementation // Message 1 // Message 2 // Message 2 // Message 5 EXPECT_EQ(4u, messages.size()); EXPECT_STREQ(messages[0].message.c_str(), "Message 1"); EXPECT_STREQ(messages[1].message.c_str(), "Message 2"); EXPECT_STREQ(messages[2].message.c_str(), "Message 2"); EXPECT_STREQ(messages[3].message.c_str(), "Message 5"); ASSERT_GL_NO_ERROR(); } // Test usage of message control. Example taken from GL_KHR_debug spec. TEST_P(DebugTestES3, MessageControl2) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); std::vector messages; glDebugMessageCallbackKHR(Callback, &messages); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); // Setup the control of de debug output for the default debug group glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); glDebugMessageControlKHR(GL_DEBUG_SOURCE_THIRD_PARTY, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); std::vector ids0 = {1234, 2345, 3456, 4567}; glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, static_cast(ids0.size()), ids0.data(), GL_FALSE); glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, GL_DONT_CARE, static_cast(ids0.size()), ids0.data(), GL_FALSE); // Push debug group 1 // Inherit of the default debug group debug output volume control // Filtered out by glDebugMessageControl glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION, 1, -1, "Message 1"); // In this section of the code, we are interested in performances. glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PERFORMANCE, GL_DONT_CARE, 0, nullptr, GL_TRUE); // But we already identify that some messages are not really useful for us. std::vector ids1 = {5678, 6789}; glDebugMessageControlKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, GL_DONT_CARE, static_cast(ids1.size()), ids1.data(), GL_FALSE); glDebugMessageInsertKHR(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PERFORMANCE, 1357, GL_DEBUG_SEVERITY_MEDIUM, -1, "Message 2"); glDebugMessageInsertKHR(GL_DEBUG_SOURCE_THIRD_PARTY, // We still filter out these messages. GL_DEBUG_TYPE_OTHER, 3579, GL_DEBUG_SEVERITY_MEDIUM, -1, "Message 3"); glPopDebugGroupKHR(); // Expected debug output from the GL implementation // Message 2 EXPECT_EQ(1u, messages.size()); EXPECT_STREQ(messages[0].message.c_str(), "Message 2"); ASSERT_GL_NO_ERROR(); } // Test basic usage of setting and getting labels TEST_P(DebugTestES3, ObjectLabels) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); GLuint renderbuffer = 0; glGenRenderbuffers(1, &renderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); const std::string &label = "renderbuffer"; glObjectLabelKHR(GL_RENDERBUFFER, renderbuffer, -1, label.c_str()); std::vector labelBuf(label.length() + 1); GLsizei labelLengthBuf = 0; glGetObjectLabelKHR(GL_RENDERBUFFER, renderbuffer, static_cast(labelBuf.size()), &labelLengthBuf, labelBuf.data()); EXPECT_EQ(static_cast(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(labelBuf.size()), &labelLengthBuf, labelBuf.data()); EXPECT_GL_ERROR(GL_INVALID_VALUE); } // Test basic usage of setting and getting labels TEST_P(DebugTestES3, ObjectPtrLabels) { ANGLE_SKIP_TEST_IF(!mDebugExtensionAvailable); GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); const std::string &label = "sync"; glObjectPtrLabelKHR(sync, -1, label.c_str()); std::vector labelBuf(label.length() + 1); GLsizei labelLengthBuf = 0; glGetObjectPtrLabelKHR(sync, static_cast(labelBuf.size()), &labelLengthBuf, labelBuf.data()); EXPECT_EQ(static_cast(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(labelBuf.size()), &labelLengthBuf, labelBuf.data()); EXPECT_GL_ERROR(GL_INVALID_VALUE); } GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DebugTestES3); ANGLE_INSTANTIATE_TEST_ES3(DebugTestES3); ANGLE_INSTANTIATE_TEST(DebugTest, ANGLE_ALL_TEST_PLATFORMS_ES1, ANGLE_ALL_TEST_PLATFORMS_ES2, ANGLE_ALL_TEST_PLATFORMS_ES3, ANGLE_ALL_TEST_PLATFORMS_ES31); } // namespace angle