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