Commit 925a0400 authored by Gerd Hoffmann's avatar Gerd Hoffmann
Browse files

gtk/opengl: add opengl context and scanout support (GtkGLArea)



This allows virtio-gpu to render in 3d mode.
Uses native opengl support which is present
in gtk versions 3.16 and newer.

Signed-off-by: default avatarGerd Hoffmann <kraxel@redhat.com>
parent 4782aeb7
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -328,6 +328,7 @@ glusterfs_zerofill="no"
archipelago="no"
gtk=""
gtkabi=""
gtk_gl="no"
gnutls=""
gnutls_hash=""
vte=""
@@ -3236,6 +3237,9 @@ if test "$opengl" != "no" ; then
    opengl_cflags="$($pkg_config --cflags $opengl_pkgs) $x11_cflags"
    opengl_libs="$($pkg_config --libs $opengl_pkgs) $x11_libs"
    opengl=yes
    if test "$gtk" = "yes" && $pkg_config --exists "$gtkpackage >= 3.16"; then
        gtk_gl="yes"
    fi
  else
    if test "$opengl" = "yes" ; then
      feature_not_found "opengl" "Please install opengl (mesa) devel pkgs: $opengl_pkgs"
@@ -4602,6 +4606,7 @@ fi
echo "pixman            $pixman"
echo "SDL support       $sdl"
echo "GTK support       $gtk"
echo "GTK GL support    $gtk_gl"
echo "GNUTLS support    $gnutls"
echo "GNUTLS hash       $gnutls_hash"
echo "GNUTLS gcrypt     $gnutls_gcrypt"
@@ -4961,6 +4966,9 @@ if test "$gtk" = "yes" ; then
  echo "CONFIG_GTK=y" >> $config_host_mak
  echo "CONFIG_GTKABI=$gtkabi" >> $config_host_mak
  echo "GTK_CFLAGS=$gtk_cflags" >> $config_host_mak
  if test "$gtk_gl" = "yes" ; then
    echo "CONFIG_GTK_GL=y" >> $config_host_mak
  fi
fi
if test "$gnutls" = "yes" ; then
  echo "CONFIG_GNUTLS=y" >> $config_host_mak
+23 −0
Original line number Diff line number Diff line
@@ -108,4 +108,27 @@ void gtk_egl_init(void);
int gd_egl_make_current(DisplayChangeListener *dcl,
                        QEMUGLContext ctx);

/* ui/gtk-gl-area.c */
void gd_gl_area_init(VirtualConsole *vc);
void gd_gl_area_draw(VirtualConsole *vc);
void gd_gl_area_update(DisplayChangeListener *dcl,
                       int x, int y, int w, int h);
void gd_gl_area_refresh(DisplayChangeListener *dcl);
void gd_gl_area_switch(DisplayChangeListener *dcl,
                       DisplaySurface *surface);
QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
                                        QEMUGLParams *params);
void gd_gl_area_destroy_context(DisplayChangeListener *dcl,
                                QEMUGLContext ctx);
void gd_gl_area_scanout(DisplayChangeListener *dcl,
                        uint32_t backing_id, bool backing_y_0_top,
                        uint32_t x, uint32_t y,
                        uint32_t w, uint32_t h);
void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
                              uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void gtk_gl_area_init(void);
QEMUGLContext gd_gl_area_get_current_context(DisplayChangeListener *dcl);
int gd_gl_area_make_current(DisplayChangeListener *dcl,
                            QEMUGLContext ctx);

#endif /* UI_GTK_H */
+5 −0
Original line number Diff line number Diff line
@@ -32,11 +32,16 @@ common-obj-y += shader.o
common-obj-y += console-gl.o
common-obj-y += egl-helpers.o
common-obj-y += egl-context.o
ifeq ($(CONFIG_GTK_GL),y)
common-obj-$(CONFIG_GTK) += gtk-gl-area.o
else
common-obj-$(CONFIG_GTK) += gtk-egl.o
endif
endif

gtk.o-cflags := $(GTK_CFLAGS) $(VTE_CFLAGS)
gtk-egl.o-cflags := $(GTK_CFLAGS) $(VTE_CFLAGS) $(OPENGL_CFLAGS)
gtk-gl-area.o-cflags := $(GTK_CFLAGS) $(VTE_CFLAGS) $(OPENGL_CFLAGS)
shader.o-cflags += $(OPENGL_CFLAGS)
console-gl.o-cflags += $(OPENGL_CFLAGS)
egl-helpers.o-cflags += $(OPENGL_CFLAGS)

