382 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			382 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License
 | |
|  * as published by the Free Software Foundation; either version 2
 | |
|  * of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program; if not, write to the Free Software Foundation,
 | |
|  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 | |
|  *
 | |
|  * The Original Code is Copyright (C) 2005 Blender Foundation.
 | |
|  * All rights reserved.
 | |
|  */
 | |
| 
 | |
| /** \file
 | |
|  * \ingroup gpu
 | |
|  *
 | |
|  * Debug features of OpenGL.
 | |
|  */
 | |
| 
 | |
| #include "BLI_compiler_attrs.h"
 | |
| #include "BLI_string.h"
 | |
| #include "BLI_system.h"
 | |
| #include "BLI_utildefines.h"
 | |
| 
 | |
| #include "BKE_global.h"
 | |
| 
 | |
| #include "GPU_debug.h"
 | |
| #include "GPU_platform.h"
 | |
| 
 | |
| #include "CLG_log.h"
 | |
| 
 | |
| #include "glew-mx.h"
 | |
| 
 | |
| #include "gl_context.hh"
 | |
| #include "gl_uniform_buffer.hh"
 | |
| 
 | |
| #include "gl_debug.hh"
 | |
| 
 | |
| #include <cstdio>
 | |
| 
 | |
| static CLG_LogRef LOG = {"gpu.debug"};
 | |
| 
 | |
| /* Avoid too much NVidia buffer info in the output log. */
 | |
| #define TRIM_NVIDIA_BUFFER_INFO 1
 | |
| /* Avoid unneeded shader statistics. */
 | |
| #define TRIM_SHADER_STATS_INFO 1
 | |
| 
 | |
