1 /*
2  * This file is part of libplacebo.
3  *
4  * libplacebo is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * libplacebo is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with libplacebo.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "common.h"
19 #include "utils.h"
20 #include "gpu.h"
21 #include "pl_thread.h"
22 
23 const struct pl_opengl_params pl_opengl_default_params = {0};
24 
25 struct priv {
26     struct pl_opengl_params params;
27     pl_log log;
28     bool is_debug;
29     bool is_debug_egl;
30 
31     // For context locking
32     pl_mutex lock;
33     int count;
34 };
35 
debug_cb(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar * message,const void * userParam)36 static void GLAPIENTRY debug_cb(GLenum source, GLenum type, GLuint id,
37                                 GLenum severity, GLsizei length,
38                                 const GLchar *message, const void *userParam)
39 {
40     pl_log log = (void *) userParam;
41     enum pl_log_level level = PL_LOG_ERR;
42 
43     switch (severity) {
44     case GL_DEBUG_SEVERITY_NOTIFICATION:level = PL_LOG_DEBUG; break;
45     case GL_DEBUG_SEVERITY_LOW:         level = PL_LOG_INFO; break;
46     case GL_DEBUG_SEVERITY_MEDIUM:      level = PL_LOG_WARN; break;
47     case GL_DEBUG_SEVERITY_HIGH:        level = PL_LOG_ERR; break;
48     }
49 
50     pl_msg(log, level, "GL: %s", message);
51 }
52 
53 #ifdef EPOXY_HAS_EGL
54 
debug_cb_egl(EGLenum error,const char * command,EGLint messageType,EGLLabelKHR threadLabel,EGLLabelKHR objectLabel,const char * message)55 static void debug_cb_egl(EGLenum error, const char *command,
56                          EGLint messageType, EGLLabelKHR threadLabel,
57                          EGLLabelKHR objectLabel, const char *message)
58 {
59     pl_log log = threadLabel;
60     enum pl_log_level level = PL_LOG_ERR;
61 
62     switch (messageType) {
63     case EGL_DEBUG_MSG_CRITICAL_KHR:    level = PL_LOG_FATAL; break;
64     case EGL_DEBUG_MSG_ERROR_KHR:       level = PL_LOG_ERR; break;
65     case EGL_DEBUG_MSG_WARN_KHR:        level = PL_LOG_WARN; break;
66     case EGL_DEBUG_MSG_INFO_KHR:        level = PL_LOG_DEBUG; break;
67     }
68 
69     pl_msg(log, level, "EGL: %s: %s %s", command, egl_err_str(error),
70            message);
71 }
72 
73 #endif // EPOXY_HAS_EGL
74 
pl_opengl_destroy(pl_opengl * ptr)75 void pl_opengl_destroy(pl_opengl *ptr)
76 {
77     pl_opengl pl_gl = *ptr;
78     if (!pl_gl)
79         return;
80 
81     struct priv *p = PL_PRIV(pl_gl);
82     if (!gl_make_current(pl_gl)) {
83         PL_WARN(p, "Failed uninitializing OpenGL context, leaking resources!");
84         return;
85     }
86 
87     if (p->is_debug)
88         glDebugMessageCallback(NULL, NULL);
89 
90 #ifdef EPOXY_HAS_EGL
91     if (p->is_debug_egl)
92         eglDebugMessageControlKHR(NULL, NULL);
93 #endif
94 
95     pl_gpu_destroy(pl_gl->gpu);
96     gl_release_current(pl_gl);
97     pl_mutex_destroy(&p->lock);
98     pl_free_ptr((void **) ptr);
99 }
100 
pl_opengl_create(pl_log log,const struct pl_opengl_params * params)101 pl_opengl pl_opengl_create(pl_log log, const struct pl_opengl_params *params)
102 {
103     params = PL_DEF(params, &pl_opengl_default_params);
104     struct pl_opengl *pl_gl = pl_zalloc_obj(NULL, pl_gl, struct priv);
105     struct priv *p = PL_PRIV(pl_gl);
106     p->params = *params;
107     p->log = log;
108 
109     pl_mutex_init_type(&p->lock, PL_MUTEX_RECURSIVE);
110     if (!gl_make_current(pl_gl)) {
111         pl_free(pl_gl);
112         return NULL;
113     }
114 
115     int ver = epoxy_gl_version();
116     if (!ver) {
117         PL_FATAL(p, "No OpenGL version detected - make sure an OpenGL context "
118                  "is bound to the current thread!");
119         goto error;
120     }
121 
122     PL_INFO(p, "Detected OpenGL version strings:");
123     PL_INFO(p, "    GL_VERSION:  %s", glGetString(GL_VERSION));
124     PL_INFO(p, "    GL_VENDOR:   %s", glGetString(GL_VENDOR));
125     PL_INFO(p, "    GL_RENDERER: %s", glGetString(GL_RENDERER));
126 
127     if (pl_msg_test(log, PL_LOG_DEBUG)) {
128         if (ver >= 30) {
129             int num_exts = 0;
130             glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts);
131             PL_DEBUG(p, "    GL_EXTENSIONS:");
132             for (int i = 0; i < num_exts; i++) {
133                 const char *ext = glGetStringi(GL_EXTENSIONS, i);
134                 PL_DEBUG(p, "        %s", ext);
135             }
136         } else {
137             PL_DEBUG(p, "    GL_EXTENSIONS: %s", glGetString(GL_EXTENSIONS));
138         }
139 
140 #ifdef EPOXY_HAS_EGL
141         if (params->egl_display) {
142             PL_DEBUG(p, "    EGL_EXTENSIONS: %s",
143                      eglQueryString(params->egl_display, EGL_EXTENSIONS));
144         }
145 #endif
146     }
147 
148     if (!params->allow_software && gl_is_software()) {
149         PL_FATAL(p, "OpenGL context is suspected to be a software rasterizer, "
150                  "but `allow_software` is false.");
151         goto error;
152     }
153 
154     if (params->debug) {
155         if (epoxy_has_gl_extension("GL_ARB_debug_output")) {
156             glDebugMessageCallback(debug_cb, log);
157             p->is_debug = true;
158         } else {
159             PL_WARN(p, "OpenGL debugging requested but GL_ARB_debug_output "
160                     "unavailable.. ignoring!");
161         }
162 
163 #ifdef EPOXY_HAS_EGL
164         if (params->egl_display && epoxy_has_egl_extension(params->egl_display, "EGL_KHR_debug")) {
165             static const EGLAttrib attribs[] = {
166                 // Enable everything under the sun, because the `pl_ctx` log
167                 // level may change at runtime.
168                 EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
169                 EGL_DEBUG_MSG_ERROR_KHR,    EGL_TRUE,
170                 EGL_DEBUG_MSG_WARN_KHR,     EGL_TRUE,
171                 EGL_DEBUG_MSG_INFO_KHR,     EGL_TRUE,
172                 EGL_NONE,
173             };
174 
175             eglDebugMessageControlKHR(debug_cb_egl, attribs);
176             eglLabelObjectKHR(NULL, EGL_OBJECT_THREAD_KHR, NULL, (void *) log);
177             p->is_debug_egl = true;
178         }
179 #endif // EPOXY_HAS_EGL
180     }
181 
182     pl_gl->gpu = pl_gpu_create_gl(log, pl_gl, params);
183     if (!pl_gl->gpu)
184         goto error;
185 
186     // Restrict version
187     if (params->max_glsl_version) {
188         struct pl_glsl_version *glsl = (struct pl_glsl_version *) &pl_gl->gpu->glsl;
189         glsl->version = PL_MIN(glsl->version, params->max_glsl_version);
190         PL_INFO(p, "Restricting GLSL version to %d... new version is %d",
191                 params->max_glsl_version, glsl->version);
192     }
193 
194     gl_release_current(pl_gl);
195     return pl_gl;
196 
197 error:
198     PL_FATAL(p, "Failed initializing opengl context!");
199     gl_release_current(pl_gl);
200     pl_opengl_destroy((pl_opengl *) &pl_gl);
201     return NULL;
202 }
203 
gl_make_current(pl_opengl gl)204 bool gl_make_current(pl_opengl gl)
205 {
206     struct priv *p = PL_PRIV(gl);
207     pl_mutex_lock(&p->lock);
208     if (!p->count && p->params.make_current) {
209         if (!p->params.make_current(p->params.priv)) {
210             PL_ERR(p, "Failed making OpenGL context current on calling thread!");
211             pl_mutex_unlock(&p->lock);
212             return false;
213         }
214     }
215 
216     p->count++;
217     return true;
218 }
219 
gl_release_current(pl_opengl gl)220 void gl_release_current(pl_opengl gl)
221 {
222     struct priv *p = PL_PRIV(gl);
223     p->count--;
224     if (!p->count && p->params.release_current)
225         p->params.release_current(p->params.priv);
226     pl_mutex_unlock(&p->lock);
227 }
228