ui/gtk-gl-area.c

0 → 100644
+223 −0
Original line number Diff line number Diff line
/*
 * GTK UI -- glarea opengl code.
 *
 * Requires 3.16+ (GtkGLArea widget).
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#include "qemu-common.h"

#include "trace.h"

#include "ui/console.h"
#include "ui/gtk.h"
#include "ui/egl-helpers.h"

#include "sysemu/sysemu.h"

static void gtk_gl_area_set_scanout_mode(VirtualConsole *vc, bool scanout)
{
    if (vc->gfx.scanout_mode == scanout) {
        return;
    }

    vc->gfx.scanout_mode = scanout;
    if (!vc->gfx.scanout_mode) {
        if (vc->gfx.fbo_id) {
            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                                      GL_COLOR_ATTACHMENT0_EXT,
                                      GL_TEXTURE_2D, 0, 0);
            glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
            glDeleteFramebuffers(1, &vc->gfx.fbo_id);
            vc->gfx.fbo_id = 0;
        }
        if (vc->gfx.surface) {
            surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
            surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
        }
    }
}

/** DisplayState Callbacks (opengl version) **/

void gd_gl_area_draw(VirtualConsole *vc)
{
    int ww, wh, y1, y2;

    if (!vc->gfx.gls) {
        return;
    }

    gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
    ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area);
    wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area);

    if (vc->gfx.scanout_mode) {
        if (!vc->gfx.fbo_id) {
            return;
        }

        glBindFramebuffer(GL_READ_FRAMEBUFFER, vc->gfx.fbo_id);
        /* GtkGLArea sets GL_DRAW_FRAMEBUFFER for us */

        glViewport(0, 0, ww, wh);
        y1 = vc->gfx.y0_top ? 0 : vc->gfx.h;
        y2 = vc->gfx.y0_top ? vc->gfx.h : 0;
        glBlitFramebuffer(0, y1, vc->gfx.w, y2,
                          0, 0, ww, wh,
                          GL_COLOR_BUFFER_BIT, GL_NEAREST);
    } else {
        if (!vc->gfx.ds) {
            return;
        }
        gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));

        surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
        surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
    }
}

void gd_gl_area_update(DisplayChangeListener *dcl,
                   int x, int y, int w, int h)
{
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);

    if (!vc->gfx.gls || !vc->gfx.ds) {
        return;
    }

    gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
    surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h);
    vc->gfx.glupdates++;
}

void gd_gl_area_refresh(DisplayChangeListener *dcl)
{
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);

    if (!vc->gfx.gls) {
        if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
            return;
        }
        gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
        vc->gfx.gls = console_gl_init_context();
        if (vc->gfx.ds) {
            surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
        }
    }

    graphic_hw_update(dcl->con);

    if (vc->gfx.glupdates) {
        vc->gfx.glupdates = 0;
        gtk_gl_area_set_scanout_mode(vc, false);
        gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
    }
}

void gd_gl_area_switch(DisplayChangeListener *dcl,
                       DisplaySurface *surface)
{
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
    bool resized = true;

    trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));

    if (vc->gfx.ds &&
        surface_width(vc->gfx.ds) == surface_width(surface) &&
        surface_height(vc->gfx.ds) == surface_height(surface)) {
        resized = false;
    }

    if (vc->gfx.gls) {
        gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
        surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
        surface_gl_create_texture(vc->gfx.gls, surface);
    }
    vc->gfx.ds = surface;

    if (resized) {
        gd_update_windowsize(vc);
    }
}

QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
                                        QEMUGLParams *params)
{
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
    GdkWindow *window;
    GdkGLContext *ctx;
    GError *err = NULL;

    gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
    window = gtk_widget_get_window(vc->gfx.drawing_area);
    ctx = gdk_window_create_gl_context(window, &err);
    gdk_gl_context_set_required_version(ctx,
                                        params->major_ver,
                                        params->minor_ver);
    gdk_gl_context_realize(ctx, &err);
    return ctx;
}

void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
{
    /* FIXME */
}

