xref: /openbmc/qemu/ui/egl-helpers.c (revision 55df6fcf)
1 /*
2  * Copyright (C) 2015-2016 Gerd Hoffmann <kraxel@redhat.com>
3  *
4  * This library 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  * This library 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 GNU
12  * 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 this library; if not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "qemu/osdep.h"
18 #include <glob.h>
19 #include <dirent.h>
20 
21 #include "qemu/error-report.h"
22 #include "ui/console.h"
23 #include "ui/egl-helpers.h"
24 
25 EGLDisplay *qemu_egl_display;
26 EGLConfig qemu_egl_config;
27 DisplayGLMode qemu_egl_mode;
28 
29 /* ------------------------------------------------------------------ */
30 
31 static void egl_fb_delete_texture(egl_fb *fb)
32 {
33     if (!fb->delete_texture) {
34         return;
35     }
36 
37     glDeleteTextures(1, &fb->texture);
38     fb->delete_texture = false;
39 }
40 
41 void egl_fb_destroy(egl_fb *fb)
42 {
43     if (!fb->framebuffer) {
44         return;
45     }
46 
47     egl_fb_delete_texture(fb);
48     glDeleteFramebuffers(1, &fb->framebuffer);
49 
50     fb->width = 0;
51     fb->height = 0;
52     fb->texture = 0;
53     fb->framebuffer = 0;
54 }
55 
56 void egl_fb_setup_default(egl_fb *fb, int width, int height)
57 {
58     fb->width = width;
59     fb->height = height;
60     fb->framebuffer = 0; /* default framebuffer */
61 }
62 
63 void egl_fb_setup_for_tex(egl_fb *fb, int width, int height,
64                           GLuint texture, bool delete)
65 {
66     egl_fb_delete_texture(fb);
67 
68     fb->width = width;
69     fb->height = height;
70     fb->texture = texture;
71     fb->delete_texture = delete;
72     if (!fb->framebuffer) {
73         glGenFramebuffers(1, &fb->framebuffer);
74     }
75 
76     glBindFramebuffer(GL_FRAMEBUFFER_EXT, fb->framebuffer);
77     glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
78                               GL_TEXTURE_2D, fb->texture, 0);
79 }
80 
81 void egl_fb_setup_new_tex(egl_fb *fb, int width, int height)
82 {
83     GLuint texture;
84 
85     glGenTextures(1, &texture);
86     glBindTexture(GL_TEXTURE_2D, texture);
87     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
88                  0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
89 
90     egl_fb_setup_for_tex(fb, width, height, texture, true);
91 }
92 
93 void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip)
94 {
95     GLuint y1, y2;
96 
97     glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
98     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->framebuffer);
99     glViewport(0, 0, dst->width, dst->height);
100     y1 = flip ? src->height : 0;
101     y2 = flip ? 0 : src->height;
102     glBlitFramebuffer(0, y1, src->width, y2,
103                       0, 0, dst->width, dst->height,
104                       GL_COLOR_BUFFER_BIT, GL_LINEAR);
105 }
106 
107 void egl_fb_read(void *dst, egl_fb *src)
108 {
109     glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
110     glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
111     glReadPixels(0, 0, src->width, src->height,
112                  GL_BGRA, GL_UNSIGNED_BYTE, dst);
113 }
114 
115 void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip)
116 {
117     glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
118     glViewport(0, 0, dst->width, dst->height);
119     glEnable(GL_TEXTURE_2D);
120     glBindTexture(GL_TEXTURE_2D, src->texture);
121     qemu_gl_run_texture_blit(gls, flip);
122 }
123 
124 void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip,
125                        int x, int y)
126 {
127     glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
128     if (flip) {
129         glViewport(x, y, src->width, src->height);
130     } else {
131         glViewport(x, dst->height - src->height - y,
132                    src->width, src->height);
133     }
134     glEnable(GL_TEXTURE_2D);
135     glBindTexture(GL_TEXTURE_2D, src->texture);
136     glEnable(GL_BLEND);
137     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
138     qemu_gl_run_texture_blit(gls, flip);
139     glDisable(GL_BLEND);
140 }
141 
142 /* ---------------------------------------------------------------------- */
143 
144 #ifdef CONFIG_OPENGL_DMABUF
145 
146 int qemu_egl_rn_fd;
147 struct gbm_device *qemu_egl_rn_gbm_dev;
148 EGLContext qemu_egl_rn_ctx;
149 
150 static int qemu_egl_rendernode_open(const char *rendernode)
151 {
152     DIR *dir;
153     struct dirent *e;
154     int r, fd;
155     char *p;
156 
157     if (rendernode) {
158         return open(rendernode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
159     }
160 
161     dir = opendir("/dev/dri");
162     if (!dir) {
163         return -1;
164     }
165 
166     fd = -1;
167     while ((e = readdir(dir))) {
168         if (e->d_type != DT_CHR) {
169             continue;
170         }
171 
172         if (strncmp(e->d_name, "renderD", 7)) {
173             continue;
174         }
175 
176         p = g_strdup_printf("/dev/dri/%s", e->d_name);
177 
178         r = open(p, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK);
179         if (r < 0) {
180             g_free(p);
181             continue;
182         }
183         fd = r;
184         g_free(p);
185         break;
186     }
187 
188     closedir(dir);
189     if (fd < 0) {
190         return -1;
191     }
192     return fd;
193 }
194 
195 int egl_rendernode_init(const char *rendernode, DisplayGLMode mode)
196 {
197     qemu_egl_rn_fd = -1;
198     int rc;
199 
200     qemu_egl_rn_fd = qemu_egl_rendernode_open(rendernode);
201     if (qemu_egl_rn_fd == -1) {
202         error_report("egl: no drm render node available");
203         goto err;
204     }
205 
206     qemu_egl_rn_gbm_dev = gbm_create_device(qemu_egl_rn_fd);
207     if (!qemu_egl_rn_gbm_dev) {
208         error_report("egl: gbm_create_device failed");
209         goto err;
210     }
211 
212     rc = qemu_egl_init_dpy_mesa((EGLNativeDisplayType)qemu_egl_rn_gbm_dev,
213                                 mode);
214     if (rc != 0) {
215         /* qemu_egl_init_dpy_mesa reports error */
216         goto err;
217     }
218 
219     if (!epoxy_has_egl_extension(qemu_egl_display,
220                                  "EGL_KHR_surfaceless_context")) {
221         error_report("egl: EGL_KHR_surfaceless_context not supported");
222         goto err;
223     }
224     if (!epoxy_has_egl_extension(qemu_egl_display,
225                                  "EGL_MESA_image_dma_buf_export")) {
226         error_report("egl: EGL_MESA_image_dma_buf_export not supported");
227         goto err;
228     }
229 
230     qemu_egl_rn_ctx = qemu_egl_init_ctx();
231     if (!qemu_egl_rn_ctx) {
232         error_report("egl: egl_init_ctx failed");
233         goto err;
234     }
235 
236     return 0;
237 
238 err:
239     if (qemu_egl_rn_gbm_dev) {
240         gbm_device_destroy(qemu_egl_rn_gbm_dev);
241     }
242     if (qemu_egl_rn_fd != -1) {
243         close(qemu_egl_rn_fd);
244     }
245 
246     return -1;
247 }
248 
249 int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc)
250 {
251     EGLImageKHR image;
252     EGLint num_planes, fd;
253 
254     image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(),
255                               EGL_GL_TEXTURE_2D_KHR,
256                               (EGLClientBuffer)(unsigned long)tex_id,
257                               NULL);
258     if (!image) {
259         return -1;
260     }
261 
262     eglExportDMABUFImageQueryMESA(qemu_egl_display, image, fourcc,
263                                   &num_planes, NULL);
264     if (num_planes != 1) {
265         eglDestroyImageKHR(qemu_egl_display, image);
266         return -1;
267     }
268     eglExportDMABUFImageMESA(qemu_egl_display, image, &fd, stride, NULL);
269     eglDestroyImageKHR(qemu_egl_display, image);
270 
271     return fd;
272 }
273 
274 void egl_dmabuf_import_texture(QemuDmaBuf *dmabuf)
275 {
276     EGLImageKHR image = EGL_NO_IMAGE_KHR;
277     EGLint attrs[] = {
278         EGL_DMA_BUF_PLANE0_FD_EXT,      dmabuf->fd,
279         EGL_DMA_BUF_PLANE0_PITCH_EXT,   dmabuf->stride,
280         EGL_DMA_BUF_PLANE0_OFFSET_EXT,  0,
281         EGL_WIDTH,                      dmabuf->width,
282         EGL_HEIGHT,                     dmabuf->height,
283         EGL_LINUX_DRM_FOURCC_EXT,       dmabuf->fourcc,
284         EGL_NONE, /* end of list */
285     };
286 
287     if (dmabuf->texture != 0) {
288         return;
289     }
290 
291     image = eglCreateImageKHR(qemu_egl_display,
292                               EGL_NO_CONTEXT,
293                               EGL_LINUX_DMA_BUF_EXT,
294                               NULL, attrs);
295     if (image == EGL_NO_IMAGE_KHR) {
296         error_report("eglCreateImageKHR failed");
297         return;
298     }
299 
300     glGenTextures(1, &dmabuf->texture);
301     glBindTexture(GL_TEXTURE_2D, dmabuf->texture);
302     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
303     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
304 
305     glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
306     eglDestroyImageKHR(qemu_egl_display, image);
307 }
308 
309 void egl_dmabuf_release_texture(QemuDmaBuf *dmabuf)
310 {
311     if (dmabuf->texture == 0) {
312         return;
313     }
314 
315     glDeleteTextures(1, &dmabuf->texture);
316     dmabuf->texture = 0;
317 }
318 
319 #endif /* CONFIG_OPENGL_DMABUF */
320 
321 /* ---------------------------------------------------------------------- */
322 
323 EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win)
324 {
325     EGLSurface esurface;
326     EGLBoolean b;
327 
328     esurface = eglCreateWindowSurface(qemu_egl_display,
329                                       qemu_egl_config,
330                                       (EGLNativeWindowType)win, NULL);
331     if (esurface == EGL_NO_SURFACE) {
332         error_report("egl: eglCreateWindowSurface failed");
333         return NULL;
334     }
335 
336     b = eglMakeCurrent(qemu_egl_display, esurface, esurface, ectx);
337     if (b == EGL_FALSE) {
338         error_report("egl: eglMakeCurrent failed");
339         return NULL;
340     }
341 
342     return esurface;
343 }
344 
345 /* ---------------------------------------------------------------------- */
346 
347 /*
348  * Taken from glamor_egl.h from the Xorg xserver, which is MIT licensed
349  *
350  * Create an EGLDisplay from a native display type. This is a little quirky
351  * for a few reasons.
352  *
353  * 1: GetPlatformDisplayEXT and GetPlatformDisplay are the API you want to
354  * use, but have different function signatures in the third argument; this
355  * happens not to matter for us, at the moment, but it means epoxy won't alias
356  * them together.
357  *
358  * 2: epoxy 1.3 and earlier don't understand EGL client extensions, which
359  * means you can't call "eglGetPlatformDisplayEXT" directly, as the resolver
360  * will crash.
361  *
362  * 3: You can't tell whether you have EGL 1.5 at this point, because
363  * eglQueryString(EGL_VERSION) is a property of the display, which we don't
364  * have yet. So you have to query for extensions no matter what. Fortunately
365  * epoxy_has_egl_extension _does_ let you query for client extensions, so
366  * we don't have to write our own extension string parsing.
367  *
368  * 4. There is no EGL_KHR_platform_base to complement the EXT one, thus one
369  * needs to know EGL 1.5 is supported in order to use the eglGetPlatformDisplay
370  * function pointer.
371  * We can workaround this (circular dependency) by probing for the EGL 1.5
372  * platform extensions (EGL_KHR_platform_gbm and friends) yet it doesn't seem
373  * like mesa will be able to advertise these (even though it can do EGL 1.5).
374  */
375 static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native,
376                                        EGLenum platform)
377 {
378     EGLDisplay dpy = EGL_NO_DISPLAY;
379 
380     /* In practise any EGL 1.5 implementation would support the EXT extension */
381     if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) {
382         PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT =
383             (void *) eglGetProcAddress("eglGetPlatformDisplayEXT");
384         if (getPlatformDisplayEXT && platform != 0) {
385             dpy = getPlatformDisplayEXT(platform, native, NULL);
386         }
387     }
388 
389     if (dpy == EGL_NO_DISPLAY) {
390         /* fallback */
391         dpy = eglGetDisplay(native);
392     }
393     return dpy;
394 }
395 
396 static int qemu_egl_init_dpy(EGLNativeDisplayType dpy,
397                              EGLenum platform,
398                              DisplayGLMode mode)
399 {
400     static const EGLint conf_att_core[] = {
401         EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
402         EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
403         EGL_RED_SIZE,   5,
404         EGL_GREEN_SIZE, 5,
405         EGL_BLUE_SIZE,  5,
406         EGL_ALPHA_SIZE, 0,
407         EGL_NONE,
408     };
409     static const EGLint conf_att_gles[] = {
410         EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
411         EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
412         EGL_RED_SIZE,   5,
413         EGL_GREEN_SIZE, 5,
414         EGL_BLUE_SIZE,  5,
415         EGL_ALPHA_SIZE, 0,
416         EGL_NONE,
417     };
418     EGLint major, minor;
419     EGLBoolean b;
420     EGLint n;
421     bool gles = (mode == DISPLAYGL_MODE_ES);
422 
423     qemu_egl_display = qemu_egl_get_display(dpy, platform);
424     if (qemu_egl_display == EGL_NO_DISPLAY) {
425         error_report("egl: eglGetDisplay failed");
426         return -1;
427     }
428 
429     b = eglInitialize(qemu_egl_display, &major, &minor);
430     if (b == EGL_FALSE) {
431         error_report("egl: eglInitialize failed");
432         return -1;
433     }
434 
435     b = eglBindAPI(gles ?  EGL_OPENGL_ES_API : EGL_OPENGL_API);
436     if (b == EGL_FALSE) {
437         error_report("egl: eglBindAPI failed (%s mode)",
438                      gles ? "gles" : "core");
439         return -1;
440     }
441 
442     b = eglChooseConfig(qemu_egl_display,
443                         gles ? conf_att_gles : conf_att_core,
444                         &qemu_egl_config, 1, &n);
445     if (b == EGL_FALSE || n != 1) {
446         error_report("egl: eglChooseConfig failed (%s mode)",
447                      gles ? "gles" : "core");
448         return -1;
449     }
450 
451     qemu_egl_mode = gles ? DISPLAYGL_MODE_ES : DISPLAYGL_MODE_CORE;
452     return 0;
453 }
454 
455 int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode)
456 {
457 #ifdef EGL_KHR_platform_x11
458     return qemu_egl_init_dpy(dpy, EGL_PLATFORM_X11_KHR, mode);
459 #else
460     return qemu_egl_init_dpy(dpy, 0, mode);
461 #endif
462 }
463 
464 int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode)
465 {
466 #ifdef EGL_MESA_platform_gbm
467     return qemu_egl_init_dpy(dpy, EGL_PLATFORM_GBM_MESA, mode);
468 #else
469     return qemu_egl_init_dpy(dpy, 0, mode);
470 #endif
471 }
472 
473 EGLContext qemu_egl_init_ctx(void)
474 {
475     static const EGLint ctx_att_core[] = {
476         EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
477         EGL_NONE
478     };
479     static const EGLint ctx_att_gles[] = {
480         EGL_CONTEXT_CLIENT_VERSION, 2,
481         EGL_NONE
482     };
483     bool gles = (qemu_egl_mode == DISPLAYGL_MODE_ES);
484     EGLContext ectx;
485     EGLBoolean b;
486 
487     ectx = eglCreateContext(qemu_egl_display, qemu_egl_config, EGL_NO_CONTEXT,
488                             gles ? ctx_att_gles : ctx_att_core);
489     if (ectx == EGL_NO_CONTEXT) {
490         error_report("egl: eglCreateContext failed");
491         return NULL;
492     }
493 
494     b = eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx);
495     if (b == EGL_FALSE) {
496         error_report("egl: eglMakeCurrent failed");
497         return NULL;
498     }
499 
500     return ectx;
501 }
502