Branch data Line data Source code
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 <ctype.h>
19 : :
20 : : #include "common.h"
21 : : #include "utils.h"
22 : : #include "gpu.h"
23 : :
24 : : const struct pl_opengl_params pl_opengl_default_params = {0};
25 : :
26 : 2373 : static void GLAPIENTRY debug_cb(GLenum source, GLenum type, GLuint id,
27 : : GLenum severity, GLsizei length,
28 : : const GLchar *message, const void *userParam)
29 : : {
30 : : pl_log log = (void *) userParam;
31 : : enum pl_log_level level = PL_LOG_ERR;
32 : :
33 [ + - - - ]: 2373 : switch (severity) {
34 : 2373 : case GL_DEBUG_SEVERITY_NOTIFICATION:level = PL_LOG_DEBUG; break;
35 : 0 : case GL_DEBUG_SEVERITY_LOW: level = PL_LOG_INFO; break;
36 : 0 : case GL_DEBUG_SEVERITY_MEDIUM: level = PL_LOG_WARN; break;
37 : : case GL_DEBUG_SEVERITY_HIGH: level = PL_LOG_ERR; break;
38 : : }
39 : :
40 : 2373 : pl_msg(log, level, "GL: %s", message);
41 : :
42 [ - + ]: 2373 : if (level <= PL_LOG_ERR)
43 : 0 : pl_log_stack_trace(log, level);
44 : 2373 : }
45 : :
46 : 0 : static void GLAPIENTRY debug_cb_egl(EGLenum error, const char *command,
47 : : EGLint messageType, EGLLabelKHR threadLabel,
48 : : EGLLabelKHR objectLabel, const char *message)
49 : : {
50 : : pl_log log = threadLabel;
51 : : enum pl_log_level level = PL_LOG_ERR;
52 : :
53 [ # # ]: 0 : switch (messageType) {
54 : : case EGL_DEBUG_MSG_CRITICAL_KHR: level = PL_LOG_FATAL; break;
55 : : case EGL_DEBUG_MSG_ERROR_KHR: level = PL_LOG_ERR; break;
56 : : case EGL_DEBUG_MSG_WARN_KHR: level = PL_LOG_WARN; break;
57 : : case EGL_DEBUG_MSG_INFO_KHR: level = PL_LOG_DEBUG; break;
58 : : }
59 : :
60 : 0 : pl_msg(log, level, "EGL: %s: %s %s", command, egl_err_str(error),
61 : : message);
62 : :
63 [ # # ]: 0 : if (level <= PL_LOG_ERR)
64 : 0 : pl_log_stack_trace(log, level);
65 : 0 : }
66 : :
67 : : // Guards access to the (thread-unsafe) glad global EGL state
68 : : static pl_static_mutex glad_egl_mutex = PL_STATIC_MUTEX_INITIALIZER;
69 : :
70 : 4 : void pl_opengl_destroy(pl_opengl *ptr)
71 : : {
72 : 4 : pl_opengl pl_gl = *ptr;
73 [ + - ]: 4 : if (!pl_gl)
74 : : return;
75 : :
76 : 4 : struct gl_ctx *p = PL_PRIV(pl_gl);
77 : 4 : gl_funcs *gl = &p->func;
78 [ - + ]: 4 : if (!gl_make_current(pl_gl)) {
79 : 0 : PL_WARN(p, "Failed uninitializing OpenGL context, leaking resources!");
80 : 0 : return;
81 : : }
82 : :
83 [ + - ]: 4 : if (p->is_debug)
84 : 4 : gl->DebugMessageCallback(NULL, NULL);
85 : :
86 [ - + ]: 4 : if (p->is_debug_egl)
87 : 0 : eglDebugMessageControlKHR(NULL, NULL);
88 : :
89 : 4 : pl_gpu_destroy(pl_gl->gpu);
90 : :
91 : : #ifdef PL_HAVE_GL_PROC_ADDR
92 [ + + ]: 4 : if (p->is_gles) {
93 : 1 : gladLoaderUnloadGLES2Context(gl);
94 : : } else {
95 : 3 : gladLoaderUnloadGLContext(gl);
96 : : }
97 : :
98 [ - + - - ]: 4 : bool used_loader = !p->params.get_proc_addr && !p->params.get_proc_addr_ex;
99 [ + - - + ]: 4 : if (p->params.egl_display && used_loader) {
100 : 0 : pl_static_mutex_lock(&glad_egl_mutex);
101 : 0 : gladLoaderUnloadEGL();
102 : 0 : pl_static_mutex_unlock(&glad_egl_mutex);
103 : : }
104 : : #endif
105 : :
106 : 4 : gl_release_current(pl_gl);
107 : 4 : pl_mutex_destroy(&p->lock);
108 : 4 : pl_free_ptr((void **) ptr);
109 : :
110 : : }
111 : :
112 : : typedef PL_ARRAY(const char *) ext_arr_t;
113 [ + - ]: 4 : static void add_exts_str(void *alloc, ext_arr_t *arr, const char *extstr)
114 : : {
115 : 4 : pl_str rest = pl_str_strip(pl_str0(pl_strdup0(alloc, pl_str0(extstr))));
116 [ + + ]: 140 : while (rest.len) {
117 : 136 : pl_str ext = pl_str_split_char(rest, ' ', &rest);
118 : 136 : ext.buf[ext.len] = '\0'; // re-use separator for terminator
119 [ - + + + : 136 : PL_ARRAY_APPEND(alloc, *arr, (char *) ext.buf);
- + ]
120 : : }
121 : 4 : }
122 : :
123 : 4 : pl_opengl pl_opengl_create(pl_log log, const struct pl_opengl_params *params)
124 : : {
125 [ - + ]: 4 : params = PL_DEF(params, &pl_opengl_default_params);
126 : 4 : struct pl_opengl_t *pl_gl = pl_zalloc_obj(NULL, pl_gl, struct gl_ctx);
127 : 4 : struct gl_ctx *p = PL_PRIV(pl_gl);
128 : 4 : gl_funcs *gl = &p->func;
129 : 4 : p->params = *params;
130 : 4 : p->log = log;
131 : :
132 [ - + ]: 4 : pl_mutex_init_type(&p->lock, PL_MUTEX_RECURSIVE);
133 [ - + ]: 4 : if (!gl_make_current(pl_gl)) {
134 : 0 : pl_free(pl_gl);
135 : 0 : return NULL;
136 : : }
137 : :
138 : : bool ok;
139 [ - + ]: 4 : if (params->get_proc_addr_ex) {
140 : 0 : ok = gladLoadGLContextUserPtr(gl, params->get_proc_addr_ex, params->proc_ctx);
141 [ + - ]: 4 : } else if (params->get_proc_addr) {
142 : 4 : ok = gladLoadGLContext(gl, params->get_proc_addr);
143 : : } else {
144 : : #ifdef PL_HAVE_GL_PROC_ADDR
145 : 0 : ok = gladLoaderLoadGLContext(gl);
146 : : #else
147 : : PL_FATAL(p, "No `glGetProcAddress` function provided, and libplacebo "
148 : : "built without its built-in OpenGL loader!");
149 : : goto error;
150 : : #endif
151 : : }
152 : :
153 [ - + ]: 4 : if (!ok) {
154 : 0 : PL_INFO(p, "Failed loading core GL, retrying as GLES...");
155 [ + + ]: 4 : } else if (gl_is_gles(pl_gl)) {
156 : 1 : PL_INFO(p, "GL context seems to be OpenGL ES, reloading as GLES...");
157 : : ok = false;
158 : : }
159 : :
160 [ - + ]: 3 : if (!ok) {
161 : : memset(gl, 0, sizeof(*gl));
162 [ - + ]: 1 : if (params->get_proc_addr_ex) {
163 : 0 : ok = gladLoadGLES2ContextUserPtr(gl, params->get_proc_addr_ex, params->proc_ctx);
164 [ + - ]: 1 : } else if (params->get_proc_addr) {
165 : 1 : ok = gladLoadGLES2Context(gl, params->get_proc_addr);
166 : : } else {
167 : : #ifdef PL_HAVE_GL_PROC_ADDR
168 : 0 : ok = gladLoaderLoadGLES2Context(gl);
169 : : #else
170 : : pl_unreachable();
171 : : #endif
172 : : }
173 : 1 : p->is_gles = ok;
174 : : }
175 : :
176 [ - + ]: 1 : if (!ok) {
177 : 0 : PL_FATAL(p, "Failed to initialize OpenGL context - make sure a valid "
178 : : "OpenGL context is bound to the current thread!");
179 : 0 : goto error;
180 : : }
181 : :
182 : 4 : const char *version = (const char *) gl->GetString(GL_VERSION);
183 [ + - ]: 4 : if (version) {
184 : : const char *ver = version;
185 [ + + + - ]: 14 : while (!isdigit(*ver) && *ver != '\0')
186 : 10 : ver++;
187 [ - + ]: 4 : if (sscanf(ver, "%d.%d", &pl_gl->major, &pl_gl->minor) != 2) {
188 : 0 : PL_FATAL(p, "Invalid GL_VERSION string: %s\n", version);
189 : 0 : goto error;
190 : : }
191 : : }
192 : :
193 [ - + ]: 4 : if (!pl_gl->major) {
194 : 0 : PL_FATAL(p, "No OpenGL version detected - make sure an OpenGL context "
195 : : "is bound to the current thread!");
196 : 0 : goto error;
197 : : }
198 : :
199 : : static const int gl_ver_req = 3;
200 [ - + ]: 4 : if (pl_gl->major < gl_ver_req) {
201 : 0 : PL_FATAL(p, "OpenGL version too old (%d < %d), please use a newer "
202 : : "OpenGL implementation or downgrade libplacebo!",
203 : : pl_gl->major, gl_ver_req);
204 : 0 : goto error;
205 : : }
206 : :
207 : 4 : PL_INFO(p, "Detected OpenGL version strings:");
208 : 4 : PL_INFO(p, " GL_VERSION: %s", version);
209 : 4 : PL_INFO(p, " GL_VENDOR: %s", (char *) gl->GetString(GL_VENDOR));
210 : 4 : PL_INFO(p, " GL_RENDERER: %s", (char *) gl->GetString(GL_RENDERER));
211 : :
212 : 4 : ext_arr_t exts = {0};
213 [ + - ]: 4 : if (pl_gl->major >= 3) {
214 : 4 : gl->GetIntegerv(GL_NUM_EXTENSIONS, &exts.num);
215 [ + - ]: 4 : PL_ARRAY_RESIZE(pl_gl, exts, exts.num);
216 [ + + ]: 936 : for (int i = 0; i < exts.num; i++)
217 : 932 : exts.elem[i] = (const char *) gl->GetStringi(GL_EXTENSIONS, i);
218 : : } else {
219 : 0 : add_exts_str(pl_gl, &exts, (const char *) gl->GetString(GL_EXTENSIONS));
220 : : }
221 : :
222 : : if (pl_msg_test(log, PL_LOG_DEBUG)) {
223 : 1 : PL_DEBUG(p, " GL_EXTENSIONS:");
224 [ + + ]: 231 : for (int i = 0; i < exts.num; i++)
225 : 230 : PL_DEBUG(p, " %s", exts.elem[i]);
226 : : }
227 : :
228 [ + - ]: 4 : if (params->egl_display) {
229 : 4 : pl_static_mutex_lock(&glad_egl_mutex);
230 [ - + ]: 4 : if (params->get_proc_addr_ex) {
231 : 0 : ok = gladLoadEGLUserPtr(params->egl_display, params->get_proc_addr_ex,
232 : 0 : params->proc_ctx);
233 [ + - ]: 4 : } else if (params->get_proc_addr) {
234 : 4 : ok = gladLoadEGL(params->egl_display, params->get_proc_addr);
235 : : } else {
236 : : #ifdef PL_HAVE_GL_PROC_ADDR
237 : 0 : ok = gladLoaderLoadEGL(params->egl_display);
238 : : #else
239 : : pl_unreachable();
240 : : #endif
241 : : }
242 : 4 : pl_static_mutex_unlock(&glad_egl_mutex);
243 : :
244 [ - + ]: 4 : if (!ok) {
245 : 0 : PL_FATAL(p, "Failed loading EGL functions - double check EGLDisplay?");
246 : 0 : goto error;
247 : : }
248 : :
249 : 4 : int start = exts.num;
250 : 4 : add_exts_str(pl_gl, &exts, eglQueryString(params->egl_display,
251 : : EGL_EXTENSIONS));
252 [ + - ]: 4 : if (exts.num > start) {
253 : 4 : PL_DEBUG(p, " EGL_EXTENSIONS:");
254 [ + + ]: 140 : for (int i = start; i < exts.num; i++)
255 : 136 : PL_DEBUG(p, " %s", exts.elem[i]);
256 : : }
257 : : }
258 : :
259 : 4 : pl_gl->extensions = exts.elem;
260 : 4 : pl_gl->num_extensions = exts.num;
261 : :
262 [ + - - + ]: 4 : if (!params->allow_software && gl_is_software(pl_gl)) {
263 : 0 : PL_FATAL(p, "OpenGL context is suspected to be a software rasterizer, "
264 : : "but `allow_software` is false.");
265 : 0 : goto error;
266 : : }
267 : :
268 [ + - ]: 4 : if (params->debug) {
269 [ + - ]: 4 : if (pl_opengl_has_ext(pl_gl, "GL_KHR_debug")) {
270 : 4 : gl->DebugMessageCallback(debug_cb, log);
271 : 4 : gl->Enable(GL_DEBUG_OUTPUT);
272 : 4 : p->is_debug = true;
273 : : } else {
274 : 0 : PL_WARN(p, "OpenGL debugging requested, but GL_KHR_debug is not "
275 : : "available... ignoring!");
276 : : }
277 : :
278 [ + - - + ]: 4 : if (params->egl_display && pl_opengl_has_ext(pl_gl, "EGL_KHR_debug")) {
279 : : static const EGLAttrib attribs[] = {
280 : : // Enable everything under the sun, because the `pl_ctx` log
281 : : // level may change at runtime.
282 : : EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
283 : : EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
284 : : EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
285 : : EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
286 : : EGL_NONE,
287 : : };
288 : :
289 : 0 : eglDebugMessageControlKHR(debug_cb_egl, attribs);
290 : 0 : eglLabelObjectKHR(NULL, EGL_OBJECT_THREAD_KHR, NULL, (void *) log);
291 : 0 : p->is_debug_egl = true;
292 : : }
293 : : }
294 : :
295 : 4 : pl_gl->gpu = pl_gpu_create_gl(log, pl_gl, params);
296 [ - + ]: 4 : if (!pl_gl->gpu)
297 : 0 : goto error;
298 : :
299 : 4 : gl_release_current(pl_gl);
300 : 4 : return pl_gl;
301 : :
302 : 0 : error:
303 : 0 : PL_FATAL(p, "Failed initializing opengl context!");
304 : 0 : gl_release_current(pl_gl);
305 : 0 : pl_opengl_destroy((pl_opengl *) &pl_gl);
306 : 0 : return NULL;
307 : : }
308 : :
309 : 13428 : bool gl_make_current(pl_opengl pl_gl)
310 : : {
311 : 13428 : struct gl_ctx *p = PL_PRIV(pl_gl);
312 : 13428 : pl_mutex_lock(&p->lock);
313 [ + + - + ]: 13428 : if (!p->count && p->params.make_current) {
314 [ # # ]: 0 : if (!p->params.make_current(p->params.priv)) {
315 : 0 : PL_ERR(p, "Failed making OpenGL context current on calling thread!");
316 : 0 : pl_mutex_unlock(&p->lock);
317 : 0 : return false;
318 : : }
319 : : }
320 : :
321 : 13428 : p->count++;
322 : 13428 : return true;
323 : : }
324 : :
325 : 13428 : void gl_release_current(pl_opengl pl_gl)
326 : : {
327 : 13428 : struct gl_ctx *p = PL_PRIV(pl_gl);
328 : 13428 : p->count--;
329 [ + + - + ]: 13428 : if (!p->count && p->params.release_current)
330 : 0 : p->params.release_current(p->params.priv);
331 : 13428 : pl_mutex_unlock(&p->lock);
332 : 13428 : }
|