void gd_gl_area_scanout(DisplayChangeListener *dcl,
                        uint32_t backing_id, bool backing_y_0_top,
                        uint32_t x, uint32_t y,
                        uint32_t w, uint32_t h)
{
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);

    vc->gfx.x = x;
    vc->gfx.y = y;
    vc->gfx.w = w;
    vc->gfx.h = h;
    vc->gfx.tex_id = backing_id;
    vc->gfx.y0_top = backing_y_0_top;

    gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));

    if (vc->gfx.tex_id == 0 || vc->gfx.w == 0 || vc->gfx.h == 0) {
        gtk_gl_area_set_scanout_mode(vc, false);
        return;
    }

    gtk_gl_area_set_scanout_mode(vc, true);
    if (!vc->gfx.fbo_id) {
        glGenFramebuffers(1, &vc->gfx.fbo_id);
    }

    glBindFramebuffer(GL_FRAMEBUFFER_EXT, vc->gfx.fbo_id);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_2D, vc->gfx.tex_id, 0);
}

void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
                          uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);

    gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
}

void gtk_gl_area_init(void)
{
    display_opengl = 1;
}

QEMUGLContext gd_gl_area_get_current_context(DisplayChangeListener *dcl)
{
    return gdk_gl_context_get_current();
}

int gd_gl_area_make_current(DisplayChangeListener *dcl,
                            QEMUGLContext ctx)
{
    gdk_gl_context_make_current(ctx);
    return 0;
}
+106 −24
Original line number Diff line number Diff line
@@ -367,6 +367,12 @@ static void gd_update_full_redraw(VirtualConsole *vc)
    GtkWidget *area = vc->gfx.drawing_area;
    int ww, wh;
    gdk_drawable_get_size(gtk_widget_get_window(area), &ww, &wh);
#if defined(CONFIG_GTK_GL)
    if (vc->gfx.gls) {
        gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
        return;
    }
#endif
    gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
}

@@ -607,6 +613,27 @@ static const DisplayChangeListenerOps dcl_ops = {

/** DisplayState Callbacks (opengl version) **/

#if defined(CONFIG_GTK_GL)

static const DisplayChangeListenerOps dcl_gl_area_ops = {
    .dpy_name             = "gtk-egl",
    .dpy_gfx_update       = gd_gl_area_update,
    .dpy_gfx_switch       = gd_gl_area_switch,
    .dpy_gfx_check_format = console_gl_check_format,
    .dpy_refresh          = gd_gl_area_refresh,
    .dpy_mouse_set        = gd_mouse_set,
    .dpy_cursor_define    = gd_cursor_define,

    .dpy_gl_ctx_create       = gd_gl_area_create_context,
    .dpy_gl_ctx_destroy      = gd_gl_area_destroy_context,
    .dpy_gl_ctx_make_current = gd_gl_area_make_current,
    .dpy_gl_ctx_get_current  = gd_gl_area_get_current_context,
    .dpy_gl_scanout          = gd_gl_area_scanout,
    .dpy_gl_update           = gd_gl_area_scanout_flush,
};

#else

static const DisplayChangeListenerOps dcl_egl_ops = {
    .dpy_name             = "gtk-egl",
    .dpy_gfx_update       = gd_egl_update,
@@ -624,7 +651,8 @@ static const DisplayChangeListenerOps dcl_egl_ops = {
    .dpy_gl_update           = gd_egl_scanout_flush,
};

#endif
#endif /* CONFIG_GTK_GL */
#endif /* CONFIG_OPENGL */

/** QEMU Events **/

@@ -674,6 +702,39 @@ static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
    return TRUE;
}

static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
{
    QemuUIInfo info;

    memset(&info, 0, sizeof(info));
    info.width = width;
    info.height = height;
    dpy_set_ui_info(vc->gfx.dcl.con, &info);
}

#if defined(CONFIG_GTK_GL)

static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context,
                                void *opaque)
{
    VirtualConsole *vc = opaque;

    if (vc->gfx.gls) {
        gd_gl_area_draw(vc);
    }
    return TRUE;
}

static void gd_resize_event(GtkGLArea *area,
                            gint width, gint height, gpointer *opaque)
{
    VirtualConsole *vc = (void *)opaque;

    gd_set_ui_info(vc, width, height);
}

#endif

static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
{
    VirtualConsole *vc = opaque;
@@ -684,8 +745,13 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)

