1 /* 2 * GTK UI -- glarea opengl code. 3 * 4 * Requires 3.16+ (GtkGLArea widget). 5 * 6 * This work is licensed under the terms of the GNU GPL, version 2 or later. 7 * See the COPYING file in the top-level directory. 8 */ 9 10 #include "qemu/osdep.h" 11 #include "qemu/main-loop.h" 12 13 #include "trace.h" 14 15 #include "ui/console.h" 16 #include "ui/gtk.h" 17 #include "ui/egl-helpers.h" 18 19 #include "system/system.h" 20 21 static void gtk_gl_area_set_scanout_mode(VirtualConsole *vc, bool scanout) 22 { 23 if (vc->gfx.scanout_mode == scanout) { 24 return; 25 } 26 27 vc->gfx.scanout_mode = scanout; 28 if (!vc->gfx.scanout_mode) { 29 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 30 egl_fb_destroy(&vc->gfx.guest_fb); 31 if (vc->gfx.surface) { 32 surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds); 33 surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds); 34 } 35 } 36 } 37 38 /** DisplayState Callbacks (opengl version) **/ 39 40 void gd_gl_area_draw(VirtualConsole *vc) 41 { 42 #ifdef CONFIG_GBM 43 QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf; 44 #endif 45 int pw, ph, gs, y1, y2; 46 int ww, wh; 47 int ww_surface, wh_surface; 48 int fbw, fbh; 49 int wx_offset, wy_offset; 50 51 if (!vc->gfx.gls || !vc->gfx.ds) { 52 return; 53 } 54 55 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 56 gs = gdk_window_get_scale_factor(gtk_widget_get_window(vc->gfx.drawing_area)); 57 fbw = surface_width(vc->gfx.ds); 58 fbh = surface_height(vc->gfx.ds); 59 ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area); 60 wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area); 61 pw = ww * gs; 62 ph = wh * gs; 63 64 gd_update_scale(vc, ww, wh, fbw, fbh); 65 66 ww_surface = fbw * vc->gfx.scale_x; 67 wh_surface = fbh * vc->gfx.scale_y; 68 69 wx_offset = wy_offset = 0; 70 if (ww > ww_surface) { 71 wx_offset = (ww - ww_surface) / 2; 72 } 73 if (wh > wh_surface) { 74 wy_offset = (wh - wh_surface) / 2; 75 } 76 77 if (vc->gfx.scanout_mode) { 78 if (!vc->gfx.guest_fb.framebuffer) { 79 return; 80 } 81 82 #ifdef CONFIG_GBM 83 if (dmabuf) { 84 if (!qemu_dmabuf_get_draw_submitted(dmabuf)) { 85 return; 86 } else { 87 qemu_dmabuf_set_draw_submitted(dmabuf, false); 88 } 89 } 90 #endif 91 92 glBindFramebuffer(GL_READ_FRAMEBUFFER, vc->gfx.guest_fb.framebuffer); 93 /* GtkGLArea sets GL_DRAW_FRAMEBUFFER for us */ 94 95 if (wx_offset > 0) { 96 glEnable(GL_SCISSOR_TEST); 97 glScissor(0, 0, wx_offset * gs, wh * gs); 98 glClear(GL_COLOR_BUFFER_BIT); 99 glScissor((ww - wx_offset) * gs, 0, wx_offset * gs, wh * gs); 100 glClear(GL_COLOR_BUFFER_BIT); 101 glDisable(GL_SCISSOR_TEST); 102 } 103 if (wy_offset > 0) { 104 glEnable(GL_SCISSOR_TEST); 105 glScissor(0, 0, ww * gs, wy_offset * gs); 106 glClear(GL_COLOR_BUFFER_BIT); 107 glScissor(0, (wh - wy_offset) * gs, ww * gs, wy_offset * gs); 108 glClear(GL_COLOR_BUFFER_BIT); 109 glDisable(GL_SCISSOR_TEST); 110 } 111 112 glViewport(0, 0, pw, ph); 113 y1 = vc->gfx.y0_top ? 0 : vc->gfx.h; 114 y2 = vc->gfx.y0_top ? vc->gfx.h : 0; 115 glBlitFramebuffer(0, y1, vc->gfx.w, y2, 116 wx_offset * gs, wy_offset * gs, 117 (ww - wx_offset) * gs, (wh - wy_offset) * gs, 118 GL_COLOR_BUFFER_BIT, GL_NEAREST); 119 #ifdef CONFIG_GBM 120 if (dmabuf) { 121 egl_dmabuf_create_sync(dmabuf); 122 } 123 #endif 124 glFlush(); 125 #ifdef CONFIG_GBM 126 if (dmabuf) { 127 int fence_fd; 128 egl_dmabuf_create_fence(dmabuf); 129 fence_fd = qemu_dmabuf_get_fence_fd(dmabuf); 130 if (fence_fd >= 0) { 131 qemu_set_fd_handler(fence_fd, gd_hw_gl_flushed, NULL, vc); 132 return; 133 } 134 graphic_hw_gl_block(vc->gfx.dcl.con, false); 135 } 136 #endif 137 } else { 138 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 139 140 surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph); 141 surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds); 142 } 143 } 144 145 void gd_gl_area_update(DisplayChangeListener *dcl, 146 int x, int y, int w, int h) 147 { 148 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); 149 150 if (!vc->gfx.gls || !vc->gfx.ds) { 151 return; 152 } 153 154 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 155 surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h); 156 vc->gfx.glupdates++; 157 gdk_gl_context_clear_current(); 158 } 159 160 void gd_gl_area_refresh(DisplayChangeListener *dcl) 161 { 162 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); 163 164 gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : vc->gfx.drawing_area); 165 166 if (vc->gfx.guest_fb.dmabuf && 167 qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) { 168 /* 169 * gd_egl_refresh() calls gd_egl_draw() if a DMA-BUF draw has already 170 * been submitted, but this function does not call gd_gl_area_draw() in 171 * such a case due to display corruption. 172 * 173 * Calling gd_gl_area_draw() is necessary to prevent a situation where 174 * there is a scheduled draw event but it won't happen bacause the window 175 * is currently in inactive state (minimized or tabified). If draw is not 176 * done for a long time, gl_block timeout and/or fence timeout (on the 177 * guest) will happen eventually. 178 * 179 * However, it is found that calling gd_gl_area_draw() here causes guest 180 * display corruption on a Wayland Compositor. The display corruption is 181 * more serious than the possible fence timeout so gd_gl_area_draw() is 182 * omitted for now. 183 */ 184 return; 185 } 186 187 if (!vc->gfx.gls) { 188 if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { 189 return; 190 } 191 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 192 vc->gfx.gls = qemu_gl_init_shader(); 193 if (vc->gfx.ds) { 194 surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds); 195 } 196 } 197 198 graphic_hw_update(dcl->con); 199 200 if (vc->gfx.glupdates) { 201 vc->gfx.glupdates = 0; 202 gtk_gl_area_set_scanout_mode(vc, false); 203 gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); 204 } 205 } 206 207 void gd_gl_area_switch(DisplayChangeListener *dcl, 208 DisplaySurface *surface) 209 { 210 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); 211 bool resized = true; 212 213 trace_gd_switch(vc->label, surface_width(surface), surface_height(surface)); 214 215 if (vc->gfx.ds && 216 surface_width(vc->gfx.ds) == surface_width(surface) && 217 surface_height(vc->gfx.ds) == surface_height(surface)) { 218 resized = false; 219 } 220 221 if (vc->gfx.gls) { 222 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 223 surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds); 224 surface_gl_create_texture(vc->gfx.gls, surface); 225 } 226 vc->gfx.ds = surface; 227 228 if (resized) { 229 gd_update_windowsize(vc); 230 } 231 } 232 233 static int gd_cmp_gl_context_version(int major, int minor, QEMUGLParams *params) 234 { 235 if (major > params->major_ver) { 236 return 1; 237 } 238 if (major < params->major_ver) { 239 return -1; 240 } 241 if (minor > params->minor_ver) { 242 return 1; 243 } 244 if (minor < params->minor_ver) { 245 return -1; 246 } 247 return 0; 248 } 249 250 QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc, 251 QEMUGLParams *params) 252 { 253 VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc); 254 GdkWindow *window; 255 GdkGLContext *ctx; 256 GError *err = NULL; 257 int major, minor; 258 259 window = gtk_widget_get_window(vc->gfx.drawing_area); 260 ctx = gdk_window_create_gl_context(window, &err); 261 if (err) { 262 g_printerr("Create gdk gl context failed: %s\n", err->message); 263 g_error_free(err); 264 return NULL; 265 } 266 gdk_gl_context_set_required_version(ctx, 267 params->major_ver, 268 params->minor_ver); 269 gdk_gl_context_realize(ctx, &err); 270 if (err) { 271 g_printerr("Realize gdk gl context failed: %s\n", err->message); 272 g_error_free(err); 273 g_clear_object(&ctx); 274 return NULL; 275 } 276 277 gdk_gl_context_make_current(ctx); 278 gdk_gl_context_get_version(ctx, &major, &minor); 279 gdk_gl_context_clear_current(); 280 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 281 282 if (gd_cmp_gl_context_version(major, minor, params) == -1) { 283 /* created ctx version < requested version */ 284 g_clear_object(&ctx); 285 } 286 287 trace_gd_gl_area_create_context(ctx, params->major_ver, params->minor_ver); 288 return ctx; 289 } 290 291 void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx) 292 { 293 GdkGLContext *current_ctx = gdk_gl_context_get_current(); 294 295 trace_gd_gl_area_destroy_context(ctx, current_ctx); 296 if (ctx == current_ctx) { 297 gdk_gl_context_clear_current(); 298 } 299 g_clear_object(&ctx); 300 } 301 302 void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, 303 uint32_t backing_id, 304 bool backing_y_0_top, 305 uint32_t backing_width, 306 uint32_t backing_height, 307 uint32_t x, uint32_t y, 308 uint32_t w, uint32_t h, 309 void *d3d_tex2d) 310 { 311 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); 312 313 vc->gfx.x = x; 314 vc->gfx.y = y; 315 vc->gfx.w = w; 316 vc->gfx.h = h; 317 vc->gfx.y0_top = backing_y_0_top; 318 319 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 320 321 if (backing_id == 0 || vc->gfx.w == 0 || vc->gfx.h == 0) { 322 gtk_gl_area_set_scanout_mode(vc, false); 323 return; 324 } 325 326 gtk_gl_area_set_scanout_mode(vc, true); 327 egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height, 328 backing_id, false); 329 } 330 331 void gd_gl_area_scanout_disable(DisplayChangeListener *dcl) 332 { 333 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); 334 335 gtk_gl_area_set_scanout_mode(vc, false); 336 } 337 338 void gd_gl_area_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 343 if (vc->gfx.guest_fb.dmabuf && 344 !qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) { 345 graphic_hw_gl_block(vc->gfx.dcl.con, true); 346 qemu_dmabuf_set_draw_submitted(vc->gfx.guest_fb.dmabuf, true); 347 gtk_gl_area_set_scanout_mode(vc, true); 348 } 349 gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); 350 } 351 352 void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, 353 QemuDmaBuf *dmabuf) 354 { 355 #ifdef CONFIG_GBM 356 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); 357 uint32_t x, y, width, height, backing_width, backing_height, texture; 358 bool y0_top; 359 360 gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); 361 egl_dmabuf_import_texture(dmabuf); 362 texture = qemu_dmabuf_get_texture(dmabuf); 363 if (!texture) { 364 return; 365 } 366 367 x = qemu_dmabuf_get_x(dmabuf); 368 y = qemu_dmabuf_get_y(dmabuf); 369 width = qemu_dmabuf_get_width(dmabuf); 370 height = qemu_dmabuf_get_height(dmabuf); 371 backing_width = qemu_dmabuf_get_backing_width(dmabuf); 372 backing_height = qemu_dmabuf_get_backing_height(dmabuf); 373 y0_top = qemu_dmabuf_get_y0_top(dmabuf); 374 375 gd_gl_area_scanout_texture(dcl, texture, y0_top, 376 backing_width, backing_height, 377 x, y, width, height, NULL); 378 379 if (qemu_dmabuf_get_allow_fences(dmabuf)) { 380 vc->gfx.guest_fb.dmabuf = dmabuf; 381 } 382 #endif 383 } 384 385 void gtk_gl_area_init(void) 386 { 387 display_opengl = 1; 388 } 389 390 int gd_gl_area_make_current(DisplayGLCtx *dgc, 391 QEMUGLContext ctx) 392 { 393 gdk_gl_context_make_current(ctx); 394 return 0; 395 } 396