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