| 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 | qemu_console_hw_gl_block(vc->gfx.dcl.con, true); |
| 90 | } |
| 91 | #endif |
| 92 | |
| 93 | glBindFramebuffer(GL_READ_FRAMEBUFFER, vc->gfx.guest_fb.framebuffer); |
| 94 | /* GtkGLArea sets GL_DRAW_FRAMEBUFFER for us */ |
| 95 | |
| 96 | if (wx_offset > 0) { |
| 97 | glEnable(GL_SCISSOR_TEST); |
| 98 | glScissor(0, 0, wx_offset * gs, wh * gs); |
| 99 | glClear(GL_COLOR_BUFFER_BIT); |
| 100 | glScissor((ww - wx_offset) * gs, 0, wx_offset * gs, wh * gs); |
| 101 | glClear(GL_COLOR_BUFFER_BIT); |
| 102 | glDisable(GL_SCISSOR_TEST); |
| 103 | } |
| 104 | if (wy_offset > 0) { |
| 105 | glEnable(GL_SCISSOR_TEST); |
| 106 | glScissor(0, 0, ww * gs, wy_offset * gs); |
| 107 | glClear(GL_COLOR_BUFFER_BIT); |
| 108 | glScissor(0, (wh - wy_offset) * gs, ww * gs, wy_offset * gs); |
| 109 | glClear(GL_COLOR_BUFFER_BIT); |
| 110 | glDisable(GL_SCISSOR_TEST); |
| 111 | } |
| 112 | |
| 113 | glViewport(0, 0, pw, ph); |
| 114 | y1 = vc->gfx.y0_top ? 0 : vc->gfx.h; |
| 115 | y2 = vc->gfx.y0_top ? vc->gfx.h : 0; |
| 116 | glBlitFramebuffer(0, y1, vc->gfx.w, y2, |
| 117 | wx_offset * gs, wy_offset * gs, |
| 118 | (ww - wx_offset) * gs, (wh - wy_offset) * gs, |
| 119 | GL_COLOR_BUFFER_BIT, GL_NEAREST); |
| 120 | #ifdef CONFIG_GBM |
| 121 | if (dmabuf) { |
| 122 | egl_dmabuf_create_sync(dmabuf); |
| 123 | } |
| 124 | #endif |
| 125 | glFlush(); |
| 126 | #ifdef CONFIG_GBM |
| 127 | if (dmabuf) { |
| 128 | int fence_fd; |
| 129 | egl_dmabuf_create_fence(dmabuf); |
| 130 | fence_fd = qemu_dmabuf_get_fence_fd(dmabuf); |
| 131 | if (fence_fd >= 0) { |
| 132 | qemu_set_fd_handler(fence_fd, gd_hw_gl_flushed, NULL, vc); |
| 133 | return; |
| 134 | } |
| 135 | qemu_console_hw_gl_block(vc->gfx.dcl.con, false); |
| 136 | } |
| 137 | #endif |
| 138 | } else { |
| 139 | gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 140 | |
| 141 | surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph); |
| 142 | surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | void gd_gl_area_update(DisplayChangeListener *dcl, |
| 147 | int x, int y, int w, int h) |
| 148 | { |
| 149 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 150 | |
| 151 | if (!vc->gfx.gls || !vc->gfx.ds) { |
| 152 | return; |
| 153 | } |
| 154 | |
| 155 | gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 156 | surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h); |
| 157 | vc->gfx.glupdates++; |
| 158 | gdk_gl_context_clear_current(); |
| 159 | } |
| 160 | |
| 161 | void gd_gl_area_refresh(DisplayChangeListener *dcl) |
| 162 | { |
| 163 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 164 | |
| 165 | gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : vc->gfx.drawing_area); |
| 166 | |
| 167 | if (!vc->gfx.gls) { |
| 168 | if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { |
| 169 | return; |
| 170 | } |
| 171 | gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 172 | vc->gfx.gls = qemu_gl_init_shader(); |
| 173 | if (vc->gfx.ds) { |
| 174 | surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds); |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | qemu_console_hw_update(dcl->con); |
| 179 | |
| 180 | if (vc->gfx.glupdates) { |
| 181 | vc->gfx.glupdates = 0; |
| 182 | gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | void gd_gl_area_switch(DisplayChangeListener *dcl, |
| 187 | DisplaySurface *surface) |
| 188 | { |
| 189 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 190 | bool resized = true; |
| 191 | |
| 192 | trace_gd_switch(vc->label, surface_width(surface), surface_height(surface)); |
| 193 | |
| 194 | if (vc->gfx.ds && |
| 195 | surface_width(vc->gfx.ds) == surface_width(surface) && |
| 196 | surface_height(vc->gfx.ds) == surface_height(surface)) { |
| 197 | resized = false; |
| 198 | } |
| 199 | |
| 200 | if (vc->gfx.gls) { |
| 201 | gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 202 | surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds); |
| 203 | surface_gl_create_texture(vc->gfx.gls, surface); |
| 204 | } |
| 205 | vc->gfx.ds = surface; |
| 206 | |
| 207 | if (resized) { |
| 208 | gd_update_windowsize(vc); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | static int gd_cmp_gl_context_version(int major, int minor, QEMUGLParams *params) |
| 213 | { |
| 214 | if (major > params->major_ver) { |
| 215 | return 1; |
| 216 | } |
| 217 | if (major < params->major_ver) { |
| 218 | return -1; |
| 219 | } |
| 220 | if (minor > params->minor_ver) { |
| 221 | return 1; |
| 222 | } |
| 223 | if (minor < params->minor_ver) { |
| 224 | return -1; |
| 225 | } |
| 226 | return 0; |
| 227 | } |
| 228 | |
| 229 | QEMUGLContext gd_gl_area_create_context(DisplayGLCtx *dgc, |
| 230 | QEMUGLParams *params) |
| 231 | { |
| 232 | VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc); |
| 233 | GdkGLContext *ctx, *current_ctx; |
| 234 | GdkWindow *window; |
| 235 | GError *err = NULL; |
| 236 | int major, minor; |
| 237 | |
| 238 | current_ctx = gdk_gl_context_get_current(); |
| 239 | |
| 240 | window = gtk_widget_get_window(vc->gfx.drawing_area); |
| 241 | ctx = gdk_window_create_gl_context(window, &err); |
| 242 | if (err) { |
| 243 | g_printerr("Create gdk gl context failed: %s\n", err->message); |
| 244 | g_error_free(err); |
| 245 | return NULL; |
| 246 | } |
| 247 | gdk_gl_context_set_required_version(ctx, |
| 248 | params->major_ver, |
| 249 | params->minor_ver); |
| 250 | gdk_gl_context_realize(ctx, &err); |
| 251 | if (err) { |
| 252 | g_printerr("Realize gdk gl context failed: %s\n", err->message); |
| 253 | g_error_free(err); |
| 254 | g_clear_object(&ctx); |
| 255 | return NULL; |
| 256 | } |
| 257 | |
| 258 | gdk_gl_context_make_current(ctx); |
| 259 | gdk_gl_context_get_version(ctx, &major, &minor); |
| 260 | |
| 261 | if (current_ctx) { |
| 262 | gdk_gl_context_make_current(current_ctx); |
| 263 | } else { |
| 264 | gdk_gl_context_clear_current(); |
| 265 | } |
| 266 | |
| 267 | if (gd_cmp_gl_context_version(major, minor, params) == -1) { |
| 268 | /* created ctx version < requested version */ |
| 269 | g_clear_object(&ctx); |
| 270 | } |
| 271 | |
| 272 | trace_gd_gl_area_create_context(ctx, params->major_ver, params->minor_ver); |
| 273 | return ctx; |
| 274 | } |
| 275 | |
| 276 | void gd_gl_area_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx) |
| 277 | { |
| 278 | GdkGLContext *current_ctx = gdk_gl_context_get_current(); |
| 279 | |
| 280 | trace_gd_gl_area_destroy_context(ctx, current_ctx); |
| 281 | if (ctx == current_ctx) { |
| 282 | gdk_gl_context_clear_current(); |
| 283 | } |
| 284 | g_clear_object(&ctx); |
| 285 | } |
| 286 | |
| 287 | void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, |
| 288 | uint32_t backing_id, |
| 289 | bool backing_y_0_top, |
| 290 | uint32_t backing_width, |
| 291 | uint32_t backing_height, |
| 292 | uint32_t x, uint32_t y, |
| 293 | uint32_t w, uint32_t h, |
| 294 | void *d3d_tex2d) |
| 295 | { |
| 296 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 297 | |
| 298 | vc->gfx.x = x; |
| 299 | vc->gfx.y = y; |
| 300 | vc->gfx.w = w; |
| 301 | vc->gfx.h = h; |
| 302 | vc->gfx.y0_top = backing_y_0_top; |
| 303 | |
| 304 | gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 305 | |
| 306 | if (backing_id == 0 || vc->gfx.w == 0 || vc->gfx.h == 0) { |
| 307 | gtk_gl_area_set_scanout_mode(vc, false); |
| 308 | return; |
| 309 | } |
| 310 | |
| 311 | gtk_gl_area_set_scanout_mode(vc, true); |
| 312 | egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height, |
| 313 | backing_id, false); |
| 314 | } |
| 315 | |
| 316 | void gd_gl_area_scanout_disable(DisplayChangeListener *dcl) |
| 317 | { |
| 318 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 319 | |
| 320 | gtk_gl_area_set_scanout_mode(vc, false); |
| 321 | } |
| 322 | |
| 323 | void gd_gl_area_scanout_flush(DisplayChangeListener *dcl, |
| 324 | uint32_t x, uint32_t y, uint32_t w, uint32_t h) |
| 325 | { |
| 326 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 327 | |
| 328 | if (vc->gfx.guest_fb.dmabuf && |
| 329 | !qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) { |
| 330 | qemu_dmabuf_set_draw_submitted(vc->gfx.guest_fb.dmabuf, true); |
| 331 | gtk_gl_area_set_scanout_mode(vc, true); |
| 332 | } |
| 333 | gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 334 | } |
| 335 | |
| 336 | void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, |
| 337 | QemuDmaBuf *dmabuf) |
| 338 | { |
| 339 | #ifdef CONFIG_GBM |
| 340 | VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); |
| 341 | uint32_t x, y, width, height, backing_width, backing_height, texture; |
| 342 | bool y0_top; |
| 343 | |
| 344 | gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); |
| 345 | egl_dmabuf_import_texture(dmabuf); |
| 346 | texture = qemu_dmabuf_get_texture(dmabuf); |
| 347 | if (!texture) { |
| 348 | return; |
| 349 | } |
| 350 | |
| 351 | x = qemu_dmabuf_get_x(dmabuf); |
| 352 | y = qemu_dmabuf_get_y(dmabuf); |
| 353 | width = qemu_dmabuf_get_width(dmabuf); |
| 354 | height = qemu_dmabuf_get_height(dmabuf); |
| 355 | backing_width = qemu_dmabuf_get_backing_width(dmabuf); |
| 356 | backing_height = qemu_dmabuf_get_backing_height(dmabuf); |
| 357 | y0_top = qemu_dmabuf_get_y0_top(dmabuf); |
| 358 | |
| 359 | gd_gl_area_scanout_texture(dcl, texture, y0_top, |
| 360 | backing_width, backing_height, |
| 361 | x, y, width, height, NULL); |
| 362 | |
| 363 | if (qemu_dmabuf_get_allow_fences(dmabuf)) { |
| 364 | vc->gfx.guest_fb.dmabuf = dmabuf; |
| 365 | } |
| 366 | #endif |
| 367 | } |
| 368 | |
| 369 | void gtk_gl_area_init(void) |
| 370 | { |
| 371 | display_opengl = 1; |
| 372 | } |
| 373 | |
| 374 | int gd_gl_area_make_current(DisplayGLCtx *dgc, |
| 375 | QEMUGLContext ctx) |
| 376 | { |
| 377 | gdk_gl_context_make_current(ctx); |
| 378 | return 0; |
| 379 | } |