/* * This file is part of libplacebo. * * libplacebo is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * libplacebo 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with libplacebo. If not, see . */ #include #include "common.h" #include "utils.h" #include "gpu.h" const struct pl_opengl_params pl_opengl_default_params = {0}; static void GLAPIENTRY debug_cb(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { pl_log log = (void *) userParam; enum pl_log_level level = PL_LOG_ERR; switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION:level = PL_LOG_DEBUG; break; case GL_DEBUG_SEVERITY_LOW: level = PL_LOG_INFO; break; case GL_DEBUG_SEVERITY_MEDIUM: level = PL_LOG_WARN; break; case GL_DEBUG_SEVERITY_HIGH: level = PL_LOG_ERR; break; } pl_msg(log, level, "GL: %s", message); if (level <= PL_LOG_ERR) pl_log_stack_trace(log, level); } static void GLAPIENTRY debug_cb_egl(EGLenum error, const char *command, EGLint messageType, EGLLabelKHR threadLabel, EGLLabelKHR objectLabel, const char *message) { pl_log log = threadLabel; enum pl_log_level level = PL_LOG_ERR; switch (messageType) { case EGL_DEBUG_MSG_CRITICAL_KHR: level = PL_LOG_FATAL; break; case EGL_DEBUG_MSG_ERROR_KHR: level = PL_LOG_ERR; break; case EGL_DEBUG_MSG_WARN_KHR: level = PL_LOG_WARN; break; case EGL_DEBUG_MSG_INFO_KHR: level = PL_LOG_DEBUG; break; } pl_msg(log, level, "EGL: %s: %s %s", command, egl_err_str(error), message); if (level <= PL_LOG_ERR) pl_log_stack_trace(log, level); } // Guards access to the (thread-unsafe) glad global EGL state static pl_static_mutex glad_egl_mutex = PL_STATIC_MUTEX_INITIALIZER; void pl_opengl_destroy(pl_opengl *ptr) { pl_opengl pl_gl = *ptr; if (!pl_gl) return; struct gl_ctx *p = PL_PRIV(pl_gl); gl_funcs *gl = &p->func; if (!gl_make_current(pl_gl)) { PL_WARN(p, "Failed uninitializing OpenGL context, leaking resources!"); return; } if (p->is_debug) gl->DebugMessageCallback(NULL, NULL); if (p->is_debug_egl) eglDebugMessageControlKHR(NULL, NULL); pl_gpu_destroy(pl_gl->gpu); #ifdef PL_HAVE_GL_PROC_ADDR if (p->is_gles) { gladLoaderUnloadGLES2Context(gl); } else { gladLoaderUnloadGLContext(gl); } bool used_loader = !p->params.get_proc_addr && !p->params.get_proc_addr_ex; if (p->params.egl_display && used_loader) { pl_static_mutex_lock(&glad_egl_mutex); gladLoaderUnloadEGL(); pl_static_mutex_unlock(&glad_egl_mutex); } #endif gl_release_current(pl_gl); pl_mutex_destroy(&p->lock); pl_free_ptr((void **) ptr); } typedef PL_ARRAY(const char *) ext_arr_t; static void add_exts_str(void *alloc, ext_arr_t *arr, const char *extstr) { pl_str rest = pl_str_strip(pl_str0(pl_strdup0(alloc, pl_str0(extstr)))); while (rest.len) { pl_str ext = pl_str_split_char(rest, ' ', &rest); ext.buf[ext.len] = '\0'; // re-use separator for terminator PL_ARRAY_APPEND(alloc, *arr, (char *) ext.buf); } } pl_opengl pl_opengl_create(pl_log log, const struct pl_opengl_params *params) { params = PL_DEF(params, &pl_opengl_default_params); struct pl_opengl_t *pl_gl = pl_zalloc_obj(NULL, pl_gl, struct gl_ctx); struct gl_ctx *p = PL_PRIV(pl_gl); gl_funcs *gl = &p->func; p->params = *params; p->log = log; pl_mutex_init_type(&p->lock, PL_MUTEX_RECURSIVE); if (!gl_make_current(pl_gl)) { pl_free(pl_gl); return NULL; } bool ok; if (params->get_proc_addr_ex) { ok = gladLoadGLContextUserPtr(gl, params->get_proc_addr_ex, params->proc_ctx); } else if (params->get_proc_addr) { ok = gladLoadGLContext(gl, params->get_proc_addr); } else { #ifdef PL_HAVE_GL_PROC_ADDR ok = gladLoaderLoadGLContext(gl); #else PL_FATAL(p, "No `glGetProcAddress` function provided, and libplacebo " "built without its built-in OpenGL loader!"); goto error; #endif } if (!ok) { PL_INFO(p, "Failed loading core GL, retrying as GLES..."); } else if (gl_is_gles(pl_gl)) { PL_INFO(p, "GL context seems to be OpenGL ES, reloading as GLES..."); ok = false; } if (!ok) { memset(gl, 0, sizeof(*gl)); if (params->get_proc_addr_ex) { ok = gladLoadGLES2ContextUserPtr(gl, params->get_proc_addr_ex, params->proc_ctx); } else if (params->get_proc_addr) { ok = gladLoadGLES2Context(gl, params->get_proc_addr); } else { #ifdef PL_HAVE_GL_PROC_ADDR ok = gladLoaderLoadGLES2Context(gl); #else pl_unreachable(); #endif } p->is_gles = ok; } if (!ok) { PL_FATAL(p, "Failed to initialize OpenGL context - make sure a valid " "OpenGL context is bound to the current thread!"); goto error; } const char *version = (const char *) gl->GetString(GL_VERSION); if (version) { const char *ver = version; while (!isdigit(*ver) && *ver != '\0') ver++; if (sscanf(ver, "%d.%d", &pl_gl->major, &pl_gl->minor) != 2) { PL_FATAL(p, "Invalid GL_VERSION string: %s\n", version); goto error; } } if (!pl_gl->major) { PL_FATAL(p, "No OpenGL version detected - make sure an OpenGL context " "is bound to the current thread!"); goto error; } static const int gl_ver_req = 3; if (pl_gl->major < gl_ver_req) { PL_FATAL(p, "OpenGL version too old (%d < %d), please use a newer " "OpenGL implementation or downgrade libplacebo!", pl_gl->major, gl_ver_req); goto error; } PL_INFO(p, "Detected OpenGL version strings:"); PL_INFO(p, " GL_VERSION: %s", version); PL_INFO(p, " GL_VENDOR: %s", (char *) gl->GetString(GL_VENDOR)); PL_INFO(p, " GL_RENDERER: %s", (char *) gl->GetString(GL_RENDERER)); ext_arr_t exts = {0}; if (pl_gl->major >= 3) { gl->GetIntegerv(GL_NUM_EXTENSIONS, &exts.num); PL_ARRAY_RESIZE(pl_gl, exts, exts.num); for (int i = 0; i < exts.num; i++) exts.elem[i] = (const char *) gl->GetStringi(GL_EXTENSIONS, i); } else { add_exts_str(pl_gl, &exts, (const char *) gl->GetString(GL_EXTENSIONS)); } if (pl_msg_test(log, PL_LOG_DEBUG)) { PL_DEBUG(p, " GL_EXTENSIONS:"); for (int i = 0; i < exts.num; i++) PL_DEBUG(p, " %s", exts.elem[i]); } if (params->egl_display) { pl_static_mutex_lock(&glad_egl_mutex); if (params->get_proc_addr_ex) { ok = gladLoadEGLUserPtr(params->egl_display, params->get_proc_addr_ex, params->proc_ctx); } else if (params->get_proc_addr) { ok = gladLoadEGL(params->egl_display, params->get_proc_addr); } else { #ifdef PL_HAVE_GL_PROC_ADDR ok = gladLoaderLoadEGL(params->egl_display); #else pl_unreachable(); #endif } pl_static_mutex_unlock(&glad_egl_mutex); if (!ok) { PL_FATAL(p, "Failed loading EGL functions - double check EGLDisplay?"); goto error; } int start = exts.num; add_exts_str(pl_gl, &exts, eglQueryString(params->egl_display, EGL_EXTENSIONS)); if (exts.num > start) { PL_DEBUG(p, " EGL_EXTENSIONS:"); for (int i = start; i < exts.num; i++) PL_DEBUG(p, " %s", exts.elem[i]); } } pl_gl->extensions = exts.elem; pl_gl->num_extensions = exts.num; if (!params->allow_software && gl_is_software(pl_gl)) { PL_FATAL(p, "OpenGL context is suspected to be a software rasterizer, " "but `allow_software` is false."); goto error; } if (params->debug) { if (pl_opengl_has_ext(pl_gl, "GL_KHR_debug")) { gl->DebugMessageCallback(debug_cb, log); gl->Enable(GL_DEBUG_OUTPUT); p->is_debug = true; } else { PL_WARN(p, "OpenGL debugging requested, but GL_KHR_debug is not " "available... ignoring!"); } if (params->egl_display && pl_opengl_has_ext(pl_gl, "EGL_KHR_debug")) { static const EGLAttrib attribs[] = { // Enable everything under the sun, because the `pl_ctx` log // level may change at runtime. EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, }; eglDebugMessageControlKHR(debug_cb_egl, attribs); eglLabelObjectKHR(NULL, EGL_OBJECT_THREAD_KHR, NULL, (void *) log); p->is_debug_egl = true; } } pl_gl->gpu = pl_gpu_create_gl(log, pl_gl, params); if (!pl_gl->gpu) goto error; gl_release_current(pl_gl); return pl_gl; error: PL_FATAL(p, "Failed initializing opengl context!"); gl_release_current(pl_gl); pl_opengl_destroy((pl_opengl *) &pl_gl); return NULL; } bool gl_make_current(pl_opengl pl_gl) { struct gl_ctx *p = PL_PRIV(pl_gl); pl_mutex_lock(&p->lock); if (!p->count && p->params.make_current) { if (!p->params.make_current(p->params.priv)) { PL_ERR(p, "Failed making OpenGL context current on calling thread!"); pl_mutex_unlock(&p->lock); return false; } } p->count++; return true; } void gl_release_current(pl_opengl pl_gl) { struct gl_ctx *p = PL_PRIV(pl_gl); p->count--; if (!p->count && p->params.release_current) p->params.release_current(p->params.priv); pl_mutex_unlock(&p->lock); }