| namespace blender::gpu::debug {
 | |
| 
 | |
| /* -------------------------------------------------------------------- */
 | |
| /** \name Debug Callbacks
 | |
|  *
 | |
|  * Hooks up debug callbacks to a debug OpenGL context using extensions or 4.3 core debug
 | |
|  * capabilities.
 | |
|  * \{ */
 | |
| 
 | |
| /* Debug callbacks need the same calling convention as OpenGL functions. */
 | |
| #if defined(_WIN32)
 | |
| #  define APIENTRY __stdcall
 | |
| #else
 | |
| #  define APIENTRY
 | |
| #endif
 | |
| 
 | |
| static void APIENTRY debug_callback(GLenum UNUSED(source),
 | |
|                                     GLenum type,
 | |
|                                     GLuint UNUSED(id),
 | |
|                                     GLenum severity,
 | |
|                                     GLsizei UNUSED(length),
 | |
|                                     const GLchar *message,
 | |
|                                     const GLvoid *UNUSED(userParm))
 | |
| {
 | |
|   if (ELEM(type, GL_DEBUG_TYPE_PUSH_GROUP, GL_DEBUG_TYPE_POP_GROUP)) {
 | |
|     /* The debug layer will emit a message each time a debug group is pushed or popped.
 | |
|      * We use that for easy command grouping inside frame analyzer tools. */
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (TRIM_NVIDIA_BUFFER_INFO &&
 | |
|       GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL) &&
 | |
|       STRPREFIX(message, "Buffer detailed info")) {
 | |
|     /** Suppress buffer infos flooding the output. */
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (TRIM_SHADER_STATS_INFO && STRPREFIX(message, "Shader Stats")) {
 | |
|     /** Suppress buffer infos flooding the output. */
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const bool use_color = CLG_color_support_get(&LOG);
 | |
| 
 | |
|   if (ELEM(severity, GL_DEBUG_SEVERITY_LOW, GL_DEBUG_SEVERITY_NOTIFICATION)) {
 | |
|     if (((LOG.type->flag & CLG_FLAG_USE) && (LOG.type->level >= CLG_SEVERITY_INFO))) {
 | |
|       const char *format = use_color ? "\033[2m%s\033[0m" : "%s";
 | |
|       CLG_logf(LOG.type, CLG_SEVERITY_INFO, "Notification", "", format, message);
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     char debug_groups[512] = "";
 | |
|     GPU_debug_get_groups_names(sizeof(debug_groups), debug_groups);
 | |
|     CLG_Severity clog_severity;
 | |
| 
 | |
|     switch (type) {
 | |
|       case GL_DEBUG_TYPE_ERROR:
 | |
|       case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
 | |
|       case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
 | |
|         clog_severity = CLG_SEVERITY_ERROR;
 | |
|         break;
 | |
|       case GL_DEBUG_TYPE_PORTABILITY:
 | |
|       case GL_DEBUG_TYPE_PERFORMANCE:
 | |
|       case GL_DEBUG_TYPE_OTHER:
 | |
|       case GL_DEBUG_TYPE_MARKER: /* KHR has this, ARB does not */
 | |
|       default:
 | |
|         clog_severity = CLG_SEVERITY_WARN;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     if (((LOG.type->flag & CLG_FLAG_USE) && (LOG.type->level >= clog_severity))) {
 | |
|       CLG_logf(LOG.type, clog_severity, debug_groups, "", "%s", message);
 | |
|       if (severity == GL_DEBUG_SEVERITY_HIGH) {
 | |
|         /* Focus on error message. */
 | |
|         if (use_color) {
 | |
|           fprintf(stderr, "\033[2m");
 | |
|         }
 | |
|         BLI_system_backtrace(stderr);
 | |
|         if (use_color) {
 | |
|           fprintf(stderr, "\033[0m\n");
 | |
|         }
 | |
|         fflush(stderr);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| #undef APIENTRY
 | |
| 
 | |
| /* This function needs to be called once per context. */
 | |
| void init_gl_callbacks()
 | |
| {
 | |
|   CLOG_ENSURE(&LOG);
 | |
| 
 | |
|   char msg[256] = "";
 | |
|   const char format[] = "Successfully hooked OpenGL debug callback using %s";
 | |
| 
 | |
|   if (GLEW_VERSION_4_3 || GLEW_KHR_debug) {
 | |
|     SNPRINTF(msg, format, GLEW_VERSION_4_3 ? "OpenGL 4.3" : "KHR_debug extension");
 | |
|     glEnable(GL_DEBUG_OUTPUT);
 | |
|     glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 | |
|     glDebugMessageCallback((GLDEBUGPROC)debug_callback, nullptr);
 | |
|     glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
 | |
|     glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION,
 | |
|                          GL_DEBUG_TYPE_MARKER,
 | |
|                          0,
 | |
|                          GL_DEBUG_SEVERITY_NOTIFICATION,
 | |
|                          -1,
 | |
|                          msg);
 | |
|   }
 | |
|   else if (GLEW_ARB_debug_output) {
 | |
|     SNPRINTF(msg, format, "ARB_debug_output");
 | |
|     glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
 | |
|     glDebugMessageCallbackARB((GLDEBUGPROCARB)debug_callback, nullptr);
 | |
|     glDebugMessageControlARB(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
 | |
|     glDebugMessageInsertARB(GL_DEBUG_SOURCE_APPLICATION_ARB,
 | |
|                             GL_DEBUG_TYPE_OTHER_ARB,
 | |
|                             0,
 | |
|                             GL_DEBUG_SEVERITY_LOW_ARB,
 | |
|                             -1,
 | |
|                             msg);
 | |
|   }
 | |
|   else {
 | |
|     CLOG_STR_WARN(&LOG, "Failed to hook OpenGL debug callback. Use fallback debug layer.");
 | |
|     init_debug_layer();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /** \} */
 | |
| 
 | |
| /* -------------------------------------------------------------------- */
 | |
| /** \name Error Checking
 | |
|  *
 | |
|  * This is only useful for implementation that does not support the KHR_debug extension OR when the
 | |
|  * implementations do not report any errors even when clearly doing shady things.
 | |
|  * \{ */
 | |
| 
 | |
| void check_gl_error(const char *info)
 | |
| {
 | |
|   GLenum error = glGetError();
 | |
| 
 | |
| #define ERROR_CASE(err) \
 | |
|   case err: { \
 | |
|     char msg[256]; \
 | |
|     SNPRINTF(msg, "%s : %s", #err, info); \
 | |
|     debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, NULL); \
 | |
|     break; \
 | |
|   }
 | |
| 
 | |
|   switch (error) {
 | |
|     ERROR_CASE(GL_INVALID_ENUM)
 | |
|     ERROR_CASE(GL_INVALID_VALUE)
 | |
|     ERROR_CASE(GL_INVALID_OPERATION)
 | |
|     ERROR_CASE(GL_INVALID_FRAMEBUFFER_OPERATION)
 | |
|     ERROR_CASE(GL_OUT_OF_MEMORY)
 | |
|     ERROR_CASE(GL_STACK_UNDERFLOW)
 | |
|     ERROR_CASE(GL_STACK_OVERFLOW)
 | |
|     case GL_NO_ERROR:
 | |
|       break;
 | |
|     default:
 | |
|       char msg[256];
 | |
|       SNPRINTF(msg, "Unknown GL error: %x : %s", error, info);
 | |
|       debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void check_gl_resources(const char *info)
 | |
| {
 | |
|   if (!(G.debug & G_DEBUG_GPU) || GPU_bgl_get()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   GLContext *ctx = GLContext::get();
 | |
|   ShaderInterface *interface = ctx->shader->interface;
 | |
|   /* NOTE: This only check binding. To be valid, the bound ubo needs to
 | |
|    * be big enough to feed the data range the shader awaits. */
 | |
|   uint16_t ubo_needed = interface->enabled_ubo_mask_;
 | |
|   ubo_needed &= ~ctx->bound_ubo_slots;
 | |
|   /* NOTE: This only check binding. To be valid, the bound texture needs to
 | |
|    * be the same format/target the shader expects. */
 | |
|   uint64_t tex_needed = interface->enabled_tex_mask_;
 | |
|   tex_needed &= ~GLContext::state_manager_active_get()->bound_texture_slots();
 | |
|   /* NOTE: This only check binding. To be valid, the bound image needs to
 | |
|    * be the same format/target the shader expects. */
 | |
|   uint8_t ima_needed = interface->enabled_ima_mask_;
 | |
|   ima_needed &= ~GLContext::state_manager_active_get()->bound_image_slots();
 | |
| 
 | |
|   if (ubo_needed == 0 && tex_needed == 0 && ima_needed == 0) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (int i = 0; ubo_needed != 0; i++, ubo_needed >>= 1) {
 | |
|     if ((ubo_needed & 1) != 0) {
 | |
|       const ShaderInput *ubo_input = interface->ubo_get(i);
 | |
|       const char *ubo_name = interface->input_name_get(ubo_input);
 | |
|       const char *sh_name = ctx->shader->name_get();
 | |
|       char msg[256];
 | |
|       SNPRINTF(msg, "Missing UBO bind at slot %d : %s > %s : %s", i, sh_name, ubo_name, info);
 | |
|       debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (int i = 0; tex_needed != 0; i++, tex_needed >>= 1) {
 | |
|     if ((tex_needed & 1) != 0) {
 | |
|       /* FIXME: texture_get might return an image input instead. */
 | |
|       const ShaderInput *tex_input = interface->texture_get(i);
 | |
|       const char *tex_name = interface->input_name_get(tex_input);
 | |
|       const char *sh_name = ctx->shader->name_get();
 | |
|       char msg[256];
 | |
|       SNPRINTF(msg, "Missing Texture bind at slot %d : %s > %s : %s", i, sh_name, tex_name, info);
 | |
|       debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (int i = 0; ima_needed != 0; i++, ima_needed >>= 1) {
 | |
|     if ((ima_needed & 1) != 0) {
 | |
|       /* FIXME: texture_get might return a texture input instead. */
 | |
|       const ShaderInput *tex_input = interface->texture_get(i);
 | |
|       const char *tex_name = interface->input_name_get(tex_input);
 | |
|       const char *sh_name = ctx->shader->name_get();
 | |
|       char msg[256];
 | |
|       SNPRINTF(msg, "Missing Image bind at slot %d : %s > %s : %s", i, sh_name, tex_name, info);
 | |
|       debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, msg, nullptr);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void raise_gl_error(const char *info)
 | |
| {
 | |
|   debug_callback(0, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, 0, info, nullptr);
 | |
| }
 | |
| 
 | |
| /** \} */
 | |
| 
 | |
| /* -------------------------------------------------------------------- */
 | |
| /** \name Object Label
 | |
|  *
 | |
|  * Useful for debugging through render-doc. Only defined if using `--debug-gpu`.
 | |
|  * Make sure to bind the object first so that it gets defined by the GL implementation.
 | |
|  * \{ */
 | |
| 
 | |
| static const char *to_str_prefix(GLenum type)
 | |
| {
 | |
|   switch (type) {
 | |
|     case GL_FRAGMENT_SHADER:
 | |
|     case GL_GEOMETRY_SHADER:
 | |
|     case GL_VERTEX_SHADER:
 | |
|     case GL_SHADER:
 | |
|     case GL_PROGRAM:
 | |
|       return "SHD-";
 | |
|     case GL_SAMPLER:
 | |
|       return "SAM-";
 | |
|     case GL_TEXTURE:
 | |
|       return "TEX-";
 | |
|     case GL_FRAMEBUFFER:
 | |
|       return "FBO-";
 | |
|     case GL_VERTEX_ARRAY:
 | |
|       return "VAO-";
 | |
|     case GL_UNIFORM_BUFFER:
 | |
|       return "UBO-";
 | |
|     case GL_BUFFER:
 | |
|       return "BUF-";
 | |
|     default:
 | |
|       return "";
 | |
|   }
 | |
| }
 | |
| static const char *to_str_suffix(GLenum type)
 | |
| {
 | |
|   switch (type) {
 | |
|     case GL_FRAGMENT_SHADER:
 | |
|       return "-Frag";
 | |
|     case GL_GEOMETRY_SHADER:
 | |
|       return "-Geom";
 | |
|     case GL_VERTEX_SHADER:
 | |
|       return "-Vert";
 | |
|     default:
 | |
|       return "";
 | |
|   }
 | |
| }
 | |
| 
 | |
| void object_label(GLenum type, GLuint object, const char *name)
 | |
| {
 | |
|   if ((G.debug & G_DEBUG_GPU) && (GLEW_VERSION_4_3 || GLEW_KHR_debug)) {
 | |
|     char label[64];
 | |
|     SNPRINTF(label, "%s%s%s", to_str_prefix(type), name, to_str_suffix(type));
 | |
|     /* Small convenience for caller. */
 | |
|     if (ELEM(type, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_VERTEX_SHADER)) {
 | |
|       type = GL_SHADER;
 | |
|     }
 | |
|     if (ELEM(type, GL_UNIFORM_BUFFER)) {
 | |
|       type = GL_BUFFER;
 | |
|     }
 | |
|     glObjectLabel(type, object, -1, label);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /** \} */
 | |
| 
 | |
| }  // namespace blender::gpu::debug
 | |
| 
 | |
| namespace blender::gpu {
 | |
| 
 | |
| /* -------------------------------------------------------------------- */
 | |
| /** \name Debug Groups
 | |
|  *
 | |
|  * Useful for debugging through render-doc. This makes all the API calls grouped into "passes".
 | |
|  * \{ */
 | |
| 
 | |
| void GLContext::debug_group_begin(const char *name, int index)
 | |
| {
 | |
|   if ((G.debug & G_DEBUG_GPU) && (GLEW_VERSION_4_3 || GLEW_KHR_debug)) {
 | |
|     /* Add 10 to avoid collision with other indices from other possible callback layers. */
 | |
|     index += 10;
 | |
|     glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, index, -1, name);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void GLContext::debug_group_end()
 | |
| {
 | |
|   if ((G.debug & G_DEBUG_GPU) && (GLEW_VERSION_4_3 || GLEW_KHR_debug)) {
 | |
|     glPopDebugGroup();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /** \} */
 | |
| 
 | |
| }  // namespace blender::gpu
 |