With libepoxy we can choose between EGL and GLX at runtime, as well as dynamically open EGL and GLX libraries without linking to them. This will make it possible to build with Wayland, EGL, GLVND support while still running on systems that only have X11, GLX and libGL. It also paves the way for headless rendering through EGL. libepoxy is a new library dependency, and is included in the precompiled libraries. GLEW is no longer a dependency, and WITH_SYSTEM_GLEW was removed. Includes contributions by Brecht Van Lommel, Ray Molenkamp, Campbell Barton and Sergey Sharybin. Ref T76428 Differential Revision: https://developer.blender.org/D15291
386 lines
12 KiB
C++
386 lines
12 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 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 "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;
|
|
}
|
|
|
|
/* NOTE: callback function can be triggered during before the platform is initialized.
|
|
* In this case invoking `GPU_type_matches` would fail and
|
|
* therefore the message is checked before the platform matching. */
|
|
if (TRIM_NVIDIA_BUFFER_INFO && STRPREFIX(message, "Buffer detailed info") &&
|
|
GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_ANY, GPU_DRIVER_OFFICIAL)) {
|
|
/** 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;
|
|
|
|
if (GPU_debug_group_match(GPU_DEBUG_SHADER_COMPILATION_GROUP)) {
|
|
/** Do not duplicate shader compilation error/warnings. */
|
|
return;
|
|
}
|
|
|
|
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
|
|
|
|
void init_gl_callbacks()
|
|
{
|
|
CLOG_ENSURE(&LOG);
|
|
|
|
char msg[256] = "";
|
|
const char format[] = "Successfully hooked OpenGL debug callback using %s";
|
|
|
|
if (epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug")) {
|
|
SNPRINTF(msg, format, epoxy_gl_version() >= 43 ? "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 (epoxy_has_gl_extension("GL_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)
|
|
{
|
|
if (!(G.debug & G_DEBUG_GPU)) {
|
|
return;
|
|
}
|
|
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, nullptr); \
|
|
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) &&
|
|
(epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug"))) {
|
|
char label[64];
|
|
SNPRINTF(label, "%s%s%s", to_str_prefix(type), name, to_str_suffix(type));
|
|
/* Small convenience for caller. */
|
|
switch (type) {
|
|
case GL_FRAGMENT_SHADER:
|
|
case GL_GEOMETRY_SHADER:
|
|
case GL_VERTEX_SHADER:
|
|
case GL_COMPUTE_SHADER:
|
|
type = GL_SHADER;
|
|
break;
|
|
case GL_UNIFORM_BUFFER:
|
|
case GL_SHADER_STORAGE_BUFFER:
|
|
case GL_ARRAY_BUFFER:
|
|
case GL_ELEMENT_ARRAY_BUFFER:
|
|
type = GL_BUFFER;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
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) &&
|
|
(epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_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) &&
|
|
(epoxy_gl_version() >= 43 || epoxy_has_gl_extension("GL_KHR_debug"))) {
|
|
glPopDebugGroup();
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
} // namespace blender::gpu
|