#if defined(CONFIG_OPENGL)
    if (vc->gfx.gls) {
#if defined(CONFIG_GTK_GL)
        /* invoke render callback please */
        return FALSE;
#else
        gd_egl_draw(vc);
        return TRUE;
#endif
    }
#endif

@@ -1473,12 +1539,8 @@ static gboolean gd_configure(GtkWidget *widget,
                             GdkEventConfigure *cfg, gpointer opaque)
{
    VirtualConsole *vc = opaque;
    QemuUIInfo info;

    memset(&info, 0, sizeof(info));
    info.width = cfg->width;
    info.height = cfg->height;
    dpy_set_ui_info(vc->gfx.dcl.con, &info);
    gd_set_ui_info(vc, cfg->width, cfg->height);
    return FALSE;
}

@@ -1635,6 +1697,15 @@ static void gd_connect_vc_gfx_signals(VirtualConsole *vc)
#if GTK_CHECK_VERSION(3, 0, 0)
    g_signal_connect(vc->gfx.drawing_area, "draw",
                     G_CALLBACK(gd_draw_event), vc);
#if defined(CONFIG_GTK_GL)
    if (display_opengl) {
        /* wire up GtkGlArea events */
        g_signal_connect(vc->gfx.drawing_area, "render",
                         G_CALLBACK(gd_render_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "resize",
                         G_CALLBACK(gd_resize_event), vc);
    }
#endif
#else
    g_signal_connect(vc->gfx.drawing_area, "expose-event",
                     G_CALLBACK(gd_expose_event), vc);
@@ -1743,26 +1814,13 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
    vc->gfx.scale_x = 1.0;
    vc->gfx.scale_y = 1.0;

    vc->gfx.drawing_area = gtk_drawing_area_new();
    gtk_widget_add_events(vc->gfx.drawing_area,
                          GDK_POINTER_MOTION_MASK |
                          GDK_BUTTON_PRESS_MASK |
                          GDK_BUTTON_RELEASE_MASK |
                          GDK_BUTTON_MOTION_MASK |
                          GDK_ENTER_NOTIFY_MASK |
                          GDK_LEAVE_NOTIFY_MASK |
                          GDK_SCROLL_MASK |
                          GDK_KEY_PRESS_MASK);
    gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);

    vc->type = GD_VC_GFX;
    vc->tab_item = vc->gfx.drawing_area;
    vc->focus = vc->gfx.drawing_area;
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
                             vc->tab_item, gtk_label_new(vc->label));

#if defined(CONFIG_OPENGL)
    if (display_opengl) {
#if defined(CONFIG_GTK_GL)
        vc->gfx.drawing_area = gtk_gl_area_new();
        vc->gfx.dcl.ops = &dcl_gl_area_ops;
#else
        vc->gfx.drawing_area = gtk_drawing_area_new();
        /*
         * gtk_widget_set_double_buffered() was deprecated in 3.14.
         * It is required for opengl rendering on X11 though.  A
@@ -1778,12 +1836,32 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
#pragma GCC diagnostic pop
#endif
        vc->gfx.dcl.ops = &dcl_egl_ops;
#endif /* CONFIG_GTK_GL */
    } else
#endif
    {
        vc->gfx.drawing_area = gtk_drawing_area_new();
        vc->gfx.dcl.ops = &dcl_ops;
    }


    gtk_widget_add_events(vc->gfx.drawing_area,
                          GDK_POINTER_MOTION_MASK |
                          GDK_BUTTON_PRESS_MASK |
                          GDK_BUTTON_RELEASE_MASK |
                          GDK_BUTTON_MOTION_MASK |
                          GDK_ENTER_NOTIFY_MASK |
                          GDK_LEAVE_NOTIFY_MASK |
                          GDK_SCROLL_MASK |
                          GDK_KEY_PRESS_MASK);
    gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);

    vc->type = GD_VC_GFX;
    vc->tab_item = vc->gfx.drawing_area;
    vc->focus = vc->gfx.drawing_area;
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
                             vc->tab_item, gtk_label_new(vc->label));

    vc->gfx.dcl.con = con;
    register_displaychangelistener(&vc->gfx.dcl);

@@ -2066,7 +2144,11 @@ void early_gtk_display_init(int opengl)
        break;
    case 1: /* on */
#if defined(CONFIG_OPENGL)
#if defined(CONFIG_GTK_GL)
        gtk_gl_area_init();
#else
        gtk_egl_init();
#endif
#endif
        break;
    default: