xref: /qemu/ui/gtk-egl.c (revision 6f388a37e6dc2df4457692afe6adb5448b7db31d)
1 /*
2  * GTK UI -- egl opengl code.
3  *
4  * Note that gtk 3.16+ (released 2015-03-23) has a GtkGLArea widget,
5  * which is GtkDrawingArea like widget with opengl rendering support.
6  *
7  * This code handles opengl support on older gtk versions, using egl
8  * to get a opengl context for the X11 window.
9  *
10  * This work is licensed under the terms of the GNU GPL, version 2 or later.
11  * See the COPYING file in the top-level directory.
12  */
13 
14 #include "qemu/osdep.h"
15 #include "qemu/main-loop.h"
16 #include "qemu/error-report.h"
17 
18 #include "trace.h"
19 
20 #include "ui/console.h"
21 #include "ui/gtk.h"
22 #include "ui/egl-helpers.h"
23 #include "ui/shader.h"
24 
25 #include "system/system.h"
26 
gtk_egl_set_scanout_mode(VirtualConsole * vc,bool scanout)27 static void gtk_egl_set_scanout_mode(VirtualConsole *vc, bool scanout)
28 {
29     if (vc->gfx.scanout_mode == scanout) {
30         return;
31     }
32 
33     vc->gfx.scanout_mode = scanout;
34     if (!vc->gfx.scanout_mode) {
35         eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
36                        vc->gfx.esurface, vc->gfx.ectx);
37         egl_fb_destroy(&vc->gfx.guest_fb);
38         if (vc->gfx.surface) {
39             surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
40             surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
41         }
42     }
43 }
44 
45 /** DisplayState Callbacks (opengl version) **/
46 
gd_egl_init(VirtualConsole * vc)47 void gd_egl_init(VirtualConsole *vc)
48 {
49     GdkWindow *gdk_window = gtk_widget_get_window(vc->gfx.drawing_area);
50     if (!gdk_window) {
51         return;
52     }
53 
54     Window x11_window = gdk_x11_window_get_xid(gdk_window);
55     if (!x11_window) {
56         return;
57     }
58 
59     vc->gfx.ectx = qemu_egl_init_ctx();
60     vc->gfx.esurface = qemu_egl_init_surface_x11
61         (vc->gfx.ectx, (EGLNativeWindowType)x11_window);
62 
63     assert(vc->gfx.esurface);
64 }
65 
gd_egl_draw(VirtualConsole * vc)66 void gd_egl_draw(VirtualConsole *vc)
67 {
68     GdkWindow *window;
69 #ifdef CONFIG_GBM
70     QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
71     int fence_fd;
72 #endif
73     int ww, wh, pw, ph, gs;
74 
75     if (!vc->gfx.gls) {
76         return;
77     }
78 
79     window = gtk_widget_get_window(vc->gfx.drawing_area);
80     gs = gdk_window_get_scale_factor(window);
81     ww = gdk_window_get_width(window);
82     wh = gdk_window_get_height(window);
83     pw = ww * gs;
84     ph = wh * gs;
85 
86     if (vc->gfx.scanout_mode) {
87 #ifdef CONFIG_GBM
88         if (dmabuf) {
89             if (!qemu_dmabuf_get_draw_submitted(dmabuf)) {
90                 return;
91             } else {
92                 qemu_dmabuf_set_draw_submitted(dmabuf, false);
93             }
94         }
95 #endif
96         gd_egl_scanout_flush(&vc->gfx.dcl, 0, 0, vc->gfx.w, vc->gfx.h);
97 
98         gd_update_scale(vc, ww, wh,
99                         surface_width(vc->gfx.ds),
100                         surface_height(vc->gfx.ds));
101 
102         glFlush();
103 #ifdef CONFIG_GBM
104         if (dmabuf) {
105             egl_dmabuf_create_fence(dmabuf);
106             fence_fd = qemu_dmabuf_get_fence_fd(dmabuf);
107             if (fence_fd >= 0) {
108                 qemu_set_fd_handler(fence_fd, gd_hw_gl_flushed, NULL, vc);
109                 return;
110             }
111             graphic_hw_gl_block(vc->gfx.dcl.con, false);
112         }
113 #endif
114     } else {
115         if (!vc->gfx.ds) {
116             return;
117         }
118         eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
119                        vc->gfx.esurface, vc->gfx.ectx);
120 
121         surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph);
122         surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
123 
124         eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
125 
126         gd_update_scale(vc, ww, wh,
127                         surface_width(vc->gfx.ds),
128                         surface_height(vc->gfx.ds));
129 
130         glFlush();
131     }
132 }
133 
gd_egl_update(DisplayChangeListener * dcl,int x,int y,int w,int h)134 void gd_egl_update(DisplayChangeListener *dcl,
135                    int x, int y, int w, int h)
136 {
137     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
138 
139     if (!vc->gfx.gls || !vc->gfx.ds) {
140         return;
141     }
142 
143     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
144                    vc->gfx.esurface, vc->gfx.ectx);
145     surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h);
146     vc->gfx.glupdates++;
147     eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE,
148                    EGL_NO_SURFACE, EGL_NO_CONTEXT);
149 }
150 
gd_egl_refresh(DisplayChangeListener * dcl)151 void gd_egl_refresh(DisplayChangeListener *dcl)
152 {
153     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
154 
155     gd_update_monitor_refresh_rate(
156             vc, vc->window ? vc->window : vc->gfx.drawing_area);
157 
158     if (vc->gfx.guest_fb.dmabuf &&
159         qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) {
160         gd_egl_draw(vc);
161         return;
162     }
163 
164     if (!vc->gfx.esurface) {
165         gd_egl_init(vc);
166         if (!vc->gfx.esurface) {
167             return;
168         }
169         vc->gfx.gls = qemu_gl_init_shader();
170         if (vc->gfx.ds) {
171             surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
172             surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
173         }
174 #ifdef CONFIG_GBM
175         if (vc->gfx.guest_fb.dmabuf) {
176             egl_dmabuf_release_texture(vc->gfx.guest_fb.dmabuf);
177             gd_egl_scanout_dmabuf(dcl, vc->gfx.guest_fb.dmabuf);
178         }
179 #endif
180     }
181 
182     graphic_hw_update(dcl->con);
183 
184     if (vc->gfx.glupdates) {
185         vc->gfx.glupdates = 0;
186         gtk_egl_set_scanout_mode(vc, false);
187         gd_egl_draw(vc);
188     }
189 }
190 
gd_egl_switch(DisplayChangeListener * dcl,DisplaySurface * surface)191 void gd_egl_switch(DisplayChangeListener *dcl,
192                    DisplaySurface *surface)
193 {
194     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
195     bool resized = true;
196 
197     trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
198 
199     if (vc->gfx.ds &&
200         surface_width(vc->gfx.ds) == surface_width(surface) &&
201         surface_height(vc->gfx.ds) == surface_height(surface)) {
202         resized = false;
203     }
204     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
205                    vc->gfx.esurface, vc->gfx.ectx);
206 
207     surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
208     vc->gfx.ds = surface;
209     if (vc->gfx.gls) {
210         surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
211     }
212 
213     if (resized) {
214         gd_update_windowsize(vc);
215     }
216 
217     eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
218                    EGL_NO_CONTEXT);
219 }
220 
gd_egl_create_context(DisplayGLCtx * dgc,QEMUGLParams * params)221 QEMUGLContext gd_egl_create_context(DisplayGLCtx *dgc,
222                                     QEMUGLParams *params)
223 {
224     VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
225 
226     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
227                    vc->gfx.esurface, vc->gfx.ectx);
228     return qemu_egl_create_context(dgc, params);
229 }
230 
gd_egl_scanout_disable(DisplayChangeListener * dcl)231 void gd_egl_scanout_disable(DisplayChangeListener *dcl)
232 {
233     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
234 
235     vc->gfx.w = 0;
236     vc->gfx.h = 0;
237     gtk_egl_set_scanout_mode(vc, false);
238 }
239 
gd_egl_scanout_texture(DisplayChangeListener * dcl,uint32_t backing_id,bool backing_y_0_top,uint32_t backing_width,uint32_t backing_height,uint32_t x,uint32_t y,uint32_t w,uint32_t h,void * d3d_tex2d)240 void gd_egl_scanout_texture(DisplayChangeListener *dcl,
241                             uint32_t backing_id, bool backing_y_0_top,
242                             uint32_t backing_width, uint32_t backing_height,
243                             uint32_t x, uint32_t y,
244                             uint32_t w, uint32_t h,
245                             void *d3d_tex2d)
246 {
247     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
248 
249     vc->gfx.x = x;
250     vc->gfx.y = y;
251     vc->gfx.w = w;
252     vc->gfx.h = h;
253     vc->gfx.y0_top = backing_y_0_top;
254 
255     if (!vc->gfx.esurface) {
256         gd_egl_init(vc);
257         if (!vc->gfx.esurface) {
258             return;
259         }
260     }
261 
262     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
263                    vc->gfx.esurface, vc->gfx.ectx);
264 
265     gtk_egl_set_scanout_mode(vc, true);
266     egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height,
267                          backing_id, false);
268 }
269 
gd_egl_scanout_dmabuf(DisplayChangeListener * dcl,QemuDmaBuf * dmabuf)270 void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
271                            QemuDmaBuf *dmabuf)
272 {
273 #ifdef CONFIG_GBM
274     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
275     uint32_t x, y, width, height, backing_width, backing_height, texture;
276     bool y0_top;
277 
278     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
279                    vc->gfx.esurface, vc->gfx.ectx);
280 
281     egl_dmabuf_import_texture(dmabuf);
282     texture = qemu_dmabuf_get_texture(dmabuf);
283     if (!texture) {
284         return;
285     }
286 
287     x = qemu_dmabuf_get_x(dmabuf);
288     y = qemu_dmabuf_get_y(dmabuf);
289     width = qemu_dmabuf_get_width(dmabuf);
290     height = qemu_dmabuf_get_height(dmabuf);
291     backing_width = qemu_dmabuf_get_backing_width(dmabuf);
292     backing_height = qemu_dmabuf_get_backing_height(dmabuf);
293     y0_top = qemu_dmabuf_get_y0_top(dmabuf);
294 
295     gd_egl_scanout_texture(dcl, texture, y0_top, backing_width, backing_height,
296                            x, y, width, height, NULL);
297 
298     if (qemu_dmabuf_get_allow_fences(dmabuf)) {
299         vc->gfx.guest_fb.dmabuf = dmabuf;
300     }
301 #endif
302 }
303 
gd_egl_cursor_dmabuf(DisplayChangeListener * dcl,QemuDmaBuf * dmabuf,bool have_hot,uint32_t hot_x,uint32_t hot_y)304 void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl,
305                           QemuDmaBuf *dmabuf, bool have_hot,
306                           uint32_t hot_x, uint32_t hot_y)
307 {
308 #ifdef CONFIG_GBM
309     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
310     uint32_t backing_width, backing_height, texture;
311 
312     if (dmabuf) {
313         egl_dmabuf_import_texture(dmabuf);
314         texture = qemu_dmabuf_get_texture(dmabuf);
315         if (!texture) {
316             return;
317         }
318 
319         backing_width = qemu_dmabuf_get_backing_width(dmabuf);
320         backing_height = qemu_dmabuf_get_backing_height(dmabuf);
321         egl_fb_setup_for_tex(&vc->gfx.cursor_fb, backing_width, backing_height,
322                              texture, false);
323     } else {
324         egl_fb_destroy(&vc->gfx.cursor_fb);
325     }
326 #endif
327 }
328 
gd_egl_cursor_position(DisplayChangeListener * dcl,uint32_t pos_x,uint32_t pos_y)329 void gd_egl_cursor_position(DisplayChangeListener *dcl,
330                             uint32_t pos_x, uint32_t pos_y)
331 {
332     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
333 
334     vc->gfx.cursor_x = pos_x * vc->gfx.scale_x;
335     vc->gfx.cursor_y = pos_y * vc->gfx.scale_y;
336 }
337 
gd_egl_scanout_flush(DisplayChangeListener * dcl,uint32_t x,uint32_t y,uint32_t w,uint32_t h)338 void gd_egl_scanout_flush(DisplayChangeListener *dcl,
339                           uint32_t x, uint32_t y, uint32_t w, uint32_t h)
340 {
341     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
342     GdkWindow *window;
343     int px_offset, py_offset;
344     int gs;
345     int pw_widget, ph_widget, pw_surface, ph_surface;
346     int ww_widget, wh_widget, ww_surface, wh_surface;
347     int fbw, fbh;
348 
349     if (!vc->gfx.scanout_mode) {
350         return;
351     }
352     if (!vc->gfx.guest_fb.framebuffer) {
353         return;
354     }
355 
356     eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
357                    vc->gfx.esurface, vc->gfx.ectx);
358 
359     window = gtk_widget_get_window(vc->gfx.drawing_area);
360     gs = gdk_window_get_scale_factor(window);
361     ww_widget = gdk_window_get_width(window);
362     wh_widget = gdk_window_get_height(window);
363     fbw = surface_width(vc->gfx.ds);
364     fbh = surface_height(vc->gfx.ds);
365 
366     gd_update_scale(vc, ww_widget, wh_widget, fbw, fbh);
367 
368     ww_surface = fbw * vc->gfx.scale_x;
369     wh_surface = fbh * vc->gfx.scale_y;
370     pw_widget = ww_widget * gs;
371     ph_widget = wh_widget * gs;
372     pw_surface = ww_surface * gs;
373     ph_surface = wh_surface * gs;
374 
375     px_offset = 0;
376     py_offset = 0;
377     if (pw_widget > pw_surface) {
378         px_offset = (pw_widget - pw_surface) / 2;
379     }
380     if (ph_widget > ph_surface) {
381         py_offset = (ph_widget - ph_surface) / 2;
382     }
383 
384     egl_fb_setup_default(&vc->gfx.win_fb, pw_surface, ph_surface,
385                          px_offset, py_offset);
386     if (vc->gfx.cursor_fb.texture) {
387         egl_texture_blit(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.guest_fb,
388                          vc->gfx.y0_top);
389         egl_texture_blend(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.cursor_fb,
390                           vc->gfx.y0_top,
391                           vc->gfx.cursor_x, vc->gfx.cursor_y,
392                           vc->gfx.scale_x, vc->gfx.scale_y);
393     } else {
394         egl_fb_blit(&vc->gfx.win_fb, &vc->gfx.guest_fb, !vc->gfx.y0_top);
395     }
396 
397 #ifdef CONFIG_GBM
398     if (vc->gfx.guest_fb.dmabuf) {
399         egl_dmabuf_create_sync(vc->gfx.guest_fb.dmabuf);
400     }
401 #endif
402 
403     eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
404 }
405 
gd_egl_flush(DisplayChangeListener * dcl,uint32_t x,uint32_t y,uint32_t w,uint32_t h)406 void gd_egl_flush(DisplayChangeListener *dcl,
407                   uint32_t x, uint32_t y, uint32_t w, uint32_t h)
408 {
409     VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
410     GtkWidget *area = vc->gfx.drawing_area;
411 
412     if (vc->gfx.guest_fb.dmabuf &&
413         !qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) {
414         graphic_hw_gl_block(vc->gfx.dcl.con, true);
415         qemu_dmabuf_set_draw_submitted(vc->gfx.guest_fb.dmabuf, true);
416         gtk_egl_set_scanout_mode(vc, true);
417         gtk_widget_queue_draw_area(area, x, y, w, h);
418         return;
419     }
420 
421     gd_egl_scanout_flush(&vc->gfx.dcl, x, y, w, h);
422 }
423 
gtk_egl_init(DisplayGLMode mode)424 void gtk_egl_init(DisplayGLMode mode)
425 {
426     GdkDisplay *gdk_display = gdk_display_get_default();
427     Display *x11_display = gdk_x11_display_get_xdisplay(gdk_display);
428 
429     if (qemu_egl_init_dpy_x11(x11_display, mode) < 0) {
430         return;
431     }
432 
433     display_opengl = 1;
434 }
435 
gd_egl_make_current(DisplayGLCtx * dgc,QEMUGLContext ctx)436 int gd_egl_make_current(DisplayGLCtx *dgc,
437                         QEMUGLContext ctx)
438 {
439     VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc);
440 
441     if (!eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
442                         vc->gfx.esurface, ctx)) {
443         error_report("egl: eglMakeCurrent failed: %s", qemu_egl_get_error_string());
444         return -1;
445     }
446 
447     return 0;
448 }
449